From 121d84863a055dbe259cdcfb98583a376bd8e2fa Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Tue, 7 Mar 2017 21:59:18 +0800
Subject: [PATCH 1/8] Extract UI jquery node

---
 public/js/lib/editor/ui-elements.js | 84 +++++++++++++++++++++++++++++
 1 file changed, 84 insertions(+)
 create mode 100644 public/js/lib/editor/ui-elements.js

diff --git a/public/js/lib/editor/ui-elements.js b/public/js/lib/editor/ui-elements.js
new file mode 100644
index 0000000..d06e640
--- /dev/null
+++ b/public/js/lib/editor/ui-elements.js
@@ -0,0 +1,84 @@
+/*
+ *  Global UI elements references
+ */
+
+export const getUIElements = () => ({
+    spinner: $(".ui-spinner"),
+    content: $(".ui-content"),
+    toolbar: {
+        shortStatus: $(".ui-short-status"),
+        status: $(".ui-status"),
+        new: $(".ui-new"),
+        publish: $(".ui-publish"),
+        extra: {
+            revision: $(".ui-extra-revision"),
+            slide: $(".ui-extra-slide")
+        },
+        download: {
+            markdown: $(".ui-download-markdown"),
+            html: $(".ui-download-html"),
+            rawhtml: $(".ui-download-raw-html"),
+            pdf: $(".ui-download-pdf-beta"),
+        },
+        export: {
+            dropbox: $(".ui-save-dropbox"),
+            googleDrive: $(".ui-save-google-drive"),
+            gist: $(".ui-save-gist"),
+            snippet: $(".ui-save-snippet")
+        },
+        import: {
+            dropbox: $(".ui-import-dropbox"),
+            googleDrive: $(".ui-import-google-drive"),
+            gist: $(".ui-import-gist"),
+            snippet: $(".ui-import-snippet"),
+            clipboard: $(".ui-import-clipboard")
+        },
+        mode: $(".ui-mode"),
+        edit: $(".ui-edit"),
+        view: $(".ui-view"),
+        both: $(".ui-both"),
+        uploadImage: $(".ui-upload-image")
+    },
+    infobar: {
+        lastchange: $(".ui-lastchange"),
+        lastchangeuser: $(".ui-lastchangeuser"),
+        nolastchangeuser: $(".ui-no-lastchangeuser"),
+        permission: {
+            permission: $(".ui-permission"),
+            label: $(".ui-permission-label"),
+            freely: $(".ui-permission-freely"),
+            editable: $(".ui-permission-editable"),
+            locked: $(".ui-permission-locked"),
+            private: $(".ui-permission-private"),
+            limited: $(".ui-permission-limited"),
+            protected: $(".ui-permission-protected")
+        },
+        delete: $(".ui-delete-note")
+    },
+    toc: {
+        toc: $('.ui-toc'),
+        affix: $('.ui-affix-toc'),
+        label: $('.ui-toc-label'),
+        dropdown: $('.ui-toc-dropdown')
+    },
+    area: {
+        edit: $(".ui-edit-area"),
+        view: $(".ui-view-area"),
+        codemirror: $(".ui-edit-area .CodeMirror"),
+        codemirrorScroll: $(".ui-edit-area .CodeMirror .CodeMirror-scroll"),
+        codemirrorSizer: $(".ui-edit-area .CodeMirror .CodeMirror-sizer"),
+        codemirrorSizerInner: $(".ui-edit-area .CodeMirror .CodeMirror-sizer > div"),
+        markdown: $(".ui-view-area .markdown-body"),
+        resize: {
+            handle: $('.ui-resizable-handle'),
+            syncToggle: $('.ui-sync-toggle')
+        }
+    },
+    modal: {
+        snippetImportProjects: $("#snippetImportModalProjects"),
+        snippetImportSnippets: $("#snippetImportModalSnippets"),
+        revision: $("#revisionModal")
+    }
+})
+
+export default getUIElements;

From 6556c284e504790bd9ed0facac3322320e0c347b Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Tue, 7 Mar 2017 22:35:54 +0800
Subject: [PATCH 2/8] Extract editor related code

- in public/js/lib/editor/index.js
---
 public/js/index.js            | 600 ++--------------------------------
 public/js/lib/editor/index.js | 459 ++++++++++++++++++++++++++
 public/js/lib/editor/utils.js |  46 +++
 3 files changed, 536 insertions(+), 569 deletions(-)
 create mode 100644 public/js/lib/editor/index.js
 create mode 100644 public/js/lib/editor/utils.js

diff --git a/public/js/index.js b/public/js/index.js
index f0c476e..0d4da4d 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -73,145 +73,12 @@ import {
 var renderer = require('./render');
 var preventXSS = renderer.preventXSS;
 
+import Editor from './lib/editor';
+
+import getUIElements from './lib/editor/ui-elements';
+
 var defaultTextHeight = 20;
 var viewportMargin = 20;
-var mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault;
-var defaultEditorMode = 'gfm';
-var defaultExtraKeys = {
-    "F10": function (cm) {
-        cm.setOption("fullScreen", !cm.getOption("fullScreen"));
-    },
-    "Esc": function (cm) {
-        if (cm.getOption('keyMap').substr(0, 3) === 'vim') return CodeMirror.Pass;
-        else if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
-    },
-    "Cmd-S": function () {
-        return false;
-    },
-    "Ctrl-S": function () {
-        return false;
-    },
-    "Enter": "newlineAndIndentContinueMarkdownList",
-    "Tab": function (cm) {
-        var tab = '\t';
-        var spaces = Array(parseInt(cm.getOption("indentUnit")) + 1).join(" ");
-        //auto indent whole line when in list or blockquote
-        var cursor = cm.getCursor();
-        var line = cm.getLine(cursor.line);
-        var regex = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))/;
-        var match;
-        var multiple = cm.getSelection().split('\n').length > 1 || cm.getSelections().length > 1;
-        if (multiple) {
-            cm.execCommand('defaultTab');
-        } else if ((match = regex.exec(line)) !== null) {
-            var ch = match[1].length;
-            var pos = {
-                line: cursor.line,
-                ch: ch
-            };
-            if (cm.getOption('indentWithTabs'))
-                cm.replaceRange(tab, pos, pos, '+input');
-            else
-                cm.replaceRange(spaces, pos, pos, '+input');
-        } else {
-            if (cm.getOption('indentWithTabs'))
-                cm.execCommand('defaultTab');
-            else {
-                cm.replaceSelection(spaces);
-            }
-        }
-    },
-    "Cmd-Left": "goLineLeftSmart",
-    "Cmd-Right": "goLineRight",
-    "Ctrl-C": function (cm) {
-        if (!mac && cm.getOption('keyMap').substr(0, 3) === 'vim') document.execCommand("copy");
-        else return CodeMirror.Pass;
-    },
-    "Ctrl-*": function (cm) {
-        wrapTextWith(cm, '*');
-    },
-    "Shift-Ctrl-8": function (cm) {
-        wrapTextWith(cm, '*');
-    },
-    "Ctrl-_": function (cm) {
-        wrapTextWith(cm, '_');
-    },
-    "Shift-Ctrl--": function (cm) {
-        wrapTextWith(cm, '_');
-    },
-    "Ctrl-~": function (cm) {
-        wrapTextWith(cm, '~');
-    },
-    "Shift-Ctrl-`": function (cm) {
-        wrapTextWith(cm, '~');
-    },
-    "Ctrl-^": function (cm) {
-        wrapTextWith(cm, '^');
-    },
-    "Shift-Ctrl-6": function (cm) {
-        wrapTextWith(cm, '^');
-    },
-    "Ctrl-+": function (cm) {
-        wrapTextWith(cm, '+');
-    },
-    "Shift-Ctrl-=": function (cm) {
-        wrapTextWith(cm, '+');
-    },
-    "Ctrl-=": function (cm) {
-        wrapTextWith(cm, '=');
-    },
-    "Shift-Ctrl-Backspace": function (cm) {
-        wrapTextWith(cm, 'Backspace');
-    }
-};
-
-var wrapSymbols = ['*', '_', '~', '^', '+', '='];
-
-function wrapTextWith(cm, symbol) {
-    if (!cm.getSelection()) {
-        return CodeMirror.Pass;
-    } else {
-        var ranges = cm.listSelections();
-        for (var i = 0; i < ranges.length; i++) {
-            var range = ranges[i];
-            if (!range.empty()) {
-                var from = range.from(), to = range.to();
-                if (symbol !== 'Backspace') {
-                    cm.replaceRange(symbol, to, to, '+input');
-                    cm.replaceRange(symbol, from, from, '+input');
-                    // workaround selection range not correct after add symbol
-                    var _ranges = cm.listSelections();
-                    var anchorIndex = editor.indexFromPos(_ranges[i].anchor);
-                    var headIndex = editor.indexFromPos(_ranges[i].head);
-                    if (anchorIndex > headIndex) {
-                        _ranges[i].anchor.ch--;
-                    } else {
-                        _ranges[i].head.ch--;
-                    }
-                    cm.setSelections(_ranges);
-                } else {
-                    var preEndPos = {
-                        line: to.line,
-                        ch: to.ch + 1
-                    };
-                    var preText = cm.getRange(to, preEndPos);
-                    var preIndex = wrapSymbols.indexOf(preText);
-                    var postEndPos = {
-                        line: from.line,
-                        ch: from.ch - 1
-                    };
-                    var postText = cm.getRange(postEndPos, from);
-                    var postIndex = wrapSymbols.indexOf(postText);
-                    // check if surround symbol are list in array and matched
-                    if (preIndex > -1 && postIndex > -1 && preIndex === postIndex) {
-                        cm.replaceRange("", to, preEndPos, '+delete');
-                        cm.replaceRange("", postEndPos, from, '+delete');
-                    }
-                }
-            }
-        }
-    }
-}
 
 var idleTime = 300000; //5 mins
 var updateViewDebounce = 100;
@@ -432,353 +299,25 @@ window.fileTypes = {
     "py": "python"
 };
 
-//editor settings
+// editor settings
 var textit = document.getElementById("textit");
-if (!textit) throw new Error("There was no textit area!");
-window.editor = CodeMirror.fromTextArea(textit, {
-    mode: defaultEditorMode,
-    backdrop: defaultEditorMode,
-    keyMap: "sublime",
-    viewportMargin: viewportMargin,
-    styleActiveLine: true,
-    lineNumbers: true,
-    lineWrapping: true,
-    showCursorWhenSelecting: true,
-    highlightSelectionMatches: true,
-    indentUnit: 4,
-    continueComments: "Enter",
-    theme: "one-dark",
-    inputStyle: "textarea",
-    matchBrackets: true,
-    autoCloseBrackets: true,
-    matchTags: {
-        bothTags: true
-    },
-    autoCloseTags: true,
-    foldGutter: true,
-    gutters: ["CodeMirror-linenumbers", "authorship-gutters", "CodeMirror-foldgutter"],
-    extraKeys: defaultExtraKeys,
-    flattenSpans: true,
-    addModeClass: true,
-    readOnly: true,
-    autoRefresh: true,
-    otherCursors: true,
-    placeholder: "← Start by entering a title here\n===\nVisit /features if you don't know what to do.\nHappy hacking :)"
-});
+if (!textit) {
+    throw new Error("There was no textit area!");
+}
+
+const editorInstance = new Editor();
+var editor = editorInstance.init(textit);
+
+// TODO: global referncing in jquery-textcomplete patch
+window.editor = editor;
+
 var inlineAttach = inlineAttachment.editors.codemirror4.attach(editor);
 defaultTextHeight = parseInt($(".CodeMirror").css('line-height'));
 
