From fff7ebd1b57467ed43925de33cfe013f6e61e5a7 Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Tue, 28 Mar 2017 11:17:30 +0800
Subject: [PATCH 01/14] Change minor TODO to FIXME

---
 public/js/index.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/public/js/index.js b/public/js/index.js
index 8c4172c..5dff54d 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -314,7 +314,7 @@ if (!textit) {
 const editorInstance = new Editor()
 var editor = editorInstance.init(textit)
 
-// TODO: global referncing in jquery-textcomplete patch
+// FIXME: global referncing in jquery-textcomplete patch
 window.editor = editor
 
 var inlineAttach = inlineAttachment.editors.codemirror4.attach(editor)
@@ -365,8 +365,8 @@ function updateStatusBar () {
 }
 
 //  initalize ui reference
-// TODO: fix ui exporting
 const ui = getUIElements()
+// FIXME: fix global ui element expose
 window.ui = ui
 
 // page actions

From 81666a726c831af23e1d04d18e8efae3ecc0930d Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Tue, 28 Mar 2017 11:18:36 +0800
Subject: [PATCH 02/14] Impl multiple codemirror event listener

---
 public/js/index.js            | 18 +++++++++---------
 public/js/lib/editor/index.js | 13 +++++++++++++
 2 files changed, 22 insertions(+), 9 deletions(-)

diff --git a/public/js/index.js b/public/js/index.js
index 5dff54d..e909eb4 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -2104,7 +2104,7 @@ function iterateLine (line) {
     }
   }
 }
