ASTRO Camp 7th
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Help
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # 0419 加密、使用者登入登出、資料庫關聯、webpacker ###### tags: `Ruby / Rails` ## 作業檢討(JS) ```javascript= <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="jq.js"></script> // 帶入後才能使用像 jQuery 的功能 </head> <body> <h1 id="h">hello</h1> <script> $('#h').on('click', function() { this.innerHTML = '被按了' }) </script> </body> </html> ``` - 建立 `jq.js` - 缺少 $ 方法 * 最簡單的做法就是直接給他需要的東西 ```javascript= $ = function (el) { // 最好不直接指定全域變數 console.log(el) return { on:function(evt, callback){ } } } ``` * 加功能上去 ```javascript= window.$ = function(el) { let element = document.querySelector(el) return { element: element, on : function(evt, callback) { switch(evt) { case 'click': this.element.addEventListener('click', callback) break } } } } ``` - 注意 let 宣告變數的存活範圍 ```javascript= window.$ = function(el) { if (el === document) { let element = document } else{ let element = document.querySelector(el) } return { element: element, on : function(evt, callback) { switch(evt) { case 'click': this.element.addEventListener('click', callback) break } } } } ``` - 連續技 - 把回傳值包成一個物件(Object) - 如果是一個物件就能繼續在後面加功能及使用功能 `.ready()` `.DOMContentLoaded` DOMContentLoaded事件是當document被完整的讀取跟解析後就會被觸發,不會等待 stylesheets, 圖片和subframes完成讀取 (load事件可以用來作為判斷頁面已經完整讀取的方法). ```javascript= <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="jq.js"></script> </head> <body> <h1 id="h">hello</h1> <script> $(body).ready( function() { $('#h').on('click', function() { this.innerHTML = '被按了' }) }) // 加入 ready 方法 </script> </body> </html> ``` #### 加入 ready 方法 ```javascript= const globalObject = (typeof window === "object") ? window : global globalObject.$ = function(el) { let element = (el === document) ? document : document.querySelector(el) // 因為 document 丟到 querySelector 會壞 所以要加判斷式 return { element, // element: element ready: function(callback) { this.element.addEventListener('DOMContentLoaded', callback) }, on: function(evt, callback) { switch(evt) { case 'click': this.element.addEventListener('click', callback) break } } } } ``` - 建議可以看一下 jQuery 的 source code - jQuery 的 slim 版本 - 少了網路功能跟動畫特效(淡入淡出...) - 只要用 query 就可以使用 ![](https://i.imgur.com/hgKhVIa.png) 把前面的東西都指定給後面那個 ```javascript= window.jQuery = window.$ = jQuery ``` 結果:產生一樣效果 ```javascript= $() jQuery() ``` - 建立簡寫 ```javascript= var arr = [] var slice = arr.slice // 以後要使用 arr.slice 的時候直接輸入 slice 就可以 ``` --- ## 加密 ```ruby= module Encrytor require 'digest' ``` ## 使用者登入 - `session` 方法:發號碼牌,暫存資料 - `session` 是一個很大的 Hash(本來就存在) 把號碼牌放到裡面 - 在瀏覽器裡開一個 cookie (server 跟 瀏覽器都產生) - 開格子 thankyou9527 /sessions_controller.rb ```ruby= def create user = User.login(user_params) # 見下面方法 (之前做好了) if user # 發號碼牌 session[:thankyou9527] = user.id # 用login方法抓到的資料 # user.id 方法是因為有 user 這個 Model, 資料庫有 users 這個 table 裡面有 id 這個欄位 # 轉去首頁 redirect_to root_path else redirect_to sign_in_sessions_path end end ``` 換頁之後 user 就消失了 透過session / 網路傳輸 所有值會變成一個字串(記憶體位置符號)(如果是數字或單純的值的話會比較容易轉回來) 不要放陣列、物件或太複雜的東西 通常 session 後面會接一個 setCookie 方法,目前rails 會自動幫忙建立 #### 回顧 User.login 方法 /models/user.rb ```ruby= def self.login(params) email = params[:email] password = params[:password] salted_password = Encryptor.salted(password) encrypted_password = Encryptor.encrypt(salted_password) find_by(email: email, password: encrypted_password) # find_by回傳值或 null end ``` 有 session 代表有登入? => 有號碼牌不代表有買飲料 後面能不能有存取行為,這是看後面的事情決定 http沒有狀態,所以也沒有登入的狀態 登入是「行為」,透過行為拿到 session 跟 cookie,不是一個狀態 如果有號碼牌 => 可以到該頁面 如果沒有號碼牌 => 回到首頁 可以在開發者工具裡面找 application 把 cookie 刪除 每次送 request 的時候都會跟著一個號碼牌,server 就會根據號碼牌給你看你可以看的東西 例: ```ruby= def new if not session[:thankyou9527] redirect_to root_path else @restaurant = Restaurant.new end end ``` #### 回顧 routes ```ruby= resources :sessions, path: 'users', only: [:destroy] do collection do get :sign_in, action: 'new' post :sign_in, action: 'create' end end ``` #### 製作登出連結(只有在有登入的時候才會出現) index.html.erb (先做出想要的效果) ```ruby= # 在網站最上方加入 <% if session[:thankyou9527] %> 登出 <% else %> 註冊 | 登入 <% end %> ``` ### view helper 使用者 id 重複出現 thankyou9527 => 在 view 裡面所以可以建立一個 view helper view helper 只有 view 才能用 找一個 app/helpers/ 裡的檔案塞進去 session 或是 params 只有在 controller 跟 view 裡面才能取用 model 不能用 app/helpers/sessions_helper.rb ```ruby= module SessionsHelper def user_signed_in? if session[:thankyou9527] return true else return false end end end ``` index.html.erb ```ruby= # 把判斷式改掉以防打錯資料 <% if user_signed_in? %> 登出 <% else %> 註冊 | 登入 <% end %> ``` #### 再建立一個小幫手提供使用者 email 顯示 ```ruby= <% if user_signed_in? %> <%= current_user.email %> | 登出 <% else %> 註冊 | 登入 <% end %> ``` #### 根據model找到使用者資料 app/helpers/sessions_helper.rb ```ruby= module SesssionsHelper def current_user User.find_by(id: session[:thankyou9527]) end end ``` 使用 find 方法會跳激烈的錯誤訊息,造成被停權的使用者無法順利登出或進行動作 ## 使用者登出 route 因為之前的route 後面可以指定 id,會造成可以幫其他使用者登出的風險 (DELETE => /users/:id(.:format) => sessions#destroy ) 所以把它直接改掉 ```ruby= resources :sessions, path: 'users', only: [] do collection do get :sign_in, action: 'new' post :sign_in, action: 'create' delete :sign_out, action: 'destroy' end end ``` sessions_controller ```ruby= def destroy # 登出 讓號碼牌失效 (撕票) session[:thankyou9527] = nil # 轉址 redirect_to root_path end ``` 加入登出連結 index.html.erb ```ruby= <% if user_signed_in? %> <%= current_user.email %> | <%= link_to '登出', sign_out_sessions_path, method: 'delete' %> <% else %> 註冊 | 登入 <% end %> ``` 確認route然後在view裡建立註冊登入連結 index.html.erb ```ruby= <% if user_signed_in? %> <%= current_user.email %> | <%= link_to '登出', sign_out_sessions_path, method: 'delete' %> <% else %> <%= link_to '註冊', sign_up_users_path %> | <%= link_to '登入', sign_in_sessions_path %> <% end %> ``` :thankyou9527 是一個變數,讓全站都一樣就好 環境變數好處:統一管理 可以使用 figaro 套件 [github](https://github.com/laserlemon/figaro) [查詢版號](https://rubygems.org/gems/figaro/versions/1.1.1) 把套件放在Gemfile裡面 如果上線需要使用就放在外面 如果只是開發需要使用就放在develop底下 terminal 執行 bundle 在 config/application.yml 裡設定變數 application.yml 不會進版控上線(包含敏感資料) 所以 figaro 會建立一個,之後可以直接建立環境變數 在執行的時候不會執行 application,但是環境變數還是會進去 如果git clone的話會因為沒有那個檔案而出錯,可以私下跟擁有者要 如果要給對方看架構,可以把 appliction.yml 改成 application.yml.sample 把骨架給他看 所以以後如果拿到有.sample的檔案就可以把.sample刪掉然後再修改裡面的資料就跑得動了 拿到新專案記得去 config 裡面看有沒有 sample檔 把原來的變數換成環境變數 ```ruby= def destroy # 把使用的變數改成環境變數名稱 session[ENV['session_name']] = nil # 轉 redirect_to root_path end ``` ## 資料庫關聯 通常會加一個欄位做對應 慣例:要連結到的 model 名字小寫_id (格式是 integer) ```shell= $ rails g migration add_user_id_to_restaurant ``` ```ruby= class AddUserIdToRestaurant < ActiveRecord::Migration[6.1] def change add_column :restaurant, :user_id, :integer add_index :restaurant, :user_id #等同上面兩行 #會自動生成id add_references(:restaurants, :user) # 會自動產生 index # add_references(:restaurants, :user, index: false) => 不要產生 index end end ``` 可使用 add_reference 功能 [Ruby API](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_reference) 如果一開始在建立 model 時已經想到了,可直接做關聯 ```shell= $rails g model Menu title restaurant_id:integer $rails g model Menu title restaurant:belongs_to ``` 建立關聯 model層的設定,使model互相有關聯 models/user.rb ```ruby= has_many :restaurants #最好使用複數 # 如果只有一間餐廳可用 has_one :restaurant # 有上面的設定才能使用下面的指令 > u.restaurants -> Restaurant.where(user_id: u.id) has_many 會建立下面兩個方法 - restaurants - restaurants= ``` has_many 做的東西有點像是 attr_accessor 會自動幫你生出好幾個方法,包含長出reader跟writer * has_many 跟 belongs to 只是為了快速找到相關的東西跟建立方法使用 models/restaurant.rb ```ruby= class Restaurant < ApplicationRecord belongs_to :user end ``` 設定 belongs_to 會把 user_id 設為必填值 (rails 5 之後的功能) (如果要把它關掉的話可以寫 `belongs_to :user, optional: true` 把選填打開 `optional: false` 必填) 在找資料的時候可以連用方法 ```shell= > r1 = restaurant.first > r1.user ## 要有 belongs_to 的話才可以使用 > r1.user.restaurants ## r1 擁有者所擁有的所有餐廳 ## 極端用法 > r1.user.restaurants.first.user.restaurants.last.user.email ``` lazy querying ```ruby= r2 = Restaurant.new(title: 'ccc') >r2.errors.any? >r2.errors.full_messages #顯示錯誤訊息內容 >r2.user_id = 10 ``` 沒有要從餐廳反查使用者的資訊的話可以不使用 belongs_to 複習 SQL ```sql= $ select * from restaurants where user_id = 7 $ Restaurant.where(user_id: 7) $ Restaurant.where(user_id: u7.id) ``` 在 restaurants_controller 驗證是否登入 ```ruby= def new if user_signed_in? # 出錯,沒有這個方法 @restaurant = Restaurant.new else redirect_to root_path end end ``` 放新方法到 private 裡面 ```ruby= def current_user User.find_by(id: session[ENV['session_name']]) end def user_signed_in? if session[ENV['session_name']] return true else return false end end ``` 加入確認是否登入的方法(放在private後面) ```ruby= def check_user! redirect_to root_path if not user_signed_in? end ``` 執行方法前都會先執行這個方法 ```ruby= before_action :check_user!, except: [:index, :show] ``` 簡化 new 方法 ```ruby= def new @restaurant = Restaurant.new end ``` 因為在其他controller也可能會使用,所以把方法直接放到上層裡去 之後如果需要確認使用者是否登入的話就直接使用 controllers/application_controller ```ruby= def current_user User.find_by(id: session[ENV['session_name']]) end def user_signed_in? if session[ENV['session_name']] return true else return false end end def check_user! redirect_to root_path if not user_signed_in? end ``` 因為 view helper 只有 view 可以用,controller 的方法只有 controller 可以用, 目前在 view 跟 controller 中都需要使用相同的方法,所以要寫兩次 所以可以在 controller 中加入特別的指令讓 view helper 也能有這個方法 controllers/application_controller ```ruby= helper_method :current_user, :user_signed_in? # 把方法匯出讓 helper 也可以使用 ``` 這樣 sessions_helper 裡的方法就可以拿掉 ```ruby= module SesssionsHelper # 變成什麼都沒有 end ``` 或是可以直接把 view helper 的模組在 controller 裡面 include 進來 (如果在 view 裡面用的比較多就放在 view 裡面,在 controller 裡面用比較多就放在 controller 裡面,之後再做處理) views/restaurants/_hello.html.erb ```ruby= <div class="field"> <%= f.label :user %> <%= f.text_field :user %> </div> ``` 如果這樣做的話可能會有不能放未清洗資料進系統的錯誤 直接清洗 user id 也可以解決但最好不要 restaurants_controller ```ruby= def create # @restaurant = Restaurant.new(restaurant_params) => 跟下面其中一個結合 # @restaurant.user_id = current_user.id => 把 id 傳給他 # @restaurant.user = current_user => 直接給他 user (同上一行) # 或是直接使用下面一行就可以了 @restaurant = current_user.restaurants.new(restaurant_params) if @restaurant.save redirect_to restaurants_path else render :new end end ``` 透過目前登入的 session 去抓欄位做更新,因為已經有對應的 user_id,所以會直接帶入 限制使用者只能看到自己擁有的餐廳 restaurants_controller (放在 private 底下) ```ruby= def find_restaurant # @restaurant = Restaurant.find(params[:id]) # 方法一 餐廳角度出發 @restaurant = Restaurant.find_by!( id: params[:id], user_id: current_user.id ) # 方法二 使用者角度出發 @restaurant = current_user.restaurants.find(params[:id]) end ``` 如果 show 底下沒有要檢查的話:把本來加在 find_restaurant 底下的方法直接加到 show 底下,然後從 before_action 裡面拿掉 ```ruby= before_action :find_restaurant, only: [:edit, :update, :destroy] def show @restaurant = Restaurant.find(params[:id]) end ``` 此時如果對非使用者所有的餐廳做編輯或刪除就會出現 404 (因為 before_action 裡的方法) --- ### 限制權限 - authentication 驗證、有無登入(門禁卡) - authorization 授權,能不能做某件事情 --- ### Rails 6 裡面的 JavaScript 要寫在哪裡? - 被拉到了第一層 `app/javascript/packs/application.js` - 可直接在裡面做編修 - 或是在`packs`層新建一個目錄,再創一個`ooxx.js` `import ''` 過去 - 如果在連結的介面做修改,重新整理要花很多時間 => 因為要整包重新編修 - 如果另外建一個資料夾放 js 檔的話,可以在裡面建立一個 `index.js` 把裡面的檔案都 import 進去 - 最後再從 packs 裡面把 `index.js` 引入 - 可以在 `javascript` 資料夾裡面加入一個 styles 資料夾,把 scss 或 css 檔案放進去,讓 webpack 一起打包 - [CSS-in-JS・可寫文章展現自己的火力](https://2019.stateofcss.com/tw/technologies/css-in-js/) - (原本在 assets/stylesheets 使用 assets pipeline) - 缺點是要一次開兩個server - rails server - bin/webpack-dev-server (JavaScript 的 import、require 差別)[可參考某網紅的文章](https://blog.niclin.tw/2019/10/03/nodejs-require-vs-es6-import-export/) ### webpack => JavaScript 工具 webpacker gem => 把 webpack 打包成一份ruby檔案或靜態檔 (為了讓大家都能使用 webpack 做的) 雖然設定麻煩又囉唆,但還是會妥協 $ bin/webpack-dev-server => 負責打包 rails 裡面 js 的東西 好處:會自動 reload 網頁、重整花較少時間 在 `config/webpacker.yml` 裡面會寫 webpacker 去哪裡找各個檔案包起來 --- ujs - 非侵入式的JS / Unobtrusive JavaScript 把 JavaScript 程式和 HTML 區分開來,讓程式碼看起來比較乾淨和整齊 [* SPA - 單頁應用 Single Page Application](https://en.wikipedia.org/wiki/Single-page_application) --- ## 題外話 GDPR 規定公司要問使用者是否可以塞 cookie 到使用者個電腦裡 個資法 request 送出時會有 header, body 瀏覽器版本 作業系統.... header 裡面會有一個是 cookie 資料 第一次進入時網站會問你是否可以塞入 可以的話下次瀏覽該網頁就會讀取你的cookie,如果有設定,網頁就會記住你的設定同不同意、取消、識別用 cookie 換裝置的話會無效 cookie 會跟 header 裡面其他資訊配合起來之後使用,如果換電腦、換瀏覽器... cookie 就不會適用 cookie 會設定時效性,有些是用完就消失、有些是一天、有些是一個月... 例:flash[] 就是一種 cookie,用完就失效 User-agent [MDN 說明](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent) cookie hijacking 劫持 cookie 從網路設備把你的 cookie 攔下來,然後用你的 cookie 去做壞事 最好不要相信免費網路,經過特別的網路裝置可能就會被攔截 cookie wireshark 分析網路行為 看裝置送封包到哪裡去 去 Gemfile 找 rack-mini-compiler 可以把左上角的查詢狀態關掉 如果使用rails 功能跑很久還出不來的話可使用: ```shell= $ spring stop // 將暫存停下來,偶爾卡住的時候可以使用 ``` hotwire 讓前端和後端做即時通訊 websocket 透過actioncable向後端要東西放到 HTML 上 讓網頁按到某個地方的時候把form_for傳進來 讓 view 簡單化,hotwire [官網連結](https://hotwire.dev/) [inline editing](https://demos.telerik.com/kendo-ui/grid/editing-inline) inline editing rails => 新的 hotwire 會跟後端要東西蓋在原本表格上,編輯完在直接打回後端確認資料儲存並更新到前端 hirb-unicode 套件 https://rubygems.org/gems/hirb-unicode?locale=zh-TW ![](https://i.imgur.com/ahmPqmT.png) ujs 非侵入式 javascript 在 html 裡面 data- 開頭的東西就是用這個 turbolink xhr 非同步傳輸 跟一般HTML寫法不一樣 所以 DOMContentLoaded 會失效 前端工具 actioncable js跟websocket工具 從後端叫前端把資料整個換掉 activestorage 檔案上傳使用 channel 即時訊息使用

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully