From db06a51299e0888b07062cefd780d514d09ebd37 Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Sun, 9 Apr 2017 20:05:48 +0800
Subject: [PATCH 01/10] Load statusbar template by string-loader

---
 package.json                                  |  3 +-
 public/js/lib/editor/index.js                 | 54 +++++++------------
 .../{views => js/lib/editor}/statusbar.html   |  0
 webpackBaseConfig.js                          |  3 ++
 yarn.lock                                     |  4 ++
 5 files changed, 29 insertions(+), 35 deletions(-)
 rename public/{views => js/lib/editor}/statusbar.html (100%)

diff --git a/package.json b/package.json
index 29c5c95..3637fd5 100644
--- a/package.json
+++ b/package.json
@@ -162,8 +162,9 @@
     "less-loader": "^2.2.3",
     "optimize-css-assets-webpack-plugin": "^1.3.0",
     "script-loader": "^0.7.0",
-    "style-loader": "^0.13.1",
     "standard": "^9.0.1",
+    "string-loader": "^0.0.1",
+    "style-loader": "^0.13.1",
     "url-loader": "^0.5.7",
     "webpack": "^1.14.0",
     "webpack-parallel-uglify-plugin": "^0.2.0"
diff --git a/public/js/lib/editor/index.js b/public/js/lib/editor/index.js
index c807a17..8cab486 100644
--- a/public/js/lib/editor/index.js
+++ b/public/js/lib/editor/index.js
@@ -1,5 +1,6 @@
 import * as utils from './utils'
 import config from './config'
+import statusBarTemplate from './statusbar.html'
 
 /* config section */
 const isMac = CodeMirror.keyMap.default === CodeMirror.keyMap.macDefault
@@ -132,40 +133,27 @@ export default class Editor {
     })
   }
 
-  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.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.statusBar = $(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()
   }
 
   updateStatusBar () {
@@ -508,8 +496,6 @@ export default class Editor {
       placeholder: "← Start by entering a title here\n===\nVisit /features if you don't know what to do.\nHappy hacking :)"
     })
 
-    this.getStatusBarTemplate()
-
     return this.editor
   }
 