-editor.on('update', function () {
+editorInstance.on('update', function () {
   $('.authorship-gutter:not([data-original-title])').tooltip({
     container: '.CodeMirror-lines',
     placement: 'right',
@@ -2655,7 +2655,7 @@ function enforceMaxLength (cm, change) {
   return false
 }
 var ignoreEmitEvents = ['setValue', 'ignoreHistory']
-editor.on('beforeChange', function (cm, change) {
+editorInstance.on('beforeChange', function (cm, change) {
   if (debug) { console.debug(change) }
   removeNullByte(cm, change)
   if (enforceMaxLength(cm, change)) {
@@ -2683,13 +2683,13 @@ editor.on('beforeChange', function (cm, change) {
   }
   if (cmClient && !socket.connected) { cmClient.editorAdapter.ignoreNextChange = true }
 })
-editor.on('cut', function () {
+editorInstance.on('cut', function () {
     // na
 })
-editor.on('paste', function () {
+editorInstance.on('paste', function () {
     // na
 })
-editor.on('changes', function (cm, changes) {
+editorInstance.on('changes', function (cm, changes) {
   updateHistory()
   var docLength = editor.getValue().length
     // workaround for big documents
@@ -2713,7 +2713,7 @@ editor.on('changes', function (cm, changes) {
     }
   }
 })
-editor.on('focus', function (cm) {
+editorInstance.on('focus', function (cm) {
   for (var i = 0; i < window.onlineUsers.length; i++) {
     if (window.onlineUsers[i].id === window.personalInfo.id) {
       window.onlineUsers[i].cursor = editor.getCursor()
@@ -2722,11 +2722,11 @@ editor.on('focus', function (cm) {
   window.personalInfo['cursor'] = editor.getCursor()
   socket.emit('cursor focus', editor.getCursor())
 })
-editor.on('cursorActivity', function (cm) {
+editorInstance.on('cursorActivity', function (cm) {
   updateStatusBar()
   cursorActivity()
 })
-editor.on('beforeSelectionChange', function (doc, selections) {
+editorInstance.on('beforeSelectionChange', function (doc, selections) {
   if (selections) { selection = selections.ranges[0] } else { selection = null }
   updateStatusBar()
 })
@@ -2744,7 +2744,7 @@ function cursorActivityInner () {
     socket.emit('cursor activity', editor.getCursor())
   }
 }
-editor.on('blur', function (cm) {
+editorInstance.on('blur', function (cm) {
   for (var i = 0; i < window.onlineUsers.length; i++) {
     if (window.onlineUsers[i].id === window.personalInfo.id) {
       window.onlineUsers[i].cursor = null
diff --git a/public/js/lib/editor/index.js b/public/js/lib/editor/index.js
index 6ae40d8..8d61724 100644
--- a/public/js/lib/editor/index.js
+++ b/public/js/lib/editor/index.js
@@ -116,6 +116,19 @@ export default class Editor {
         utils.wrapTextWith(this.editor, cm, 'Backspace')
       }
     }
+    this.eventListeners = {}
+  }
+
+  on (event, cb) {
+    if (!this.eventListeners[event]) {
+      this.eventListeners[event] = [cb]
+    } else {
+      this.eventListeners[event].push(cb)
+    }
+
+    this.editor.on(event, (...args) => {
+      this.eventListeners[event].forEach(cb => cb(...args))
+    })
   }
 
   getStatusBarTemplate (callback) {

From b86ecb1342673628151f729edc0c8714b1d07de0 Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Tue, 28 Mar 2017 11:57:44 +0800
Subject: [PATCH 03/14] Extract selection update from updateStatusbar

---
 public/js/index.js            | 56 +++++++++++++++++++----------------
 public/js/lib/editor/index.js |  3 +-
 public/views/statusbar.html   |  7 +++--
 3 files changed, 38 insertions(+), 28 deletions(-)

diff --git a/public/js/index.js b/public/js/index.js
index e909eb4..6754a92 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -320,33 +320,10 @@ window.editor = editor
 var inlineAttach = inlineAttachment.editors.codemirror4.attach(editor)
 defaultTextHeight = parseInt($('.CodeMirror').css('line-height'))
 
-var selection = null
-
 function updateStatusBar () {
   if (!editorInstance.statusBar) return
   var cursor = editor.getCursor()
   var cursorText = 'Line ' + (cursor.line + 1) + ', Columns ' + (cursor.ch + 1)
-  if (selection) {
-    var anchor = selection.anchor
-    var head = selection.head
-    var start = head.line <= anchor.line ? head : anchor
-    var end = head.line >= anchor.line ? head : anchor
-    var selectionText = ' — Selected '
-    var selectionCharCount = Math.abs(head.ch - anchor.ch)
-    // borrow from brackets EditorStatusBar.js
-    if (start.line !== end.line) {
-      var lines = end.line - start.line + 1
-      if (end.ch === 0) {
-        lines--
-      }
-      selectionText += lines + ' lines'
-    } else if (selectionCharCount > 0) {
-      selectionText += selectionCharCount + ' columns'
-    }
-    if (start.line !== end.line || selectionCharCount > 0) {
-      cursorText += selectionText
-    }
-  }
   editorInstance.statusCursor.text(cursorText)
   var fileText = ' — ' + editor.lineCount() + ' Lines'
   editorInstance.statusFile.text(fileText)
@@ -2726,9 +2703,38 @@ editorInstance.on('cursorActivity', function (cm) {
   updateStatusBar()
   cursorActivity()
 })
+
+editorInstance.on('beforeSelectionChange', updateStatusBar)
 editorInstance.on('beforeSelectionChange', function (doc, selections) {
-  if (selections) { selection = selections.ranges[0] } else { selection = null }
-  updateStatusBar()
+  // check selection and whether the statusbar has added
+  if (selections && editorInstance.statusSelection) {
+    const selection = selections.ranges[0]
+
+    const anchor = selection.anchor
+    const head = selection.head
+    const start = head.line <= anchor.line ? head : anchor
+    const end = head.line >= anchor.line ? head : anchor
+    const selectionCharCount = Math.abs(head.ch - anchor.ch)
+
+    let selectionText = ' — Selected '
+
+    // borrow from brackets EditorStatusBar.js
+    if (start.line !== end.line) {
+      var lines = end.line - start.line + 1
+      if (end.ch === 0) {
+        lines--
+      }
+      selectionText += lines + ' lines'
+    } else if (selectionCharCount > 0) {
+      selectionText += selectionCharCount + ' columns'
+    }
+
+    if (start.line !== end.line || selectionCharCount > 0) {
+      editorInstance.statusSelection.text(selectionText)
+    } else {
+      editorInstance.statusSelection.text('')
+    }
+  }
 })
 
 var cursorActivity = _.debounce(cursorActivityInner, cursorActivityDebounce)
diff --git a/public/js/lib/editor/index.js b/public/js/lib/editor/index.js
index 8d61724..6eec34a 100644
--- a/public/js/lib/editor/index.js
+++ b/public/js/lib/editor/index.js
@@ -144,7 +144,8 @@ export default class Editor {
       return
     }
     this.statusBar = $(this.statusBarTemplate)
-    this.statusCursor = this.statusBar.find('.status-cursor')
+    this.statusCursor = this.statusBar.find('.status-cursor > .status-line-column')
+    this.statusSelection = this.statusBar.find('.status-cursor > .status-selection')
     this.statusFile = this.statusBar.find('.status-file')
     this.statusIndicators = this.statusBar.find('.status-indicators')
     this.statusIndent = this.statusBar.find('.status-indent')
diff --git a/public/views/statusbar.html b/public/views/statusbar.html
index 068b8d6..24cbf6c 100644
--- a/public/views/statusbar.html
+++ b/public/views/statusbar.html
@@ -1,6 +1,9 @@
 <div class="status-bar">
     <div class="status-info">
-        <div class="status-cursor"></div>
+        <div class="status-cursor">
+          <span class="status-line-column"></span>
+          <span class="status-selection"></span>
+        </div>
         <div class="status-file"></div>
     </div>
     <div class="status-indicators">
@@ -35,4 +38,4 @@
             <a class="ui-spellcheck-toggle" title="Toggle spellcheck"><i class="fa fa-check fa-fw"></i></a>
         </div>
     </div>
-</div>
\ No newline at end of file
+</div>

From d7c068cbfdc8d6173ea722ce02f9151d8e5ff807 Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Tue, 28 Mar 2017 12:10:35 +0800
Subject: [PATCH 04/14] Rewrite cursorActivity with multi listener style

- adjust function order to prevent standard lint failure
---
 public/js/index.js | 34 +++++++++++++++++-----------------
 1 file changed, 17 insertions(+), 17 deletions(-)

diff --git a/public/js/index.js b/public/js/index.js
index 6754a92..4059431 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -2699,10 +2699,23 @@ editorInstance.on('focus', function (cm) {
   window.personalInfo['cursor'] = editor.getCursor()
   socket.emit('cursor focus', editor.getCursor())
 })
-editorInstance.on('cursorActivity', function (cm) {
-  updateStatusBar()
-  cursorActivity()
-})
+
+const cursorActivity = _.debounce(cursorActivityInner, cursorActivityDebounce)
+
+function cursorActivityInner () {
+  if (editorHasFocus() && !Visibility.hidden()) {
+    for (var i = 0; i < window.onlineUsers.length; i++) {
+      if (window.onlineUsers[i].id === window.personalInfo.id) {
+        window.onlineUsers[i].cursor = editor.getCursor()
+      }
+    }
+    window.personalInfo['cursor'] = editor.getCursor()
+    socket.emit('cursor activity', editor.getCursor())
+  }
+}
+
+editorInstance.on('cursorActivity', updateStatusBar)
+editorInstance.on('cursorActivity', cursorActivity)
 
 editorInstance.on('beforeSelectionChange', updateStatusBar)
 editorInstance.on('beforeSelectionChange', function (doc, selections) {
@@ -2737,19 +2750,6 @@ editorInstance.on('beforeSelectionChange', function (doc, selections) {
   }
 })
 
-var cursorActivity = _.debounce(cursorActivityInner, cursorActivityDebounce)
-
-function cursorActivityInner () {
-  if (editorHasFocus() && !Visibility.hidden()) {
-    for (var i = 0; i < window.onlineUsers.length; i++) {
-      if (window.onlineUsers[i].id === window.personalInfo.id) {
-        window.onlineUsers[i].cursor = editor.getCursor()
-      }
-    }
-    window.personalInfo['cursor'] = editor.getCursor()
-    socket.emit('cursor activity', editor.getCursor())
-  }
-}
 editorInstance.on('blur', function (cm) {
   for (var i = 0; i < window.onlineUsers.length; i++) {
     if (window.onlineUsers[i].id === window.personalInfo.id) {

From 579dda951517ea688784f57a15e4152c559bc4ad Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Tue, 28 Mar 2017 12:11:05 +0800
Subject: [PATCH 05/14] Update focus argument with cm instance

---
 public/js/index.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/js/index.js b/public/js/index.js
index 4059431..5b3d477 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -2690,7 +2690,7 @@ editorInstance.on('changes', function (cm, changes) {
     }
   }
 })
-editorInstance.on('focus', function (cm) {
+editorInstance.on('focus', function (editor) {
   for (var i = 0; i < window.onlineUsers.length; i++) {
     if (window.onlineUsers[i].id === window.personalInfo.id) {
       window.onlineUsers[i].cursor = editor.getCursor()

From af5ef52f4b3f77326a97cf733f6bdb680d14f927 Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Tue, 28 Mar 2017 12:15:56 +0800
Subject: [PATCH 06/14] Add cm instance to cursorActivity argument

---
 public/js/index.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/public/js/index.js b/public/js/index.js
index 5b3d477..82f647d 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -1845,7 +1845,7 @@ socket.on('disconnect', function (data) {
 socket.on('reconnect', function (data) {
     // sync back any change in offline
   emitUserStatus(true)
-  cursorActivity()
+  cursorActivity(editor)
   socket.emit('online users')
 })
 socket.on('connect', function (data) {
@@ -2702,7 +2702,7 @@ editorInstance.on('focus', function (editor) {
 
 const cursorActivity = _.debounce(cursorActivityInner, cursorActivityDebounce)
 
-function cursorActivityInner () {
+function cursorActivityInner (editor) {
   if (editorHasFocus() && !Visibility.hidden()) {
     for (var i = 0; i < window.onlineUsers.length; i++) {
       if (window.onlineUsers[i].id === window.personalInfo.id) {

From 46ed658d8bd4fb916dd4964692aa23dc18c128ca Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Tue, 28 Mar 2017 15:24:52 +0800
Subject: [PATCH 07/14] Promisify getStatusBarTemplate method

---
 public/js/lib/editor/index.js | 54 ++++++++++++++++++-----------------
 1 file changed, 28 insertions(+), 26 deletions(-)

diff --git a/public/js/lib/editor/index.js b/public/js/lib/editor/index.js
index 6eec34a..a68092f 100644
--- a/public/js/lib/editor/index.js
+++ b/public/js/lib/editor/index.js
@@ -131,38 +131,40 @@ export default class Editor {
     })
   }
 
-  getStatusBarTemplate (callback) {
-    $.get(window.serverurl + '/views/statusbar.html', template => {
-      this.statusBarTemplate = template
-      if (callback) callback()
+  getStatusBarTemplate () {
+    return new Promise((resolve, reject) => {
+      $.get(window.serverurl + '/views/statusbar.html').done(template => {
+        this.statusBarTemplate = template
+        resolve()
+      }).fail(reject)
     })
   }
 
   addStatusBar () {
     if (!this.statusBarTemplate) {
-      this.getStatusBarTemplate(this.addStatusBar)
-      return
-    }
-    this.statusBar = $(this.statusBarTemplate)
-    this.statusCursor = this.statusBar.find('.status-cursor > .status-line-column')
-    this.statusSelection = this.statusBar.find('.status-cursor > .status-selection')
-    this.statusFile = this.statusBar.find('.status-file')
-    this.statusIndicators = this.statusBar.find('.status-indicators')
-    this.statusIndent = this.statusBar.find('.status-indent')
-    this.statusKeymap = this.statusBar.find('.status-keymap')
-    this.statusLength = this.statusBar.find('.status-length')
-    this.statusTheme = this.statusBar.find('.status-theme')
-    this.statusSpellcheck = this.statusBar.find('.status-spellcheck')
-    this.statusPreferences = this.statusBar.find('.status-preferences')
-    this.statusPanel = this.editor.addPanel(this.statusBar[0], {
-      position: 'bottom'
-    })
+      this.getStatusBarTemplate.then(this.addStatusBar)
+    } else {
+      this.statusBar = $(this.statusBarTemplate)
+      this.statusCursor = this.statusBar.find('.status-cursor > .status-line-column')
+      this.statusSelection = this.statusBar.find('.status-cursor > .status-selection')
+      this.statusFile = this.statusBar.find('.status-file')
+      this.statusIndicators = this.statusBar.find('.status-indicators')
+      this.statusIndent = this.statusBar.find('.status-indent')
+      this.statusKeymap = this.statusBar.find('.status-keymap')
+      this.statusLength = this.statusBar.find('.status-length')
+      this.statusTheme = this.statusBar.find('.status-theme')
+      this.statusSpellcheck = this.statusBar.find('.status-spellcheck')
+      this.statusPreferences = this.statusBar.find('.status-preferences')
+      this.statusPanel = this.editor.addPanel(this.statusBar[0], {
+        position: 'bottom'
+      })
 
-    this.setIndent()
-    this.setKeymap()
-    this.setTheme()
-    this.setSpellcheck()
-    this.setPreferences()
+      this.setIndent()
+      this.setKeymap()
+      this.setTheme()
+      this.setSpellcheck()
+      this.setPreferences()
+    }
   }
 
   setIndent () {

From df743ab902e1427b12842ff9542ce9189c37f2b6 Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Tue, 28 Mar 2017 17:11:20 +0800
Subject: [PATCH 08/14] =?UTF-8?q?Fix=20listener=20=E2=80=9Cthis=E2=80=9D?=
 =?UTF-8?q?=20context?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 public/js/lib/editor/index.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/js/lib/editor/index.js b/public/js/lib/editor/index.js
index a68092f..dead0c4 100644
--- a/public/js/lib/editor/index.js
+++ b/public/js/lib/editor/index.js
@@ -127,7 +127,7 @@ export default class Editor {
     }
 
     this.editor.on(event, (...args) => {
-      this.eventListeners[event].forEach(cb => cb(...args))
+      this.eventListeners[event].forEach(cb => cb.bind(this)(...args))
     })
   }
 

From f5b95c5d3692169b48daff3c6f3538f53dd62380 Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Tue, 28 Mar 2017 17:16:32 +0800
Subject: [PATCH 09/14] Move updateStatusBar method into editor class

---
 public/js/index.js             | 34 +++++++---------------------------
 public/js/lib/editor/config.js |  5 +++++
 public/js/lib/editor/index.js  | 23 +++++++++++++++++++++++
 3 files changed, 35 insertions(+), 27 deletions(-)
 create mode 100644 public/js/lib/editor/config.js

diff --git a/public/js/index.js b/public/js/index.js
index 82f647d..d51ecb5 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -79,6 +79,7 @@ var renderer = require('./render')
 var preventXSS = renderer.preventXSS
 
 import Editor from './lib/editor'
+import EditorConfig from './lib/editor/config'
 
 import getUIElements from './lib/editor/ui-elements'
 
@@ -320,27 +321,6 @@ window.editor = editor
 var inlineAttach = inlineAttachment.editors.codemirror4.attach(editor)
 defaultTextHeight = parseInt($('.CodeMirror').css('line-height'))
 
-function updateStatusBar () {
-  if (!editorInstance.statusBar) return
-  var cursor = editor.getCursor()
-  var cursorText = 'Line ' + (cursor.line + 1) + ', Columns ' + (cursor.ch + 1)
-  editorInstance.statusCursor.text(cursorText)
-  var fileText = ' — ' + editor.lineCount() + ' Lines'
-  editorInstance.statusFile.text(fileText)
-  var docLength = editor.getValue().length
-  editorInstance.statusLength.text('Length ' + docLength)
-  if (docLength > (docmaxlength * 0.95)) {
-    editorInstance.statusLength.css('color', 'red')
-    editorInstance.statusLength.attr('title', 'Your almost reach note max length limit.')
-  } else if (docLength > (docmaxlength * 0.8)) {
-    editorInstance.statusLength.css('color', 'orange')
-    editorInstance.statusLength.attr('title', 'You nearly fill the note, consider to make more pieces.')
-  } else {
-    editorInstance.statusLength.css('color', 'white')
-    editorInstance.statusLength.attr('title', 'You could write up to ' + docmaxlength + ' characters in this note.')
-  }
-}
-
 //  initalize ui reference
 const ui = getUIElements()
 // FIXME: fix global ui element expose
@@ -830,7 +810,7 @@ function changeMode (type) {
     // add and update status bar
     if (!editorInstance.statusBar) {
       editorInstance.addStatusBar()
-      updateStatusBar()
+      editorInstance.updateStatusBar()
     }
     // work around foldGutter might not init properly
     editor.setOption('foldGutter', false)
@@ -2105,12 +2085,12 @@ socket.on('check', function (data) {
 socket.on('permission', function (data) {
   updatePermission(data.permission)
 })
-var docmaxlength = null
+
 var permission = null
 socket.on('refresh', function (data) {
     // console.log(data);
-  docmaxlength = data.docmaxlength
-  editor.setOption('maxLength', docmaxlength)
+  EditorConfig.docmaxlength = data.docmaxlength
+  editor.setOption('maxLength', EditorConfig.docmaxlength)
   updateInfo(data)
   updatePermission(data.permission)
   if (!window.loaded) {
@@ -2714,10 +2694,10 @@ function cursorActivityInner (editor) {
   }
 }
 
-editorInstance.on('cursorActivity', updateStatusBar)
+editorInstance.on('cursorActivity', editorInstance.updateStatusBar)
 editorInstance.on('cursorActivity', cursorActivity)
 
-editorInstance.on('beforeSelectionChange', updateStatusBar)
+editorInstance.on('beforeSelectionChange', editorInstance.updateStatusBar)
 editorInstance.on('beforeSelectionChange', function (doc, selections) {
   // check selection and whether the statusbar has added
   if (selections && editorInstance.statusSelection) {
diff --git a/public/js/lib/editor/config.js b/public/js/lib/editor/config.js
new file mode 100644
index 0000000..9508b84
--- /dev/null
+++ b/public/js/lib/editor/config.js
@@ -0,0 +1,5 @@
+let config = {
+  docmaxlength: null
+}
+
+export default config
diff --git a/public/js/lib/editor/index.js b/public/js/lib/editor/index.js
index dead0c4..c807a17 100644
--- a/public/js/lib/editor/index.js
+++ b/public/js/lib/editor/index.js
@@ -1,4 +1,5 @@
 import * as utils from './utils'
+import config from './config'
 
 /* config section */
 const isMac = CodeMirror.keyMap.default === CodeMirror.keyMap.macDefault
@@ -167,6 +168,28 @@ export default class Editor {
     }
   }
 
+  updateStatusBar () {
+    if (!this.statusBar) return
+
+    var cursor = this.editor.getCursor()
+    var cursorText = 'Line ' + (cursor.line + 1) + ', Columns ' + (cursor.ch + 1)
+    this.statusCursor.text(cursorText)
+    var fileText = ' — ' + editor.lineCount() + ' Lines'
+    this.statusFile.text(fileText)
+    var docLength = editor.getValue().length
+    this.statusLength.text('Length ' + docLength)
+    if (docLength > (config.docmaxlength * 0.95)) {
+      this.statusLength.css('color', 'red')
+      this.statusLength.attr('title', 'Your almost reach note max length limit.')
+    } else if (docLength > (config.docmaxlength * 0.8)) {
+      this.statusLength.css('color', 'orange')
+      this.statusLength.attr('title', 'You nearly fill the note, consider to make more pieces.')
+    } else {
+      this.statusLength.css('color', 'white')
+      this.statusLength.attr('title', 'You could write up to ' + config.docmaxlength + ' characters in this note.')
+    }
+  }
+
   setIndent () {
     var cookieIndentType = Cookies.get('indent_type')
     var cookieTabSize = parseInt(Cookies.get('tab_size'))

From 7637a6a8a650a8247a02a7c2ccaf5d370b3a0625 Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Tue, 28 Mar 2017 17:32:42 +0800
Subject: [PATCH 10/14] Update cm instance in changes event argument

---
 public/js/index.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/js/index.js b/public/js/index.js
index d51ecb5..f1a101e 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -2646,7 +2646,7 @@ editorInstance.on('cut', function () {
 editorInstance.on('paste', function () {
     // na
 })
-editorInstance.on('changes', function (cm, changes) {
+editorInstance.on('changes', function (editor, changes) {
   updateHistory()
   var docLength = editor.getValue().length
     // workaround for big documents

From ba1bef015fa2a596c9ba86b935f2d76624f53b9e Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Tue, 28 Mar 2017 18:31:36 +0800
Subject: [PATCH 11/14] Update to es6 module import style

---
 public/js/index.js | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/public/js/index.js b/public/js/index.js
index f1a101e..b84cc43 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -12,14 +12,14 @@ require('../css/site.css')
 
 require('highlight.js/styles/github-gist.css')
 
-var toMarkdown = require('to-markdown')
+import toMarkdown from 'to-markdown'
 
-var saveAs = require('file-saver').saveAs
-var randomColor = require('randomcolor')
+import { saveAs } from 'file-saver'
+import randomColor from 'randomcolor'
 
-var _ = require('lodash')
+import _ from 'lodash'
 
-var List = require('list.js')
+import List from 'list.js'
 
 import {
     checkLoginStateChanged,
@@ -75,8 +75,7 @@ import {
     removeHistory
 } from './history'
 
-var renderer = require('./render')
-var preventXSS = renderer.preventXSS
+import { preventXSS } from './render'
 
 import Editor from './lib/editor'
 import EditorConfig from './lib/editor/config'

From b711ecfadb83f59316021f2266234b7fdba54c53 Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Tue, 28 Mar 2017 19:30:06 +0800
Subject: [PATCH 12/14] Drop global variable ui exposing

---
 public/js/extra.js | 9 ++++++---
 public/js/index.js | 2 --
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/public/js/extra.js b/public/js/extra.js
index fecd8a8..a5be425 100644
--- a/public/js/extra.js
+++ b/public/js/extra.js
@@ -19,6 +19,9 @@ require('./lib/common/login')
 require('../vendor/md-toc')
 var Viz = require('viz.js')
 
+import getUIElements from './lib/editor/ui-elements'
+const ui = getUIElements()
+
 // auto update last change
 window.createtime = null
 window.lastchangetime = null
@@ -634,7 +637,7 @@ function generateCleanHTML (view) {
 }
 
 export function exportToRawHTML (view) {
-  const filename = `${renderFilename(window.ui.area.markdown)}.html`
+  const filename = `${renderFilename(ui.area.markdown)}.html`
   const src = generateCleanHTML(view)
   $(src).find('a.anchor').remove()
   const html = src[0].outerHTML
@@ -646,8 +649,8 @@ export function exportToRawHTML (view) {
 
 // extract markdown body to html and compile to template
 export function exportToHTML (view) {
-  const title = renderTitle(window.ui.area.markdown)
-  const filename = `${renderFilename(window.ui.area.markdown)}.html`
+  const title = renderTitle(ui.area.markdown)
+  const filename = `${renderFilename(ui.area.markdown)}.html`
   const src = generateCleanHTML(view)
     // generate toc
   const toc = $('#ui-toc').clone()
diff --git a/public/js/index.js b/public/js/index.js
index b84cc43..86564e9 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -322,8 +322,6 @@ defaultTextHeight = parseInt($('.CodeMirror').css('line-height'))
 
 //  initalize ui reference
 const ui = getUIElements()
-// FIXME: fix global ui element expose
-window.ui = ui
 
 // page actions
 var opts = {

From a938cac42a46a3421473d19dc76d41c3e706ef5c Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Tue, 28 Mar 2017 20:38:31 +0800
Subject: [PATCH 13/14] Fix indentations

---
 public/js/index.js | 100 ++++++++++++++++++++++-----------------------
 1 file changed, 48 insertions(+), 52 deletions(-)

diff --git a/public/js/index.js b/public/js/index.js
index 86564e9..8a7e83b 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -443,29 +443,29 @@ $(document).ready(function () {
     clearMap()
   }
   checkEditorStyle()
-    /* we need this only on touch devices */
+  /* we need this only on touch devices */
   if (window.isTouchDevice) {
-        /* cache dom references */
+    /* cache dom references */
     var $body = jQuery('body')
 
-        /* bind events */
+    /* bind events */
     $(document)
-            .on('focus', 'textarea, input', function () {
-              $body.addClass('fixfixed')
-            })
-            .on('blur', 'textarea, input', function () {
-              $body.removeClass('fixfixed')
-            })
+    .on('focus', 'textarea, input', function () {
+      $body.addClass('fixfixed')
+    })
+    .on('blur', 'textarea, input', function () {
+      $body.removeClass('fixfixed')
+    })
   }
-    // showup
+  // showup
   $().showUp('.navbar', {
     upClass: 'navbar-hide',
     downClass: 'navbar-show'
   })
-    // tooltip
+  // tooltip
   $('[data-toggle="tooltip"]').tooltip()
-    // shortcuts
-    // allow on all tags
+  // shortcuts
+  // allow on all tags
   key.filter = function (e) { return true }
   key('ctrl+alt+e', function (e) {
     changeMode(modeType.edit)
@@ -476,7 +476,7 @@ $(document).ready(function () {
   key('ctrl+alt+b', function (e) {
     changeMode(modeType.both)
   })
-    // toggle-dropdown
+  // toggle-dropdown
   $(document).on('click', '.toggle-dropdown .dropdown-menu', function (e) {
     e.stopPropagation()
   })
@@ -491,10 +491,10 @@ $(window).resize(function () {
 })
 // when page unload
 $(window).on('unload', function () {
-    // updateHistoryInner();
+// updateHistoryInner();
 })
 $(window).on('error', function () {
-    // setNeedRefresh();
+  // setNeedRefresh();
 })
 
 setupSyncAreas(ui.area.codemirrorScroll, ui.area.view, ui.area.markdown)
@@ -516,7 +516,7 @@ function windowResizeInner (callback) {
   checkEditorStyle()
   checkTocStyle()
   checkCursorMenu()
-    // refresh editor
+  // refresh editor
   if (window.loaded) {
     if (editor.getOption('scrollbarStyle') === 'native') {
       setTimeout(function () {
@@ -526,13 +526,13 @@ function windowResizeInner (callback) {
         if (callback && typeof callback === 'function') { callback() }
       }, 1)
     } else {
-            // force it load all docs at once to prevent scroll knob blink
+      // force it load all docs at once to prevent scroll knob blink
       editor.setOption('viewportMargin', Infinity)
       setTimeout(function () {
         clearMap()
         autoSyncscroll()
         editor.setOption('viewportMargin', viewportMargin)
-                // add or update user cursors
+        // add or update user cursors
         for (var i = 0; i < window.onlineUsers.length; i++) {
           if (window.onlineUsers[i].id !== window.personalInfo.id) { buildCursor(window.onlineUsers[i]) }
         }
@@ -581,9 +581,9 @@ function checkEditorStyle () {
     ui.area.codemirrorScroll.css('height', '')
     ui.area.codemirrorScroll.css('min-height', desireHeight + 'px')
   }
-    // workaround editor will have wrong doc height when editor height changed
+  // workaround editor will have wrong doc height when editor height changed
   editor.setSize(null, ui.area.edit.height())
-    // make editor resizable
+  // make editor resizable
   if (!ui.area.resize.handle.length) {
     ui.area.edit.resizable({
       handles: 'e',
@@ -662,8 +662,8 @@ var checkEditorScrollbar = _.debounce(function () {
 }, 50)
 
 function checkEditorScrollbarInner () {
-    // workaround simple scroll bar knob
-    // will get wrong position when editor height changed
+  // workaround simple scroll bar knob
+  // will get wrong position when editor height changed
   var scrollInfo = editor.getScrollInfo()
   editor.scrollTo(null, scrollInfo.top - 1)
   editor.scrollTo(null, scrollInfo.top)
@@ -687,7 +687,7 @@ function checkTocStyle () {
   } else {
     newbool = false
   }
-    // toc scrollspy
+  // toc scrollspy
   ui.toc.toc.removeClass('scrollspy-body, scrollspy-view')
   ui.toc.affix.removeClass('scrollspy-body, scrollspy-view')
   if (window.currentMode === modeType.both) {
@@ -1895,10 +1895,6 @@ function initMarkAndCheckGutter (mark, author, timestamp) {
   }
   return mark
 }
-var gutterStylePrefix = 'border-left: 3px solid '
-var gutterStylePostfix = '; height: ' + defaultTextHeight + 'px; margin-left: 3px;'
-var textMarkderStylePrefix = 'background-image: linear-gradient(to top, '
-var textMarkderStylePostfix = ' 1px, transparent 1px);'
 var addStyleRule = (function () {
   var added = {}
   var styleElement = document.createElement('style')
@@ -2015,11 +2011,11 @@ function updateAuthorshipInner () {
   for (let i = 0; i < addTextMarkers.length; i++) {
     let textMarker = addTextMarkers[i]
     let author = authors[textMarker.userid]
-    var rgbcolor = hex2rgb(author.color)
-    var colorString = 'rgba(' + rgbcolor.red + ',' + rgbcolor.green + ',' + rgbcolor.blue + ',0.7)'
-    var styleString = textMarkderStylePrefix + colorString + textMarkderStylePostfix
-    let className = 'authorship-inline-' + author.color.substr(1)
-    var rule = '.' + className + '{' + styleString + '}'
+    const rgbcolor = hex2rgb(author.color)
+    const colorString = `rgba(${rgbcolor.red},${rgbcolor.green},${rgbcolor.blue},0.7)`
+    const styleString = `background-image: linear-gradient(to top, ${colorString} 1px, transparent 1px);`
+    let className = `authorship-inline-${author.color.substr(1)}`
+    const rule = `.${className} { ${styleString} }`
     addStyleRule(rule)
     editor.markText(textMarker.pos[0], textMarker.pos[1], {
       className: 'authorship-inline ' + className,
@@ -2033,12 +2029,12 @@ function iterateLine (line) {
   var author = currMark ? authors[currMark.gutter.userid] : null
   if (currMark && author) {
     let className = 'authorship-gutter-' + author.color.substr(1)
-    var gutters = line.gutterMarkers
+    const gutters = line.gutterMarkers
     if (!gutters || !gutters['authorship-gutters'] ||
-            !gutters['authorship-gutters'].className ||
-            !gutters['authorship-gutters'].className.indexOf(className) < 0) {
-      var styleString = gutterStylePrefix + author.color + gutterStylePostfix
-      var rule = '.' + className + '{' + styleString + '}'
+        !gutters['authorship-gutters'].className ||
+        !gutters['authorship-gutters'].className.indexOf(className) < 0) {
+      const styleString = `border-left: 3px solid ${author.color}; height: ${defaultTextHeight}px; margin-left: 3px;`
+      const rule = `.${className} { ${styleString} }`
       addStyleRule(rule)
       var gutter = $('<div>', {
         class: 'authorship-gutter ' + className,
@@ -2069,14 +2065,14 @@ editorInstance.on('update', function () {
     placement: 'bottom',
     delay: { 'show': 500, 'hide': 100 }
   })
-    // clear tooltip which described element has been removed
+  // clear tooltip which described element has been removed
   $('[id^="tooltip"]').each(function (index, element) {
     var $ele = $(element)
     if ($('[aria-describedby="' + $ele.attr('id') + '"]').length <= 0) $ele.remove()
   })
 })
 socket.on('check', function (data) {
-    // console.log(data);
+  // console.log(data);
   updateInfo(data)
 })
 socket.on('permission', function (data) {
@@ -2096,7 +2092,7 @@ socket.on('refresh', function (data) {
     if (nocontent) {
       if (window.visibleXS) { window.currentMode = modeType.edit } else { window.currentMode = modeType.both }
     }
-        // parse mode from url
+    // parse mode from url
     if (window.location.search.length > 0) {
       var urlMode = modeType[window.location.search.substr(1)]
       if (urlMode) window.currentMode = urlMode
@@ -2112,9 +2108,9 @@ socket.on('refresh', function (data) {
     emitUserStatus() // send first user status
     updateOnlineStatus() // update first online status
     setTimeout(function () {
-            // work around editor not refresh or doc not fully loaded
+      // work around editor not refresh or doc not fully loaded
       windowResizeInner()
-            // work around might not scroll to hash
+      // work around might not scroll to hash
       scrollToHash()
     }, 1)
   }
@@ -2148,7 +2144,7 @@ socket.on('doc', function (obj) {
     ui.spinner.hide()
     ui.content.fadeIn()
   } else {
-        // if current doc is equal to the doc before disconnect
+    // if current doc is equal to the doc before disconnect
     if (setDoc && bodyMismatch) editor.clearHistory()
     else if (window.lastInfo.history) editor.setHistory(window.lastInfo.history)
     window.lastInfo.history = null
@@ -2156,9 +2152,9 @@ socket.on('doc', function (obj) {
 
   if (!cmClient) {
     cmClient = window.cmClient = new EditorClient(
-            obj.revision, obj.clients,
-            new SocketIOAdapter(socket), new CodeMirrorAdapter(editor)
-        )
+      obj.revision, obj.clients,
+      new SocketIOAdapter(socket), new CodeMirrorAdapter(editor)
+    )
     synchronized_ = cmClient.state
   } else if (setDoc) {
     if (bodyMismatch) {
@@ -2263,11 +2259,11 @@ socket.on('cursor blur', function (data) {
 var options = {
   valueNames: ['id', 'name'],
   item: '<li class="ui-user-item">' +
-            '<span class="id" style="display:none;"></span>' +
-            '<a href="#">' +
-                '<span class="pull-left"><i class="ui-user-icon"></i></span><span class="ui-user-name name"></span><span class="pull-right"><i class="fa fa-circle ui-user-status"></i></span>' +
-            '</a>' +
-           '</li>'
+        '<span class="id" style="display:none;"></span>' +
+        '<a href="#">' +
+            '<span class="pull-left"><i class="ui-user-icon"></i></span><span class="ui-user-name name"></span><span class="pull-right"><i class="fa fa-circle ui-user-status"></i></span>' +
+        '</a>' +
+        '</li>'
 }
 var onlineUserList = new List('online-user-list', options)
 var shortOnlineUserList = new List('short-online-user-list', options)

From a4385ec19dc3ad89a8e084f9bce560c7197275a8 Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Fri, 31 Mar 2017 20:58:58 +0800
Subject: [PATCH 14/14] Update editorconfig

---
 .editorconfig | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/.editorconfig b/.editorconfig
index e271be7..75e2a69 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,12 +1,16 @@
 root = true
 
-# Tab indentation
 [*]
 indent_style = space
 indent_size = 2
 trim_trailing_whitespace = true
 insert_final_newline = true
 
+[{*.html,*.ejs}]
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = true
+
 [*.md]
 trim_trailing_whitespace = false