-var statusBarTemplate = null;
-var statusBar = null;
-var statusPanel = null;
-var statusCursor = null;
-var statusFile = null;
-var statusIndicators = null;
-var statusLength = null;
-var statusKeymap = null;
-var statusIndent = null;
-var statusTheme = null;
-var statusSpellcheck = null;
-var statusPreferences = null;
-
-function getStatusBarTemplate(callback) {
-    $.get(serverurl + '/views/statusbar.html', function (template) {
-        statusBarTemplate = template;
-        if (callback) callback();
-    });
-}
-getStatusBarTemplate();
-
-function addStatusBar() {
-    if (!statusBarTemplate) {
-        getStatusBarTemplate(addStatusBar);
-        return;
-    }
-    statusBar = $(statusBarTemplate);
-    statusCursor = statusBar.find('.status-cursor');
-    statusFile = statusBar.find('.status-file');
-    statusIndicators = statusBar.find('.status-indicators');
-    statusIndent = statusBar.find('.status-indent');
-    statusKeymap = statusBar.find('.status-keymap');
-    statusLength = statusBar.find('.status-length');
-    statusTheme = statusBar.find('.status-theme');
-    statusSpellcheck = statusBar.find('.status-spellcheck');
-    statusPreferences = statusBar.find('.status-preferences');
-    statusPanel = editor.addPanel(statusBar[0], {
-        position: "bottom"
-    });
-
-    setIndent();
-    setKeymap();
-    setTheme();
-    setSpellcheck();
-    setPreferences();
-}
-
-function setIndent() {
-    var cookieIndentType = Cookies.get('indent_type');
-    var cookieTabSize = parseInt(Cookies.get('tab_size'));
-    var cookieSpaceUnits = parseInt(Cookies.get('space_units'));
-    if (cookieIndentType) {
-        if (cookieIndentType == 'tab') {
-            editor.setOption('indentWithTabs', true);
-            if (cookieTabSize)
-                editor.setOption('indentUnit', cookieTabSize);
-        } else if (cookieIndentType == 'space') {
-            editor.setOption('indentWithTabs', false);
-            if (cookieSpaceUnits)
-                editor.setOption('indentUnit', cookieSpaceUnits);
-        }
-    }
-    if (cookieTabSize)
-        editor.setOption('tabSize', cookieTabSize);
-
-    var type = statusIndicators.find('.indent-type');
-    var widthLabel = statusIndicators.find('.indent-width-label');
-    var widthInput = statusIndicators.find('.indent-width-input');
-
-    function setType() {
-        if (editor.getOption('indentWithTabs')) {
-            Cookies.set('indent_type', 'tab', {
-                expires: 365
-            });
-            type.text('Tab Size:');
-        } else {
-            Cookies.set('indent_type', 'space', {
-                expires: 365
-            });
-            type.text('Spaces:');
-        }
-    }
-    setType();
-
-    function setUnit() {
-        var unit = editor.getOption('indentUnit');
-        if (editor.getOption('indentWithTabs')) {
-            Cookies.set('tab_size', unit, {
-                expires: 365
-            });
-        } else {
-            Cookies.set('space_units', unit, {
-                expires: 365
-            });
-        }
-        widthLabel.text(unit);
-    }
-    setUnit();
-
-    type.click(function () {
-        if (editor.getOption('indentWithTabs')) {
-            editor.setOption('indentWithTabs', false);
-            cookieSpaceUnits = parseInt(Cookies.get('space_units'));
-            if (cookieSpaceUnits)
-                editor.setOption('indentUnit', cookieSpaceUnits)
-        } else {
-            editor.setOption('indentWithTabs', true);
-            cookieTabSize = parseInt(Cookies.get('tab_size'));
-            if (cookieTabSize) {
-                editor.setOption('indentUnit', cookieTabSize);
-                editor.setOption('tabSize', cookieTabSize);
-            }
-        }
-        setType();
-        setUnit();
-    });
-    widthLabel.click(function () {
-        if (widthLabel.is(':visible')) {
-            widthLabel.addClass('hidden');
-            widthInput.removeClass('hidden');
-            widthInput.val(editor.getOption('indentUnit'));
-            widthInput.select();
-        } else {
-            widthLabel.removeClass('hidden');
-            widthInput.addClass('hidden');
-        }
-    });
-    widthInput.on('change', function () {
-        var val = parseInt(widthInput.val());
-        if (!val) val = editor.getOption('indentUnit');
-        if (val < 1) val = 1;
-        else if (val > 10) val = 10;
-
-        if (editor.getOption('indentWithTabs')) {
-            editor.setOption('tabSize', val);
-        }
-        editor.setOption('indentUnit', val);
-        setUnit();
-    });
-    widthInput.on('blur', function () {
-        widthLabel.removeClass('hidden');
-        widthInput.addClass('hidden');
-    });
-}
-
-function setKeymap() {
-    var cookieKeymap = Cookies.get('keymap');
-    if (cookieKeymap)
-        editor.setOption('keyMap', cookieKeymap);
-
-    var label = statusIndicators.find('.ui-keymap-label');
-    var sublime = statusIndicators.find('.ui-keymap-sublime');
-    var emacs = statusIndicators.find('.ui-keymap-emacs');
-    var vim = statusIndicators.find('.ui-keymap-vim');
-
-    function setKeymapLabel() {
-        var keymap = editor.getOption('keyMap');
-        Cookies.set('keymap', keymap, {
-            expires: 365
-        });
-        label.text(keymap);
-        restoreOverrideEditorKeymap();
-        setOverrideBrowserKeymap();
-    }
-    setKeymapLabel();
-
-    sublime.click(function () {
-        editor.setOption('keyMap', 'sublime');
-        setKeymapLabel();
-    });
-    emacs.click(function () {
-        editor.setOption('keyMap', 'emacs');
-        setKeymapLabel();
-    });
-    vim.click(function () {
-        editor.setOption('keyMap', 'vim');
-        setKeymapLabel();
-    });
-}
-
-function setTheme() {
-    var cookieTheme = Cookies.get('theme');
-    if (cookieTheme) {
-        editor.setOption('theme', cookieTheme);
-    }
-
-    var themeToggle = statusTheme.find('.ui-theme-toggle');
-    themeToggle.click(function () {
-        var theme = editor.getOption('theme');
-        if (theme == "one-dark") {
-            theme = "default";
-        } else {
-            theme = "one-dark";
-        }
-        editor.setOption('theme', theme);
-        Cookies.set('theme', theme, {
-            expires: 365
-        });
-        checkTheme();
-    });
-    function checkTheme() {
-        var theme = editor.getOption('theme');
-        if (theme == "one-dark") {
-            themeToggle.removeClass('active');
-        } else {
-            themeToggle.addClass('active');
-        }
-    }
-    checkTheme();
-}
-
-function setSpellcheck() {
-    var cookieSpellcheck = Cookies.get('spellcheck');
-    if (cookieSpellcheck) {
-        var mode = null;
-        if (cookieSpellcheck === 'true' || cookieSpellcheck === true) {
-            mode = 'spell-checker';
-        } else {
-            mode = defaultEditorMode;
-        }
-        if (mode && mode !== editor.getOption('mode')) {
-            editor.setOption('mode', mode);
-        }
-    }
-
-    var spellcheckToggle = statusSpellcheck.find('.ui-spellcheck-toggle');
-    spellcheckToggle.click(function () {
-        var mode = editor.getOption('mode');
-        if (mode == defaultEditorMode) {
-            mode = "spell-checker";
-        } else {
-            mode = defaultEditorMode;
-        }
-        if (mode && mode !== editor.getOption('mode')) {
-            editor.setOption('mode', mode);
-        }
-        Cookies.set('spellcheck', (mode == "spell-checker"), {
-            expires: 365
-        });
-        checkSpellcheck();
-    });
-    function checkSpellcheck() {
-        var mode = editor.getOption('mode');
-        if (mode == defaultEditorMode) {
-            spellcheckToggle.removeClass('active');
-        } else {
-            spellcheckToggle.addClass('active');
-        }
-    }
-    checkSpellcheck();
-
-    //workaround spellcheck might not activate beacuse the ajax loading
-    if (num_loaded < 2) {
-        var spellcheckTimer = setInterval(function () {
-            if (num_loaded >= 2) {
-                if (editor.getOption('mode') == "spell-checker")
-                    editor.setOption('mode', "spell-checker");
-                clearInterval(spellcheckTimer);
-            }
-        }, 100);
-    }
-}
-
-var jumpToAddressBarKeymapName = mac ? "Cmd-L" : "Ctrl-L";
-var jumpToAddressBarKeymapValue = null;
-function resetEditorKeymapToBrowserKeymap() {
-    var keymap = editor.getOption('keyMap');
-    if (!jumpToAddressBarKeymapValue) {
-        jumpToAddressBarKeymapValue = CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName];
-        delete CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName];
-    }
-}
-function restoreOverrideEditorKeymap() {
-    var keymap = editor.getOption('keyMap');
-    if (jumpToAddressBarKeymapValue) {
-        CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName] = jumpToAddressBarKeymapValue;
-        jumpToAddressBarKeymapValue = null;
-    }
-}
-function setOverrideBrowserKeymap() {
-    var overrideBrowserKeymap = $('.ui-preferences-override-browser-keymap label > input[type="checkbox"]');
-    if(overrideBrowserKeymap.is(":checked")) {
-        Cookies.set('preferences-override-browser-keymap', true, {
-            expires: 365
-        });
-        restoreOverrideEditorKeymap();
-    } else {
-        Cookies.remove('preferences-override-browser-keymap');
-        resetEditorKeymapToBrowserKeymap();
-    }
-}
-
-function setPreferences() {
-    var overrideBrowserKeymap = $('.ui-preferences-override-browser-keymap label > input[type="checkbox"]');
-    var cookieOverrideBrowserKeymap = Cookies.get('preferences-override-browser-keymap');
-    if (cookieOverrideBrowserKeymap && cookieOverrideBrowserKeymap === "true") {
-        overrideBrowserKeymap.prop('checked', true);
-    } else {
-        overrideBrowserKeymap.prop('checked', false);
-    }
-    setOverrideBrowserKeymap();
-
-    overrideBrowserKeymap.change(function() {
-        setOverrideBrowserKeymap();
-    });
-}
-
 var selection = null;
 
 function updateStatusBar() {
-    if (!statusBar) return;
+    if (!editorInstance.statusBar) return;
     var cursor = editor.getCursor();
     var cursorText = 'Line ' + (cursor.line + 1) + ', Columns ' + (cursor.ch + 1);
     if (selection) {
@@ -800,102 +339,25 @@ function updateStatusBar() {
         if (start.line !== end.line || selectionCharCount > 0)
             cursorText += selectionText;
     }
-    statusCursor.text(cursorText);
+    editorInstance.statusCursor.text(cursorText);
     var fileText = ' — ' + editor.lineCount() + ' Lines';
-    statusFile.text(fileText);
+    editorInstance.statusFile.text(fileText);
     var docLength = editor.getValue().length;
-    statusLength.text('Length ' + docLength);
+    editorInstance.statusLength.text('Length ' + docLength);
     if (docLength > (docmaxlength * 0.95)) {
-        statusLength.css('color', 'red');
-        statusLength.attr('title', 'Your almost reach note max length limit.');
+        editorInstance.statusLength.css('color', 'red');
+        editorInstance.statusLength.attr('title', 'Your almost reach note max length limit.');
     } else if (docLength > (docmaxlength * 0.8)) {
-        statusLength.css('color', 'orange');
-        statusLength.attr('title', 'You nearly fill the note, consider to make more pieces.');
+        editorInstance.statusLength.css('color', 'orange');
+        editorInstance.statusLength.attr('title', 'You nearly fill the note, consider to make more pieces.');
     } else {
-        statusLength.css('color', 'white');
-        statusLength.attr('title', 'You could write up to ' + docmaxlength + ' characters in this note.');
+        editorInstance.statusLength.css('color', 'white');
+        editorInstance.statusLength.attr('title', 'You could write up to ' + docmaxlength + ' characters in this note.');
     }
 }
 
-//ui vars
-window.ui = {
-    spinner: $(".ui-spinner"),
-    content: $(".ui-content"),
-    toolbar: {
-        shortStatus: $(".ui-short-status"),
-        status: $(".ui-status"),
-        new: $(".ui-new"),
-        publish: $(".ui-publish"),
-        extra: {
-            revision: $(".ui-extra-revision"),
-            slide: $(".ui-extra-slide")
-        },
-        download: {
-            markdown: $(".ui-download-markdown"),
-            html: $(".ui-download-html"),
-            rawhtml: $(".ui-download-raw-html"),
-            pdf: $(".ui-download-pdf-beta"),
-        },
-        export: {
-            dropbox: $(".ui-save-dropbox"),
-            googleDrive: $(".ui-save-google-drive"),
-            gist: $(".ui-save-gist"),
-            snippet: $(".ui-save-snippet")
-        },
-        import: {
-            dropbox: $(".ui-import-dropbox"),
-            googleDrive: $(".ui-import-google-drive"),
-            gist: $(".ui-import-gist"),
-            snippet: $(".ui-import-snippet"),
-            clipboard: $(".ui-import-clipboard")
-        },
-        mode: $(".ui-mode"),
-        edit: $(".ui-edit"),
-        view: $(".ui-view"),
-        both: $(".ui-both"),
-        uploadImage: $(".ui-upload-image")
-    },
-    infobar: {
-        lastchange: $(".ui-lastchange"),
-        lastchangeuser: $(".ui-lastchangeuser"),
-        nolastchangeuser: $(".ui-no-lastchangeuser"),
-        permission: {
-            permission: $(".ui-permission"),
-            label: $(".ui-permission-label"),
-            freely: $(".ui-permission-freely"),
-            editable: $(".ui-permission-editable"),
-            locked: $(".ui-permission-locked"),
-            private: $(".ui-permission-private"),
-            limited: $(".ui-permission-limited"),
-            protected: $(".ui-permission-protected")
-        },
-        delete: $(".ui-delete-note")
-    },
-    toc: {
-        toc: $('.ui-toc'),
-        affix: $('.ui-affix-toc'),
-        label: $('.ui-toc-label'),
-        dropdown: $('.ui-toc-dropdown')
-    },
-    area: {
-        edit: $(".ui-edit-area"),
-        view: $(".ui-view-area"),
-        codemirror: $(".ui-edit-area .CodeMirror"),
-        codemirrorScroll: $(".ui-edit-area .CodeMirror .CodeMirror-scroll"),
-        codemirrorSizer: $(".ui-edit-area .CodeMirror .CodeMirror-sizer"),
-        codemirrorSizerInner: $(".ui-edit-area .CodeMirror .CodeMirror-sizer > div"),
-        markdown: $(".ui-view-area .markdown-body"),
-        resize: {
-            handle: $('.ui-resizable-handle'),
-            syncToggle: $('.ui-sync-toggle')
-        }
-    },
-    modal: {
-        snippetImportProjects: $("#snippetImportModalProjects"),
-        snippetImportSnippets: $("#snippetImportModalSnippets"),
-        revision: $("#revisionModal")
-    }
-};
+//  initalize ui reference
+const ui = getUIElements();
 
 //page actions
 var opts = {
@@ -1146,7 +608,7 @@ var lastEditorWidth = 0;
 var previousFocusOnEditor = null;
 
 function checkEditorStyle() {
-    var desireHeight = statusBar ? (ui.area.edit.height() - statusBar.outerHeight()) : ui.area.edit.height();
+    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' || currentMode == modeType.both) {
@@ -1381,8 +843,8 @@ function changeMode(type) {
     if (currentMode == modeType.edit || currentMode == modeType.both) {
         ui.toolbar.uploadImage.fadeIn();
         //add and update status bar
-        if (!statusBar) {
-            addStatusBar();
+        if (!editorInstance.statusBar) {
+            editorInstance.addStatusBar();
             updateStatusBar();
         }
         //work around foldGutter might not init properly
@@ -4069,6 +3531,6 @@ $(editor.getInputField())
         },
         'textComplete:hide': function (e) {
             $(this).data('autocompleting', false);
-            editor.setOption("extraKeys", defaultExtraKeys);
+            editor.setOption("extraKeys", editorInstance.defaultExtraKeys);
         }
     });
diff --git a/public/js/lib/editor/index.js b/public/js/lib/editor/index.js
new file mode 100644
index 0000000..ec22ac9
--- /dev/null
+++ b/public/js/lib/editor/index.js
@@ -0,0 +1,459 @@
+import * as utils from './utils';
+
+/* config section */
+const isMac = CodeMirror.keyMap.default === CodeMirror.keyMap.macDefault;
+const defaultEditorMode = 'gfm';
+const viewportMargin = 20;
+
+const jumpToAddressBarKeymapName = isMac ? "Cmd-L" : "Ctrl-L";
+
+export default class Editor {
+    constructor() {
+        this.editor = null;
+
+        this.defaultExtraKeys = {
+            "F10": function (cm) {
+                cm.setOption("fullScreen", !cm.getOption("fullScreen"));
+            },
+            "Esc": function (cm) {
+                if (cm.getOption('keyMap').substr(0, 3) === 'vim') return CodeMirror.Pass;
+                else if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
+            },
+            "Cmd-S": function () {
+                return false;
+            },
+            "Ctrl-S": function () {
+                return false;
+            },
+            "Enter": "newlineAndIndentContinueMarkdownList",
+            "Tab": function (cm) {
+                var tab = '\t';
+
+                // contruct x length spaces
+                var spaces = Array(parseInt(cm.getOption("indentUnit")) + 1).join(" ");
+
+                //auto indent whole line when in list or blockquote
+                var cursor = cm.getCursor();
+                var line = cm.getLine(cursor.line);
+
+                // this regex match the following patterns
+                // 1. blockquote starts with "> " or ">>"
+                // 2. unorder list starts with *+-
+                // 3. order list starts with "1." or "1)"
+                var regex = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))/;
+
+                var match;
+                var multiple = cm.getSelection().split('\n').length > 1 || cm.getSelections().length > 1;
+
+                if (multiple) {
+                    cm.execCommand('defaultTab');
+                } else if ((match = regex.exec(line)) !== null) {
+                    var ch = match[1].length;
+                    var pos = {
+                        line: cursor.line,
+                        ch: ch
+                    };
+                    if (cm.getOption('indentWithTabs'))
+                        cm.replaceRange(tab, pos, pos, '+input');
+                    else
+                        cm.replaceRange(spaces, pos, pos, '+input');
+                } else {
+                    if (cm.getOption('indentWithTabs'))
+                        cm.execCommand('defaultTab');
+                    else {
+                        cm.replaceSelection(spaces);
+                    }
+                }
+            },
+            "Cmd-Left": "goLineLeftSmart",
+            "Cmd-Right": "goLineRight",
+            "Ctrl-C": function (cm) {
+                if (!isMac && cm.getOption('keyMap').substr(0, 3) === 'vim') {
+                    document.execCommand("copy");
+                } else {
+                    return CodeMirror.Pass;
+                }
+            },
+            "Ctrl-*": (cm) => {
+                utils.wrapTextWith(this.editor, cm, '*');
+            },
+            "Shift-Ctrl-8": (cm) => {
+                utils.wrapTextWith(this.editor, cm, '*');
+            },
+            "Ctrl-_": (cm) => {
+                utils.wrapTextWith(this.editor, cm, '_');
+            },
+            "Shift-Ctrl--": (cm) => {
+                utils.wrapTextWith(this.editor, cm, '_');
+            },
+            "Ctrl-~": (cm) => {
+                utils.wrapTextWith(this.editor, cm, '~');
+            },
+            "Shift-Ctrl-`": (cm) => {
+                utils.wrapTextWith(this.editor, cm, '~');
+            },
+            "Ctrl-^": (cm) => {
+                utils.wrapTextWith(this.editor, cm, '^');
+            },
+            "Shift-Ctrl-6": (cm) => {
+                utils.wrapTextWith(this.editor, cm, '^');
+            },
+            "Ctrl-+": (cm) => {
+                utils.wrapTextWith(this.editor, cm, '+');
+            },
+            "Shift-Ctrl-=": (cm) => {
+                utils.wrapTextWith(this.editor, cm, '+');
+            },
+            "Ctrl-=": (cm) => {
+                utils.wrapTextWith(this.editor, cm, '=');
+            },
+            "Shift-Ctrl-Backspace": (cm) => {
+                utils.wrapTextWith(this.editor, cm, 'Backspace');
+            }
+        };
+
+        this.jumpToAddressBarKeymapValue = null;
+    }
+
+    getStatusBarTemplate(callback) {
+        $.get(window.serverurl + '/views/statusbar.html', (template) => {
+            this.statusBarTemplate = template;
+            if (callback) callback();
+        });
+    }
+
+    addStatusBar() {
+        if (!this.statusBarTemplate) {
+            this.getStatusBarTemplate(this.addStatusBar);
+            return;
+        }
+        this.statusBar = $(this.statusBarTemplate);
+        this.statusCursor = this.statusBar.find('.status-cursor');
+        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();
+    }
+
+    setIndent() {
+        var cookieIndentType = Cookies.get('indent_type');
+        var cookieTabSize = parseInt(Cookies.get('tab_size'));
+        var cookieSpaceUnits = parseInt(Cookies.get('space_units'));
+        if (cookieIndentType) {
+            if (cookieIndentType == 'tab') {
+                this.editor.setOption('indentWithTabs', true);
+                if (cookieTabSize)
+                    this.editor.setOption('indentUnit', cookieTabSize);
+            } else if (cookieIndentType == 'space') {
+                this.editor.setOption('indentWithTabs', false);
+                if (cookieSpaceUnits)
+                    this.editor.setOption('indentUnit', cookieSpaceUnits);
+            }
+        }
+        if (cookieTabSize)
+            this.editor.setOption('tabSize', cookieTabSize);
+
+        var type = this.statusIndicators.find('.indent-type');
+        var widthLabel = this.statusIndicators.find('.indent-width-label');
+        var widthInput = this.statusIndicators.find('.indent-width-input');
+
+        const setType = () => {
+            if (this.editor.getOption('indentWithTabs')) {
+                Cookies.set('indent_type', 'tab', {
+                    expires: 365
+                });
+                type.text('Tab Size:');
+            } else {
+                Cookies.set('indent_type', 'space', {
+                    expires: 365
+                });
+                type.text('Spaces:');
+            }
+        }
+        setType();
+
+        const setUnit = () => {
+            var unit = this.editor.getOption('indentUnit');
+            if (this.editor.getOption('indentWithTabs')) {
+                Cookies.set('tab_size', unit, {
+                    expires: 365
+                });
+            } else {
+                Cookies.set('space_units', unit, {
+                    expires: 365
+                });
+            }
+            widthLabel.text(unit);
+        }
+        setUnit();
+
+        type.click(() => {
+            if (this.editor.getOption('indentWithTabs')) {
+                this.editor.setOption('indentWithTabs', false);
+                cookieSpaceUnits = parseInt(Cookies.get('space_units'));
+                if (cookieSpaceUnits)
+                    this.editor.setOption('indentUnit', cookieSpaceUnits)
+            } else {
+                this.editor.setOption('indentWithTabs', true);
+                cookieTabSize = parseInt(Cookies.get('tab_size'));
+                if (cookieTabSize) {
+                    this.editor.setOption('indentUnit', cookieTabSize);
+                    this.editor.setOption('tabSize', cookieTabSize);
+                }
+            }
+            setType();
+            setUnit();
+        });
+        widthLabel.click(() => {
+            if (widthLabel.is(':visible')) {
+                widthLabel.addClass('hidden');
+                widthInput.removeClass('hidden');
+                widthInput.val(this.editor.getOption('indentUnit'));
+                widthInput.select();
+            } else {
+                widthLabel.removeClass('hidden');
+                widthInput.addClass('hidden');
+            }
+        });
+        widthInput.on('change', () => {
+            var val = parseInt(widthInput.val());
+            if (!val) val = this.editor.getOption('indentUnit');
+            if (val < 1) val = 1;
+            else if (val > 10) val = 10;
+
+            if (this.editor.getOption('indentWithTabs')) {
+                this.editor.setOption('tabSize', val);
+            }
+            this.editor.setOption('indentUnit', val);
+            setUnit();
+        });
+        widthInput.on('blur', function () {
+            widthLabel.removeClass('hidden');
+            widthInput.addClass('hidden');
+        });
+    }
+
+    setKeymap() {
+        var cookieKeymap = Cookies.get('keymap');
+        if (cookieKeymap)
+            this.editor.setOption('keyMap', cookieKeymap);
+
+        var label = this.statusIndicators.find('.ui-keymap-label');
+        var sublime = this.statusIndicators.find('.ui-keymap-sublime');
+        var emacs = this.statusIndicators.find('.ui-keymap-emacs');
+        var vim = this.statusIndicators.find('.ui-keymap-vim');
+
+        const setKeymapLabel = () => {
+            var keymap = this.editor.getOption('keyMap');
+            Cookies.set('keymap', keymap, {
+                expires: 365
+            });
+            label.text(keymap);
+            this.restoreOverrideEditorKeymap();
+            this.setOverrideBrowserKeymap();
+        }
+        setKeymapLabel();
+
+        sublime.click(() => {
+            this.editor.setOption('keyMap', 'sublime');
+            setKeymapLabel();
+        });
+        emacs.click(() => {
+            this.editor.setOption('keyMap', 'emacs');
+            setKeymapLabel();
+        });
+        vim.click(() => {
+            this.editor.setOption('keyMap', 'vim');
+            setKeymapLabel();
+        });
+    }
+
+    setTheme() {
+        var cookieTheme = Cookies.get('theme');
+        if (cookieTheme) {
+            this.editor.setOption('theme', cookieTheme);
+        }
+
+        var themeToggle = this.statusTheme.find('.ui-theme-toggle');
+
+        const checkTheme = () => {
+            var theme = this.editor.getOption('theme');
+            if (theme == "one-dark") {
+                themeToggle.removeClass('active');
+            } else {
+                themeToggle.addClass('active');
+            }
+        }
+
+        themeToggle.click(() => {
+            var theme = this.editor.getOption('theme');
+            if (theme == "one-dark") {
+                theme = "default";
+            } else {
+                theme = "one-dark";
+            }
+            this.editor.setOption('theme', theme);
+            Cookies.set('theme', theme, {
+                expires: 365
+            });
+
+            checkTheme();
+        });
+
+        checkTheme();
+    }
+
+    setSpellcheck() {
+        var cookieSpellcheck = Cookies.get('spellcheck');
+        if (cookieSpellcheck) {
+            var mode = null;
+            if (cookieSpellcheck === 'true' || cookieSpellcheck === true) {
+                mode = 'spell-checker';
+            } else {
+                mode = defaultEditorMode;
+            }
+            if (mode && mode !== this.editor.getOption('mode')) {
+                this.editor.setOption('mode', mode);
+            }
+        }
+
+        var spellcheckToggle = this.statusSpellcheck.find('.ui-spellcheck-toggle');
+
+        const checkSpellcheck = () => {
+            var mode = this.editor.getOption('mode');
+            if (mode == defaultEditorMode) {
+                spellcheckToggle.removeClass('active');
+            } else {
+                spellcheckToggle.addClass('active');
+            }
+        }
+
+        spellcheckToggle.click(() => {
+            var mode = this.editor.getOption('mode');
+            if (mode == defaultEditorMode) {
+                mode = "spell-checker";
+            } else {
+                mode = defaultEditorMode;
+            }
+            if (mode && mode !== this.editor.getOption('mode')) {
+                this.editor.setOption('mode', mode);
+            }
+            Cookies.set('spellcheck', (mode == "spell-checker"), {
+                expires: 365
+            });
+
+            checkSpellcheck();
+        });
+
+        checkSpellcheck();
+
+        //workaround spellcheck might not activate beacuse the ajax loading
+        if (window.num_loaded < 2) {
+            var spellcheckTimer = setInterval(() => {
+                if (window.num_loaded >= 2) {
+                    if (this.editor.getOption('mode') == "spell-checker") {
+                        this.editor.setOption('mode', "spell-checker");
+                    }
+                    clearInterval(spellcheckTimer);
+                }
+            }, 100);
+        }
+    }
+
+    resetEditorKeymapToBrowserKeymap() {
+        var keymap = this.editor.getOption('keyMap');
+        if (!this.jumpToAddressBarKeymapValue) {
+            this.jumpToAddressBarKeymapValue = CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName];
+            delete CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName];
+        }
+    }
+
+    restoreOverrideEditorKeymap() {
+        var keymap = this.editor.getOption('keyMap');
+        if (this.jumpToAddressBarKeymapValue) {
+            CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName] = this.jumpToAddressBarKeymapValue;
+            this.jumpToAddressBarKeymapValue = null;
+        }
+    }
+    setOverrideBrowserKeymap() {
+        var overrideBrowserKeymap = $('.ui-preferences-override-browser-keymap label > input[type="checkbox"]');
+        if (overrideBrowserKeymap.is(":checked")) {
+            Cookies.set('preferences-override-browser-keymap', true, {
+                expires: 365
+            });
+            this.restoreOverrideEditorKeymap();
+        } else {
+            Cookies.remove('preferences-override-browser-keymap');
+            this.resetEditorKeymapToBrowserKeymap();
+        }
+    }
+
+    setPreferences() {
+        var overrideBrowserKeymap = $('.ui-preferences-override-browser-keymap label > input[type="checkbox"]');
+        var cookieOverrideBrowserKeymap = Cookies.get('preferences-override-browser-keymap');
+        if (cookieOverrideBrowserKeymap && cookieOverrideBrowserKeymap === "true") {
+            overrideBrowserKeymap.prop('checked', true);
+        } else {
+            overrideBrowserKeymap.prop('checked', false);
+        }
+        this.setOverrideBrowserKeymap();
+
+        overrideBrowserKeymap.change(() => {
+            this.setOverrideBrowserKeymap();
+        });
+    }
+
+    init(textit) {
+        this.editor = CodeMirror.fromTextArea(textit, {
+            mode: defaultEditorMode,
+            backdrop: defaultEditorMode,
+            keyMap: "sublime",
+            viewportMargin: viewportMargin,
+            styleActiveLine: true,
+            lineNumbers: true,
+            lineWrapping: true,
+            showCursorWhenSelecting: true,
+            highlightSelectionMatches: true,
+            indentUnit: 4,
+            continueComments: "Enter",
+            theme: "one-dark",
+            inputStyle: "textarea",
+            matchBrackets: true,
+            autoCloseBrackets: true,
+            matchTags: {
+                bothTags: true
+            },
+            autoCloseTags: true,
+            foldGutter: true,
+            gutters: ["CodeMirror-linenumbers", "authorship-gutters", "CodeMirror-foldgutter"],
+            extraKeys: this.defaultExtraKeys,
+            flattenSpans: true,
+            addModeClass: true,
+            readOnly: true,
+            autoRefresh: true,
+            otherCursors: true,
+            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;
+    }
+
+    getEditor() {
+        return this.editor;
+    }
+}
diff --git a/public/js/lib/editor/utils.js b/public/js/lib/editor/utils.js
new file mode 100644
index 0000000..120d364
--- /dev/null
+++ b/public/js/lib/editor/utils.js
@@ -0,0 +1,46 @@
+const wrapSymbols = ['*', '_', '~', '^', '+', '='];
+export function wrapTextWith(editor, cm, symbol) {
+    if (!cm.getSelection()) {
+        return CodeMirror.Pass;
+    } else {
+        var ranges = cm.listSelections();
+        for (var i = 0; i < ranges.length; i++) {
+            var range = ranges[i];
+            if (!range.empty()) {
+                var from = range.from(), to = range.to();
+                if (symbol !== 'Backspace') {
+                    cm.replaceRange(symbol, to, to, '+input');
+                    cm.replaceRange(symbol, from, from, '+input');
+                    // workaround selection range not correct after add symbol
+                    var _ranges = cm.listSelections();
+                    var anchorIndex = editor.indexFromPos(_ranges[i].anchor);
+                    var headIndex = editor.indexFromPos(_ranges[i].head);
+                    if (anchorIndex > headIndex) {
+                        _ranges[i].anchor.ch--;
+                    } else {
+                        _ranges[i].head.ch--;
+                    }
+                    cm.setSelections(_ranges);
+                } else {
+                    var preEndPos = {
+                        line: to.line,
+                        ch: to.ch + 1
+                    };
+                    var preText = cm.getRange(to, preEndPos);
+                    var preIndex = wrapSymbols.indexOf(preText);
+                    var postEndPos = {
+                        line: from.line,
+                        ch: from.ch - 1
+                    };
+                    var postText = cm.getRange(postEndPos, from);
+                    var postIndex = wrapSymbols.indexOf(postText);
+                    // check if surround symbol are list in array and matched
+                    if (preIndex > -1 && postIndex > -1 && preIndex === postIndex) {
+                        cm.replaceRange("", to, preEndPos, '+delete');
+                        cm.replaceRange("", postEndPos, from, '+delete');
+                    }
+                }
+            }
+        }
+    }
+}

From 9b513f619fe74a579fadd807f86bd38f1049c56b Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Thu, 9 Mar 2017 15:39:42 +0800
Subject: [PATCH 3/8] Use JavaScript standard style

---
 public/js/lib/editor/index.js       | 893 ++++++++++++++--------------
 public/js/lib/editor/ui-elements.js | 146 ++---
 public/js/lib/editor/utils.js       |  88 +--
 3 files changed, 577 insertions(+), 550 deletions(-)

diff --git a/public/js/lib/editor/index.js b/public/js/lib/editor/index.js
index ec22ac9..a97fd97 100644
--- a/public/js/lib/editor/index.js
+++ b/public/js/lib/editor/index.js
@@ -1,459 +1,482 @@
-import * as utils from './utils';
+import * as utils from './utils'
 
 /* config section */
-const isMac = CodeMirror.keyMap.default === CodeMirror.keyMap.macDefault;
-const defaultEditorMode = 'gfm';
-const viewportMargin = 20;
+const isMac = CodeMirror.keyMap.default === CodeMirror.keyMap.macDefault
+const defaultEditorMode = 'gfm'
+const viewportMargin = 20
 
-const jumpToAddressBarKeymapName = isMac ? "Cmd-L" : "Ctrl-L";
+const jumpToAddressBarKeymapName = isMac ? 'Cmd-L' : 'Ctrl-L'
 
 export default class Editor {
-    constructor() {
-        this.editor = null;
+  constructor () {
+    this.editor = null
 
-        this.defaultExtraKeys = {
-            "F10": function (cm) {
-                cm.setOption("fullScreen", !cm.getOption("fullScreen"));
-            },
-            "Esc": function (cm) {
-                if (cm.getOption('keyMap').substr(0, 3) === 'vim') return CodeMirror.Pass;
-                else if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
-            },
-            "Cmd-S": function () {
-                return false;
-            },
-            "Ctrl-S": function () {
-                return false;
-            },
-            "Enter": "newlineAndIndentContinueMarkdownList",
-            "Tab": function (cm) {
-                var tab = '\t';
-
-                // contruct x length spaces
-                var spaces = Array(parseInt(cm.getOption("indentUnit")) + 1).join(" ");
-
-                //auto indent whole line when in list or blockquote
-                var cursor = cm.getCursor();
-                var line = cm.getLine(cursor.line);
-
-                // this regex match the following patterns
-                // 1. blockquote starts with "> " or ">>"
-                // 2. unorder list starts with *+-
-                // 3. order list starts with "1." or "1)"
-                var regex = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))/;
-
-                var match;
-                var multiple = cm.getSelection().split('\n').length > 1 || cm.getSelections().length > 1;
-
-                if (multiple) {
-                    cm.execCommand('defaultTab');
-                } else if ((match = regex.exec(line)) !== null) {
-                    var ch = match[1].length;
-                    var pos = {
-                        line: cursor.line,
-                        ch: ch
-                    };
-                    if (cm.getOption('indentWithTabs'))
-                        cm.replaceRange(tab, pos, pos, '+input');
-                    else
-                        cm.replaceRange(spaces, pos, pos, '+input');
-                } else {
-                    if (cm.getOption('indentWithTabs'))
-                        cm.execCommand('defaultTab');
-                    else {
-                        cm.replaceSelection(spaces);
-                    }
-                }
-            },
-            "Cmd-Left": "goLineLeftSmart",
-            "Cmd-Right": "goLineRight",
-            "Ctrl-C": function (cm) {
-                if (!isMac && cm.getOption('keyMap').substr(0, 3) === 'vim') {
-                    document.execCommand("copy");
-                } else {
-                    return CodeMirror.Pass;
-                }
-            },
-            "Ctrl-*": (cm) => {
-                utils.wrapTextWith(this.editor, cm, '*');
-            },
-            "Shift-Ctrl-8": (cm) => {
-                utils.wrapTextWith(this.editor, cm, '*');
-            },
-            "Ctrl-_": (cm) => {
-                utils.wrapTextWith(this.editor, cm, '_');
-            },
-            "Shift-Ctrl--": (cm) => {
-                utils.wrapTextWith(this.editor, cm, '_');
-            },
-            "Ctrl-~": (cm) => {
-                utils.wrapTextWith(this.editor, cm, '~');
-            },
-            "Shift-Ctrl-`": (cm) => {
-                utils.wrapTextWith(this.editor, cm, '~');
-            },
-            "Ctrl-^": (cm) => {
-                utils.wrapTextWith(this.editor, cm, '^');
-            },
-            "Shift-Ctrl-6": (cm) => {
-                utils.wrapTextWith(this.editor, cm, '^');
-            },
-            "Ctrl-+": (cm) => {
-                utils.wrapTextWith(this.editor, cm, '+');
-            },
-            "Shift-Ctrl-=": (cm) => {
-                utils.wrapTextWith(this.editor, cm, '+');
-            },
-            "Ctrl-=": (cm) => {
-                utils.wrapTextWith(this.editor, cm, '=');
-            },
-            "Shift-Ctrl-Backspace": (cm) => {
-                utils.wrapTextWith(this.editor, cm, 'Backspace');
-            }
-        };
-
-        this.jumpToAddressBarKeymapValue = null;
-    }
-
-    getStatusBarTemplate(callback) {
-        $.get(window.serverurl + '/views/statusbar.html', (template) => {
-            this.statusBarTemplate = template;
-            if (callback) callback();
-        });
-    }
-
-    addStatusBar() {
-        if (!this.statusBarTemplate) {
-            this.getStatusBarTemplate(this.addStatusBar);
-            return;
+    this.defaultExtraKeys = {
+      F10: function (cm) {
+        cm.setOption('fullScreen', !cm.getOption('fullScreen'))
+      },
+      Esc: function (cm) {
+        if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
+          return CodeMirror.Pass
+        } else if (cm.getOption('fullScreen')) {
+          cm.setOption('fullScreen', false)
         }
-        this.statusBar = $(this.statusBarTemplate);
-        this.statusCursor = this.statusBar.find('.status-cursor');
-        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"
-        });
+      },
+      'Cmd-S': function () {
+        return false
+      },
+      'Ctrl-S': function () {
+        return false
+      },
+      Enter: 'newlineAndIndentContinueMarkdownList',
+      Tab: function (cm) {
+        var tab = '\t'
 
-        this.setIndent();
-        this.setKeymap();
-        this.setTheme();
-        this.setSpellcheck();
-        this.setPreferences();
-    }
+        // contruct x length spaces
+        var spaces = Array(parseInt(cm.getOption('indentUnit')) + 1).join(' ')
 
-    setIndent() {
-        var cookieIndentType = Cookies.get('indent_type');
-        var cookieTabSize = parseInt(Cookies.get('tab_size'));
-        var cookieSpaceUnits = parseInt(Cookies.get('space_units'));
-        if (cookieIndentType) {
-            if (cookieIndentType == 'tab') {
-                this.editor.setOption('indentWithTabs', true);
-                if (cookieTabSize)
-                    this.editor.setOption('indentUnit', cookieTabSize);
-            } else if (cookieIndentType == 'space') {
-                this.editor.setOption('indentWithTabs', false);
-                if (cookieSpaceUnits)
-                    this.editor.setOption('indentUnit', cookieSpaceUnits);
-            }
-        }
-        if (cookieTabSize)
-            this.editor.setOption('tabSize', cookieTabSize);
+        // auto indent whole line when in list or blockquote
+        var cursor = cm.getCursor()
+        var line = cm.getLine(cursor.line)
 
-        var type = this.statusIndicators.find('.indent-type');
-        var widthLabel = this.statusIndicators.find('.indent-width-label');
-        var widthInput = this.statusIndicators.find('.indent-width-input');
+        // this regex match the following patterns
+        // 1. blockquote starts with "> " or ">>"
+        // 2. unorder list starts with *+-
+        // 3. order list starts with "1." or "1)"
+        var regex = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))/
 