diff --git a/public/views/statusbar.html b/public/js/lib/editor/statusbar.html
similarity index 100%
rename from public/views/statusbar.html
rename to public/js/lib/editor/statusbar.html
diff --git a/webpackBaseConfig.js b/webpackBaseConfig.js
index 87f7cee..36a49f2 100644
--- a/webpackBaseConfig.js
+++ b/webpackBaseConfig.js
@@ -412,6 +412,9 @@ module.exports = {
     }, {
       test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
       loader: 'file'
+    }, {
+      test: /\.html$/,
+      loader: 'string'
     }, {
       test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
       loader: 'url?prefix=font/&limit=5000'
diff --git a/yarn.lock b/yarn.lock
index 360c575..88addc9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6529,6 +6529,10 @@ strict-uri-encode@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
 
+string-loader@^0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/string-loader/-/string-loader-0.0.1.tgz#496f3cccc990213e0dd5411499f9ac6a6a6f2ff8"
+
 string-natural-compare@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-2.0.2.tgz#c5ce4e278ab5d1265ae6fc55435aeb7b76fcb001"

From c6c11c54ef62ed5c87dc7eb8139805a2889cbcc8 Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Sun, 9 Apr 2017 21:14:23 +0800
Subject: [PATCH 02/10] Expose internal editor config variable

---
 public/js/index.js            | 7 +++----
 public/js/lib/editor/index.js | 1 +
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/public/js/index.js b/public/js/index.js
index 9a8ee11..d34bfa9 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -78,7 +78,6 @@ import {
 import { preventXSS } from './render'
 
 import Editor from './lib/editor'
-import EditorConfig from './lib/editor/config'
 
 import getUIElements from './lib/editor/ui-elements'
 
@@ -446,7 +445,7 @@ $(document).ready(function () {
   /* we need this only on touch devices */
   if (window.isTouchDevice) {
     /* cache dom references */
-    var $body = jQuery('body')
+    var $body = $('body')
 
     /* bind events */
     $(document)
@@ -2082,8 +2081,8 @@ socket.on('permission', function (data) {
 var permission = null
 socket.on('refresh', function (data) {
     // console.log(data);
-  EditorConfig.docmaxlength = data.docmaxlength
-  editor.setOption('maxLength', EditorConfig.docmaxlength)
+  editorInstance.config.docmaxlength = data.docmaxlength
+  editor.setOption('maxLength', editorInstance.config.docmaxlength)
   updateInfo(data)
   updatePermission(data.permission)
   if (!window.loaded) {
diff --git a/public/js/lib/editor/index.js b/public/js/lib/editor/index.js
index 8cab486..2991998 100644
--- a/public/js/lib/editor/index.js
+++ b/public/js/lib/editor/index.js
@@ -119,6 +119,7 @@ export default class Editor {
       }
     }
     this.eventListeners = {}
+    this.config = config
   }
 
   on (event, cb) {

From 432f215a456071d166d05ae3f51bf91e454a46e0 Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Tue, 11 Apr 2017 11:37:41 +0800
Subject: [PATCH 03/10] Fix indentation

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

diff --git a/public/js/index.js b/public/js/index.js
index d34bfa9..66e3da2 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -2499,12 +2499,12 @@ function buildCursor (user) {
     cursor.attr('data-mode', 'hover')
     cursortag.delay(2000).fadeOut('fast')
     cursor.hover(
-            function () {
-              if (cursor.attr('data-mode') === 'hover') { cursortag.stop(true).fadeIn('fast') }
-            },
-            function () {
-              if (cursor.attr('data-mode') === 'hover') { cursortag.stop(true).fadeOut('fast') }
-            })
+      function () {
+        if (cursor.attr('data-mode') === 'hover') { cursortag.stop(true).fadeIn('fast') }
+      },
+      function () {
+        if (cursor.attr('data-mode') === 'hover') { cursortag.stop(true).fadeOut('fast') }
+      })
 
     var hideCursorTagDelay = 2000
     var hideCursorTagTimer = null

From 18a6f9063ebab9913883f8bef78ad95736e7627d Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Tue, 11 Apr 2017 11:48:39 +0800
Subject: [PATCH 04/10] Change some global variables to local

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

diff --git a/public/js/index.js b/public/js/index.js
index 66e3da2..d28f4d6 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -258,14 +258,14 @@ var defaultMode = modeType.view
 
 // global vars
 window.loaded = false
-window.needRefresh = false
-window.isDirty = false
-window.editShown = false
-window.visibleXS = false
-window.visibleSM = false
-window.visibleMD = false
-window.visibleLG = false
-window.isTouchDevice = 'ontouchstart' in document.documentElement
+let needRefresh = false
+let isDirty = false
+let editShown = false
+let visibleXS = false
+let visibleSM = false
+let visibleMD = false
+let visibleLG = false
+const isTouchDevice = 'ontouchstart' in document.documentElement
 window.currentMode = defaultMode
 window.currentStatus = statusType.offline
 window.lastInfo = {
@@ -393,7 +393,7 @@ function setRefreshModal (status) {
 }
 
 function setNeedRefresh () {
-  window.needRefresh = true
+  needRefresh = true
   editor.setOption('readOnly', true)
   socket.disconnect()
   showStatus(statusType.offline)
@@ -415,7 +415,7 @@ Visibility.change(function (e, state) {
     }
   } else {
     if (wasFocus) {
-      if (!window.visibleXS) {
+      if (!visibleXS) {
         editor.focus()
         editor.refresh()
       }
@@ -432,7 +432,7 @@ $(document).ready(function () {
   checkResponsive()
     // if in smaller screen, we don't need advanced scrollbar
   var scrollbarStyle
-  if (window.visibleXS) {
+  if (visibleXS) {
     scrollbarStyle = 'native'
   } else {
     scrollbarStyle = 'overlay'
@@ -443,7 +443,7 @@ $(document).ready(function () {
   }
   checkEditorStyle()
   /* we need this only on touch devices */
-  if (window.isTouchDevice) {
+  if (isTouchDevice) {
     /* cache dom references */
     var $body = $('body')
 
@@ -553,12 +553,12 @@ function editorHasFocus () {
 
 // 768-792px have a gap
 function checkResponsive () {
-  window.visibleXS = $('.visible-xs').is(':visible')
-  window.visibleSM = $('.visible-sm').is(':visible')
-  window.visibleMD = $('.visible-md').is(':visible')
-  window.visibleLG = $('.visible-lg').is(':visible')
+  visibleXS = $('.visible-xs').is(':visible')
+  visibleSM = $('.visible-sm').is(':visible')
+  visibleMD = $('.visible-md').is(':visible')
+  visibleLG = $('.visible-lg').is(':visible')
 
-  if (window.visibleXS && window.currentMode === modeType.both) {
+  if (visibleXS && window.currentMode === modeType.both) {
     if (editorHasFocus()) { changeMode(modeType.edit) } else { changeMode(modeType.view) }
   }
 
@@ -780,9 +780,9 @@ function changeMode (type) {
     case modeType.edit:
       ui.area.edit.show()
       ui.area.view.hide()
-      if (!window.editShown) {
+      if (!editShown) {
         editor.refresh()
-        window.editShown = true
+        editShown = true
       }
       break
     case modeType.view:
@@ -1764,11 +1764,11 @@ var socket = io.connect({
 // overwrite original event for checking login state
 var on = socket.on
 socket.on = function () {
-  if (!checkLoginStateChanged() && !window.needRefresh) { return on.apply(socket, arguments) }
+  if (!checkLoginStateChanged() && !needRefresh) { return on.apply(socket, arguments) }
 }
 var emit = socket.emit
 socket.emit = function () {
-  if (!checkLoginStateChanged() && !window.needRefresh) { emit.apply(socket, arguments) }
+  if (!checkLoginStateChanged() && !needRefresh) { emit.apply(socket, arguments) }
 }
 socket.on('info', function (data) {
   console.error(data)
@@ -1814,7 +1814,7 @@ socket.on('disconnect', function (data) {
   if (!editor.getOption('readOnly')) { editor.setOption('readOnly', true) }
   if (!retryTimer) {
     retryTimer = setInterval(function () {
-      if (!window.needRefresh) socket.connect()
+      if (!needRefresh) socket.connect()
     }, 1000)
   }
 })
@@ -2089,7 +2089,7 @@ socket.on('refresh', function (data) {
         // auto change mode if no content detected
     var nocontent = editor.getValue().length <= 0
     if (nocontent) {
-      if (window.visibleXS) { window.currentMode = modeType.edit } else { window.currentMode = modeType.both }
+      if (visibleXS) { window.currentMode = modeType.edit } else { window.currentMode = modeType.both }
     }
     // parse mode from url
     if (window.location.search.length > 0) {
@@ -2097,7 +2097,7 @@ socket.on('refresh', function (data) {
       if (urlMode) window.currentMode = urlMode
     }
     changeMode(window.currentMode)
-    if (nocontent && !window.visibleXS) {
+    if (nocontent && !visibleXS) {
       editor.focus()
       editor.refresh()
     }
@@ -2169,7 +2169,7 @@ socket.on('doc', function (obj) {
   }
 
   if (setDoc && bodyMismatch) {
-    window.isDirty = true
+    isDirty = true
     updateView()
   }
 
@@ -2177,12 +2177,12 @@ socket.on('doc', function (obj) {
 })
 
 socket.on('ack', function () {
-  window.isDirty = true
+  isDirty = true
   updateView()
 })
 
 socket.on('operation', function () {
-  window.isDirty = true
+  isDirty = true
   updateView()
 })
 
@@ -2395,7 +2395,7 @@ var userStatusCache = null
 function emitUserStatus (force) {
   if (!window.loaded) return
   var type = null
-  if (window.visibleXS) { type = 'xs' } else if (window.visibleSM) { type = 'sm' } else if (window.visibleMD) { type = 'md' } else if (window.visibleLG) { type = 'lg' }
+  if (visibleXS) { type = 'xs' } else if (visibleSM) { type = 'sm' } else if (visibleMD) { type = 'md' } else if (visibleLG) { type = 'lg' }
 
   window.personalInfo['idle'] = idle.isAway
   window.personalInfo['type'] = type
@@ -2800,7 +2800,7 @@ function restoreInfo () {
 // view actions
 function refreshView () {
   ui.area.markdown.html('')
-  window.isDirty = true
+  isDirty = true
   updateViewInner()
 }
 
@@ -2812,7 +2812,7 @@ var lastResult = null
 var postUpdateEvent = null
 
 function updateViewInner () {
-  if (window.currentMode === modeType.edit || !window.isDirty) return
+  if (window.currentMode === modeType.edit || !isDirty) return
   var value = editor.getValue()
   var lastMeta = md.meta
   md.meta = {}
@@ -2860,7 +2860,7 @@ function updateViewInner () {
   generateScrollspy()
   updateScrollspy()
   smoothHashScroll()
-  window.isDirty = false
+  isDirty = false
   clearMap()
     // buildMap();
   updateTitleReminder()

From 68ccee20b3709b9a0499d659c74ac0a298a9ffeb Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Tue, 11 Apr 2017 11:48:59 +0800
Subject: [PATCH 05/10] Extract modeType

---
 public/js/index.js               | 12 +-----------
 public/js/lib/editor/modeType.js | 11 +++++++++++
 public/js/syncscroll.js          |  5 +++--
 3 files changed, 15 insertions(+), 13 deletions(-)
 create mode 100644 public/js/lib/editor/modeType.js

diff --git a/public/js/index.js b/public/js/index.js
index d28f4d6..27b0295 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -80,6 +80,7 @@ import { preventXSS } from './render'
 import Editor from './lib/editor'
 
 import getUIElements from './lib/editor/ui-elements'
+import modeType from './lib/editor/modeType'
 
 var defaultTextHeight = 20
 var viewportMargin = 20
@@ -226,17 +227,6 @@ var supportExtraTags = [
     }
   }
 ]
-window.modeType = {
-  edit: {
-    name: 'edit'
-  },
-  view: {
-    name: 'view'
-  },
-  both: {
-    name: 'both'
-  }
-}
 var statusType = {
   connected: {
     msg: 'CONNECTED',
diff --git a/public/js/lib/editor/modeType.js b/public/js/lib/editor/modeType.js
new file mode 100644
index 0000000..f321210
--- /dev/null
+++ b/public/js/lib/editor/modeType.js
@@ -0,0 +1,11 @@
+export default {
+  edit: {
+    name: 'edit'
+  },
+  view: {
+    name: 'view'
+  },
+  both: {
+    name: 'both'
+  }
+}
diff --git a/public/js/syncscroll.js b/public/js/syncscroll.js
index c227f83..ad21e57 100644
--- a/public/js/syncscroll.js
+++ b/public/js/syncscroll.js
@@ -5,6 +5,7 @@
 import markdownitContainer from 'markdown-it-container'
 
 import { md } from './extra'
+import modeType from './lib/editor/modeType'
 
 function addPart (tokens, idx) {
   if (tokens[idx].map && tokens[idx].level === 0) {
@@ -228,7 +229,7 @@ function buildMapInner (callback) {
 let viewScrollingTimer = null
 
 export function syncScrollToEdit (event, preventAnimate) {
-  if (window.currentMode !== window.modeType.both || !window.syncscroll || !editArea) return
+  if (window.currentMode !== modeType.both || !window.syncscroll || !editArea) return
   if (window.preventSyncScrollToEdit) {
     if (typeof window.preventSyncScrollToEdit === 'number') {
       window.preventSyncScrollToEdit--
@@ -310,7 +311,7 @@ function viewScrollingTimeoutInner () {
 let editScrollingTimer = null
 
 export function syncScrollToView (event, preventAnimate) {
-  if (window.currentMode !== window.modeType.both || !window.syncscroll || !viewArea) return
+  if (window.currentMode !== modeType.both || !window.syncscroll || !viewArea) return
   if (window.preventSyncScrollToView) {
     if (typeof preventSyncScrollToView === 'number') {
       window.preventSyncScrollToView--

From 88c0c688561e8656763391699c385cc89461d776 Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Tue, 11 Apr 2017 12:07:04 +0800
Subject: [PATCH 06/10] Change more global var to global

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

diff --git a/public/js/index.js b/public/js/index.js
index 27b0295..81c24eb 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -124,7 +124,7 @@ var supportHeaders = [
     search: '###### tags:'
   }
 ]
-var supportReferrals = [
+const supportReferrals = [
   {
     text: '[reference link]',
     search: '[]'
@@ -170,7 +170,7 @@ var supportReferrals = [
     search: '[]'
   }
 ]
-var supportExternals = [
+const supportExternals = [
   {
     text: '{%youtube youtubeid %}',
     search: 'youtube'
@@ -196,12 +196,12 @@ var supportExternals = [
     search: 'pdf'
   }
 ]
-var supportExtraTags = [
+const supportExtraTags = [
   {
     text: '[name tag]',
     search: '[]',
     command: function () {
-      return '[name=' + window.personalInfo.name + ']'
+      return '[name=' + personalInfo.name + ']'
     }
   },
   {
@@ -215,7 +215,7 @@ var supportExtraTags = [
     text: '[my color tag]',
     search: '[]',
     command: function () {
-      return '[color=' + window.personalInfo.color + ']'
+      return '[color=' + personalInfo.color + ']'
     }
   },
   {
@@ -227,7 +227,7 @@ var supportExtraTags = [
     }
   }
 ]
-var statusType = {
+const statusType = {
   connected: {
     msg: 'CONNECTED',
     label: 'label-warning',
@@ -244,7 +244,7 @@ var statusType = {
     fa: 'fa-plug'
   }
 }
-var defaultMode = modeType.view
+const defaultMode = modeType.view
 
 // global vars
 window.loaded = false
@@ -257,8 +257,8 @@ let visibleMD = false
 let visibleLG = false
 const isTouchDevice = 'ontouchstart' in document.documentElement
 window.currentMode = defaultMode
-window.currentStatus = statusType.offline
-window.lastInfo = {
+let currentStatus = statusType.offline
+let lastInfo = {
   needRestore: false,
   cursor: null,
   scroll: null,
@@ -281,9 +281,9 @@ window.lastInfo = {
   },
   history: null
 }
-window.personalInfo = {}
-window.onlineUsers = []
-window.fileTypes = {
+let personalInfo = {}
+let onlineUsers = []
+const fileTypes = {
   'pl': 'perl',
   'cgi': 'perl',
   'js': 'javascript',
@@ -295,7 +295,7 @@ window.fileTypes = {
 }
 
 // editor settings
-var textit = document.getElementById('textit')
+const textit = document.getElementById('textit')
 if (!textit) {
   throw new Error('There was no textit area!')
 }
@@ -522,8 +522,8 @@ function windowResizeInner (callback) {
         autoSyncscroll()
         editor.setOption('viewportMargin', viewportMargin)
         // 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]) }
+        for (var i = 0; i < onlineUsers.length; i++) {
+          if (onlineUsers[i].id !== personalInfo.id) { buildCursor(onlineUsers[i]) }
         }
         updateScrollspy()
         if (callback && typeof callback === 'function') { callback() }
@@ -696,7 +696,7 @@ function checkTocStyle () {
 }
 
 function showStatus (type, num) {
-  window.currentStatus = type
+  currentStatus = type
   var shortStatus = ui.toolbar.shortStatus
   var status = ui.toolbar.status
   var label = $('<span class="label"></span>')
@@ -707,7 +707,7 @@ function showStatus (type, num) {
   shortStatus.html('')
   status.html('')
 
-  switch (window.currentStatus) {
+  switch (currentStatus) {
     case statusType.connected:
       label.addClass(statusType.connected.label)
       fa.addClass(statusType.connected.fa)
@@ -1533,7 +1533,7 @@ $('#snippetImportModalConfirm').click(function () {
                       if (raw) {
                         content += '\n\n'
                         if (fileInfo[1] !== 'md') {
-                          content += '```' + window.fileTypes[fileInfo[1]] + '\n'
+                          content += '```' + fileTypes[fileInfo[1]] + '\n'
                         }
                         content += raw
                         if (fileInfo[1] !== 'md') {
@@ -1706,7 +1706,7 @@ function updatePermission (newPermission) {
       title = 'Only owner can view & edit'
       break
   }
-  if (window.personalInfo.userid && window.owner && window.personalInfo.userid === window.owner) {
+  if (personalInfo.userid && window.owner && personalInfo.userid === window.owner) {
     label += ' <i class="fa fa-caret-down"></i>'
     ui.infobar.permission.label.removeClass('disabled')
   } else {
@@ -1723,7 +1723,7 @@ function havePermission () {
       break
     case 'editable':
     case 'limited':
-      if (!window.personalInfo.login) {
+      if (!personalInfo.login) {
         bool = false
       } else {
         bool = true
@@ -1732,7 +1732,7 @@ function havePermission () {
     case 'locked':
     case 'private':
     case 'protected':
-      if (!window.owner || window.personalInfo.userid !== window.owner) {
+      if (!window.owner || personalInfo.userid !== window.owner) {
         bool = false
       } else {
         bool = true
@@ -1779,7 +1779,7 @@ socket.on('error', function (data) {
   if (data.message && data.message.indexOf('AUTH failed') === 0) { location.href = serverurl + '/403' }
 })
 socket.on('delete', function () {
-  if (window.personalInfo.login) {
+  if (personalInfo.login) {
     deleteServerHistory(noteid, function (err, data) {
       if (!err) location.href = serverurl
     })
@@ -1799,7 +1799,7 @@ socket.on('disconnect', function (data) {
   showStatus(statusType.offline)
   if (window.loaded) {
     saveInfo()
-    window.lastInfo.history = editor.getHistory()
+    lastInfo.history = editor.getHistory()
   }
   if (!editor.getOption('readOnly')) { editor.setOption('readOnly', true) }
   if (!retryTimer) {
@@ -1817,7 +1817,7 @@ socket.on('reconnect', function (data) {
 socket.on('connect', function (data) {
   clearInterval(retryTimer)
   retryTimer = null
-  window.personalInfo['id'] = socket.id
+  personalInfo['id'] = socket.id
   showStatus(statusType.connected)
   socket.emit('version')
 })
@@ -2135,8 +2135,8 @@ socket.on('doc', function (obj) {
   } else {
     // 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
+    else if (lastInfo.history) editor.setHistory(lastInfo.history)
+    lastInfo.history = null
   }
 
   if (!cmClient) {
@@ -2178,7 +2178,7 @@ socket.on('operation', function () {
 
 socket.on('online users', function (data) {
   if (debug) { console.debug(data) }
-  window.onlineUsers = data.users
+  onlineUsers = data.users
   updateOnlineStatus()
   $('.CodeMirror-other-cursors').children().each(function (key, value) {
     var found = false
@@ -2194,14 +2194,14 @@ socket.on('online users', function (data) {
   })
   for (var i = 0; i < data.users.length; i++) {
     var user = data.users[i]
-    if (user.id !== socket.id) { buildCursor(user) } else { window.personalInfo = user }
+    if (user.id !== socket.id) { buildCursor(user) } else { personalInfo = user }
   }
 })
 socket.on('user status', function (data) {
   if (debug) { console.debug(data) }
-  for (var i = 0; i < window.onlineUsers.length; i++) {
-    if (window.onlineUsers[i].id === data.id) {
-      window.onlineUsers[i] = data
+  for (var i = 0; i < onlineUsers.length; i++) {
+    if (onlineUsers[i].id === data.id) {
+      onlineUsers[i] = data
     }
   }
   updateOnlineStatus()
@@ -2209,9 +2209,9 @@ socket.on('user status', function (data) {
 })
 socket.on('cursor focus', function (data) {
   if (debug) { console.debug(data) }
-  for (var i = 0; i < window.onlineUsers.length; i++) {
-    if (window.onlineUsers[i].id === data.id) {
-      window.onlineUsers[i].cursor = data.cursor
+  for (var i = 0; i < onlineUsers.length; i++) {
+    if (onlineUsers[i].id === data.id) {
+      onlineUsers[i].cursor = data.cursor
     }
   }
   if (data.id !== socket.id) { buildCursor(data) }
@@ -2223,18 +2223,18 @@ socket.on('cursor focus', function (data) {
 })
 socket.on('cursor activity', function (data) {
   if (debug) { console.debug(data) }
-  for (var i = 0; i < window.onlineUsers.length; i++) {
-    if (window.onlineUsers[i].id === data.id) {
-      window.onlineUsers[i].cursor = data.cursor
+  for (var i = 0; i < onlineUsers.length; i++) {
+    if (onlineUsers[i].id === data.id) {
+      onlineUsers[i].cursor = data.cursor
     }
   }
   if (data.id !== socket.id) { buildCursor(data) }
 })
 socket.on('cursor blur', function (data) {
   if (debug) { console.debug(data) }
-  for (var i = 0; i < window.onlineUsers.length; i++) {
-    if (window.onlineUsers[i].id === data.id) {
-      window.onlineUsers[i].cursor = null
+  for (var i = 0; i < onlineUsers.length; i++) {
+    if (onlineUsers[i].id === data.id) {
+      onlineUsers[i].cursor = null
     }
   }
   if (data.id !== socket.id) { buildCursor(data) }
@@ -2259,7 +2259,7 @@ var shortOnlineUserList = new List('short-online-user-list', options)
 
 function updateOnlineStatus () {
   if (!window.loaded || !socket.connected) return
-  var _onlineUsers = deduplicateOnlineUsers(window.onlineUsers)
+  var _onlineUsers = deduplicateOnlineUsers(onlineUsers)
   showStatus(statusType.online, _onlineUsers.length)
   var items = onlineUserList.items
     // update or remove current list items
@@ -2310,8 +2310,8 @@ function sortOnlineUserList (list) {
     sortFunction: function (a, b) {
       var usera = a.values()
       var userb = b.values()
-      var useraIsSelf = (usera.id === window.personalInfo.id || (usera.login && usera.userid === window.personalInfo.userid))
-      var userbIsSelf = (userb.id === window.personalInfo.id || (userb.login && userb.userid === window.personalInfo.userid))
+      var useraIsSelf = (usera.id === personalInfo.id || (usera.login && usera.userid === personalInfo.userid))
+      var userbIsSelf = (userb.id === personalInfo.id || (userb.login && userb.userid === personalInfo.userid))
       if (useraIsSelf && !userbIsSelf) {
         return -1
       } else if (!useraIsSelf && userbIsSelf) {
@@ -2362,7 +2362,7 @@ function deduplicateOnlineUsers (list) {
       for (var j = 0; j < _onlineUsers.length; j++) {
         if (_onlineUsers[j].userid === user.userid) {
           // keep self color when login
-          if (user.id === window.personalInfo.id) {
+          if (user.id === personalInfo.id) {
             _onlineUsers[j].color = user.color
           }
           // keep idle state if any of self client not idle
@@ -2387,12 +2387,12 @@ function emitUserStatus (force) {
   var type = null
   if (visibleXS) { type = 'xs' } else if (visibleSM) { type = 'sm' } else if (visibleMD) { type = 'md' } else if (visibleLG) { type = 'lg' }
 
-  window.personalInfo['idle'] = idle.isAway
-  window.personalInfo['type'] = type
+  personalInfo['idle'] = idle.isAway
+  personalInfo['type'] = type
 
-  for (var i = 0; i < window.onlineUsers.length; i++) {
-    if (window.onlineUsers[i].id === window.personalInfo.id) {
-      window.onlineUsers[i] = window.personalInfo
+  for (var i = 0; i < onlineUsers.length; i++) {
+    if (onlineUsers[i].id === personalInfo.id) {
+      onlineUsers[i] = personalInfo
     }
   }
 
@@ -2653,12 +2653,12 @@ editorInstance.on('changes', function (editor, changes) {
   }
 })
 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()
+  for (var i = 0; i < onlineUsers.length; i++) {
+    if (onlineUsers[i].id === personalInfo.id) {
+      onlineUsers[i].cursor = editor.getCursor()
     }
   }
-  window.personalInfo['cursor'] = editor.getCursor()
+  personalInfo['cursor'] = editor.getCursor()
   socket.emit('cursor focus', editor.getCursor())
 })
 
@@ -2666,12 +2666,12 @@ const cursorActivity = _.debounce(cursorActivityInner, cursorActivityDebounce)
 
 function cursorActivityInner (editor) {
   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()
+    for (var i = 0; i < onlineUsers.length; i++) {
+      if (onlineUsers[i].id === personalInfo.id) {
+        onlineUsers[i].cursor = editor.getCursor()
       }
     }
-    window.personalInfo['cursor'] = editor.getCursor()
+    personalInfo['cursor'] = editor.getCursor()
     socket.emit('cursor activity', editor.getCursor())
   }
 }
@@ -2713,12 +2713,12 @@ editorInstance.on('beforeSelectionChange', function (doc, selections) {
 })
 
 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
+  for (var i = 0; i < onlineUsers.length; i++) {
+    if (onlineUsers[i].id === personalInfo.id) {
+      onlineUsers[i].cursor = null
     }
   }
-  window.personalInfo['cursor'] = null
+  personalInfo['cursor'] = null
   socket.emit('cursor blur')
 })
 
@@ -2729,61 +2729,61 @@ function saveInfo () {
   switch (window.currentMode) {
     case modeType.edit:
       if (scrollbarStyle === 'native') {
-        window.lastInfo.edit.scroll.left = left
-        window.lastInfo.edit.scroll.top = top
+        lastInfo.edit.scroll.left = left
+        lastInfo.edit.scroll.top = top
       } else {
-        window.lastInfo.edit.scroll = editor.getScrollInfo()
+        lastInfo.edit.scroll = editor.getScrollInfo()
       }
       break
     case modeType.view:
-      window.lastInfo.view.scroll.left = left
-      window.lastInfo.view.scroll.top = top
+      lastInfo.view.scroll.left = left
+      lastInfo.view.scroll.top = top
       break
     case modeType.both:
-      window.lastInfo.edit.scroll = editor.getScrollInfo()
-      window.lastInfo.view.scroll.left = ui.area.view.scrollLeft()
-      window.lastInfo.view.scroll.top = ui.area.view.scrollTop()
+      lastInfo.edit.scroll = editor.getScrollInfo()
+      lastInfo.view.scroll.left = ui.area.view.scrollLeft()
+      lastInfo.view.scroll.top = ui.area.view.scrollTop()
       break
   }
-  window.lastInfo.edit.cursor = editor.getCursor()
-  window.lastInfo.edit.selections = editor.listSelections()
-  window.lastInfo.needRestore = true
+  lastInfo.edit.cursor = editor.getCursor()
+  lastInfo.edit.selections = editor.listSelections()
+  lastInfo.needRestore = true
 }
 
 function restoreInfo () {
   var scrollbarStyle = editor.getOption('scrollbarStyle')
-  if (window.lastInfo.needRestore) {
-    var line = window.lastInfo.edit.cursor.line
-    var ch = window.lastInfo.edit.cursor.ch
+  if (lastInfo.needRestore) {
+    var line = lastInfo.edit.cursor.line
+    var ch = lastInfo.edit.cursor.ch
     editor.setCursor(line, ch)
-    editor.setSelections(window.lastInfo.edit.selections)
+    editor.setSelections(lastInfo.edit.selections)
     switch (window.currentMode) {
       case modeType.edit:
         if (scrollbarStyle === 'native') {
-          $(window).scrollLeft(window.lastInfo.edit.scroll.left)
-          $(window).scrollTop(window.lastInfo.edit.scroll.top)
+          $(window).scrollLeft(lastInfo.edit.scroll.left)
+          $(window).scrollTop(lastInfo.edit.scroll.top)
         } else {
-          let left = window.lastInfo.edit.scroll.left
-          let top = window.lastInfo.edit.scroll.top
+          let left = lastInfo.edit.scroll.left
+          let top = lastInfo.edit.scroll.top
           editor.scrollIntoView()
           editor.scrollTo(left, top)
         }
         break
       case modeType.view:
-        $(window).scrollLeft(window.lastInfo.view.scroll.left)
-        $(window).scrollTop(window.lastInfo.view.scroll.top)
+        $(window).scrollLeft(lastInfo.view.scroll.left)
+        $(window).scrollTop(lastInfo.view.scroll.top)
         break
       case modeType.both:
-        let left = window.lastInfo.edit.scroll.left
-        let top = window.lastInfo.edit.scroll.top
+        let left = lastInfo.edit.scroll.left
+        let top = lastInfo.edit.scroll.top
         editor.scrollIntoView()
         editor.scrollTo(left, top)
-        ui.area.view.scrollLeft(window.lastInfo.view.scroll.left)
-        ui.area.view.scrollTop(window.lastInfo.view.scroll.top)
+        ui.area.view.scrollLeft(lastInfo.view.scroll.left)
+        ui.area.view.scrollTop(lastInfo.view.scroll.top)
         break
     }
 
-    window.lastInfo.needRestore = false
+    lastInfo.needRestore = false
   }
 }
 

From d9221f6011aab9355f842b6e001df75b71120960 Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Tue, 11 Apr 2017 14:17:14 +0800
Subject: [PATCH 07/10] Remove CodeMirror-other-cursors dom creation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Since it’s done via hackmdio/CodeMirror#1
---
 public/js/index.js | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/public/js/index.js b/public/js/index.js
index 81c24eb..0ca6827 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -2466,9 +2466,6 @@ function buildCursor (user) {
       iconClass = 'fa-desktop'
       break
   }
-  if ($('.CodeMirror-other-cursors').length <= 0) {
-    $("<div class='CodeMirror-other-cursors'>").insertAfter('.CodeMirror-cursors')
-  }
   if ($('div[data-clientid="' + user.id + '"]').length <= 0) {
     let cursor = $('<div data-clientid="' + user.id + '" class="CodeMirror-other-cursor" style="display:none;"></div>')
     cursor.attr('data-line', user.cursor.line)

From 0e9afde5fa87d7039d4cebd43e5613541c56849a Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Tue, 11 Apr 2017 21:33:54 +0800
Subject: [PATCH 08/10] Move syncsroll under lib

---
 public/js/index.js                | 2 +-
 public/js/{ => lib}/syncscroll.js | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)
 rename public/js/{ => lib}/syncscroll.js (99%)

diff --git a/public/js/index.js b/public/js/index.js
index 0ca6827..245cf29 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -65,7 +65,7 @@ import {
     setupSyncAreas,
     syncScrollToEdit,
     syncScrollToView
-} from './syncscroll'
+} from './lib/syncscroll'
 
 import {
     writeHistory,
diff --git a/public/js/syncscroll.js b/public/js/lib/syncscroll.js
similarity index 99%
rename from public/js/syncscroll.js
rename to public/js/lib/syncscroll.js
index ad21e57..6b07e79 100644
--- a/public/js/syncscroll.js
+++ b/public/js/lib/syncscroll.js
@@ -4,8 +4,8 @@
 
 import markdownitContainer from 'markdown-it-container'
 
-import { md } from './extra'
-import modeType from './lib/editor/modeType'
+import { md } from '../extra'
+import modeType from '../lib/editor/modeType'
 
 function addPart (tokens, idx) {
   if (tokens[idx].map && tokens[idx].level === 0) {

From 4839838d0cdfca29a9e87fcf966ec026ee99a14f Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Wed, 12 Apr 2017 09:21:13 +0800
Subject: [PATCH 09/10] Manage syncscroll / currentMode in appState

---
 public/js/index.js                     | 67 +++++++++++++-------------
 public/js/lib/appState.js              |  8 +++
 public/js/lib/{editor => }/modeType.js |  0
 public/js/lib/syncscroll.js            | 31 ++++++------
 4 files changed, 58 insertions(+), 48 deletions(-)
 create mode 100644 public/js/lib/appState.js
 rename public/js/lib/{editor => }/modeType.js (100%)

diff --git a/public/js/index.js b/public/js/index.js
index 245cf29..b336af9 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -80,7 +80,8 @@ import { preventXSS } from './render'
 import Editor from './lib/editor'
 
 import getUIElements from './lib/editor/ui-elements'
-import modeType from './lib/editor/modeType'
+import modeType from './lib/modeType'
+import appState from './lib/appState'
 
 var defaultTextHeight = 20
 var viewportMargin = 20
@@ -244,7 +245,6 @@ const statusType = {
     fa: 'fa-plug'
   }
 }
-const defaultMode = modeType.view
 
 // global vars
 window.loaded = false
@@ -256,7 +256,6 @@ let visibleSM = false
 let visibleMD = false
 let visibleLG = false
 const isTouchDevice = 'ontouchstart' in document.documentElement
-window.currentMode = defaultMode
 let currentStatus = statusType.offline
 let lastInfo = {
   needRestore: false,
@@ -486,7 +485,7 @@ $(window).on('error', function () {
   // setNeedRefresh();
 })
 
-setupSyncAreas(ui.area.codemirrorScroll, ui.area.view, ui.area.markdown)
+setupSyncAreas(ui.area.codemirrorScroll, ui.area.view, ui.area.markdown, editor)
 
 function autoSyncscroll () {
   if (editorHasFocus()) {
@@ -548,7 +547,7 @@ function checkResponsive () {
   visibleMD = $('.visible-md').is(':visible')
   visibleLG = $('.visible-lg').is(':visible')
 
-  if (visibleXS && window.currentMode === modeType.both) {
+  if (visibleXS && appState.currentMode === modeType.both) {
     if (editorHasFocus()) { changeMode(modeType.edit) } else { changeMode(modeType.view) }
   }
 
@@ -562,7 +561,7 @@ function checkEditorStyle () {
   var desireHeight = editorInstance.statusBar ? (ui.area.edit.height() - editorInstance.statusBar.outerHeight()) : ui.area.edit.height()
     // set editor height and min height based on scrollbar style and mode
   var scrollbarStyle = editor.getOption('scrollbarStyle')
-  if (scrollbarStyle === 'overlay' || window.currentMode === modeType.both) {
+  if (scrollbarStyle === 'overlay' || appState.currentMode === modeType.both) {
     ui.area.codemirrorScroll.css('height', desireHeight + 'px')
     ui.area.codemirrorScroll.css('min-height', '')
     checkEditorScrollbar()
@@ -618,7 +617,7 @@ function checkEditorStyle () {
       previousFocusOnEditor = null
     })
     ui.area.resize.syncToggle.click(function () {
-      window.syncscroll = !window.syncscroll
+      appState.syncscroll = !appState.syncscroll
       checkSyncToggle()
     })
     ui.area.resize.handle.append(ui.area.resize.syncToggle)
@@ -632,7 +631,7 @@ function checkEditorStyle () {
 }
 
 function checkSyncToggle () {
-  if (window.syncscroll) {
+  if (appState.syncscroll) {
     if (previousFocusOnEditor) {
       window.preventSyncScrollToView = false
       syncScrollToView()
@@ -679,10 +678,10 @@ function checkTocStyle () {
   // toc scrollspy
   ui.toc.toc.removeClass('scrollspy-body, scrollspy-view')
   ui.toc.affix.removeClass('scrollspy-body, scrollspy-view')
-  if (window.currentMode === modeType.both) {
+  if (appState.currentMode === modeType.both) {
     ui.toc.toc.addClass('scrollspy-view')
     ui.toc.affix.addClass('scrollspy-view')
-  } else if (window.currentMode !== modeType.both && !newbool) {
+  } else if (appState.currentMode !== modeType.both && !newbool) {
     ui.toc.toc.addClass('scrollspy-body')
     ui.toc.affix.addClass('scrollspy-body')
   } else {
@@ -737,7 +736,7 @@ function showStatus (type, num) {
 }
 
 function toggleMode () {
-  switch (window.currentMode) {
+  switch (appState.currentMode) {
     case modeType.edit:
       changeMode(modeType.view)
       break
@@ -757,8 +756,8 @@ function changeMode (type) {
   lockNavbar()
   saveInfo()
   if (type) {
-    lastMode = window.currentMode
-    window.currentMode = type
+    lastMode = appState.currentMode
+    appState.currentMode = type
   }
   var responsiveClass = 'col-lg-6 col-md-6 col-sm-6'
   var scrollClass = 'ui-scrollable'
@@ -766,7 +765,7 @@ function changeMode (type) {
   ui.area.edit.removeClass(responsiveClass)
   ui.area.view.removeClass(scrollClass)
   ui.area.view.removeClass(responsiveClass)
-  switch (window.currentMode) {
+  switch (appState.currentMode) {
     case modeType.edit:
       ui.area.edit.show()
       ui.area.view.hide()
@@ -787,11 +786,11 @@ function changeMode (type) {
       break
   }
   // save mode to url
-  if (history.replaceState && window.loaded) history.replaceState(null, '', serverurl + '/' + noteid + '?' + window.currentMode.name)
-  if (window.currentMode === modeType.view) {
+  if (history.replaceState && window.loaded) history.replaceState(null, '', serverurl + '/' + noteid + '?' + appState.currentMode.name)
+  if (appState.currentMode === modeType.view) {
     editor.getInputField().blur()
   }
-  if (window.currentMode === modeType.edit || window.currentMode === modeType.both) {
+  if (appState.currentMode === modeType.edit || appState.currentMode === modeType.both) {
     ui.toolbar.uploadImage.fadeIn()
     // add and update status bar
     if (!editorInstance.statusBar) {
@@ -804,14 +803,14 @@ function changeMode (type) {
   } else {
     ui.toolbar.uploadImage.fadeOut()
   }
-  if (window.currentMode !== modeType.edit) {
+  if (appState.currentMode !== modeType.edit) {
     $(document.body).css('background-color', 'white')
     updateView()
   } else {
     $(document.body).css('background-color', ui.area.codemirror.css('background-color'))
   }
     // check resizable editor style
-  if (window.currentMode === modeType.both) {
+  if (appState.currentMode === modeType.both) {
     if (lastEditorWidth > 0) {
       ui.area.edit.css('width', lastEditorWidth + 'px')
     } else {
@@ -827,22 +826,22 @@ function changeMode (type) {
 
   restoreInfo()
 
-  if (lastMode === modeType.view && window.currentMode === modeType.both) {
+  if (lastMode === modeType.view && appState.currentMode === modeType.both) {
     window.preventSyncScrollToView = 2
     syncScrollToEdit(null, true)
   }
 
-  if (lastMode === modeType.edit && window.currentMode === modeType.both) {
+  if (lastMode === modeType.edit && appState.currentMode === modeType.both) {
     window.preventSyncScrollToEdit = 2
     syncScrollToView(null, true)
   }
 
-  if (lastMode === modeType.both && window.currentMode !== modeType.both) {
+  if (lastMode === modeType.both && appState.currentMode !== modeType.both) {
     window.preventSyncScrollToView = false
     window.preventSyncScrollToEdit = false
   }
 
-  if (lastMode !== modeType.edit && window.currentMode === modeType.edit) {
+  if (lastMode !== modeType.edit && appState.currentMode === modeType.edit) {
     editor.refresh()
   }
 
@@ -1360,7 +1359,7 @@ ui.modal.snippetImportSnippets.change(function () {
 })
 
 function scrollToTop () {
-  if (window.currentMode === modeType.both) {
+  if (appState.currentMode === modeType.both) {
     if (editor.getScrollInfo().top !== 0) { editor.scrollTo(0, 0) } else {
       ui.area.view.animate({
         scrollTop: 0
@@ -1374,7 +1373,7 @@ function scrollToTop () {
 }
 
 function scrollToBottom () {
-  if (window.currentMode === modeType.both) {
+  if (appState.currentMode === modeType.both) {
     var scrollInfo = editor.getScrollInfo()
     var scrollHeight = scrollInfo.height
     if (scrollInfo.top !== scrollHeight) { editor.scrollTo(0, scrollHeight * 2) } else {
@@ -2079,14 +2078,14 @@ socket.on('refresh', function (data) {
         // auto change mode if no content detected
     var nocontent = editor.getValue().length <= 0
     if (nocontent) {
-      if (visibleXS) { window.currentMode = modeType.edit } else { window.currentMode = modeType.both }
+      if (visibleXS) { appState.currentMode = modeType.edit } else { appState.currentMode = modeType.both }
     }
     // parse mode from url
     if (window.location.search.length > 0) {
       var urlMode = modeType[window.location.search.substr(1)]
-      if (urlMode) window.currentMode = urlMode
+      if (urlMode) appState.currentMode = urlMode
     }
-    changeMode(window.currentMode)
+    changeMode(appState.currentMode)
     if (nocontent && !visibleXS) {
       editor.focus()
       editor.refresh()
@@ -2446,7 +2445,7 @@ function checkCursorTag (coord, ele) {
 }
 
 function buildCursor (user) {
-  if (window.currentMode === modeType.view) return
+  if (appState.currentMode === modeType.view) return
   if (!user.cursor) return
   var coord = editor.charCoords(user.cursor, 'windows')
   coord.left = coord.left < 4 ? 4 : coord.left
@@ -2723,7 +2722,7 @@ function saveInfo () {
   var scrollbarStyle = editor.getOption('scrollbarStyle')
   var left = $(window).scrollLeft()
   var top = $(window).scrollTop()
-  switch (window.currentMode) {
+  switch (appState.currentMode) {
     case modeType.edit:
       if (scrollbarStyle === 'native') {
         lastInfo.edit.scroll.left = left
@@ -2754,7 +2753,7 @@ function restoreInfo () {
     var ch = lastInfo.edit.cursor.ch
     editor.setCursor(line, ch)
     editor.setSelections(lastInfo.edit.selections)
-    switch (window.currentMode) {
+    switch (appState.currentMode) {
       case modeType.edit:
         if (scrollbarStyle === 'native') {
           $(window).scrollLeft(lastInfo.edit.scroll.left)
@@ -2799,7 +2798,7 @@ var lastResult = null
 var postUpdateEvent = null
 
 function updateViewInner () {
-  if (window.currentMode === modeType.edit || !isDirty) return
+  if (appState.currentMode === modeType.edit || !isDirty) return
   var value = editor.getValue()
   var lastMeta = md.meta
   md.meta = {}
@@ -2816,13 +2815,13 @@ function updateViewInner () {
         // prevent XSS
     ui.area.markdown.html(preventXSS(ui.area.markdown.html()))
     ui.area.markdown.addClass('slides')
-    window.syncscroll = false
+    appState.syncscroll = false
     checkSyncToggle()
   } else {
     if (lastMeta.type && lastMeta.type === 'slide') {
       refreshView()
       ui.area.markdown.removeClass('slides')
-      window.syncscroll = true
+      appState.syncscroll = true
       checkSyncToggle()
     }
         // only render again when meta changed
diff --git a/public/js/lib/appState.js b/public/js/lib/appState.js
new file mode 100644
index 0000000..fb8030e
--- /dev/null
+++ b/public/js/lib/appState.js
@@ -0,0 +1,8 @@
+import modeType from './modeType'
+
+let state = {
+  syncscroll: true,
+  currentMode: modeType.view
+}
+
+export default state
diff --git a/public/js/lib/editor/modeType.js b/public/js/lib/modeType.js
similarity index 100%
rename from public/js/lib/editor/modeType.js
rename to public/js/lib/modeType.js
diff --git a/public/js/lib/syncscroll.js b/public/js/lib/syncscroll.js
index 6b07e79..cee317e 100644
--- a/public/js/lib/syncscroll.js
+++ b/public/js/lib/syncscroll.js
@@ -5,7 +5,8 @@
 import markdownitContainer from 'markdown-it-container'
 
 import { md } from '../extra'
-import modeType from '../lib/editor/modeType'
+import modeType from './modeType'
+import appState from './appState'
 
 function addPart (tokens, idx) {
   if (tokens[idx].map && tokens[idx].level === 0) {
@@ -110,9 +111,6 @@ md.use(markdownitContainer, 'info', { render: renderContainer })
 md.use(markdownitContainer, 'warning', { render: renderContainer })
 md.use(markdownitContainer, 'danger', { render: renderContainer })
 
-// FIXME: expose syncscroll to window
-window.syncscroll = true
-
 window.preventSyncScrollToEdit = false
 window.preventSyncScrollToView = false
 
@@ -127,10 +125,15 @@ let editArea = null
 let viewArea = null
 let markdownArea = null
 
-export function setupSyncAreas (edit, view, markdown) {
+let editor
+
+export function setupSyncAreas (edit, view, markdown, _editor) {
   editArea = edit
   viewArea = view
   markdownArea = markdown
+
+  editor = _editor
+
   editArea.on('scroll', _.throttle(syncScrollToView, editScrollThrottle))
   viewArea.on('scroll', _.throttle(syncScrollToEdit, viewScrollThrottle))
 }
@@ -162,8 +165,8 @@ function buildMapInner (callback) {
   viewBottom = viewArea[0].scrollHeight - viewArea.height()
 
   acc = 0
-  const lines = window.editor.getValue().split('\n')
-  const lineHeight = window.editor.defaultTextHeight()
+  const lines = editor.getValue().split('\n')
+  const lineHeight = editor.defaultTextHeight()
   for (i = 0; i < lines.length; i++) {
     const str = lines[i]
 
@@ -174,7 +177,7 @@ function buildMapInner (callback) {
       continue
     }
 
-    const h = window.editor.heightAtLine(i + 1) - window.editor.heightAtLine(i)
+    const h = editor.heightAtLine(i + 1) - editor.heightAtLine(i)
     acc += Math.round(h / lineHeight)
   }
   _lineHeightMap.push(acc)
@@ -229,7 +232,7 @@ function buildMapInner (callback) {
 let viewScrollingTimer = null
 
 export function syncScrollToEdit (event, preventAnimate) {
-  if (window.currentMode !== modeType.both || !window.syncscroll || !editArea) return
+  if (appState.currentMode !== modeType.both || !appState.syncscroll || !editArea) return
   if (window.preventSyncScrollToEdit) {
     if (typeof window.preventSyncScrollToEdit === 'number') {
       window.preventSyncScrollToEdit--
@@ -269,8 +272,8 @@ export function syncScrollToEdit (event, preventAnimate) {
   let posTo = 0
   let topDiffPercent = 0
   let posToNextDiff = 0
-  const scrollInfo = window.editor.getScrollInfo()
-  const textHeight = window.editor.defaultTextHeight()
+  const scrollInfo = editor.getScrollInfo()
+  const textHeight = editor.defaultTextHeight()
   const preLastLineHeight = scrollInfo.height - scrollInfo.clientHeight - textHeight
   const preLastLineNo = Math.round(preLastLineHeight / textHeight)
   const preLastLinePos = scrollMap[preLastLineNo]
@@ -311,7 +314,7 @@ function viewScrollingTimeoutInner () {
 let editScrollingTimer = null
 
 export function syncScrollToView (event, preventAnimate) {
-  if (window.currentMode !== modeType.both || !window.syncscroll || !viewArea) return
+  if (appState.currentMode !== modeType.both || !appState.syncscroll || !viewArea) return
   if (window.preventSyncScrollToView) {
     if (typeof preventSyncScrollToView === 'number') {
       window.preventSyncScrollToView--
@@ -330,8 +333,8 @@ export function syncScrollToView (event, preventAnimate) {
 
   let lineNo, posTo
   let topDiffPercent, posToNextDiff
-  const scrollInfo = window.editor.getScrollInfo()
-  const textHeight = window.editor.defaultTextHeight()
+  const scrollInfo = editor.getScrollInfo()
+  const textHeight = editor.defaultTextHeight()
   lineNo = Math.floor(scrollInfo.top / textHeight)
     // if reach the last line, will start lerp to the bottom
   const diffToBottom = (scrollInfo.top + scrollInfo.clientHeight) - (scrollInfo.height - textHeight)

From be99350655ca33aaa14b99d7b44b529aba0c8773 Mon Sep 17 00:00:00 2001
From: Wu Cheng-Han <jacky_cute0808@hotmail.com>
Date: Tue, 9 May 2017 22:11:57 +0800
Subject: [PATCH 10/10] Fix to implement toggle of TOC in HTML template

---
 public/views/html.hbs | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/public/views/html.hbs b/public/views/html.hbs
index 5ef5192..a300ddd 100644
--- a/public/views/html.hbs
+++ b/public/views/html.hbs
@@ -160,6 +160,29 @@
             removeHash();
         });
 
+        var toggle = $('.expand-toggle');
+        var tocExpand = false;
+
+        checkExpandToggle();
+        toggle.click(function (e) {
+            e.preventDefault();
+            e.stopPropagation();
+            tocExpand = !tocExpand;
+            checkExpandToggle();
+        })
+
+        function checkExpandToggle () {
+            var toc = $('.ui-toc-dropdown .toc');
+            var toggle = $('.expand-toggle');
+            if (!tocExpand) {
+                toc.removeClass('expand');
+                toggle.text('Expand all');
+            } else {
+                toc.addClass('expand');
+                toggle.text('Collapse all');
+            }
+        }
+
         function scrollToTop() {
             $('body, html').stop(true, true).animate({
                 scrollTop: 0