-        const setType = () => {
-            if (this.editor.getOption('indentWithTabs')) {
-                Cookies.set('indent_type', 'tab', {
-                    expires: 365
-                });
-                type.text('Tab Size:');
-            } else {
-                Cookies.set('indent_type', 'space', {
-                    expires: 365
-                });
-                type.text('Spaces:');
-            }
-        }
-        setType();
+        var match
+        var multiple = cm.getSelection().split('\n').length > 1 ||
+          cm.getSelections().length > 1
 
-        const setUnit = () => {
-            var unit = this.editor.getOption('indentUnit');
-            if (this.editor.getOption('indentWithTabs')) {
-                Cookies.set('tab_size', unit, {
-                    expires: 365
-                });
-            } else {
-                Cookies.set('space_units', unit, {
-                    expires: 365
-                });
-            }
-            widthLabel.text(unit);
-        }
-        setUnit();
-
-        type.click(() => {
-            if (this.editor.getOption('indentWithTabs')) {
-                this.editor.setOption('indentWithTabs', false);
-                cookieSpaceUnits = parseInt(Cookies.get('space_units'));
-                if (cookieSpaceUnits)
-                    this.editor.setOption('indentUnit', cookieSpaceUnits)
-            } else {
-                this.editor.setOption('indentWithTabs', true);
-                cookieTabSize = parseInt(Cookies.get('tab_size'));
-                if (cookieTabSize) {
-                    this.editor.setOption('indentUnit', cookieTabSize);
-                    this.editor.setOption('tabSize', cookieTabSize);
-                }
-            }
-            setType();
-            setUnit();
-        });
-        widthLabel.click(() => {
-            if (widthLabel.is(':visible')) {
-                widthLabel.addClass('hidden');
-                widthInput.removeClass('hidden');
-                widthInput.val(this.editor.getOption('indentUnit'));
-                widthInput.select();
-            } else {
-                widthLabel.removeClass('hidden');
-                widthInput.addClass('hidden');
-            }
-        });
-        widthInput.on('change', () => {
-            var val = parseInt(widthInput.val());
-            if (!val) val = this.editor.getOption('indentUnit');
-            if (val < 1) val = 1;
-            else if (val > 10) val = 10;
-
-            if (this.editor.getOption('indentWithTabs')) {
-                this.editor.setOption('tabSize', val);
-            }
-            this.editor.setOption('indentUnit', val);
-            setUnit();
-        });
-        widthInput.on('blur', function () {
-            widthLabel.removeClass('hidden');
-            widthInput.addClass('hidden');
-        });
-    }
-
-    setKeymap() {
-        var cookieKeymap = Cookies.get('keymap');
-        if (cookieKeymap)
-            this.editor.setOption('keyMap', cookieKeymap);
-
-        var label = this.statusIndicators.find('.ui-keymap-label');
-        var sublime = this.statusIndicators.find('.ui-keymap-sublime');
-        var emacs = this.statusIndicators.find('.ui-keymap-emacs');
-        var vim = this.statusIndicators.find('.ui-keymap-vim');
-
-        const setKeymapLabel = () => {
-            var keymap = this.editor.getOption('keyMap');
-            Cookies.set('keymap', keymap, {
-                expires: 365
-            });
-            label.text(keymap);
-            this.restoreOverrideEditorKeymap();
-            this.setOverrideBrowserKeymap();
-        }
-        setKeymapLabel();
-
-        sublime.click(() => {
-            this.editor.setOption('keyMap', 'sublime');
-            setKeymapLabel();
-        });
-        emacs.click(() => {
-            this.editor.setOption('keyMap', 'emacs');
-            setKeymapLabel();
-        });
-        vim.click(() => {
-            this.editor.setOption('keyMap', 'vim');
-            setKeymapLabel();
-        });
-    }
-
-    setTheme() {
-        var cookieTheme = Cookies.get('theme');
-        if (cookieTheme) {
-            this.editor.setOption('theme', cookieTheme);
-        }
-
-        var themeToggle = this.statusTheme.find('.ui-theme-toggle');
-
-        const checkTheme = () => {
-            var theme = this.editor.getOption('theme');
-            if (theme == "one-dark") {
-                themeToggle.removeClass('active');
-            } else {
-                themeToggle.addClass('active');
-            }
-        }
-
-        themeToggle.click(() => {
-            var theme = this.editor.getOption('theme');
-            if (theme == "one-dark") {
-                theme = "default";
-            } else {
-                theme = "one-dark";
-            }
-            this.editor.setOption('theme', theme);
-            Cookies.set('theme', theme, {
-                expires: 365
-            });
-
-            checkTheme();
-        });
-
-        checkTheme();
-    }
-
-    setSpellcheck() {
-        var cookieSpellcheck = Cookies.get('spellcheck');
-        if (cookieSpellcheck) {
-            var mode = null;
-            if (cookieSpellcheck === 'true' || cookieSpellcheck === true) {
-                mode = 'spell-checker';
-            } else {
-                mode = defaultEditorMode;
-            }
-            if (mode && mode !== this.editor.getOption('mode')) {
-                this.editor.setOption('mode', mode);
-            }
-        }
-
-        var spellcheckToggle = this.statusSpellcheck.find('.ui-spellcheck-toggle');
-
-        const checkSpellcheck = () => {
-            var mode = this.editor.getOption('mode');
-            if (mode == defaultEditorMode) {
-                spellcheckToggle.removeClass('active');
-            } else {
-                spellcheckToggle.addClass('active');
-            }
-        }
-
-        spellcheckToggle.click(() => {
-            var mode = this.editor.getOption('mode');
-            if (mode == defaultEditorMode) {
-                mode = "spell-checker";
-            } else {
-                mode = defaultEditorMode;
-            }
-            if (mode && mode !== this.editor.getOption('mode')) {
-                this.editor.setOption('mode', mode);
-            }
-            Cookies.set('spellcheck', (mode == "spell-checker"), {
-                expires: 365
-            });
-
-            checkSpellcheck();
-        });
-
-        checkSpellcheck();
-
-        //workaround spellcheck might not activate beacuse the ajax loading
-        if (window.num_loaded < 2) {
-            var spellcheckTimer = setInterval(() => {
-                if (window.num_loaded >= 2) {
-                    if (this.editor.getOption('mode') == "spell-checker") {
-                        this.editor.setOption('mode', "spell-checker");
-                    }
-                    clearInterval(spellcheckTimer);
-                }
-            }, 100);
-        }
-    }
-
-    resetEditorKeymapToBrowserKeymap() {
-        var keymap = this.editor.getOption('keyMap');
-        if (!this.jumpToAddressBarKeymapValue) {
-            this.jumpToAddressBarKeymapValue = CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName];
-            delete CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName];
-        }
-    }
-
-    restoreOverrideEditorKeymap() {
-        var keymap = this.editor.getOption('keyMap');
-        if (this.jumpToAddressBarKeymapValue) {
-            CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName] = this.jumpToAddressBarKeymapValue;
-            this.jumpToAddressBarKeymapValue = null;
-        }
-    }
-    setOverrideBrowserKeymap() {
-        var overrideBrowserKeymap = $('.ui-preferences-override-browser-keymap label > input[type="checkbox"]');
-        if (overrideBrowserKeymap.is(":checked")) {
-            Cookies.set('preferences-override-browser-keymap', true, {
-                expires: 365
-            });
-            this.restoreOverrideEditorKeymap();
+        if (multiple) {
+          cm.execCommand('defaultTab')
+        } else if ((match = regex.exec(line)) !== null) {
+          var ch = match[1].length
+          var pos = {
+            line: cursor.line,
+            ch: ch
+          }
+          if (cm.getOption('indentWithTabs')) {
+            cm.replaceRange(tab, pos, pos, '+input')
+          } else {
+            cm.replaceRange(spaces, pos, pos, '+input')
+          }
         } else {
-            Cookies.remove('preferences-override-browser-keymap');
-            this.resetEditorKeymapToBrowserKeymap();
+          if (cm.getOption('indentWithTabs')) {
+            cm.execCommand('defaultTab')
+          } else {
+            cm.replaceSelection(spaces)
+          }
         }
-    }
-
-    setPreferences() {
-        var overrideBrowserKeymap = $('.ui-preferences-override-browser-keymap label > input[type="checkbox"]');
-        var cookieOverrideBrowserKeymap = Cookies.get('preferences-override-browser-keymap');
-        if (cookieOverrideBrowserKeymap && cookieOverrideBrowserKeymap === "true") {
-            overrideBrowserKeymap.prop('checked', true);
+      },
+      'Cmd-Left': 'goLineLeftSmart',
+      'Cmd-Right': 'goLineRight',
+      'Ctrl-C': function (cm) {
+        if (!isMac && cm.getOption('keyMap').substr(0, 3) === 'vim') {
+          document.execCommand('copy')
         } else {
-            overrideBrowserKeymap.prop('checked', false);
+          return CodeMirror.Pass
         }
-        this.setOverrideBrowserKeymap();
-
-        overrideBrowserKeymap.change(() => {
-            this.setOverrideBrowserKeymap();
-        });
+      },
+      'Ctrl-*': cm => {
+        utils.wrapTextWith(this.editor, cm, '*')
+      },
+      'Shift-Ctrl-8': cm => {
+        utils.wrapTextWith(this.editor, cm, '*')
+      },
+      'Ctrl-_': cm => {
+        utils.wrapTextWith(this.editor, cm, '_')
+      },
+      'Shift-Ctrl--': cm => {
+        utils.wrapTextWith(this.editor, cm, '_')
+      },
+      'Ctrl-~': cm => {
+        utils.wrapTextWith(this.editor, cm, '~')
+      },
+      'Shift-Ctrl-`': cm => {
+        utils.wrapTextWith(this.editor, cm, '~')
+      },
+      'Ctrl-^': cm => {
+        utils.wrapTextWith(this.editor, cm, '^')
+      },
+      'Shift-Ctrl-6': cm => {
+        utils.wrapTextWith(this.editor, cm, '^')
+      },
+      'Ctrl-+': cm => {
+        utils.wrapTextWith(this.editor, cm, '+')
+      },
+      'Shift-Ctrl-=': cm => {
+        utils.wrapTextWith(this.editor, cm, '+')
+      },
+      'Ctrl-=': cm => {
+        utils.wrapTextWith(this.editor, cm, '=')
+      },
+      'Shift-Ctrl-Backspace': cm => {
+        utils.wrapTextWith(this.editor, cm, 'Backspace')
+      }
     }
 
-    init(textit) {
-        this.editor = CodeMirror.fromTextArea(textit, {
-            mode: defaultEditorMode,
-            backdrop: defaultEditorMode,
-            keyMap: "sublime",
-            viewportMargin: viewportMargin,
-            styleActiveLine: true,
-            lineNumbers: true,
-            lineWrapping: true,
-            showCursorWhenSelecting: true,
-            highlightSelectionMatches: true,
-            indentUnit: 4,
-            continueComments: "Enter",
-            theme: "one-dark",
-            inputStyle: "textarea",
-            matchBrackets: true,
-            autoCloseBrackets: true,
-            matchTags: {
-                bothTags: true
-            },
-            autoCloseTags: true,
-            foldGutter: true,
-            gutters: ["CodeMirror-linenumbers", "authorship-gutters", "CodeMirror-foldgutter"],
-            extraKeys: this.defaultExtraKeys,
-            flattenSpans: true,
-            addModeClass: true,
-            readOnly: true,
-            autoRefresh: true,
-            otherCursors: true,
-            placeholder: "← Start by entering a title here\n===\nVisit /features if you don't know what to do.\nHappy hacking :)"
-        });
+    this.jumpToAddressBarKeymapValue = null
+  }
 
-        this.getStatusBarTemplate();
+  getStatusBarTemplate (callback) {
+    $.get(window.serverurl + '/views/statusbar.html', template => {
+      this.statusBarTemplate = template
+      if (callback) callback()
+    })
+  }
 
-        return this.editor;
+  addStatusBar () {
+    if (!this.statusBarTemplate) {
+      this.getStatusBarTemplate(this.addStatusBar)
+      return
+    }
+    this.statusBar = $(this.statusBarTemplate)
+    this.statusCursor = this.statusBar.find('.status-cursor')
+    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()
+  }
+
+  setIndent () {
+    var cookieIndentType = Cookies.get('indent_type')
+    var cookieTabSize = parseInt(Cookies.get('tab_size'))
+    var cookieSpaceUnits = parseInt(Cookies.get('space_units'))
+    if (cookieIndentType) {
+      if (cookieIndentType === 'tab') {
+        this.editor.setOption('indentWithTabs', true)
+        if (cookieTabSize) {
+          this.editor.setOption('indentUnit', cookieTabSize)
+        }
+      } else if (cookieIndentType === 'space') {
+        this.editor.setOption('indentWithTabs', false)
+        if (cookieSpaceUnits) {
+          this.editor.setOption('indentUnit', cookieSpaceUnits)
+        }
+      }
+    }
+    if (cookieTabSize) {
+      this.editor.setOption('tabSize', cookieTabSize)
     }
 
-    getEditor() {
-        return this.editor;
+    var type = this.statusIndicators.find('.indent-type')
+    var widthLabel = this.statusIndicators.find('.indent-width-label')
+    var widthInput = this.statusIndicators.find('.indent-width-input')
+
+    const setType = () => {
+      if (this.editor.getOption('indentWithTabs')) {
+        Cookies.set('indent_type', 'tab', {
+          expires: 365
+        })
+        type.text('Tab Size:')
+      } else {
+        Cookies.set('indent_type', 'space', {
+          expires: 365
+        })
+        type.text('Spaces:')
+      }
     }
+    setType()
+
+    const setUnit = () => {
+      var unit = this.editor.getOption('indentUnit')
+      if (this.editor.getOption('indentWithTabs')) {
+        Cookies.set('tab_size', unit, {
+          expires: 365
+        })
+      } else {
+        Cookies.set('space_units', unit, {
+          expires: 365
+        })
+      }
+      widthLabel.text(unit)
+    }
+    setUnit()
+
+    type.click(() => {
+      if (this.editor.getOption('indentWithTabs')) {
+        this.editor.setOption('indentWithTabs', false)
+        cookieSpaceUnits = parseInt(Cookies.get('space_units'))
+        if (cookieSpaceUnits) {
+          this.editor.setOption('indentUnit', cookieSpaceUnits)
+        }
+      } else {
+        this.editor.setOption('indentWithTabs', true)
+        cookieTabSize = parseInt(Cookies.get('tab_size'))
+        if (cookieTabSize) {
+          this.editor.setOption('indentUnit', cookieTabSize)
+          this.editor.setOption('tabSize', cookieTabSize)
+        }
+      }
+      setType()
+      setUnit()
+    })
+    widthLabel.click(() => {
+      if (widthLabel.is(':visible')) {
+        widthLabel.addClass('hidden')
+        widthInput.removeClass('hidden')
+        widthInput.val(this.editor.getOption('indentUnit'))
+        widthInput.select()
+      } else {
+        widthLabel.removeClass('hidden')
+        widthInput.addClass('hidden')
+      }
+    })
+    widthInput.on('change', () => {
+      var val = parseInt(widthInput.val())
+      if (!val) val = this.editor.getOption('indentUnit')
+      if (val < 1) val = 1
+      else if (val > 10) val = 10
+
+      if (this.editor.getOption('indentWithTabs')) {
+        this.editor.setOption('tabSize', val)
+      }
+      this.editor.setOption('indentUnit', val)
+      setUnit()
+    })
+    widthInput.on('blur', function () {
+      widthLabel.removeClass('hidden')
+      widthInput.addClass('hidden')
+    })
+  }
+
+  setKeymap () {
+    var cookieKeymap = Cookies.get('keymap')
+    if (cookieKeymap) {
+      this.editor.setOption('keyMap', cookieKeymap)
+    }
+
+    var label = this.statusIndicators.find('.ui-keymap-label')
+    var sublime = this.statusIndicators.find('.ui-keymap-sublime')
+    var emacs = this.statusIndicators.find('.ui-keymap-emacs')
+    var vim = this.statusIndicators.find('.ui-keymap-vim')
+
+    const setKeymapLabel = () => {
+      var keymap = this.editor.getOption('keyMap')
+      Cookies.set('keymap', keymap, {
+        expires: 365
+      })
+      label.text(keymap)
+      this.restoreOverrideEditorKeymap()
+      this.setOverrideBrowserKeymap()
+    }
+    setKeymapLabel()
+
+    sublime.click(() => {
+      this.editor.setOption('keyMap', 'sublime')
+      setKeymapLabel()
+    })
+    emacs.click(() => {
+      this.editor.setOption('keyMap', 'emacs')
+      setKeymapLabel()
+    })
+    vim.click(() => {
+      this.editor.setOption('keyMap', 'vim')
+      setKeymapLabel()
+    })
+  }
+
+  setTheme () {
+    var cookieTheme = Cookies.get('theme')
+    if (cookieTheme) {
+      this.editor.setOption('theme', cookieTheme)
+    }
+
+    var themeToggle = this.statusTheme.find('.ui-theme-toggle')
+
+    const checkTheme = () => {
+      var theme = this.editor.getOption('theme')
+      if (theme === 'one-dark') {
+        themeToggle.removeClass('active')
+      } else {
+        themeToggle.addClass('active')
+      }
+    }
+
+    themeToggle.click(() => {
+      var theme = this.editor.getOption('theme')
+      if (theme === 'one-dark') {
+        theme = 'default'
+      } else {
+        theme = 'one-dark'
+      }
+      this.editor.setOption('theme', theme)
+      Cookies.set('theme', theme, {
+        expires: 365
+      })
+
+      checkTheme()
+    })
+
+    checkTheme()
+  }
+
+  setSpellcheck () {
+    var cookieSpellcheck = Cookies.get('spellcheck')
+    if (cookieSpellcheck) {
+      var mode = null
+      if (cookieSpellcheck === 'true' || cookieSpellcheck === true) {
+        mode = 'spell-checker'
+      } else {
+        mode = defaultEditorMode
+      }
+      if (mode && mode !== this.editor.getOption('mode')) {
+        this.editor.setOption('mode', mode)
+      }
+    }
+
+    var spellcheckToggle = this.statusSpellcheck.find('.ui-spellcheck-toggle')
+
+    const checkSpellcheck = () => {
+      var mode = this.editor.getOption('mode')
+      if (mode === defaultEditorMode) {
+        spellcheckToggle.removeClass('active')
+      } else {
+        spellcheckToggle.addClass('active')
+      }
+    }
+
+    spellcheckToggle.click(() => {
+      var mode = this.editor.getOption('mode')
+      if (mode === defaultEditorMode) {
+        mode = 'spell-checker'
+      } else {
+        mode = defaultEditorMode
+      }
+      if (mode && mode !== this.editor.getOption('mode')) {
+        this.editor.setOption('mode', mode)
+      }
+      Cookies.set('spellcheck', mode === 'spell-checker', {
+        expires: 365
+      })
+
+      checkSpellcheck()
+    })
+
+    checkSpellcheck()
+
+    // workaround spellcheck might not activate beacuse the ajax loading
+    if (window.num_loaded < 2) {
+      var spellcheckTimer = setInterval(
+        () => {
+          if (window.num_loaded >= 2) {
+            if (this.editor.getOption('mode') === 'spell-checker') {
+              this.editor.setOption('mode', 'spell-checker')
+            }
+            clearInterval(spellcheckTimer)
+          }
+        },
+        100,
+      )
+    }
+  }
+
+  resetEditorKeymapToBrowserKeymap () {
+    var keymap = this.editor.getOption('keyMap')
+    if (!this.jumpToAddressBarKeymapValue) {
+      this.jumpToAddressBarKeymapValue = CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName]
+      delete CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName]
+    }
+  }
+
+  restoreOverrideEditorKeymap () {
+    var keymap = this.editor.getOption('keyMap')
+    if (this.jumpToAddressBarKeymapValue) {
+      CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName] = this.jumpToAddressBarKeymapValue
+      this.jumpToAddressBarKeymapValue = null
+    }
+  }
+  setOverrideBrowserKeymap () {
+    var overrideBrowserKeymap = $(
+      '.ui-preferences-override-browser-keymap label > input[type="checkbox"]',
+    )
+    if (overrideBrowserKeymap.is(':checked')) {
+      Cookies.set('preferences-override-browser-keymap', true, {
+        expires: 365
+      })
+      this.restoreOverrideEditorKeymap()
+    } else {
+      Cookies.remove('preferences-override-browser-keymap')
+      this.resetEditorKeymapToBrowserKeymap()
+    }
+  }
+
+  setPreferences () {
+    var overrideBrowserKeymap = $(
+      '.ui-preferences-override-browser-keymap label > input[type="checkbox"]',
+    )
+    var cookieOverrideBrowserKeymap = Cookies.get(
+      'preferences-override-browser-keymap',
+    )
+    if (cookieOverrideBrowserKeymap && cookieOverrideBrowserKeymap === 'true') {
+      overrideBrowserKeymap.prop('checked', true)
+    } else {
+      overrideBrowserKeymap.prop('checked', false)
+    }
+    this.setOverrideBrowserKeymap()
+
+    overrideBrowserKeymap.change(() => {
+      this.setOverrideBrowserKeymap()
+    })
+  }
+
+  init (textit) {
+    this.editor = CodeMirror.fromTextArea(textit, {
+      mode: defaultEditorMode,
+      backdrop: defaultEditorMode,
+      keyMap: 'sublime',
+      viewportMargin: viewportMargin,
+      styleActiveLine: true,
+      lineNumbers: true,
+      lineWrapping: true,
+      showCursorWhenSelecting: true,
+      highlightSelectionMatches: true,
+      indentUnit: 4,
+      continueComments: 'Enter',
+      theme: 'one-dark',
+      inputStyle: 'textarea',
+      matchBrackets: true,
+      autoCloseBrackets: true,
+      matchTags: {
+        bothTags: true
+      },
+      autoCloseTags: true,
+      foldGutter: true,
+      gutters: [
+        'CodeMirror-linenumbers',
+        'authorship-gutters',
+        'CodeMirror-foldgutter'
+      ],
+      extraKeys: this.defaultExtraKeys,
+      flattenSpans: true,
+      addModeClass: true,
+      readOnly: true,
+      autoRefresh: true,
+      otherCursors: true,
+      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
+  }
+
+  getEditor () {
+    return this.editor
+  }
 }
diff --git a/public/js/lib/editor/ui-elements.js b/public/js/lib/editor/ui-elements.js
index d06e640..0d330d7 100644
--- a/public/js/lib/editor/ui-elements.js
+++ b/public/js/lib/editor/ui-elements.js
@@ -3,82 +3,84 @@
  */
 
 export const getUIElements = () => ({
-    spinner: $(".ui-spinner"),
-    content: $(".ui-content"),
-    toolbar: {
-        shortStatus: $(".ui-short-status"),
-        status: $(".ui-status"),
-        new: $(".ui-new"),
-        publish: $(".ui-publish"),
-        extra: {
-            revision: $(".ui-extra-revision"),
-            slide: $(".ui-extra-slide")
-        },
-        download: {
-            markdown: $(".ui-download-markdown"),
-            html: $(".ui-download-html"),
-            rawhtml: $(".ui-download-raw-html"),
-            pdf: $(".ui-download-pdf-beta"),
-        },
-        export: {
-            dropbox: $(".ui-save-dropbox"),
-            googleDrive: $(".ui-save-google-drive"),
-            gist: $(".ui-save-gist"),
-            snippet: $(".ui-save-snippet")
-        },
-        import: {
-            dropbox: $(".ui-import-dropbox"),
-            googleDrive: $(".ui-import-google-drive"),
-            gist: $(".ui-import-gist"),
-            snippet: $(".ui-import-snippet"),
-            clipboard: $(".ui-import-clipboard")
-        },
-        mode: $(".ui-mode"),
-        edit: $(".ui-edit"),
-        view: $(".ui-view"),
-        both: $(".ui-both"),
-        uploadImage: $(".ui-upload-image")
+  spinner: $('.ui-spinner'),
+  content: $('.ui-content'),
+  toolbar: {
+    shortStatus: $('.ui-short-status'),
+    status: $('.ui-status'),
+    new: $('.ui-new'),
+    publish: $('.ui-publish'),
+    extra: {
+      revision: $('.ui-extra-revision'),
+      slide: $('.ui-extra-slide')
     },
-    infobar: {
-        lastchange: $(".ui-lastchange"),
-        lastchangeuser: $(".ui-lastchangeuser"),
-        nolastchangeuser: $(".ui-no-lastchangeuser"),
-        permission: {
-            permission: $(".ui-permission"),
-            label: $(".ui-permission-label"),
-            freely: $(".ui-permission-freely"),
-            editable: $(".ui-permission-editable"),
-            locked: $(".ui-permission-locked"),
-            private: $(".ui-permission-private"),
-            limited: $(".ui-permission-limited"),
-            protected: $(".ui-permission-protected")
-        },
-        delete: $(".ui-delete-note")
+    download: {
+      markdown: $('.ui-download-markdown'),
+      html: $('.ui-download-html'),
+      rawhtml: $('.ui-download-raw-html'),
+      pdf: $('.ui-download-pdf-beta')
     },
-    toc: {
-        toc: $('.ui-toc'),
-        affix: $('.ui-affix-toc'),
-        label: $('.ui-toc-label'),
-        dropdown: $('.ui-toc-dropdown')
+    export: {
+      dropbox: $('.ui-save-dropbox'),
+      googleDrive: $('.ui-save-google-drive'),
+      gist: $('.ui-save-gist'),
+      snippet: $('.ui-save-snippet')
     },
-    area: {
-        edit: $(".ui-edit-area"),
-        view: $(".ui-view-area"),
-        codemirror: $(".ui-edit-area .CodeMirror"),
-        codemirrorScroll: $(".ui-edit-area .CodeMirror .CodeMirror-scroll"),
-        codemirrorSizer: $(".ui-edit-area .CodeMirror .CodeMirror-sizer"),
-        codemirrorSizerInner: $(".ui-edit-area .CodeMirror .CodeMirror-sizer > div"),
-        markdown: $(".ui-view-area .markdown-body"),
-        resize: {
-            handle: $('.ui-resizable-handle'),
-            syncToggle: $('.ui-sync-toggle')
-        }
+    import: {
+      dropbox: $('.ui-import-dropbox'),
+      googleDrive: $('.ui-import-google-drive'),
+      gist: $('.ui-import-gist'),
+      snippet: $('.ui-import-snippet'),
+      clipboard: $('.ui-import-clipboard')
     },
-    modal: {
-        snippetImportProjects: $("#snippetImportModalProjects"),
-        snippetImportSnippets: $("#snippetImportModalSnippets"),
-        revision: $("#revisionModal")
+    mode: $('.ui-mode'),
+    edit: $('.ui-edit'),
+    view: $('.ui-view'),
+    both: $('.ui-both'),
+    uploadImage: $('.ui-upload-image')
+  },
+  infobar: {
+    lastchange: $('.ui-lastchange'),
+    lastchangeuser: $('.ui-lastchangeuser'),
+    nolastchangeuser: $('.ui-no-lastchangeuser'),
+    permission: {
+      permission: $('.ui-permission'),
+      label: $('.ui-permission-label'),
+      freely: $('.ui-permission-freely'),
+      editable: $('.ui-permission-editable'),
+      locked: $('.ui-permission-locked'),
+      private: $('.ui-permission-private'),
+      limited: $('.ui-permission-limited'),
+      protected: $('.ui-permission-protected')
+    },
+    delete: $('.ui-delete-note')
+  },
+  toc: {
+    toc: $('.ui-toc'),
+    affix: $('.ui-affix-toc'),
+    label: $('.ui-toc-label'),
+    dropdown: $('.ui-toc-dropdown')
+  },
+  area: {
+    edit: $('.ui-edit-area'),
+    view: $('.ui-view-area'),
+    codemirror: $('.ui-edit-area .CodeMirror'),
+    codemirrorScroll: $('.ui-edit-area .CodeMirror .CodeMirror-scroll'),
+    codemirrorSizer: $('.ui-edit-area .CodeMirror .CodeMirror-sizer'),
+    codemirrorSizerInner: $(
+      '.ui-edit-area .CodeMirror .CodeMirror-sizer > div',
+    ),
+    markdown: $('.ui-view-area .markdown-body'),
+    resize: {
+      handle: $('.ui-resizable-handle'),
+      syncToggle: $('.ui-sync-toggle')
     }
+  },
+  modal: {
+    snippetImportProjects: $('#snippetImportModalProjects'),
+    snippetImportSnippets: $('#snippetImportModalSnippets'),
+    revision: $('#revisionModal')
+  }
 })
 
-export default getUIElements;
+export default getUIElements
diff --git a/public/js/lib/editor/utils.js b/public/js/lib/editor/utils.js
index 120d364..3702a16 100644
--- a/public/js/lib/editor/utils.js
+++ b/public/js/lib/editor/utils.js
@@ -1,46 +1,48 @@
-const wrapSymbols = ['*', '_', '~', '^', '+', '='];
-export function wrapTextWith(editor, cm, symbol) {
-    if (!cm.getSelection()) {
-        return CodeMirror.Pass;
-    } else {
-        var ranges = cm.listSelections();
-        for (var i = 0; i < ranges.length; i++) {
-            var range = ranges[i];
-            if (!range.empty()) {
-                var from = range.from(), to = range.to();
-                if (symbol !== 'Backspace') {
-                    cm.replaceRange(symbol, to, to, '+input');
-                    cm.replaceRange(symbol, from, from, '+input');
-                    // workaround selection range not correct after add symbol
-                    var _ranges = cm.listSelections();
-                    var anchorIndex = editor.indexFromPos(_ranges[i].anchor);
-                    var headIndex = editor.indexFromPos(_ranges[i].head);
-                    if (anchorIndex > headIndex) {
-                        _ranges[i].anchor.ch--;
-                    } else {
-                        _ranges[i].head.ch--;
-                    }
-                    cm.setSelections(_ranges);
-                } else {
-                    var preEndPos = {
-                        line: to.line,
-                        ch: to.ch + 1
-                    };
-                    var preText = cm.getRange(to, preEndPos);
-                    var preIndex = wrapSymbols.indexOf(preText);
-                    var postEndPos = {
-                        line: from.line,
-                        ch: from.ch - 1
-                    };
-                    var postText = cm.getRange(postEndPos, from);
-                    var postIndex = wrapSymbols.indexOf(postText);
-                    // check if surround symbol are list in array and matched
-                    if (preIndex > -1 && postIndex > -1 && preIndex === postIndex) {
-                        cm.replaceRange("", to, preEndPos, '+delete');
-                        cm.replaceRange("", postEndPos, from, '+delete');
-                    }
-                }
-            }
+const wrapSymbols = ['*', '_', '~', '^', '+', '=']
+export function wrapTextWith (editor, cm, symbol) {
+  if (!cm.getSelection()) {
+    return CodeMirror.Pass
+  } else {
+    var ranges = cm.listSelections()
+    for (var i = 0; i < ranges.length; i++) {
+      var range = ranges[i]
+      if (!range.empty()) {
+        const from = range.from()
+        const to = range.to()
+
+        if (symbol !== 'Backspace') {
+          cm.replaceRange(symbol, to, to, '+input')
+          cm.replaceRange(symbol, from, from, '+input')
+          // workaround selection range not correct after add symbol
+          var _ranges = cm.listSelections()
+          var anchorIndex = editor.indexFromPos(_ranges[i].anchor)
+          var headIndex = editor.indexFromPos(_ranges[i].head)
+          if (anchorIndex > headIndex) {
+            _ranges[i].anchor.ch--
+          } else {
+            _ranges[i].head.ch--
+          }
+          cm.setSelections(_ranges)
+        } else {
+          var preEndPos = {
+            line: to.line,
+            ch: to.ch + 1
+          }
+          var preText = cm.getRange(to, preEndPos)
+          var preIndex = wrapSymbols.indexOf(preText)
+          var postEndPos = {
+            line: from.line,
+            ch: from.ch - 1
+          }
+          var postText = cm.getRange(postEndPos, from)
+          var postIndex = wrapSymbols.indexOf(postText)
+          // check if surround symbol are list in array and matched
+          if (preIndex > -1 && postIndex > -1 && preIndex === postIndex) {
+            cm.replaceRange('', to, preEndPos, '+delete')
+            cm.replaceRange('', postEndPos, from, '+delete')
+          }
         }
+      }
     }
+  }
 }

From b4424419c0eb2e07c6783d6853346853957e78a8 Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Mon, 13 Mar 2017 21:42:09 +0800
Subject: [PATCH 4/8] Add standarjs globals

---
 package.json | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/package.json b/package.json
index 0578932..01c867d 100644
--- a/package.json
+++ b/package.json
@@ -170,6 +170,26 @@
     "webpack-parallel-uglify-plugin": "^0.2.0"
   },
   "standard": {
+    "globals": [
+      "$",
+      "CodeMirror",
+      "Cookies",
+      "moment", "editor",
+      "ui",
+      "Spinner",
+      "modeType",
+      "Idle",
+      "serverurl",
+      "key",
+      "gapi",
+      "Dropbox",
+      "FilePicker",
+      "ot",
+      "MediaUploader",
+      "hex2rgb",
+      "num_loaded",
+      "Visibility"
+    ],
     "ignore": [
       "lib/ot",
       "public/vendor"

From e97b609d91adab015148184935369216a199038e Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Mon, 13 Mar 2017 21:42:33 +0800
Subject: [PATCH 5/8] Update new editorconfig indent favor

---
 .editorconfig | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.editorconfig b/.editorconfig
index 619c178..e271be7 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -3,7 +3,7 @@ root = true
 # Tab indentation
 [*]
 indent_style = space
-indent_size = 4
+indent_size = 2
 trim_trailing_whitespace = true
 insert_final_newline = true
 

From cc30d370f7cf662eb4ea21fb93148a30bd8764d2 Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Mon, 13 Mar 2017 22:00:20 +0800
Subject: [PATCH 6/8] Fix variable exporting error

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

diff --git a/public/js/index.js b/public/js/index.js
index 7764fb5..53dd647 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -83,6 +83,7 @@ import getUIElements from './lib/editor/ui-elements'
 
 var defaultTextHeight = 20
 var viewportMargin = 20
+var defaultEditorMode = 'gfm'
 
 var idleTime = 300000 // 5 mins
 var updateViewDebounce = 100
@@ -363,7 +364,9 @@ function updateStatusBar () {
 }
 
 //  initalize ui reference
+// TODO: fix ui exporting
 const ui = getUIElements()
+window.ui = ui
 
 // page actions
 var opts = {
diff --git a/public/js/lib/editor/index.js b/public/js/lib/editor/index.js
index a97fd97..6ae40d8 100644
--- a/public/js/lib/editor/index.js
+++ b/public/js/lib/editor/index.js
@@ -10,7 +10,7 @@ const jumpToAddressBarKeymapName = isMac ? 'Cmd-L' : 'Ctrl-L'
 export default class Editor {
   constructor () {
     this.editor = null
-
+    this.jumpToAddressBarKeymapValue = null
     this.defaultExtraKeys = {
       F10: function (cm) {
         cm.setOption('fullScreen', !cm.getOption('fullScreen'))
@@ -116,8 +116,6 @@ export default class Editor {
         utils.wrapTextWith(this.editor, cm, 'Backspace')
       }
     }
-
-    this.jumpToAddressBarKeymapValue = null
   }
 
   getStatusBarTemplate (callback) {

From 24f1413654947a00ed81c5480164eca25b531e51 Mon Sep 17 00:00:00 2001
From: Yukai Huang <yukaihuangtw@gmail.com>
Date: Mon, 13 Mar 2017 22:00:29 +0800
Subject: [PATCH 7/8] Add inlineAttachment  to global

---
 package.json | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 01c867d..d1b4c03 100644
--- a/package.json
+++ b/package.json
@@ -188,7 +188,8 @@
       "MediaUploader",
       "hex2rgb",
       "num_loaded",
-      "Visibility"
+      "Visibility",
+      "inlineAttachment"
     ],
     "ignore": [
       "lib/ot",

From 16d80edc653dc02407cd570adfc485300ec9a66b Mon Sep 17 00:00:00 2001
From: Max Wu <jackymaxj@gmail.com>
Date: Tue, 14 Mar 2017 23:30:35 +0800
Subject: [PATCH 8/8] Fix badges and links in README.md

---
 README.md | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index a93c573..ad3c2af 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 HackMD
 ===
 
-[![Standard - JavaScript Style Guide](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard)
+[![Standard - JavaScript Style Guide][standardjs-image]][standardjs-url]
 
 [![Join the chat at https://gitter.im/hackmdio/hackmd][gitter-image]][gitter-url]
 [![build status][travis-image]][travis-url]
@@ -227,7 +227,10 @@ Additionally, now can show other clients' selections.
 See more at [http://operational-transformation.github.io/](http://operational-transformation.github.io/)
 
 **License under MIT.**
+
 [gitter-image]: https://badges.gitter.im/Join%20Chat.svg
 [gitter-url]: https://gitter.im/hackmdio/hackmd?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
 [travis-image]: https://travis-ci.org/hackmdio/hackmd.svg?branch=master
 [travis-url]: https://travis-ci.org/hackmdio/hackmd
+[standardjs-image]: https://cdn.rawgit.com/feross/standard/master/badge.svg
+[standardjs-url]: https://github.com/feross/standard