From 5bc642d02e8955b200bb21cf30e863fdf0c53765 Mon Sep 17 00:00:00 2001
From: BoHong Li <a60814billy@gmail.com>
Date: Thu, 9 Mar 2017 02:41:05 +0800
Subject: [PATCH] Use JavaScript Standard Style (part 2)

Fixed all fail on frontend code.
---
 public/js/cover.js               |  731 ++--
 public/js/extra.js               | 1944 +++++----
 public/js/google-drive-picker.js |  227 +-
 public/js/google-drive-upload.js |  229 +-
 public/js/history.js             |  568 ++-
 public/js/htmlExport.js          |   12 +-
 public/js/index.js               | 6863 +++++++++++++++---------------
 public/js/lib/common/login.js    |  133 +-
 public/js/lib/config/index.js    |   28 +-
 public/js/locale.js              |   42 +-
 public/js/pretty.js              |  211 +-
 public/js/render.js              |   86 +-
 public/js/reveal-markdown.js     |  741 ++--
 public/js/slide.js               |  189 +-
 public/js/syncscroll.js          |  580 +--
 public/vendor/md-toc.js          |  226 +-
 webpack.config.js                |   60 +-
 webpack.production.js            |  118 +-
 webpackBaseConfig.js             |  834 ++--
 19 files changed, 6790 insertions(+), 7032 deletions(-)

diff --git a/public/js/cover.js b/public/js/cover.js
index bc6e73f..a45a1c1 100644
--- a/public/js/cover.js
+++ b/public/js/cover.js
@@ -1,7 +1,10 @@
-require('./locale');
+/* eslint-env browser, jquery */
+/* global moment, serverurl */
 
-require('../css/cover.css');
-require('../css/site.css');
+require('./locale')
+
+require('../css/cover.css')
+require('../css/site.css')
 
 import {
     checkIfAuth,
@@ -9,7 +12,7 @@ import {
     getLoginState,
     resetCheckAuth,
     setloginStateChangeEvent
-} from './lib/common/login';
+} from './lib/common/login'
 
 import {
     clearDuplicatedHistory,
@@ -23,411 +26,403 @@ import {
     removeHistory,
     saveHistory,
     saveStorageHistoryToServer
-} from './history';
+} from './history'
 
-import { saveAs } from 'file-saver';
-import List from 'list.js';
-import S from 'string';
+import { saveAs } from 'file-saver'
+import List from 'list.js'
+import S from 'string'
 
 const options = {
-    valueNames: ['id', 'text', 'timestamp', 'fromNow', 'time', 'tags', 'pinned'],
-    item: '<li class="col-xs-12 col-sm-6 col-md-6 col-lg-4">\
-            <span class="id" style="display:none;"></span>\
-            <a href="#">\
-                <div class="item">\
-                    <div class="ui-history-pin fa fa-thumb-tack fa-fw"></div>\
-                    <div class="ui-history-close fa fa-close fa-fw" data-toggle="modal" data-target=".delete-modal"></div>\
-                    <div class="content">\
-                        <h4 class="text"></h4>\
-                        <p>\
-                            <i><i class="fa fa-clock-o"></i> visited </i><i class="fromNow"></i>\
-                            <br>\
-                            <i class="timestamp" style="display:none;"></i>\
-                            <i class="time"></i>\
-                        </p>\
-                        <p class="tags"></p>\
-                    </div>\
-                </div>\
-            </a>\
-           </li>',
-    page: 18,
-    plugins: [
-        ListPagination({
-            outerWindow: 1
-        })
-    ]
-};
-const historyList = new List('history', options);
+  valueNames: ['id', 'text', 'timestamp', 'fromNow', 'time', 'tags', 'pinned'],
+  item: '<li class="col-xs-12 col-sm-6 col-md-6 col-lg-4">' +
+            '<span class="id" style="display:none;"></span>' +
+            '<a href="#">' +
+                '<div class="item">' +
+                    '<div class="ui-history-pin fa fa-thumb-tack fa-fw"></div>' +
+                    '<div class="ui-history-close fa fa-close fa-fw" data-toggle="modal" data-target=".delete-modal"></div>' +
+                    '<div class="content">' +
+                        '<h4 class="text"></h4>' +
+                        '<p>' +
+                            '<i><i class="fa fa-clock-o"></i> visited </i><i class="fromNow"></i>' +
+                            '<br>' +
+                            '<i class="timestamp" style="display:none;"></i>' +
+                            '<i class="time"></i>' +
+                        '</p>' +
+                        '<p class="tags"></p>' +
+                    '</div>' +
+                '</div>' +
+            '</a>' +
+           '</li>',
+  page: 18,
+  plugins: [
+    window.ListPagination({
+      outerWindow: 1
+    })
+  ]
+}
+const historyList = new List('history', options)
 
-migrateHistoryFromTempCallback = pageInit;
-setloginStateChangeEvent(pageInit);
+window.migrateHistoryFromTempCallback = pageInit
+setloginStateChangeEvent(pageInit)
 
-pageInit();
+pageInit()
 
-function pageInit() {
-    checkIfAuth(
+function pageInit () {
+  checkIfAuth(
         data => {
-            $('.ui-signin').hide();
-            $('.ui-or').hide();
-            $('.ui-welcome').show();
-            if (data.photo) $('.ui-avatar').prop('src', data.photo).show();
-            else $('.ui-avatar').prop('src', '').hide();
-            $('.ui-name').html(data.name);
-            $('.ui-signout').show();
-            $(".ui-history").click();
-            parseServerToHistory(historyList, parseHistoryCallback);
+          $('.ui-signin').hide()
+          $('.ui-or').hide()
+          $('.ui-welcome').show()
+          if (data.photo) $('.ui-avatar').prop('src', data.photo).show()
+          else $('.ui-avatar').prop('src', '').hide()
+          $('.ui-name').html(data.name)
+          $('.ui-signout').show()
+          $('.ui-history').click()
+          parseServerToHistory(historyList, parseHistoryCallback)
         },
         () => {
-            $('.ui-signin').show();
-            $('.ui-or').show();
-            $('.ui-welcome').hide();
-            $('.ui-avatar').prop('src', '').hide();
-            $('.ui-name').html('');
-            $('.ui-signout').hide();
-            parseStorageToHistory(historyList, parseHistoryCallback);
+          $('.ui-signin').show()
+          $('.ui-or').show()
+          $('.ui-welcome').hide()
+          $('.ui-avatar').prop('src', '').hide()
+          $('.ui-name').html('')
+          $('.ui-signout').hide()
+          parseStorageToHistory(historyList, parseHistoryCallback)
         }
-    );
+    )
 }
 
-$(".masthead-nav li").click(function () {
-    $(this).siblings().removeClass("active");
-    $(this).addClass("active");
-});
+$('.masthead-nav li').click(function () {
+  $(this).siblings().removeClass('active')
+  $(this).addClass('active')
+})
 
 // prevent empty link change hash
 $('a[href="#"]').click(function (e) {
-    e.preventDefault();
-});
+  e.preventDefault()
+})
 
-$(".ui-home").click(function (e) {
-    if (!$("#home").is(':visible')) {
-        $(".section:visible").hide();
-        $("#home").fadeIn();
-    }
-});
+$('.ui-home').click(function (e) {
+  if (!$('#home').is(':visible')) {
+    $('.section:visible').hide()
+    $('#home').fadeIn()
+  }
+})
 
-$(".ui-history").click(() => {
-    if (!$("#history").is(':visible')) {
-        $(".section:visible").hide();
-        $("#history").fadeIn();
-    }
-});
+$('.ui-history').click(() => {
+  if (!$('#history').is(':visible')) {
+    $('.section:visible').hide()
+    $('#history').fadeIn()
+  }
+})
 
-function checkHistoryList() {
-    if ($("#history-list").children().length > 0) {
-        $('.pagination').show();
-        $(".ui-nohistory").hide();
-        $(".ui-import-from-browser").hide();
-    } else if ($("#history-list").children().length == 0) {
-        $('.pagination').hide();
-        $(".ui-nohistory").slideDown();
-        getStorageHistory(data => {
-            if (data && data.length > 0 && getLoginState() && historyList.items.length == 0) {
-                $(".ui-import-from-browser").slideDown();
-            }
-        });
-    }
+function checkHistoryList () {
+  if ($('#history-list').children().length > 0) {
+    $('.pagination').show()
+    $('.ui-nohistory').hide()
+    $('.ui-import-from-browser').hide()
+  } else if ($('#history-list').children().length === 0) {
+    $('.pagination').hide()
+    $('.ui-nohistory').slideDown()
+    getStorageHistory(data => {
+      if (data && data.length > 0 && getLoginState() && historyList.items.length === 0) {
+        $('.ui-import-from-browser').slideDown()
+      }
+    })
+  }
 }
 
-function parseHistoryCallback(list, notehistory) {
-    checkHistoryList();
-    //sort by pinned then timestamp
-    list.sort('', {
-        sortFunction(a, b) {
-            const notea = a.values();
-            const noteb = b.values();
-            if (notea.pinned && !noteb.pinned) {
-                return -1;
-            } else if (!notea.pinned && noteb.pinned) {
-                return 1;
-            } else {
-                if (notea.timestamp > noteb.timestamp) {
-                    return -1;
-                } else if (notea.timestamp < noteb.timestamp) {
-                    return 1;
-                } else {
-                    return 0;
-                }
-            }
-        }
-    });
-    // parse filter tags
-    const filtertags = [];
-    for (let i = 0, l = list.items.length; i < l; i++) {
-        const tags = list.items[i]._values.tags;
-        if (tags && tags.length > 0) {
-            for (let j = 0; j < tags.length; j++) {
-                //push info filtertags if not found
-                let found = false;
-                if (filtertags.includes(tags[j]))
-                    found = true;
-                if (!found)
-                    filtertags.push(tags[j]);
-            }
+function parseHistoryCallback (list, notehistory) {
+  checkHistoryList()
+    // sort by pinned then timestamp
+  list.sort('', {
+    sortFunction (a, b) {
+      const notea = a.values()
+      const noteb = b.values()
+      if (notea.pinned && !noteb.pinned) {
+        return -1
+      } else if (!notea.pinned && noteb.pinned) {
+        return 1
+      } else {
+        if (notea.timestamp > noteb.timestamp) {
+          return -1
+        } else if (notea.timestamp < noteb.timestamp) {
+          return 1
+        } else {
+          return 0
         }
+      }
     }
-    buildTagsFilter(filtertags);
+  })
+    // parse filter tags
+  const filtertags = []
+  for (let i = 0, l = list.items.length; i < l; i++) {
+    const tags = list.items[i]._values.tags
+    if (tags && tags.length > 0) {
+      for (let j = 0; j < tags.length; j++) {
+                // push info filtertags if not found
+        let found = false
+        if (filtertags.includes(tags[j])) { found = true }
+        if (!found) { filtertags.push(tags[j]) }
+      }
+    }
+  }
+  buildTagsFilter(filtertags)
 }
 
 // update items whenever list updated
 historyList.on('updated', e => {
-    for (let i = 0, l = e.items.length; i < l; i++) {
-        const item = e.items[i];
-        if (item.visible()) {
-            const itemEl = $(item.elm);
-            const values = item._values;
-            const a = itemEl.find("a");
-            const pin = itemEl.find(".ui-history-pin");
-            const tagsEl = itemEl.find(".tags");
-            //parse link to element a
-            a.attr('href', `${serverurl}/${values.id}`);
-            //parse pinned
-            if (values.pinned) {
-                pin.addClass('active');
-            } else {
-                pin.removeClass('active');
-            }
-            //parse tags
-            const tags = values.tags;
-            if (tags && tags.length > 0 && tagsEl.children().length <= 0) {
-                const labels = [];
-                for (let j = 0; j < tags.length; j++) {
-                    //push into the item label
-                    labels.push(`<span class='label label-default'>${tags[j]}</span>`);
-                }
-                tagsEl.html(labels.join(' '));
-            }
+  for (let i = 0, l = e.items.length; i < l; i++) {
+    const item = e.items[i]
+    if (item.visible()) {
+      const itemEl = $(item.elm)
+      const values = item._values
+      const a = itemEl.find('a')
+      const pin = itemEl.find('.ui-history-pin')
+      const tagsEl = itemEl.find('.tags')
+            // parse link to element a
+      a.attr('href', `${serverurl}/${values.id}`)
+            // parse pinned
+      if (values.pinned) {
+        pin.addClass('active')
+      } else {
+        pin.removeClass('active')
+      }
+            // parse tags
+      const tags = values.tags
+      if (tags && tags.length > 0 && tagsEl.children().length <= 0) {
+        const labels = []
+        for (let j = 0; j < tags.length; j++) {
+                    // push into the item label
+          labels.push(`<span class='label label-default'>${tags[j]}</span>`)
         }
+        tagsEl.html(labels.join(' '))
+      }
     }
-    $(".ui-history-close").off('click');
-    $(".ui-history-close").on('click', historyCloseClick);
-    $(".ui-history-pin").off('click');
-    $(".ui-history-pin").on('click', historyPinClick);
-});
+  }
+  $('.ui-history-close').off('click')
+  $('.ui-history-close').on('click', historyCloseClick)
+  $('.ui-history-pin').off('click')
+  $('.ui-history-pin').on('click', historyPinClick)
+})
 
-function historyCloseClick(e) {
-    e.preventDefault();
-    const id = $(this).closest("a").siblings("span").html();
-    const value = historyList.get('id', id)[0]._values;
-    $('.ui-delete-modal-msg').text('Do you really want to delete below history?');
-    $('.ui-delete-modal-item').html(`<i class="fa fa-file-text"></i> ${value.text}<br><i class="fa fa-clock-o"></i> ${value.time}`);
-    clearHistory = false;
-    deleteId = id;
+function historyCloseClick (e) {
+  e.preventDefault()
+  const id = $(this).closest('a').siblings('span').html()
+  const value = historyList.get('id', id)[0]._values
+  $('.ui-delete-modal-msg').text('Do you really want to delete below history?')
+  $('.ui-delete-modal-item').html(`<i class="fa fa-file-text"></i> ${value.text}<br><i class="fa fa-clock-o"></i> ${value.time}`)
+  clearHistory = false
+  deleteId = id
 }
 
-function historyPinClick(e) {
-    e.preventDefault();
-    const $this = $(this);
-    const id = $this.closest("a").siblings("span").html();
-    const item = historyList.get('id', id)[0];
-    const values = item._values;
-    let pinned = values.pinned;
-    if (!values.pinned) {
-        pinned = true;
-        item._values.pinned = true;
-    } else {
-        pinned = false;
-        item._values.pinned = false;
-    }
-    checkIfAuth(() => {
-        postHistoryToServer(id, {
-            pinned
-        }, (err, result) => {
-            if (!err) {
-                if (pinned)
-                    $this.addClass('active');
-                else
-                    $this.removeClass('active');
-            }
-        });
-    }, () => {
-        getHistory(notehistory => {
-            for(let i = 0; i < notehistory.length; i++) {
-                if (notehistory[i].id == id) {
-                    notehistory[i].pinned = pinned;
-                    break;
-                }
-            }
-            saveHistory(notehistory);
-            if (pinned)
-                $this.addClass('active');
-            else
-                $this.removeClass('active');
-        });
-    });
+function historyPinClick (e) {
+  e.preventDefault()
+  const $this = $(this)
+  const id = $this.closest('a').siblings('span').html()
+  const item = historyList.get('id', id)[0]
+  const values = item._values
+  let pinned = values.pinned
+  if (!values.pinned) {
+    pinned = true
+    item._values.pinned = true
+  } else {
+    pinned = false
+    item._values.pinned = false
+  }
+  checkIfAuth(() => {
+    postHistoryToServer(id, {
+      pinned
+    }, (err, result) => {
+      if (!err) {
+        if (pinned) { $this.addClass('active') } else { $this.removeClass('active') }
+      }
+    })
+  }, () => {
+    getHistory(notehistory => {
+      for (let i = 0; i < notehistory.length; i++) {
+        if (notehistory[i].id === id) {
+          notehistory[i].pinned = pinned
+          break
+        }
+      }
+      saveHistory(notehistory)
+      if (pinned) { $this.addClass('active') } else { $this.removeClass('active') }
+    })
+  })
 }
 
-//auto update item fromNow every minutes
-setInterval(updateItemFromNow, 60000);
+// auto update item fromNow every minutes
+setInterval(updateItemFromNow, 60000)
 
-function updateItemFromNow() {
-    const items = $('.item').toArray();
-    for (let i = 0; i < items.length; i++) {
-        const item = $(items[i]);
-        const timestamp = parseInt(item.find('.timestamp').text());
-        item.find('.fromNow').text(moment(timestamp).fromNow());
-    }
+function updateItemFromNow () {
+  const items = $('.item').toArray()
+  for (let i = 0; i < items.length; i++) {
+    const item = $(items[i])
+    const timestamp = parseInt(item.find('.timestamp').text())
+    item.find('.fromNow').text(moment(timestamp).fromNow())
+  }
 }
 
-var clearHistory = false;
-var deleteId = null;
+var clearHistory = false
+var deleteId = null
 
-function deleteHistory() {
-    checkIfAuth(() => {
-        deleteServerHistory(deleteId, (err, result) => {
-            if (!err) {
-                if (clearHistory) {
-                    historyList.clear();
-                    checkHistoryList();
-                } else {
-                    historyList.remove('id', deleteId);
-                    checkHistoryList();
-                }
-            }
-            $('.delete-modal').modal('hide');
-            deleteId = null;
-            clearHistory = false;
-        });
-    }, () => {
+function deleteHistory () {
+  checkIfAuth(() => {
+    deleteServerHistory(deleteId, (err, result) => {
+      if (!err) {
         if (clearHistory) {
-            saveHistory([]);
-            historyList.clear();
-            checkHistoryList();
-            deleteId = null;
+          historyList.clear()
+          checkHistoryList()
         } else {
-            if (!deleteId) return;
-            getHistory(notehistory => {
-                const newnotehistory = removeHistory(deleteId, notehistory);
-                saveHistory(newnotehistory);
-                historyList.remove('id', deleteId);
-                checkHistoryList();
-                deleteId = null;
-            });
+          historyList.remove('id', deleteId)
+          checkHistoryList()
         }
-        $('.delete-modal').modal('hide');
-        clearHistory = false;
-    });
-}
-
-$(".ui-delete-modal-confirm").click(() => {
-    deleteHistory();
-});
-
-$(".ui-import-from-browser").click(() => {
-    saveStorageHistoryToServer(() => {
-        parseStorageToHistory(historyList, parseHistoryCallback);
-    });
-});
-
-$(".ui-save-history").click(() => {
-    getHistory(data => {
-        const history = JSON.stringify(data);
-        const blob = new Blob([history], {
-            type: "application/json;charset=utf-8"
-        });
-        saveAs(blob, `hackmd_history_${moment().format('YYYYMMDDHHmmss')}`, true);
-    });
-});
-
-$(".ui-open-history").bind("change", e => {
-    const files = e.target.files || e.dataTransfer.files;
-    const file = files[0];
-    const reader = new FileReader();
-    reader.onload = () => {
-        const notehistory = JSON.parse(reader.result);
-        //console.log(notehistory);
-        if (!reader.result) return;
-        getHistory(data => {
-            let mergedata = data.concat(notehistory);
-            mergedata = clearDuplicatedHistory(mergedata);
-            saveHistory(mergedata);
-            parseHistory(historyList, parseHistoryCallback);
-        });
-        $(".ui-open-history").replaceWith($(".ui-open-history").val('').clone(true));
-    };
-    reader.readAsText(file);
-});
-
-$(".ui-clear-history").click(() => {
-    $('.ui-delete-modal-msg').text('Do you really want to clear all history?');
-    $('.ui-delete-modal-item').html('There is no turning back.');
-    clearHistory = true;
-    deleteId = null;
-});
-
-$(".ui-refresh-history").click(() => {
-    const lastTags = $(".ui-use-tags").select2('val');
-    $(".ui-use-tags").select2('val', '');
-    historyList.filter();
-    const lastKeyword = $('.search').val();
-    $('.search').val('');
-    historyList.search();
-    $('#history-list').slideUp('fast');
-    $('.pagination').hide();
-
-    resetCheckAuth();
-    historyList.clear();
-    parseHistory(historyList, (list, notehistory) => {
-        parseHistoryCallback(list, notehistory);
-        $(".ui-use-tags").select2('val', lastTags);
-        $(".ui-use-tags").trigger('change');
-        historyList.search(lastKeyword);
-        $('.search').val(lastKeyword);
-        checkHistoryList();
-        $('#history-list').slideDown('fast');
-    });
-});
-
-$(".ui-logout").click(() => {
-    clearLoginState();
-    location.href = `${serverurl}/logout`;
-});
-
-let filtertags = [];
-$(".ui-use-tags").select2({
-    placeholder: $(".ui-use-tags").attr('placeholder'),
-    multiple: true,
-    data() {
-        return {
-            results: filtertags
-        };
-    }
-});
-$('.select2-input').css('width', 'inherit');
-buildTagsFilter([]);
-
-function buildTagsFilter(tags) {
-    for (let i = 0; i < tags.length; i++)
-        tags[i] = {
-            id: i,
-            text: S(tags[i]).unescapeHTML().s
-        };
-    filtertags = tags;
-}
-$(".ui-use-tags").on('change', function () {
-    const tags = [];
-    const data = $(this).select2('data');
-    for (let i = 0; i < data.length; i++)
-        tags.push(data[i].text);
-    if (tags.length > 0) {
-        historyList.filter(item => {
-            const values = item.values();
-            if (!values.tags) return false;
-            let found = false;
-            for (let i = 0; i < tags.length; i++) {
-                if (values.tags.includes(tags[i])) {
-                    found = true;
-                    break;
-                }
-            }
-            return found;
-        });
+      }
+      $('.delete-modal').modal('hide')
+      deleteId = null
+      clearHistory = false
+    })
+  }, () => {
+    if (clearHistory) {
+      saveHistory([])
+      historyList.clear()
+      checkHistoryList()
+      deleteId = null
     } else {
-        historyList.filter();
+      if (!deleteId) return
+      getHistory(notehistory => {
+        const newnotehistory = removeHistory(deleteId, notehistory)
+        saveHistory(newnotehistory)
+        historyList.remove('id', deleteId)
+        checkHistoryList()
+        deleteId = null
+      })
     }
-    checkHistoryList();
-});
+    $('.delete-modal').modal('hide')
+    clearHistory = false
+  })
+}
+
+$('.ui-delete-modal-confirm').click(() => {
+  deleteHistory()
+})
+
+$('.ui-import-from-browser').click(() => {
+  saveStorageHistoryToServer(() => {
+    parseStorageToHistory(historyList, parseHistoryCallback)
+  })
+})
+
+$('.ui-save-history').click(() => {
+  getHistory(data => {
+    const history = JSON.stringify(data)
+    const blob = new Blob([history], {
+      type: 'application/json;charset=utf-8'
+    })
+    saveAs(blob, `hackmd_history_${moment().format('YYYYMMDDHHmmss')}`, true)
+  })
+})
+
+$('.ui-open-history').bind('change', e => {
+  const files = e.target.files || e.dataTransfer.files
+  const file = files[0]
+  const reader = new FileReader()
+  reader.onload = () => {
+    const notehistory = JSON.parse(reader.result)
+        // console.log(notehistory);
+    if (!reader.result) return
+    getHistory(data => {
+      let mergedata = data.concat(notehistory)
+      mergedata = clearDuplicatedHistory(mergedata)
+      saveHistory(mergedata)
+      parseHistory(historyList, parseHistoryCallback)
+    })
+    $('.ui-open-history').replaceWith($('.ui-open-history').val('').clone(true))
+  }
+  reader.readAsText(file)
+})
+
+$('.ui-clear-history').click(() => {
+  $('.ui-delete-modal-msg').text('Do you really want to clear all history?')
+  $('.ui-delete-modal-item').html('There is no turning back.')
+  clearHistory = true
+  deleteId = null
+})
+
+$('.ui-refresh-history').click(() => {
+  const lastTags = $('.ui-use-tags').select2('val')
+  $('.ui-use-tags').select2('val', '')
+  historyList.filter()
+  const lastKeyword = $('.search').val()
+  $('.search').val('')
+  historyList.search()
+  $('#history-list').slideUp('fast')
+  $('.pagination').hide()
+
+  resetCheckAuth()
+  historyList.clear()
+  parseHistory(historyList, (list, notehistory) => {
+    parseHistoryCallback(list, notehistory)
+    $('.ui-use-tags').select2('val', lastTags)
+    $('.ui-use-tags').trigger('change')
+    historyList.search(lastKeyword)
+    $('.search').val(lastKeyword)
+    checkHistoryList()
+    $('#history-list').slideDown('fast')
+  })
+})
+
+$('.ui-logout').click(() => {
+  clearLoginState()
+  location.href = `${serverurl}/logout`
+})
+
+let filtertags = []
+$('.ui-use-tags').select2({
+  placeholder: $('.ui-use-tags').attr('placeholder'),
+  multiple: true,
+  data () {
+    return {
+      results: filtertags
+    }
+  }
+})
+$('.select2-input').css('width', 'inherit')
+buildTagsFilter([])
+
+function buildTagsFilter (tags) {
+  for (let i = 0; i < tags.length; i++) {
+    tags[i] = {
+      id: i,
+      text: S(tags[i]).unescapeHTML().s
+    }
+  }
+  filtertags = tags
+}
+$('.ui-use-tags').on('change', function () {
+  const tags = []
+  const data = $(this).select2('data')
+  for (let i = 0; i < data.length; i++) { tags.push(data[i].text) }
+  if (tags.length > 0) {
+    historyList.filter(item => {
+      const values = item.values()
+      if (!values.tags) return false
+      let found = false
+      for (let i = 0; i < tags.length; i++) {
+        if (values.tags.includes(tags[i])) {
+          found = true
+          break
+        }
+      }
+      return found
+    })
+  } else {
+    historyList.filter()
+  }
+  checkHistoryList()
+})
 
 $('.search').keyup(() => {
-    checkHistoryList();
-});
+  checkHistoryList()
+})
diff --git a/public/js/extra.js b/public/js/extra.js
index a3e840d..844d52c 100644
--- a/public/js/extra.js
+++ b/public/js/extra.js
@@ -1,1150 +1,1140 @@
-require('prismjs/themes/prism.css');
-require('prismjs/components/prism-wiki');
-require('prismjs/components/prism-haskell');
-require('prismjs/components/prism-go');
-require('prismjs/components/prism-typescript');
-require('prismjs/components/prism-jsx');
+/* eslint-env browser, jquery */
+/* global moment, serverurl */
 
-import Prism from 'prismjs';
-import hljs from 'highlight.js';
-import PDFObject from 'pdfobject';
-import S from 'string';
-import { saveAs } from 'file-saver';
+require('prismjs/themes/prism.css')
+require('prismjs/components/prism-wiki')
+require('prismjs/components/prism-haskell')
+require('prismjs/components/prism-go')
+require('prismjs/components/prism-typescript')
+require('prismjs/components/prism-jsx')
 
-require('./lib/common/login');
-require('../vendor/md-toc');
-var Viz = require("viz.js");
+import Prism from 'prismjs'
+import hljs from 'highlight.js'
+import PDFObject from 'pdfobject'
+import S from 'string'
+import { saveAs } from 'file-saver'
 
-//auto update last change
-window.createtime = null;
-window.lastchangetime = null;
+require('./lib/common/login')
+require('../vendor/md-toc')
+var Viz = require('viz.js')
+
+// auto update last change
+window.createtime = null
+window.lastchangetime = null
 window.lastchangeui = {
-    status: $(".ui-status-lastchange"),
-    time: $(".ui-lastchange"),
-    user: $(".ui-lastchangeuser"),
-    nouser: $(".ui-no-lastchangeuser")
+  status: $('.ui-status-lastchange'),
+  time: $('.ui-lastchange'),
+  user: $('.ui-lastchangeuser'),
+  nouser: $('.ui-no-lastchangeuser')
 }
 
-const ownerui = $(".ui-owner");
+const ownerui = $('.ui-owner')
 
-export function updateLastChange() {
-    if (!lastchangeui) return;
-    if (createtime) {
-        if (createtime && !lastchangetime) {
-            lastchangeui.status.text('created');
-        } else {
-            lastchangeui.status.text('changed');
-        }
-        const time = lastchangetime || createtime;
-        lastchangeui.time.html(moment(time).fromNow());
-        lastchangeui.time.attr('title', moment(time).format('llll'));
-    }
-}
-setInterval(updateLastChange, 60000);
-
-window.lastchangeuser = null;
-window.lastchangeuserprofile = null;
-
-export function updateLastChangeUser() {
-    if (lastchangeui) {
-      if (lastchangeuser && lastchangeuserprofile) {
-          const icon = lastchangeui.user.children('i');
-          icon.attr('title', lastchangeuserprofile.name).tooltip('fixTitle');
-          if (lastchangeuserprofile.photo)
-              icon.attr('style', `background-image:url(${lastchangeuserprofile.photo})`);
-          lastchangeui.user.show();
-          lastchangeui.nouser.hide();
-      } else {
-          lastchangeui.user.hide();
-          lastchangeui.nouser.show();
-      }
-    }
-}
-
-window.owner = null;
-window.ownerprofile = null;
-
-export function updateOwner() {
-    if (ownerui) {
-      if (owner && ownerprofile && owner !== lastchangeuser) {
-          const icon = ownerui.children('i');
-          icon.attr('title', ownerprofile.name).tooltip('fixTitle');
-          const styleString = `background-image:url(${ownerprofile.photo})`;
-          if (ownerprofile.photo && icon.attr('style') !== styleString)
-              icon.attr('style', styleString);
-          ownerui.show();
-      } else {
-          ownerui.hide();
-      }
-    }
-}
-
-//get title
-function getTitle(view) {
-    let title = "";
-    if (md && md.meta && md.meta.title && (typeof md.meta.title == "string" || typeof md.meta.title == "number")) {
-        title = md.meta.title;
+export function updateLastChange () {
+  if (!window.lastchangeui) return
+  if (window.createtime) {
+    if (window.createtime && !window.lastchangetime) {
+      window.lastchangeui.status.text('created')
     } else {
-        const h1s = view.find("h1");
-        if (h1s.length > 0) {
-            title = h1s.first().text();
-        } else {
-            title = null;
-        }
+      window.lastchangeui.status.text('changed')
     }
-    return title;
+    const time = window.lastchangetime || window.createtime
+    window.lastchangeui.time.html(moment(time).fromNow())
+    window.lastchangeui.time.attr('title', moment(time).format('llll'))
+  }
 }
+setInterval(updateLastChange, 60000)
 
-//render title
-export function renderTitle(view) {
-    let title = getTitle(view);
-    if (title) {
-        title += ' - HackMD';
+window.lastchangeuser = null
+window.lastchangeuserprofile = null
+
+export function updateLastChangeUser () {
+  if (window.lastchangeui) {
+    if (window.lastchangeuser && window.lastchangeuserprofile) {
+      const icon = window.lastchangeui.user.children('i')
+      icon.attr('title', window.lastchangeuserprofile.name).tooltip('fixTitle')
+      if (window.lastchangeuserprofile.photo) { icon.attr('style', `background-image:url(${window.lastchangeuserprofile.photo})`) }
+      window.lastchangeui.user.show()
+      window.lastchangeui.nouser.hide()
     } else {
-        title = 'HackMD - Collaborative markdown notes';
+      window.lastchangeui.user.hide()
+      window.lastchangeui.nouser.show()
     }
-    return title;
+  }
 }
 
-//render filename
-export function renderFilename(view) {
-    let filename = getTitle(view);
-    if (!filename) {
-        filename = 'Untitled';
+window.owner = null
+window.ownerprofile = null
+
+export function updateOwner () {
+  if (window.ownerui) {
+    if (window.owner && window.ownerprofile && window.owner !== window.lastchangeuser) {
+      const icon = ownerui.children('i')
+      icon.attr('title', window.ownerprofile.name).tooltip('fixTitle')
+      const styleString = `background-image:url(${window.ownerprofile.photo})`
+      if (window.ownerprofile.photo && icon.attr('style') !== styleString) { icon.attr('style', styleString) }
+      ownerui.show()
+    } else {
+      ownerui.hide()
     }
-    return filename;
+  }
+}
+
+// get title
+function getTitle (view) {
+  let title = ''
+  if (md && md.meta && md.meta.title && (typeof md.meta.title === 'string' || typeof md.meta.title === 'number')) {
+    title = md.meta.title
+  } else {
+    const h1s = view.find('h1')
+    if (h1s.length > 0) {
+      title = h1s.first().text()
+    } else {
+      title = null
+    }
+  }
+  return title
+}
+
+// render title
+export function renderTitle (view) {
+  let title = getTitle(view)
+  if (title) {
+    title += ' - HackMD'
+  } else {
+    title = 'HackMD - Collaborative markdown notes'
+  }
+  return title
+}
+
+// render filename
+export function renderFilename (view) {
+  let filename = getTitle(view)
+  if (!filename) {
+    filename = 'Untitled'
+  }
+  return filename
 }
 
 // render tags
-export function renderTags(view) {
-    const tags = [];
-    const rawtags = [];
-    if (md && md.meta && md.meta.tags && (typeof md.meta.tags == "string" || typeof md.meta.tags == "number")) {
-        const metaTags = (`${md.meta.tags}`).split(',');
-        for (var i = 0; i < metaTags.length; i++) {
-            const text = metaTags[i].trim();
-            if (text) rawtags.push(text);
-        }
-    } else {
-        view.find('h6').each((key, value) => {
-            if (/^tags/gmi.test($(value).text())) {
-                const codes = $(value).find("code");
-                for (let i = 0; i < codes.length; i++) {
-                    const text = codes[i].innerHTML.trim();
-                    if (text) rawtags.push(text);
-                }
-            }
-        });
+export function renderTags (view) {
+  const tags = []
+  const rawtags = []
+  if (md && md.meta && md.meta.tags && (typeof md.meta.tags === 'string' || typeof md.meta.tags === 'number')) {
+    const metaTags = (`${md.meta.tags}`).split(',')
+    for (let i = 0; i < metaTags.length; i++) {
+      const text = metaTags[i].trim()
+      if (text) rawtags.push(text)
     }
-    for (var i = 0; i < rawtags.length; i++) {
-        let found = false;
-        for (let j = 0; j < tags.length; j++) {
-            if (tags[j] == rawtags[i]) {
-                found = true;
-                break;
-            }
+  } else {
+    view.find('h6').each((key, value) => {
+      if (/^tags/gmi.test($(value).text())) {
+        const codes = $(value).find('code')
+        for (let i = 0; i < codes.length; i++) {
+          const text = codes[i].innerHTML.trim()
+          if (text) rawtags.push(text)
         }
-        if (!found)
-            tags.push(rawtags[i]);
+      }
+    })
+  }
+  for (let i = 0; i < rawtags.length; i++) {
+    let found = false
+    for (let j = 0; j < tags.length; j++) {
+      if (tags[j] === rawtags[i]) {
+        found = true
+        break
+      }
     }
-    return tags;
+    if (!found) { tags.push(rawtags[i]) }
+  }
+  return tags
 }
 
-function slugifyWithUTF8(text) {
-    let newText = S(text.toLowerCase()).trim().stripTags().dasherize().s;
-    newText = newText.replace(/([\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\[\\\]\^\`\{\|\}\~])/g, '');
-    return newText;
+function slugifyWithUTF8 (text) {
+  let newText = S(text.toLowerCase()).trim().stripTags().dasherize().s
+  newText = newText.replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, '')
+  return newText
 }
 
-export function isValidURL(str) {
-    const pattern = new RegExp('^(https?:\\/\\/)?' + // protocol
+export function isValidURL (str) {
+  const pattern = new RegExp('^(https?:\\/\\/)?' + // protocol
         '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
         '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
         '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
         '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
-        '(\\#[-a-z\\d_]*)?$', 'i'); // fragment locator
-    if (!pattern.test(str)) {
-        return false;
-    } else {
-        return true;
-    }
+        '(\\#[-a-z\\d_]*)?$', 'i') // fragment locator
+  if (!pattern.test(str)) {
+    return false
+  } else {
+    return true
+  }
 }
 
-//parse meta
-export function parseMeta(md, edit, view, toc, tocAffix) {
-    let lang = null;
-    let dir = null;
-    let breaks = true;
-    if (md && md.meta) {
-        const meta = md.meta;
-        lang = meta.lang;
-        dir = meta.dir;
-        breaks = meta.breaks;
-    }
-    //text language
-    if (lang && typeof lang == "string") {
-        view.attr('lang', lang);
-        toc.attr('lang', lang);
-        tocAffix.attr('lang', lang);
-        if (edit)
-            edit.attr('lang', lang);
-    } else {
-        view.removeAttr('lang');
-        toc.removeAttr('lang');
-        tocAffix.removeAttr('lang');
-        if (edit)
-            edit.removeAttr('lang', lang);
-    }
-    //text direction
-    if (dir && typeof dir == "string") {
-        view.attr('dir', dir);
-        toc.attr('dir', dir);
-        tocAffix.attr('dir', dir);
-    } else {
-        view.removeAttr('dir');
-        toc.removeAttr('dir');
-        tocAffix.removeAttr('dir');
-    }
-    //breaks
-    if (typeof breaks === 'boolean' && !breaks) {
-        md.options.breaks = false;
-    } else {
-        md.options.breaks = true;
-    }
+// parse meta
+export function parseMeta (md, edit, view, toc, tocAffix) {
+  let lang = null
+  let dir = null
+  let breaks = true
+  if (md && md.meta) {
+    const meta = md.meta
+    lang = meta.lang
+    dir = meta.dir
+    breaks = meta.breaks
+  }
+    // text language
+  if (lang && typeof lang === 'string') {
+    view.attr('lang', lang)
+    toc.attr('lang', lang)
+    tocAffix.attr('lang', lang)
+    if (edit) { edit.attr('lang', lang) }
+  } else {
+    view.removeAttr('lang')
+    toc.removeAttr('lang')
+    tocAffix.removeAttr('lang')
+    if (edit) { edit.removeAttr('lang', lang) }
+  }
+    // text direction
+  if (dir && typeof dir === 'string') {
+    view.attr('dir', dir)
+    toc.attr('dir', dir)
+    tocAffix.attr('dir', dir)
+  } else {
+    view.removeAttr('dir')
+    toc.removeAttr('dir')
+    tocAffix.removeAttr('dir')
+  }
+    // breaks
+  if (typeof breaks === 'boolean' && !breaks) {
+    md.options.breaks = false
+  } else {
+    md.options.breaks = true
+  }
 }
 
-window.viewAjaxCallback = null;
+window.viewAjaxCallback = null
 
-//regex for extra tags
-const spaceregex = /\s*/;
-const notinhtmltagregex = /(?![^<]*>|[^<>]*<\/)/;
-let coloregex = /\[color=([#|\(|\)|\s|\,|\w]*?)\]/;
-coloregex = new RegExp(coloregex.source + notinhtmltagregex.source, "g");
-let nameregex = /\[name=(.*?)\]/;
-let timeregex = /\[time=([:|,|+|-|\(|\)|\s|\w]*?)\]/;
-const nameandtimeregex = new RegExp(nameregex.source + spaceregex.source + timeregex.source + notinhtmltagregex.source, "g");
-nameregex = new RegExp(nameregex.source + notinhtmltagregex.source, "g");
-timeregex = new RegExp(timeregex.source + notinhtmltagregex.source, "g");
+// regex for extra tags
+const spaceregex = /\s*/
+const notinhtmltagregex = /(?![^<]*>|[^<>]*<\/)/
+let coloregex = /\[color=([#|(|)|\s|,|\w]*?)\]/
+coloregex = new RegExp(coloregex.source + notinhtmltagregex.source, 'g')
+let nameregex = /\[name=(.*?)\]/
+let timeregex = /\[time=([:|,|+|-|(|)|\s|\w]*?)\]/
+const nameandtimeregex = new RegExp(nameregex.source + spaceregex.source + timeregex.source + notinhtmltagregex.source, 'g')
+nameregex = new RegExp(nameregex.source + notinhtmltagregex.source, 'g')
+timeregex = new RegExp(timeregex.source + notinhtmltagregex.source, 'g')
 
-function replaceExtraTags(html) {
-    html = html.replace(coloregex, '<span class="color" data-color="$1"></span>');
-    html = html.replace(nameandtimeregex, '<small><i class="fa fa-user"></i> $1 <i class="fa fa-clock-o"></i> $2</small>');
-    html = html.replace(nameregex, '<small><i class="fa fa-user"></i> $1</small>');
-    html = html.replace(timeregex, '<small><i class="fa fa-clock-o"></i> $1</small>');
-    return html;
+function replaceExtraTags (html) {
+  html = html.replace(coloregex, '<span class="color" data-color="$1"></span>')
+  html = html.replace(nameandtimeregex, '<small><i class="fa fa-user"></i> $1 <i class="fa fa-clock-o"></i> $2</small>')
+  html = html.replace(nameregex, '<small><i class="fa fa-user"></i> $1</small>')
+  html = html.replace(timeregex, '<small><i class="fa fa-clock-o"></i> $1</small>')
+  return html
 }
 
-if (typeof mermaid !== 'undefined' && mermaid) mermaid.startOnLoad = false;
+if (typeof window.mermaid !== 'undefined' && window.mermaid) window.mermaid.startOnLoad = false
 
-//dynamic event or object binding here
-export function finishView(view) {
-    //todo list
-    const lis = view.find('li.raw').removeClass("raw").sortByDepth().toArray();
+// dynamic event or object binding here
+export function finishView (view) {
+    // todo list
+  const lis = view.find('li.raw').removeClass('raw').sortByDepth().toArray()
 
-    for (let li of lis) {
-        let html = $(li).clone()[0].innerHTML;
-        const p = $(li).children('p');
-        if (p.length == 1) {
-            html = p.html();
-            li = p[0];
-        }
-        html = replaceExtraTags(html);
-        li.innerHTML = html;
-        let disabled = 'disabled';
-        if(typeof editor !== 'undefined' && havePermission())
-            disabled = '';
-        if (/^\s*\[[x ]\]\s*/.test(html)) {
-            li.innerHTML = html.replace(/^\s*\[ \]\s*/, `<input type="checkbox" class="task-list-item-checkbox "${disabled}><label></label>`)
-                .replace(/^\s*\[x\]\s*/, `<input type="checkbox" class="task-list-item-checkbox" checked ${disabled}><label></label>`);
-            li.setAttribute('class', 'task-list-item');
-        }
-        if (typeof editor !== 'undefined' && havePermission())
-            $(li).find('input').change(toggleTodoEvent);
-        //color tag in list will convert it to tag icon with color
-        const tag_color = $(li).closest('ul').find(".color");
-        tag_color.each((key, value) => {
-            $(value).addClass('fa fa-tag').css('color', $(value).attr('data-color'));
-        });
+  for (let li of lis) {
+    let html = $(li).clone()[0].innerHTML
+    const p = $(li).children('p')
+    if (p.length === 1) {
+      html = p.html()
+      li = p[0]
     }
+    html = replaceExtraTags(html)
+    li.innerHTML = html
+    let disabled = 'disabled'
+    if (typeof editor !== 'undefined' && window.havePermission()) { disabled = '' }
+    if (/^\s*\[[x ]\]\s*/.test(html)) {
+      li.innerHTML = html.replace(/^\s*\[ \]\s*/, `<input type="checkbox" class="task-list-item-checkbox "${disabled}><label></label>`)
+                .replace(/^\s*\[x\]\s*/, `<input type="checkbox" class="task-list-item-checkbox" checked ${disabled}><label></label>`)
+      li.setAttribute('class', 'task-list-item')
+    }
+    if (typeof editor !== 'undefined' && window.havePermission()) { $(li).find('input').change(toggleTodoEvent) }
+        // color tag in list will convert it to tag icon with color
+    const tagColor = $(li).closest('ul').find('.color')
+    tagColor.each((key, value) => {
+      $(value).addClass('fa fa-tag').css('color', $(value).attr('data-color'))
+    })
+  }
 
-    //youtube
-    view.find("div.youtube.raw").removeClass("raw")
+    // youtube
+  view.find('div.youtube.raw').removeClass('raw')
         .click(function () {
-            imgPlayiframe(this, '//www.youtube.com/embed/');
-        });
-    //vimeo
-    view.find("div.vimeo.raw").removeClass("raw")
+          imgPlayiframe(this, '//www.youtube.com/embed/')
+        })
+    // vimeo
+  view.find('div.vimeo.raw').removeClass('raw')
         .click(function () {
-            imgPlayiframe(this, '//player.vimeo.com/video/');
+          imgPlayiframe(this, '//player.vimeo.com/video/')
         })
         .each((key, value) => {
-            $.ajax({
-                type: 'GET',
-                url: `//vimeo.com/api/v2/video/${$(value).attr('data-videoid')}.json`,
-                jsonp: 'callback',
-                dataType: 'jsonp',
-                success(data) {
-                    const thumbnail_src = data[0].thumbnail_large;
-                    const image = `<img src="${thumbnail_src}" />`;
-                    $(value).prepend(image);
-                    if(viewAjaxCallback) viewAjaxCallback();
-                }
-            });
-        });
-    //gist
-    view.find("code[data-gist-id]").each((key, value) => {
-        if ($(value).children().length == 0)
-            $(value).gist(viewAjaxCallback);
-    });
-    //sequence diagram
-    const sequences = view.find("div.sequence-diagram.raw").removeClass("raw");
-    sequences.each((key, value) => {
-        try {
-            var $value = $(value);
-            const $ele = $(value).parent().parent();
-
-            const sequence = $value;
-            sequence.sequenceDiagram({
-                theme: 'simple'
-            });
-
-            $ele.addClass('sequence-diagram');
-            $value.children().unwrap().unwrap();
-            const svg = $ele.find('> svg');
-            svg[0].setAttribute('viewBox', `0 0 ${svg.attr('width')} ${svg.attr('height')}`);
-            svg[0].setAttribute('preserveAspectRatio', 'xMidYMid meet');
-        } catch (err) {
-            $value.unwrap();
-            $value.parent().append('<div class="alert alert-warning">' + err + '</div>');
-            console.warn(err);
-        }
-    });
-    //flowchart
-    const flow = view.find("div.flow-chart.raw").removeClass("raw");
-    flow.each((key, value) => {
-        try {
-            var $value = $(value);
-            const $ele = $(value).parent().parent();
-
-            const chart = flowchart.parse($value.text());
-            $value.html('');
-            chart.drawSVG(value, {
-                'line-width': 2,
-                'fill': 'none',
-                'font-size': '16px',
-                'font-family': "'Andale Mono', monospace"
-            });
-
-            $ele.addClass('flow-chart');
-            $value.children().unwrap().unwrap();
-        } catch (err) {
-            $value.unwrap();
-            $value.parent().append('<div class="alert alert-warning">' + err + '</div>');
-            console.warn(err);
-        }
-    });
-    //graphviz
-    var graphvizs = view.find("div.graphviz.raw").removeClass("raw");
-    graphvizs.each(function (key, value) {
-        try {
-            var $value = $(value);
-            var $ele = $(value).parent().parent();
-
-            var graphviz = Viz($value.text());
-            if (!graphviz) throw Error('viz.js output empty graph');
-            $value.html(graphviz);
-
-            $ele.addClass('graphviz');
-            $value.children().unwrap().unwrap();
-        } catch (err) {
-            $value.unwrap();
-            $value.parent().append('<div class="alert alert-warning">' + err + '</div>');
-            console.warn(err);
-        }
-    });
-    //mermaid
-    const mermaids = view.find("div.mermaid.raw").removeClass("raw");
-    mermaids.each((key, value) => {
-        try {
-            var $value = $(value);
-            const $ele = $(value).closest('pre');
-
-            let mermaidError = null;
-            mermaid.parseError = (err, hash) => {
-                mermaidError = err;
-            };
-
-            if (mermaidAPI.parse($value.text())) {
-                $ele.addClass('mermaid');
-                $ele.html($value.text());
-                mermaid.init(undefined, $ele);
-            } else {
-                throw new Error(mermaidError);
+          $.ajax({
+            type: 'GET',
+            url: `//vimeo.com/api/v2/video/${$(value).attr('data-videoid')}.json`,
+            jsonp: 'callback',
+            dataType: 'jsonp',
+            success (data) {
+              const thumbnailSrc = data[0].thumbnail_large
+              const image = `<img src="${thumbnailSrc}" />`
+              $(value).prepend(image)
+              if (window.viewAjaxCallback) window.viewAjaxCallback()
             }
-        } catch (err) {
-            $value.unwrap();
-            $value.parent().append('<div class="alert alert-warning">' + err + '</div>');
-            console.warn(err);
-        }
-    });
-    //image href new window(emoji not included)
-    const images = view.find("img.raw[src]").removeClass("raw");
-    images.each((key, value) => {
-        // if it's already wrapped by link, then ignore
-        const $value = $(value);
-        $value[0].onload = e => {
-            if(viewAjaxCallback) viewAjaxCallback();
-        };
-    });
-    //blockquote
-    const blockquote = view.find("blockquote.raw").removeClass("raw");
-    const blockquote_p = blockquote.find("p");
-    blockquote_p.each((key, value) => {
-        let html = $(value).html();
-        html = replaceExtraTags(html);
-        $(value).html(html);
-    });
-    //color tag in blockquote will change its left border color
-    const blockquote_color = blockquote.find(".color");
-    blockquote_color.each((key, value) => {
-        $(value).closest("blockquote").css('border-left-color', $(value).attr('data-color'));
-    });
-    //slideshare
-    view.find("div.slideshare.raw").removeClass("raw")
-        .each((key, value) => {
-            $.ajax({
-                type: 'GET',
-                url: `//www.slideshare.net/api/oembed/2?url=http://www.slideshare.net/${$(value).attr('data-slideshareid')}&format=json`,
-                jsonp: 'callback',
-                dataType: 'jsonp',
-                success(data) {
-                    const $html = $(data.html);
-                    const iframe = $html.closest('iframe');
-                    const caption = $html.closest('div');
-                    const inner = $('<div class="inner"></div>').append(iframe);
-                    const height = iframe.attr('height');
-                    const width = iframe.attr('width');
-                    const ratio = (height / width) * 100;
-                    inner.css('padding-bottom', `${ratio}%`);
-                    $(value).html(inner).append(caption);
-                    if(viewAjaxCallback) viewAjaxCallback();
-                }
-            });
-        });
-    //speakerdeck
-    view.find("div.speakerdeck.raw").removeClass("raw")
-        .each((key, value) => {
-            const url = `https://speakerdeck.com/oembed.json?url=https%3A%2F%2Fspeakerdeck.com%2F${encodeURIComponent($(value).attr('data-speakerdeckid'))}`;
-            //use yql because speakerdeck not support jsonp
-            $.ajax({
-                url: 'https://query.yahooapis.com/v1/public/yql',
-                data: {
-                    q: `select * from json where url ='${url}'`,
-                    format: "json"
-                },
-                dataType: "jsonp",
-                success(data) {
-                    if (!data.query || !data.query.results) return;
-                    const json = data.query.results.json;
-                    const html = json.html;
-                    var ratio = json.height / json.width;
-                    $(value).html(html);
-                    const iframe = $(value).children('iframe');
-                    const src = iframe.attr('src');
-                    if (src.indexOf('//') == 0)
-                        iframe.attr('src', `https:${src}`);
-                    const inner = $('<div class="inner"></div>').append(iframe);
-                    const height = iframe.attr('height');
-                    const width = iframe.attr('width');
-                    var ratio = (height / width) * 100;
-                    inner.css('padding-bottom', `${ratio}%`);
-                    $(value).html(inner);
-                    if(viewAjaxCallback) viewAjaxCallback();
-                }
-            });
-        });
-    //pdf
-    view.find("div.pdf.raw").removeClass("raw")
-            .each(function (key, value) {
-                const url = $(value).attr('data-pdfurl');
-                const inner = $('<div></div>');
-                $(this).append(inner);
-                PDFObject.embed(url, inner, {
-                    height: '400px'
-                });
-            });
-    //syntax highlighting
-    view.find("code.raw").removeClass("raw")
-        .each((key, value) => {
-            const langDiv = $(value);
-            if (langDiv.length > 0) {
-                const reallang = langDiv[0].className.replace(/hljs|wrap/g, '').trim();
-                const codeDiv = langDiv.find('.code');
-                let code = "";
-                if (codeDiv.length > 0) code = codeDiv.html();
-                else code = langDiv.html();
-                if (!reallang) {
-                    var result = {
-                        value: code
-                    };
-                } else if (reallang == "haskell" || reallang == "go" || reallang == "typescript" || reallang == "jsx") {
-                    code = S(code).unescapeHTML().s;
-                    var result = {
-                        value: Prism.highlight(code, Prism.languages[reallang])
-                    };
-                } else if (reallang == "tiddlywiki" || reallang == "mediawiki") {
-                    code = S(code).unescapeHTML().s;
-                    var result = {
-                        value: Prism.highlight(code, Prism.languages.wiki)
-                    };
-                } else {
-                    code = S(code).unescapeHTML().s;
-                    const languages = hljs.listLanguages();
-                    if (!languages.includes(reallang)) {
-                        var result = hljs.highlightAuto(code);
-                    } else {
-                        var result = hljs.highlight(reallang, code);
-                    }
-                }
-                if (codeDiv.length > 0) codeDiv.html(result.value);
-                else langDiv.html(result.value);
-            }
-        });
-    //mathjax
-    const mathjaxdivs = view.find('span.mathjax.raw').removeClass("raw").toArray();
+          })
+        })
+    // gist
+  view.find('code[data-gist-id]').each((key, value) => {
+    if ($(value).children().length === 0) { $(value).gist(window.viewAjaxCallback) }
+  })
+    // sequence diagram
+  const sequences = view.find('div.sequence-diagram.raw').removeClass('raw')
+  sequences.each((key, value) => {
     try {
-        if (mathjaxdivs.length > 1) {
-            MathJax.Hub.Queue(["Typeset", MathJax.Hub, mathjaxdivs]);
-            MathJax.Hub.Queue(viewAjaxCallback);
-        } else if (mathjaxdivs.length > 0) {
-            MathJax.Hub.Queue(["Typeset", MathJax.Hub, mathjaxdivs[0]]);
-            MathJax.Hub.Queue(viewAjaxCallback);
-        }
+      var $value = $(value)
+      const $ele = $(value).parent().parent()
+
+      const sequence = $value
+      sequence.sequenceDiagram({
+        theme: 'simple'
+      })
+
+      $ele.addClass('sequence-diagram')
+      $value.children().unwrap().unwrap()
+      const svg = $ele.find('> svg')
+      svg[0].setAttribute('viewBox', `0 0 ${svg.attr('width')} ${svg.attr('height')}`)
+      svg[0].setAttribute('preserveAspectRatio', 'xMidYMid meet')
     } catch (err) {
-        console.warn(err);
+      $value.unwrap()
+      $value.parent().append('<div class="alert alert-warning">' + err + '</div>')
+      console.warn(err)
     }
-    //render title
-    document.title = renderTitle(view);
-}
+  })
+    // flowchart
+  const flow = view.find('div.flow-chart.raw').removeClass('raw')
+  flow.each((key, value) => {
+    try {
+      var $value = $(value)
+      const $ele = $(value).parent().parent()
 
-//only static transform should be here
-export function postProcess(code) {
-    const result = $(`<div>${code}</div>`);
-    //link should open in new window or tab
-    result.find('a:not([href^="#"]):not([target])').attr('target', '_blank');
-	//update continue line numbers
-	const linenumberdivs = result.find('.gutter.linenumber').toArray();
-	for (let i = 0; i < linenumberdivs.length; i++) {
-		if ($(linenumberdivs[i]).hasClass('continue')) {
-			const startnumber = linenumberdivs[i - 1] ? parseInt($(linenumberdivs[i - 1]).find('> span').last().attr('data-linenumber')) : 0;
-			$(linenumberdivs[i]).find('> span').each((key, value) => {
-				$(value).attr('data-linenumber', startnumber + key + 1);
-			});
-		}
-	}
-    // show yaml meta paring error
-    if (md.metaError) {
-        var warning = result.find('div#meta-error');
-        if (warning && warning.length > 0) {
-            warning.text(md.metaError)
-        } else {
-            warning = $('<div id="meta-error" class="alert alert-warning">' + md.metaError + '</div>')
-            result.prepend(warning);
-        }
+      const chart = window.flowchart.parse($value.text())
+      $value.html('')
+      chart.drawSVG(value, {
+        'line-width': 2,
+        'fill': 'none',
+        'font-size': '16px',
+        'font-family': "'Andale Mono', monospace"
+      })
+
+      $ele.addClass('flow-chart')
+      $value.children().unwrap().unwrap()
+    } catch (err) {
+      $value.unwrap()
+      $value.parent().append('<div class="alert alert-warning">' + err + '</div>')
+      console.warn(err)
     }
-    return result;
-}
-window.postProcess = postProcess;
+  })
+    // graphviz
+  var graphvizs = view.find('div.graphviz.raw').removeClass('raw')
+  graphvizs.each(function (key, value) {
+    try {
+      var $value = $(value)
+      var $ele = $(value).parent().parent()
 
-function generateCleanHTML(view) {
-    const src = view.clone();
-    const eles = src.find('*');
-    //remove syncscroll parts
-    eles.removeClass('part');
-    src.find('*[class=""]').removeAttr('class');
-    eles.removeAttr('data-startline data-endline');
-    src.find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll');
-    //remove gist content
-    src.find("code[data-gist-id]").children().remove();
-    //disable todo list
-    src.find("input.task-list-item-checkbox").attr('disabled', '');
-    //replace emoji image path
-    src.find("img.emoji").each((key, value) => {
-        let name = $(value).attr('alt');
-        name = name.substr(1);
-        name = name.slice(0, name.length - 1);
-        $(value).attr('src', `https://www.tortue.me/emoji/${name}.png`);
-    });
-    //replace video to iframe
-    src.find("div[data-videoid]").each((key, value) => {
-        const id = $(value).attr('data-videoid');
-        const style = $(value).attr('style');
-        let url = null;
-        if ($(value).hasClass('youtube')) {
-            url = 'https://www.youtube.com/embed/';
-        } else if ($(value).hasClass('vimeo')) {
-            url = 'https://player.vimeo.com/video/';
-        }
-        if (url) {
-            const iframe = $('<iframe frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>');
-            iframe.attr('src', url + id);
-            iframe.attr('style', style);
-            $(value).html(iframe);
-        }
-    });
-    return src;
-}
+      var graphviz = Viz($value.text())
+      if (!graphviz) throw Error('viz.js output empty graph')
+      $value.html(graphviz)
 
-export function exportToRawHTML(view) {
-    const filename = `${renderFilename(ui.area.markdown)}.html`;
-    const src = generateCleanHTML(view);
-    $(src).find('a.anchor').remove();
-    const html = src[0].outerHTML;
-    const blob = new Blob([html], {
-        type: "text/html;charset=utf-8"
-    });
-    saveAs(blob, filename, true);
-}
+      $ele.addClass('graphviz')
+      $value.children().unwrap().unwrap()
+    } catch (err) {
+      $value.unwrap()
+      $value.parent().append('<div class="alert alert-warning">' + err + '</div>')
+      console.warn(err)
+    }
+  })
+    // mermaid
+  const mermaids = view.find('div.mermaid.raw').removeClass('raw')
+  mermaids.each((key, value) => {
+    try {
+      var $value = $(value)
+      const $ele = $(value).closest('pre')
 
-//extract markdown body to html and compile to template
-export function exportToHTML(view) {
-    const title = renderTitle(ui.area.markdown);
-    const filename = `${renderFilename(ui.area.markdown)}.html`;
-    const src = generateCleanHTML(view);
-    //generate toc
-    const toc = $('#ui-toc').clone();
-    toc.find('*').removeClass('active').find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll');
-    const tocAffix = $('#ui-toc-affix').clone();
-    tocAffix.find('*').removeClass('active').find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll');
-    //generate html via template
-    $.get(`${serverurl}/build/html.min.css`, css => {
-        $.get(`${serverurl}/views/html.hbs`, data => {
-            const template = Handlebars.compile(data);
-            const context = {
-                url: serverurl,
-                title,
-                css,
-                html: src[0].outerHTML,
-                'ui-toc': toc.html(),
-                'ui-toc-affix': tocAffix.html(),
-                lang: (md && md.meta && md.meta.lang) ? `lang="${md.meta.lang}"` : null,
-                dir: (md && md.meta && md.meta.dir) ? `dir="${md.meta.dir}"` : null
-            };
-            const html = template(context);
-            //        console.log(html);
-            const blob = new Blob([html], {
-                type: "text/html;charset=utf-8"
-            });
-            saveAs(blob, filename, true);
-        });
-    });
-}
+      let mermaidError = null
+      window.mermaid.parseError = (err, hash) => {
+        mermaidError = err
+      }
 
-//jQuery sortByDepth
-$.fn.sortByDepth = function () {
-    const ar = this.map(function () {
-            return {
-                length: $(this).parents().length,
-                elt: this
+      if (window.mermaidAPI.parse($value.text())) {
+        $ele.addClass('mermaid')
+        $ele.html($value.text())
+        window.mermaid.init(undefined, $ele)
+      } else {
+        throw new Error(mermaidError)
+      }
+    } catch (err) {
+      $value.unwrap()
+      $value.parent().append('<div class="alert alert-warning">' + err + '</div>')
+      console.warn(err)
+    }
+  })
+    // image href new window(emoji not included)
+  const images = view.find('img.raw[src]').removeClass('raw')
+  images.each((key, value) => {
+        // if it's already wrapped by link, then ignore
+    const $value = $(value)
+    $value[0].onload = e => {
+      if (window.viewAjaxCallback) window.viewAjaxCallback()
+    }
+  })
+    // blockquote
+  const blockquote = view.find('blockquote.raw').removeClass('raw')
+  const blockquoteP = blockquote.find('p')
+  blockquoteP.each((key, value) => {
+    let html = $(value).html()
+    html = replaceExtraTags(html)
+    $(value).html(html)
+  })
+    // color tag in blockquote will change its left border color
+  const blockquoteColor = blockquote.find('.color')
+  blockquoteColor.each((key, value) => {
+    $(value).closest('blockquote').css('border-left-color', $(value).attr('data-color'))
+  })
+    // slideshare
+  view.find('div.slideshare.raw').removeClass('raw')
+        .each((key, value) => {
+          $.ajax({
+            type: 'GET',
+            url: `//www.slideshare.net/api/oembed/2?url=http://www.slideshare.net/${$(value).attr('data-slideshareid')}&format=json`,
+            jsonp: 'callback',
+            dataType: 'jsonp',
+            success (data) {
+              const $html = $(data.html)
+              const iframe = $html.closest('iframe')
+              const caption = $html.closest('div')
+              const inner = $('<div class="inner"></div>').append(iframe)
+              const height = iframe.attr('height')
+              const width = iframe.attr('width')
+              const ratio = (height / width) * 100
+              inner.css('padding-bottom', `${ratio}%`)
+              $(value).html(inner).append(caption)
+              if (window.viewAjaxCallback) window.viewAjaxCallback()
             }
-        }).get();
-
-    const result = [];
-    let i = ar.length;
-    ar.sort((a, b) => a.length - b.length);
-    while (i--) {
-        result.push(ar[i].elt);
-    }
-    return $(result);
-};
-
-function toggleTodoEvent(e) {
-    const startline = $(this).closest('li').attr('data-startline') - 1;
-    const line = editor.getLine(startline);
-    const matches = line.match(/^[>\s]*[\-\+\*]\s\[([x ])\]/);
-    if (matches && matches.length >= 2) {
-        let checked = null;
-        if (matches[1] == 'x')
-            checked = true;
-        else if (matches[1] == ' ')
-            checked = false;
-        const replacements = matches[0].match(/(^[>\s]*[\-\+\*]\s\[)([x ])(\])/);
-        editor.replaceRange(checked ? ' ' : 'x', {
-            line: startline,
-            ch: replacements[1].length
-        }, {
-            line: startline,
-            ch: replacements[1].length + 1
-        }, '+input');
+          })
+        })
+    // speakerdeck
+  view.find('div.speakerdeck.raw').removeClass('raw')
+        .each((key, value) => {
+          const url = `https://speakerdeck.com/oembed.json?url=https%3A%2F%2Fspeakerdeck.com%2F${encodeURIComponent($(value).attr('data-speakerdeckid'))}`
+            // use yql because speakerdeck not support jsonp
+          $.ajax({
+            url: 'https://query.yahooapis.com/v1/public/yql',
+            data: {
+              q: `select * from json where url ='${url}'`,
+              format: 'json'
+            },
+            dataType: 'jsonp',
+            success (data) {
+              if (!data.query || !data.query.results) return
+              const json = data.query.results.json
+              const html = json.html
+              var ratio = json.height / json.width
+              $(value).html(html)
+              const iframe = $(value).children('iframe')
+              const src = iframe.attr('src')
+              if (src.indexOf('//') === 0) { iframe.attr('src', `https:${src}`) }
+              const inner = $('<div class="inner"></div>').append(iframe)
+              const height = iframe.attr('height')
+              const width = iframe.attr('width')
+              ratio = (height / width) * 100
+              inner.css('padding-bottom', `${ratio}%`)
+              $(value).html(inner)
+              if (window.viewAjaxCallback) window.viewAjaxCallback()
+            }
+          })
+        })
+    // pdf
+  view.find('div.pdf.raw').removeClass('raw')
+            .each(function (key, value) {
+              const url = $(value).attr('data-pdfurl')
+              const inner = $('<div></div>')
+              $(this).append(inner)
+              PDFObject.embed(url, inner, {
+                height: '400px'
+              })
+            })
+    // syntax highlighting
+  view.find('code.raw').removeClass('raw')
+        .each((key, value) => {
+          const langDiv = $(value)
+          if (langDiv.length > 0) {
+            const reallang = langDiv[0].className.replace(/hljs|wrap/g, '').trim()
+            const codeDiv = langDiv.find('.code')
+            let code = ''
+            if (codeDiv.length > 0) code = codeDiv.html()
+            else code = langDiv.html()
+            var result
+            if (!reallang) {
+              result = {
+                value: code
+              }
+            } else if (reallang === 'haskell' || reallang === 'go' || reallang === 'typescript' || reallang === 'jsx') {
+              code = S(code).unescapeHTML().s
+              result = {
+                value: Prism.highlight(code, Prism.languages[reallang])
+              }
+            } else if (reallang === 'tiddlywiki' || reallang === 'mediawiki') {
+              code = S(code).unescapeHTML().s
+              result = {
+                value: Prism.highlight(code, Prism.languages.wiki)
+              }
+            } else {
+              code = S(code).unescapeHTML().s
+              const languages = hljs.listLanguages()
+              if (!languages.includes(reallang)) {
+                result = hljs.highlightAuto(code)
+              } else {
+                result = hljs.highlight(reallang, code)
+              }
+            }
+            if (codeDiv.length > 0) codeDiv.html(result.value)
+            else langDiv.html(result.value)
+          }
+        })
+    // mathjax
+  const mathjaxdivs = view.find('span.mathjax.raw').removeClass('raw').toArray()
+  try {
+    if (mathjaxdivs.length > 1) {
+      window.MathJax.Hub.Queue(['Typeset', window.MathJax.Hub, mathjaxdivs])
+      window.MathJax.Hub.Queue(window.viewAjaxCallback)
+    } else if (mathjaxdivs.length > 0) {
+      window.MathJax.Hub.Queue(['Typeset', window.MathJax.Hub, mathjaxdivs[0]])
+      window.MathJax.Hub.Queue(window.viewAjaxCallback)
     }
+  } catch (err) {
+    console.warn(err)
+  }
+    // render title
+  document.title = renderTitle(view)
 }
 
-//remove hash
-function removeHash() {
-    history.pushState("", document.title, window.location.pathname + window.location.search);
-}
-
-let tocExpand = false;
-
-function checkExpandToggle() {
-    const toc = $('.ui-toc-dropdown .toc');
-    const toggle = $('.expand-toggle');
-    if (!tocExpand) {
-        toc.removeClass('expand');
-        toggle.text('Expand all');
+// only static transform should be here
+export function postProcess (code) {
+  const result = $(`<div>${code}</div>`)
+  // link should open in new window or tab
+  result.find('a:not([href^="#"]):not([target])').attr('target', '_blank')
+  // update continue line numbers
+  const linenumberdivs = result.find('.gutter.linenumber').toArray()
+  for (let i = 0; i < linenumberdivs.length; i++) {
+    if ($(linenumberdivs[i]).hasClass('continue')) {
+      const startnumber = linenumberdivs[i - 1] ? parseInt($(linenumberdivs[i - 1]).find('> span').last().attr('data-linenumber')) : 0
+      $(linenumberdivs[i]).find('> span').each((key, value) => {
+        $(value).attr('data-linenumber', startnumber + key + 1)
+      })
+    }
+  }
+  // show yaml meta paring error
+  if (md.metaError) {
+    var warning = result.find('div#meta-error')
+    if (warning && warning.length > 0) {
+      warning.text(md.metaError)
     } else {
-        toc.addClass('expand');
-        toggle.text('Collapse all');
+      warning = $('<div id="meta-error" class="alert alert-warning">' + md.metaError + '</div>')
+      result.prepend(warning)
     }
+  }
+  return result
+}
+window.postProcess = postProcess
+
+function generateCleanHTML (view) {
+  const src = view.clone()
+  const eles = src.find('*')
+    // remove syncscroll parts
+  eles.removeClass('part')
+  src.find('*[class=""]').removeAttr('class')
+  eles.removeAttr('data-startline data-endline')
+  src.find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll')
+    // remove gist content
+  src.find('code[data-gist-id]').children().remove()
+    // disable todo list
+  src.find('input.task-list-item-checkbox').attr('disabled', '')
+    // replace emoji image path
+  src.find('img.emoji').each((key, value) => {
+    let name = $(value).attr('alt')
+    name = name.substr(1)
+    name = name.slice(0, name.length - 1)
+    $(value).attr('src', `https://www.tortue.me/emoji/${name}.png`)
+  })
+    // replace video to iframe
+  src.find('div[data-videoid]').each((key, value) => {
+    const id = $(value).attr('data-videoid')
+    const style = $(value).attr('style')
+    let url = null
+    if ($(value).hasClass('youtube')) {
+      url = 'https://www.youtube.com/embed/'
+    } else if ($(value).hasClass('vimeo')) {
+      url = 'https://player.vimeo.com/video/'
+    }
+    if (url) {
+      const iframe = $('<iframe frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>')
+      iframe.attr('src', url + id)
+      iframe.attr('style', style)
+      $(value).html(iframe)
+    }
+  })
+  return src
 }
 
-//toc
-export function generateToc(id) {
-    const target = $(`#${id}`);
-    target.html('');
-    new Toc('doc', {
-        'level': 3,
-        'top': -1,
-        'class': 'toc',
-        'ulClass': 'nav',
-        'targetId': id,
-        'process': getHeaderContent
-    });
-    if (target.text() == 'undefined')
-        target.html('');
-    const tocMenu = $('<div class="toc-menu"></div');
-    const toggle = $('<a class="expand-toggle" href="#">Expand all</a>');
-    const backtotop = $('<a class="back-to-top" href="#">Back to top</a>');
-    const gotobottom = $('<a class="go-to-bottom" href="#">Go to bottom</a>');
-    checkExpandToggle();
-    toggle.click(e => {
-        e.preventDefault();
-        e.stopPropagation();
-        tocExpand = !tocExpand;
-        checkExpandToggle();
-    });
-    backtotop.click(e => {
-        e.preventDefault();
-        e.stopPropagation();
-        if (scrollToTop)
-            scrollToTop();
-        removeHash();
-    });
-    gotobottom.click(e => {
-        e.preventDefault();
-        e.stopPropagation();
-        if (scrollToBottom)
-            scrollToBottom();
-        removeHash();
-    });
-    tocMenu.append(toggle).append(backtotop).append(gotobottom);
-    target.append(tocMenu);
+export function exportToRawHTML (view) {
+  const filename = `${renderFilename(window.ui.area.markdown)}.html`
+  const src = generateCleanHTML(view)
+  $(src).find('a.anchor').remove()
+  const html = src[0].outerHTML
+  const blob = new Blob([html], {
+    type: 'text/html;charset=utf-8'
+  })
+  saveAs(blob, filename, true)
 }
 
-//smooth all hash trigger scrolling
-export function smoothHashScroll() {
-    const hashElements = $("a[href^='#']:not([smoothhashscroll])").toArray();
+// extract markdown body to html and compile to template
+export function exportToHTML (view) {
+  const title = renderTitle(window.ui.area.markdown)
+  const filename = `${renderFilename(window.ui.area.markdown)}.html`
+  const src = generateCleanHTML(view)
+    // generate toc
+  const toc = $('#ui-toc').clone()
+  toc.find('*').removeClass('active').find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll')
+  const tocAffix = $('#ui-toc-affix').clone()
+  tocAffix.find('*').removeClass('active').find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll')
+    // generate html via template
+  $.get(`${serverurl}/build/html.min.css`, css => {
+    $.get(`${serverurl}/views/html.hbs`, data => {
+      const template = window.Handlebars.compile(data)
+      const context = {
+        url: serverurl,
+        title,
+        css,
+        html: src[0].outerHTML,
+        'ui-toc': toc.html(),
+        'ui-toc-affix': tocAffix.html(),
+        lang: (md && md.meta && md.meta.lang) ? `lang="${md.meta.lang}"` : null,
+        dir: (md && md.meta && md.meta.dir) ? `dir="${md.meta.dir}"` : null
+      }
+      const html = template(context)
+            //        console.log(html);
+      const blob = new Blob([html], {
+        type: 'text/html;charset=utf-8'
+      })
+      saveAs(blob, filename, true)
+    })
+  })
+}
 
-    for (const element of hashElements) {
-        const $element = $(element);
-        const hash = element.hash;
-        if (hash) {
-            $element.on('click', function (e) {
+// jQuery sortByDepth
+$.fn.sortByDepth = function () {
+  const ar = this.map(function () {
+    return {
+      length: $(this).parents().length,
+      elt: this
+    }
+  }).get()
+
+  const result = []
+  let i = ar.length
+  ar.sort((a, b) => a.length - b.length)
+  while (i--) {
+    result.push(ar[i].elt)
+  }
+  return $(result)
+}
+
+function toggleTodoEvent (e) {
+  const startline = $(this).closest('li').attr('data-startline') - 1
+  const line = window.editor.getLine(startline)
+  const matches = line.match(/^[>\s]*[-+*]\s\[([x ])\]/)
+  if (matches && matches.length >= 2) {
+    let checked = null
+    if (matches[1] === 'x') { checked = true } else if (matches[1] === ' ') { checked = false }
+    const replacements = matches[0].match(/(^[>\s]*[-+*]\s\[)([x ])(\])/)
+    window.editor.replaceRange(checked ? ' ' : 'x', {
+      line: startline,
+      ch: replacements[1].length
+    }, {
+      line: startline,
+      ch: replacements[1].length + 1
+    }, '+input')
+  }
+}
+
+// remove hash
+function removeHash () {
+  history.pushState('', document.title, window.location.pathname + window.location.search)
+}
+
+let tocExpand = false
+
+function checkExpandToggle () {
+  const toc = $('.ui-toc-dropdown .toc')
+  const toggle = $('.expand-toggle')
+  if (!tocExpand) {
+    toc.removeClass('expand')
+    toggle.text('Expand all')
+  } else {
+    toc.addClass('expand')
+    toggle.text('Collapse all')
+  }
+}
+
+// toc
+export function generateToc (id) {
+  const target = $(`#${id}`)
+  target.html('')
+  /* eslint-disable no-unused-vars */
+  var toc = new window.Toc('doc', {
+    'level': 3,
+    'top': -1,
+    'class': 'toc',
+    'ulClass': 'nav',
+    'targetId': id,
+    'process': getHeaderContent
+  })
+  /* eslint-enable no-unsed-vars */
+  if (target.text() === 'undefined') { target.html('') }
+  const tocMenu = $('<div class="toc-menu"></div')
+  const toggle = $('<a class="expand-toggle" href="#">Expand all</a>')
+  const backtotop = $('<a class="back-to-top" href="#">Back to top</a>')
+  const gotobottom = $('<a class="go-to-bottom" href="#">Go to bottom</a>')
+  checkExpandToggle()
+  toggle.click(e => {
+    e.preventDefault()
+    e.stopPropagation()
+    tocExpand = !tocExpand
+    checkExpandToggle()
+  })
+  backtotop.click(e => {
+    e.preventDefault()
+    e.stopPropagation()
+    if (window.scrollToTop) { window.scrollToTop() }
+    removeHash()
+  })
+  gotobottom.click(e => {
+    e.preventDefault()
+    e.stopPropagation()
+    if (window.scrollToBottom) { window.scrollToBottom() }
+    removeHash()
+  })
+  tocMenu.append(toggle).append(backtotop).append(gotobottom)
+  target.append(tocMenu)
+}
+
+// smooth all hash trigger scrolling
+export function smoothHashScroll () {
+  const hashElements = $("a[href^='#']:not([smoothhashscroll])").toArray()
+
+  for (const element of hashElements) {
+    const $element = $(element)
+    const hash = element.hash
+    if (hash) {
+      $element.on('click', function (e) {
                 // store hash
-                const hash = decodeURIComponent(this.hash);
+        const hash = decodeURIComponent(this.hash)
                 // escape special characters in jquery selector
-                const $hash = $(hash.replace(/(:|\.|\[|\]|,)/g, "\\$1"));
+        const $hash = $(hash.replace(/(:|\.|\[|\]|,)/g, '\\$1'))
                 // return if no element been selected
-                if ($hash.length <= 0) return;
+        if ($hash.length <= 0) return
                 // prevent default anchor click behavior
-                e.preventDefault();
+        e.preventDefault()
                 // animate
-                $('body, html').stop(true, true).animate({
-                    scrollTop: $hash.offset().top
-                }, 100, "linear", () => {
+        $('body, html').stop(true, true).animate({
+          scrollTop: $hash.offset().top
+        }, 100, 'linear', () => {
                     // when done, add hash to url
                     // (default click behaviour)
-                    window.location.hash = hash;
-                });
-            });
-            $element.attr('smoothhashscroll', '');
-        }
+          window.location.hash = hash
+        })
+      })
+      $element.attr('smoothhashscroll', '')
     }
+  }
 }
 
-function imgPlayiframe(element, src) {
-    if (!$(element).attr("data-videoid")) return;
-    const iframe = $("<iframe frameborder='0' webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>");
-    $(iframe).attr("src", `${src + $(element).attr("data-videoid")}?autoplay=1`);
-    $(element).find('img').css('visibility', 'hidden');
-    $(element).append(iframe);
+function imgPlayiframe (element, src) {
+  if (!$(element).attr('data-videoid')) return
+  const iframe = $("<iframe frameborder='0' webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>")
+  $(iframe).attr('src', `${src + $(element).attr('data-videoid')}?autoplay=1`)
+  $(element).find('img').css('visibility', 'hidden')
+  $(element).append(iframe)
 }
 
 const anchorForId = id => {
-    const anchor = document.createElement("a");
-    anchor.className = "anchor hidden-xs";
-    anchor.href = `#${id}`;
-    anchor.innerHTML = "<span class=\"octicon octicon-link\"></span>";
-    anchor.title = id;
-    return anchor;
-};
+  const anchor = document.createElement('a')
+  anchor.className = 'anchor hidden-xs'
+  anchor.href = `#${id}`
+  anchor.innerHTML = '<span class="octicon octicon-link"></span>'
+  anchor.title = id
+  return anchor
+}
 
 const linkifyAnchors = (level, containingElement) => {
-    const headers = containingElement.getElementsByTagName(`h${level}`);
+  const headers = containingElement.getElementsByTagName(`h${level}`)
 
-    for (let i = 0, l = headers.length; i < l; i++) {
-        let header = headers[i];
-        if (header.getElementsByClassName("anchor").length == 0) {
-            if (typeof header.id == "undefined" || header.id == "") {
-                //to escape characters not allow in css and humanize
-                const id = slugifyWithUTF8(getHeaderContent(header));
-                header.id = id;
-            }
-            header.insertBefore(anchorForId(header.id), header.firstChild);
-        }
-    }
-};
-
-export function autoLinkify(view) {
-    const contentBlock = view[0];
-    if (!contentBlock) {
-        return;
-    }
-    for (let level = 1; level <= 6; level++) {
-        linkifyAnchors(level, contentBlock);
+  for (let i = 0, l = headers.length; i < l; i++) {
+    let header = headers[i]
+    if (header.getElementsByClassName('anchor').length === 0) {
+      if (typeof header.id === 'undefined' || header.id === '') {
+                // to escape characters not allow in css and humanize
+        const id = slugifyWithUTF8(getHeaderContent(header))
+        header.id = id
+      }
+      header.insertBefore(anchorForId(header.id), header.firstChild)
     }
+  }
 }
 
-function getHeaderContent(header) {
-    const headerHTML = $(header).clone();
-    headerHTML.find('.MathJax_Preview').remove();
-    headerHTML.find('.MathJax').remove();
-    return headerHTML[0].innerHTML;
+export function autoLinkify (view) {
+  const contentBlock = view[0]
+  if (!contentBlock) {
+    return
+  }
+  for (let level = 1; level <= 6; level++) {
+    linkifyAnchors(level, contentBlock)
+  }
 }
 
-export function deduplicatedHeaderId(view) {
-    const headers = view.find(':header.raw').removeClass('raw').toArray();
-    for (let i = 0; i < headers.length; i++) {
-        const id = $(headers[i]).attr('id');
-        if (!id) continue;
-        const duplicatedHeaders = view.find(`:header[id="${id}"]`).toArray();
-        for (let j = 0; j < duplicatedHeaders.length; j++) {
-            if (duplicatedHeaders[j] != headers[i]) {
-                const newId = id + j;
-                const $duplicatedHeader = $(duplicatedHeaders[j]);
-                $duplicatedHeader.attr('id', newId);
-                const $headerLink = $duplicatedHeader.find(`> a.anchor[href="#${id}"]`);
-                $headerLink.attr('href', `#${newId}`);
-                $headerLink.attr('title', newId);
-            }
-        }
+function getHeaderContent (header) {
+  const headerHTML = $(header).clone()
+  headerHTML.find('.MathJax_Preview').remove()
+  headerHTML.find('.MathJax').remove()
+  return headerHTML[0].innerHTML
+}
+
+export function deduplicatedHeaderId (view) {
+  const headers = view.find(':header.raw').removeClass('raw').toArray()
+  for (let i = 0; i < headers.length; i++) {
+    const id = $(headers[i]).attr('id')
+    if (!id) continue
+    const duplicatedHeaders = view.find(`:header[id="${id}"]`).toArray()
+    for (let j = 0; j < duplicatedHeaders.length; j++) {
+      if (duplicatedHeaders[j] !== headers[i]) {
+        const newId = id + j
+        const $duplicatedHeader = $(duplicatedHeaders[j])
+        $duplicatedHeader.attr('id', newId)
+        const $headerLink = $duplicatedHeader.find(`> a.anchor[href="#${id}"]`)
+        $headerLink.attr('href', `#${newId}`)
+        $headerLink.attr('title', newId)
+      }
     }
+  }
 }
 
-export function renderTOC(view) {
-    const tocs = view.find('.toc').toArray();
-	for (let i = 0; i < tocs.length; i++) {
-        const toc = $(tocs[i]);
-        const id = `toc${i}`;
-        toc.attr('id', id);
-        const target = $(`#${id}`);
-        target.html('');
-        new Toc('doc', {
-            'level': 3,
-            'top': -1,
-            'class': 'toc',
-            'targetId': id,
-            'process': getHeaderContent
-        });
-        if (target.text() == 'undefined')
-            target.html('');
-        target.replaceWith(target.html());
+export function renderTOC (view) {
+  const tocs = view.find('.toc').toArray()
+  for (let i = 0; i < tocs.length; i++) {
+    const toc = $(tocs[i])
+    const id = `toc${i}`
+    toc.attr('id', id)
+    const target = $(`#${id}`)
+    target.html('')
+    /* eslint-disable no-unused-vars */
+    var toc = new window.Toc('doc', {
+      'level': 3,
+      'top': -1,
+      'class': 'toc',
+      'targetId': id,
+      'process': getHeaderContent
+    })
+    /* eslint-enable no-unused-vars */
+    if (target.text() === 'undefined') { target.html('') }
+    target.replaceWith(target.html())
+  }
+}
+
+export function scrollToHash () {
+  const hash = location.hash
+  location.hash = ''
+  location.hash = hash
+}
+
+function highlightRender (code, lang) {
+  if (!lang || /no(-?)highlight|plain|text/.test(lang)) { return }
+  code = S(code).escapeHTML().s
+  if (lang === 'sequence') {
+    return `<div class="sequence-diagram raw">${code}</div>`
+  } else if (lang === 'flow') {
+    return `<div class="flow-chart raw">${code}</div>`
+  } else if (lang === 'graphviz') {
+    return `<div class="graphviz raw">${code}</div>`
+  } else if (lang === 'mermaid') {
+    return `<div class="mermaid raw">${code}</div>`
+  }
+  const result = {
+    value: code
+  }
+  const showlinenumbers = /=$|=\d+$|=\+$/.test(lang)
+  if (showlinenumbers) {
+    let startnumber = 1
+    const matches = lang.match(/=(\d+)$/)
+    if (matches) { startnumber = parseInt(matches[1]) }
+    const lines = result.value.split('\n')
+    const linenumbers = []
+    for (let i = 0; i < lines.length - 1; i++) {
+      linenumbers[i] = `<span data-linenumber='${startnumber + i}'></span>`
     }
+    const continuelinenumber = /=\+$/.test(lang)
+    const linegutter = `<div class='gutter linenumber${continuelinenumber ? ' continue' : ''}'>${linenumbers.join('\n')}</div>`
+    result.value = `<div class='wrapper'>${linegutter}<div class='code'>${result.value}</div></div>`
+  }
+  return result.value
 }
 
-export function scrollToHash() {
-    const hash = location.hash;
-    location.hash = "";
-    location.hash = hash;
-}
-
-function highlightRender(code, lang) {
-    if (!lang || /no(-?)highlight|plain|text/.test(lang))
-        return;
-    code = S(code).escapeHTML().s
-    if (lang == 'sequence') {
-        return `<div class="sequence-diagram raw">${code}</div>`;
-    } else if (lang == 'flow') {
-        return `<div class="flow-chart raw">${code}</div>`;
-    } else if (lang == 'graphviz') {
-        return `<div class="graphviz raw">${code}</div>`;
-    } else if (lang == 'mermaid') {
-        return `<div class="mermaid raw">${code}</div>`;
-    }
-    const result = {
-        value: code
-    };
-	const showlinenumbers = /\=$|\=\d+$|\=\+$/.test(lang);
-    if (showlinenumbers) {
-		let startnumber = 1;
-		const matches = lang.match(/\=(\d+)$/);
-		if (matches)
-			startnumber = parseInt(matches[1]);
-        const lines = result.value.split('\n');
-        const linenumbers = [];
-        for (let i = 0; i < lines.length - 1; i++) {
-            linenumbers[i] = `<span data-linenumber='${startnumber + i}'></span>`;
-        }
-		const continuelinenumber = /\=\+$/.test(lang);
-        const linegutter = `<div class='gutter linenumber${continuelinenumber ? " continue" : ""}'>${linenumbers.join('\n')}</div>`;
-        result.value = `<div class='wrapper'>${linegutter}<div class='code'>${result.value}</div></div>`;
-    }
-    return result.value;
-}
-
-import markdownit from 'markdown-it';
-import markdownitContainer from 'markdown-it-container';
+import markdownit from 'markdown-it'
+import markdownitContainer from 'markdown-it-container'
 
 export let md = markdownit('default', {
-    html: true,
-    breaks: true,
-    langPrefix: "",
-    linkify: true,
-    typographer: true,
-    highlight: highlightRender
-});
-window.md = md;
+  html: true,
+  breaks: true,
+  langPrefix: '',
+  linkify: true,
+  typographer: true,
+  highlight: highlightRender
+})
+window.md = md
 
-md.use(require('markdown-it-abbr'));
-md.use(require('markdown-it-footnote'));
-md.use(require('markdown-it-deflist'));
-md.use(require('markdown-it-mark'));
-md.use(require('markdown-it-ins'));
-md.use(require('markdown-it-sub'));
-md.use(require('markdown-it-sup'));
+md.use(require('markdown-it-abbr'))
+md.use(require('markdown-it-footnote'))
+md.use(require('markdown-it-deflist'))
+md.use(require('markdown-it-mark'))
+md.use(require('markdown-it-ins'))
+md.use(require('markdown-it-sub'))
+md.use(require('markdown-it-sup'))
 md.use(require('markdown-it-mathjax')({
-    beforeMath: '<span class="mathjax raw">',
-    afterMath: '</span>',
-    beforeInlineMath: '<span class="mathjax raw">\\(',
-    afterInlineMath: '\\)</span>',
-    beforeDisplayMath: '<span class="mathjax raw">\\[',
-    afterDisplayMath: '\\]</span>'
-}));
-md.use(require('markdown-it-imsize'));
+  beforeMath: '<span class="mathjax raw">',
+  afterMath: '</span>',
+  beforeInlineMath: '<span class="mathjax raw">\\(',
+  afterInlineMath: '\\)</span>',
+  beforeDisplayMath: '<span class="mathjax raw">\\[',
+  afterDisplayMath: '\\]</span>'
+}))
+md.use(require('markdown-it-imsize'))
 
 md.use(require('markdown-it-emoji'), {
-    shortcuts: {}
-});
+  shortcuts: {}
+})
 
-emojify.setConfig({
-    blacklist: {
-        elements: ['script', 'textarea', 'a', 'pre', 'code', 'svg'],
-        classes: ['no-emojify']
-    },
-    img_dir: `${serverurl}/build/emojify.js/dist/images/basic`,
-    ignore_emoticons: true
-});
+window.emojify.setConfig({
+  blacklist: {
+    elements: ['script', 'textarea', 'a', 'pre', 'code', 'svg'],
+    classes: ['no-emojify']
+  },
+  img_dir: `${serverurl}/build/emojify.js/dist/images/basic`,
+  ignore_emoticons: true
+})
 
-md.renderer.rules.emoji = (token, idx) => emojify.replace(`:${token[idx].markup}:`);
+md.renderer.rules.emoji = (token, idx) => window.emojify.replace(`:${token[idx].markup}:`)
 
-function renderContainer(tokens, idx, options, env, self) {
-    tokens[idx].attrJoin('role', 'alert');
-    tokens[idx].attrJoin('class', 'alert');
-    tokens[idx].attrJoin('class', `alert-${tokens[idx].info.trim()}`);
-    return self.renderToken(...arguments);
+function renderContainer (tokens, idx, options, env, self) {
+  tokens[idx].attrJoin('role', 'alert')
+  tokens[idx].attrJoin('class', 'alert')
+  tokens[idx].attrJoin('class', `alert-${tokens[idx].info.trim()}`)
+  return self.renderToken(...arguments)
 }
-md.use(markdownitContainer, 'success', { render: renderContainer });
-md.use(markdownitContainer, 'info', { render: renderContainer });
-md.use(markdownitContainer, 'warning', { render: renderContainer });
-md.use(markdownitContainer, 'danger', { render: renderContainer });
+md.use(markdownitContainer, 'success', { render: renderContainer })
+md.use(markdownitContainer, 'info', { render: renderContainer })
+md.use(markdownitContainer, 'warning', { render: renderContainer })
+md.use(markdownitContainer, 'danger', { render: renderContainer })
 
 md.renderer.rules.image = function (tokens, idx, options, env, self) {
-    tokens[idx].attrJoin('class', 'raw');
-    return self.renderToken(...arguments);
-};
+  tokens[idx].attrJoin('class', 'raw')
+  return self.renderToken(...arguments)
+}
 md.renderer.rules.list_item_open = function (tokens, idx, options, env, self) {
-    tokens[idx].attrJoin('class', 'raw');
-    return self.renderToken(...arguments);
-};
+  tokens[idx].attrJoin('class', 'raw')
+  return self.renderToken(...arguments)
+}
 md.renderer.rules.blockquote_open = function (tokens, idx, options, env, self) {
-    tokens[idx].attrJoin('class', 'raw');
-    return self.renderToken(...arguments);
-};
+  tokens[idx].attrJoin('class', 'raw')
+  return self.renderToken(...arguments)
+}
 md.renderer.rules.heading_open = function (tokens, idx, options, env, self) {
-    tokens[idx].attrJoin('class', 'raw');
-    return self.renderToken(...arguments);
-};
+  tokens[idx].attrJoin('class', 'raw')
+  return self.renderToken(...arguments)
+}
 md.renderer.rules.fence = (tokens, idx, options, env, self) => {
-    const token = tokens[idx];
-    const info = token.info ? md.utils.unescapeAll(token.info).trim() : '';
-    let langName = '';
-    let highlighted;
+  const token = tokens[idx]
+  const info = token.info ? md.utils.unescapeAll(token.info).trim() : ''
+  let langName = ''
+  let highlighted
 
-    if (info) {
-        langName = info.split(/\s+/g)[0];
-        if (/\!$/.test(info)) token.attrJoin('class', 'wrap');
-        token.attrJoin('class', options.langPrefix + langName.replace(/\=$|\=\d+$|\=\+$|\!$|\=\!$/, ''));
-        token.attrJoin('class', 'hljs');
-        token.attrJoin('class', 'raw');
-    }
+  if (info) {
+    langName = info.split(/\s+/g)[0]
+    if (/!$/.test(info)) token.attrJoin('class', 'wrap')
+    token.attrJoin('class', options.langPrefix + langName.replace(/=$|=\d+$|=\+$|!$|=!$/, ''))
+    token.attrJoin('class', 'hljs')
+    token.attrJoin('class', 'raw')
+  }
 
-    if (options.highlight) {
-        highlighted = options.highlight(token.content, langName) || md.utils.escapeHtml(token.content);
-    } else {
-        highlighted = md.utils.escapeHtml(token.content);
-    }
+  if (options.highlight) {
+    highlighted = options.highlight(token.content, langName) || md.utils.escapeHtml(token.content)
+  } else {
+    highlighted = md.utils.escapeHtml(token.content)
+  }
 
-    if (highlighted.indexOf('<pre') === 0) {
-        return `${highlighted}\n`;
-    }
+  if (highlighted.indexOf('<pre') === 0) {
+    return `${highlighted}\n`
+  }
 
-    return  `<pre><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n`;
-};
+  return `<pre><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n`
+}
 
 /* Defined regex markdown it plugins */
-import Plugin from 'markdown-it-regexp';
+import Plugin from 'markdown-it-regexp'
 
-//youtube
+// youtube
 const youtubePlugin = new Plugin(
     // regexp to match
     /{%youtube\s*([\d\D]*?)\s*%}/,
 
     (match, utils) => {
-        const videoid = match[1];
-        if (!videoid) return;
-        const div = $('<div class="youtube raw"></div>');
-        div.attr('data-videoid', videoid);
-        const thumbnail_src = `//img.youtube.com/vi/${videoid}/hqdefault.jpg`;
-        const image = `<img src="${thumbnail_src}" />`;
-        div.append(image);
-        const icon = '<i class="icon fa fa-youtube-play fa-5x"></i>';
-        div.append(icon);
-        return div[0].outerHTML;
+      const videoid = match[1]
+      if (!videoid) return
+      const div = $('<div class="youtube raw"></div>')
+      div.attr('data-videoid', videoid)
+      const thumbnailSrc = `//img.youtube.com/vi/${videoid}/hqdefault.jpg`
+      const image = `<img src="${thumbnailSrc}" />`
+      div.append(image)
+      const icon = '<i class="icon fa fa-youtube-play fa-5x"></i>'
+      div.append(icon)
+      return div[0].outerHTML
     }
-);
-//vimeo
+)
+// vimeo
 const vimeoPlugin = new Plugin(
     // regexp to match
     /{%vimeo\s*([\d\D]*?)\s*%}/,
 
     (match, utils) => {
-        const videoid = match[1];
-        if (!videoid) return;
-        const div = $('<div class="vimeo raw"></div>');
-        div.attr('data-videoid', videoid);
-        const icon = '<i class="icon fa fa-vimeo-square fa-5x"></i>';
-        div.append(icon);
-        return div[0].outerHTML;
+      const videoid = match[1]
+      if (!videoid) return
+      const div = $('<div class="vimeo raw"></div>')
+      div.attr('data-videoid', videoid)
+      const icon = '<i class="icon fa fa-vimeo-square fa-5x"></i>'
+      div.append(icon)
+      return div[0].outerHTML
     }
-);
-//gist
+)
+// gist
 const gistPlugin = new Plugin(
     // regexp to match
     /{%gist\s*([\d\D]*?)\s*%}/,
 
     (match, utils) => {
-        const gistid = match[1];
-        const code = `<code data-gist-id="${gistid}"/>`;
-        return code;
+      const gistid = match[1]
+      const code = `<code data-gist-id="${gistid}"/>`
+      return code
     }
-);
-//TOC
+)
+// TOC
 const tocPlugin = new Plugin(
     // regexp to match
     /^\[TOC\]$/i,
 
     (match, utils) => '<div class="toc"></div>'
-);
-//slideshare
+)
+// slideshare
 const slidesharePlugin = new Plugin(
     // regexp to match
     /{%slideshare\s*([\d\D]*?)\s*%}/,
 
     (match, utils) => {
-        const slideshareid = match[1];
-        const div = $('<div class="slideshare raw"></div>');
-        div.attr('data-slideshareid', slideshareid);
-        return div[0].outerHTML;
+      const slideshareid = match[1]
+      const div = $('<div class="slideshare raw"></div>')
+      div.attr('data-slideshareid', slideshareid)
+      return div[0].outerHTML
     }
-);
-//speakerdeck
+)
+// speakerdeck
 const speakerdeckPlugin = new Plugin(
     // regexp to match
     /{%speakerdeck\s*([\d\D]*?)\s*%}/,
 
     (match, utils) => {
-        const speakerdeckid = match[1];
-        const div = $('<div class="speakerdeck raw"></div>');
-        div.attr('data-speakerdeckid', speakerdeckid);
-        return div[0].outerHTML;
+      const speakerdeckid = match[1]
+      const div = $('<div class="speakerdeck raw"></div>')
+      div.attr('data-speakerdeckid', speakerdeckid)
+      return div[0].outerHTML
     }
-);
-//pdf
+)
+// pdf
 const pdfPlugin = new Plugin(
     // regexp to match
     /{%pdf\s*([\d\D]*?)\s*%}/,
 
     (match, utils) => {
-        const pdfurl = match[1];
-        if (!isValidURL(pdfurl)) return match[0];
-        const div = $('<div class="pdf raw"></div>');
-        div.attr('data-pdfurl', pdfurl);
-        return div[0].outerHTML;
+      const pdfurl = match[1]
+      if (!isValidURL(pdfurl)) return match[0]
+      const div = $('<div class="pdf raw"></div>')
+      div.attr('data-pdfurl', pdfurl)
+      return div[0].outerHTML
     }
-);
+)
 
-//yaml meta, from https://github.com/eugeneware/remarkable-meta
-function get(state, line) {
-    const pos = state.bMarks[line];
-    const max = state.eMarks[line];
-    return state.src.substr(pos, max - pos);
+// yaml meta, from https://github.com/eugeneware/remarkable-meta
+function get (state, line) {
+  const pos = state.bMarks[line]
+  const max = state.eMarks[line]
+  return state.src.substr(pos, max - pos)
 }
 
-function meta(state, start, end, silent) {
-    if (start !== 0 || state.blkIndent !== 0) return false;
-    if (state.tShift[start] < 0) return false;
-    if (!get(state, start).match(/^---$/)) return false;
+function meta (state, start, end, silent) {
+  if (start !== 0 || state.blkIndent !== 0) return false
+  if (state.tShift[start] < 0) return false
+  if (!get(state, start).match(/^---$/)) return false
 
-    const data = [];
-    for (var line = start + 1; line < end; line++) {
-        const str = get(state, line);
-        if (str.match(/^(\.{3}|-{3})$/)) break;
-        if (state.tShift[line] < 0) break;
-        data.push(str);
-    }
+  const data = []
+  for (var line = start + 1; line < end; line++) {
+    const str = get(state, line)
+    if (str.match(/^(\.{3}|-{3})$/)) break
+    if (state.tShift[line] < 0) break
+    data.push(str)
+  }
 
-    if (line >= end) return false;
+  if (line >= end) return false
 
-    try {
-        md.meta = jsyaml.safeLoad(data.join('\n')) || {};
-        delete md.metaError;
-    } catch(err) {
-        md.metaError = err;
-        console.warn(err);
-        return false;
-    }
+  try {
+    md.meta = window.jsyaml.safeLoad(data.join('\n')) || {}
+    delete md.metaError
+  } catch (err) {
+    md.metaError = err
+    console.warn(err)
+    return false
+  }
 
-    state.line = line + 1;
+  state.line = line + 1
 
-    return true;
+  return true
 }
 
-function metaPlugin(md) {
-    md.meta = md.meta || {};
-    md.block.ruler.before('code', 'meta', meta, {
-        alt: []
-    });
+function metaPlugin (md) {
+  md.meta = md.meta || {}
+  md.block.ruler.before('code', 'meta', meta, {
+    alt: []
+  })
 }
 
-md.use(metaPlugin);
-md.use(youtubePlugin);
-md.use(vimeoPlugin);
-md.use(gistPlugin);
-md.use(tocPlugin);
-md.use(slidesharePlugin);
-md.use(speakerdeckPlugin);
-md.use(pdfPlugin);
+md.use(metaPlugin)
+md.use(youtubePlugin)
+md.use(vimeoPlugin)
+md.use(gistPlugin)
+md.use(tocPlugin)
+md.use(slidesharePlugin)
+md.use(speakerdeckPlugin)
+md.use(pdfPlugin)
 
 export default {
   md
-};
+}
diff --git a/public/js/google-drive-picker.js b/public/js/google-drive-picker.js
index 94aa77f..5006cd2 100644
--- a/public/js/google-drive-picker.js
+++ b/public/js/google-drive-picker.js
@@ -1,119 +1,118 @@
-/**!
+/** !
  * Google Drive File Picker Example
  * By Daniel Lo Nigro (http://dan.cx/)
  */
-(function() {
-	/**
-	 * Initialise a Google Driver file picker
-	 */
-	var FilePicker = window.FilePicker = function(options) {
-		// Config
-		this.apiKey = options.apiKey;
-		this.clientId = options.clientId;
-		
-		// Elements
-		this.buttonEl = options.buttonEl;
-		
-		// Events
-		this.onSelect = options.onSelect;
-		this.buttonEl.on('click', this.open.bind(this));		
-	
-		// Disable the button until the API loads, as it won't work properly until then.
-		this.buttonEl.prop('disabled', true);
+(function () {
+  /**
+   * Initialise a Google Driver file picker
+   */
+  var FilePicker = window.FilePicker = function (options) {
+    // Config
+    this.apiKey = options.apiKey
+    this.clientId = options.clientId
 
-		// Load the drive API
-		gapi.client.setApiKey(this.apiKey);
-		gapi.client.load('drive', 'v2', this._driveApiLoaded.bind(this));
-		google.load('picker', '1', { callback: this._pickerApiLoaded.bind(this) });
-	}
+    // Elements
+    this.buttonEl = options.buttonEl
 
-	FilePicker.prototype = {
-		/**
-		 * Open the file picker.
-		 */
-		open: function() {		
-			// Check if the user has already authenticated
-			var token = gapi.auth.getToken();
-			if (token) {
-				this._showPicker();
-			} else {
-				// The user has not yet authenticated with Google
-				// We need to do the authentication before displaying the Drive picker.
-				this._doAuth(false, function() { this._showPicker(); }.bind(this));
-			}
-		},
-		
-		/**
-		 * Show the file picker once authentication has been done.
-		 * @private
-		 */
-		_showPicker: function() {
-			var accessToken = gapi.auth.getToken().access_token;
-            var view = new google.picker.DocsView();
-            view.setMimeTypes("text/markdown,text/html");
-            view.setIncludeFolders(true);
-            view.setOwnedByMe(true);
-			this.picker = new google.picker.PickerBuilder().
-                enableFeature(google.picker.Feature.NAV_HIDDEN).
-				addView(view).
-				setAppId(this.clientId).
-				setOAuthToken(accessToken).
-				setCallback(this._pickerCallback.bind(this)).
-				build().
-				setVisible(true);
-		},
-		
-		/**
-		 * Called when a file has been selected in the Google Drive file picker.
-		 * @private
-		 */
-		_pickerCallback: function(data) {
-			if (data[google.picker.Response.ACTION] == google.picker.Action.PICKED) {
-				var file = data[google.picker.Response.DOCUMENTS][0],
-					id = file[google.picker.Document.ID],
-					request = gapi.client.drive.files.get({
-						fileId: id
-					});
-					
-				request.execute(this._fileGetCallback.bind(this));
-			}
-		},
-		/**
-		 * Called when file details have been retrieved from Google Drive.
-		 * @private
-		 */
-		_fileGetCallback: function(file) {
-			if (this.onSelect) {
-				this.onSelect(file);
-			}
-		},
-		
-		/**
-		 * Called when the Google Drive file picker API has finished loading.
-		 * @private
-		 */
-		_pickerApiLoaded: function() {
-			this.buttonEl.prop('disabled', false);
-		},
-		
-		/**
-		 * Called when the Google Drive API has finished loading.
-		 * @private
-		 */
-		_driveApiLoaded: function() {
-			this._doAuth(true);
-		},
-		
-		/**
-		 * Authenticate with Google Drive via the Google JavaScript API.
-		 * @private
-		 */
-		_doAuth: function(immediate, callback) {	
-			gapi.auth.authorize({
-				client_id: this.clientId,
-				scope: 'https://www.googleapis.com/auth/drive.readonly',
-				immediate: immediate
-			}, callback ? callback : function() {});
-		}
-	};
-}());
+    // Events
+    this.onSelect = options.onSelect
+    this.buttonEl.on('click', this.open.bind(this))
+
+    // Disable the button until the API loads, as it won't work properly until then.
+    this.buttonEl.prop('disabled', true)
+
+    // Load the drive API
+    window.gapi.client.setApiKey(this.apiKey)
+    window.gapi.client.load('drive', 'v2', this._driveApiLoaded.bind(this))
+    window.google.load('picker', '1', { callback: this._pickerApiLoaded.bind(this) })
+  }
+
+  FilePicker.prototype = {
+    /**
+     * Open the file picker.
+     */
+    open: function () {
+      // Check if the user has already authenticated
+      var token = window.gapi.auth.getToken()
+      if (token) {
+        this._showPicker()
+      } else {
+        // The user has not yet authenticated with Google
+        // We need to do the authentication before displaying the Drive picker.
+        this._doAuth(false, function () { this._showPicker() }.bind(this))
+      }
+    },
+
+    /**
+     * Show the file picker once authentication has been done.
+     * @private
+     */
+    _showPicker: function () {
+      var accessToken = window.gapi.auth.getToken().access_token
+      var view = new window.google.picker.DocsView()
+      view.setMimeTypes('text/markdown,text/html')
+      view.setIncludeFolders(true)
+      view.setOwnedByMe(true)
+      this.picker = new window.google.picker.PickerBuilder()
+                      .enableFeature(window.google.picker.Feature.NAV_HIDDEN)
+                      .addView(view)
+                      .setAppId(this.clientId)
+                      .setOAuthToken(accessToken)
+                      .setCallback(this._pickerCallback.bind(this))
+                      .build()
+                      .setVisible(true)
+    },
+
+    /**
+     * Called when a file has been selected in the Google Drive file picker.
+     * @private
+     */
+    _pickerCallback: function (data) {
+      if (data[window.google.picker.Response.ACTION] === window.google.picker.Action.PICKED) {
+        var file = data[window.google.picker.Response.DOCUMENTS][0]
+        var id = file[window.google.picker.Document.ID]
+        var request = window.gapi.client.drive.files.get({
+          fileId: id
+        })
+        request.execute(this._fileGetCallback.bind(this))
+      }
+    },
+    /**
+     * Called when file details have been retrieved from Google Drive.
+     * @private
+     */
+    _fileGetCallback: function (file) {
+      if (this.onSelect) {
+        this.onSelect(file)
+      }
+    },
+
+    /**
+     * Called when the Google Drive file picker API has finished loading.
+     * @private
+     */
+    _pickerApiLoaded: function () {
+      this.buttonEl.prop('disabled', false)
+    },
+
+    /**
+     * Called when the Google Drive API has finished loading.
+     * @private
+     */
+    _driveApiLoaded: function () {
+      this._doAuth(true)
+    },
+
+    /**
+     * Authenticate with Google Drive via the Google JavaScript API.
+     * @private
+     */
+    _doAuth: function (immediate, callback) {
+      window.gapi.auth.authorize({
+        client_id: this.clientId,
+        scope: 'https://www.googleapis.com/auth/drive.readonly',
+        immediate: immediate
+      }, callback || function () {})
+    }
+  }
+}())
diff --git a/public/js/google-drive-upload.js b/public/js/google-drive-upload.js
index eabc5b7..6c0e8a6 100644
--- a/public/js/google-drive-upload.js
+++ b/public/js/google-drive-upload.js
@@ -1,30 +1,31 @@
+/* eslint-env browser, jquery */
 /**
  * Helper for implementing retries with backoff. Initial retry
  * delay is 1 second, increasing by 2x (+jitter) for subsequent retries
  *
  * @constructor
  */
-var RetryHandler = function() {
-  this.interval = 1000; // Start at one second
-  this.maxInterval = 60 * 1000; // Don't wait longer than a minute 
-};
+var RetryHandler = function () {
+  this.interval = 1000 // Start at one second
+  this.maxInterval = 60 * 1000 // Don't wait longer than a minute
+}
 
 /**
  * Invoke the function after waiting
  *
  * @param {function} fn Function to invoke
  */
-RetryHandler.prototype.retry = function(fn) {
-  setTimeout(fn, this.interval);
-  this.interval = this.nextInterval_();
-};
+RetryHandler.prototype.retry = function (fn) {
+  setTimeout(fn, this.interval)
+  this.interval = this.nextInterval_()
+}
 
 /**
  * Reset the counter (e.g. after successful request.)
  */
-RetryHandler.prototype.reset = function() {
-  this.interval = 1000;
-};
+RetryHandler.prototype.reset = function () {
+  this.interval = 1000
+}
 
 /**
  * Calculate the next wait time.
@@ -32,10 +33,10 @@ RetryHandler.prototype.reset = function() {
  *
  * @private
  */
-RetryHandler.prototype.nextInterval_ = function() {
-  var interval = this.interval * 2 + this.getRandomInt_(0, 1000);
-  return Math.min(interval, this.maxInterval);
-};
+RetryHandler.prototype.nextInterval_ = function () {
+  var interval = this.interval * 2 + this.getRandomInt_(0, 1000)
+  return Math.min(interval, this.maxInterval)
+}
 
 /**
  * Get a random int in the range of min to max. Used to add jitter to wait times.
@@ -44,10 +45,9 @@ RetryHandler.prototype.nextInterval_ = function() {
  * @param {number} max Upper bounds
  * @private
  */
-RetryHandler.prototype.getRandomInt_ = function(min, max) {
-  return Math.floor(Math.random() * (max - min + 1) + min);
-};
-
+RetryHandler.prototype.getRandomInt_ = function (min, max) {
+  return Math.floor(Math.random() * (max - min + 1) + min)
+}
 
 /**
  * Helper class for resumable uploads using XHR/CORS. Can upload any Blob-like item, whether
@@ -75,116 +75,115 @@ RetryHandler.prototype.getRandomInt_ = function(min, max) {
  * @param {function} [options.onProgress] Callback for status for the in-progress upload
  * @param {function} [options.onError] Callback if upload fails
  */
-var MediaUploader = function(options) {
-  var noop = function() {};
-  this.file = options.file;
-  this.contentType = options.contentType || this.file.type || 'application/octet-stream';
+var MediaUploader = function (options) {
+  var noop = function () {}
+  this.file = options.file
+  this.contentType = options.contentType || this.file.type || 'application/octet-stream'
   this.metadata = options.metadata || {
     'title': this.file.name,
     'mimeType': this.contentType
-  };
-  this.token = options.token;
-  this.onComplete = options.onComplete || noop;
-  this.onProgress = options.onProgress || noop;
-  this.onError = options.onError || noop;
-  this.offset = options.offset || 0;
-  this.chunkSize = options.chunkSize || 0;
-  this.retryHandler = new RetryHandler();
-
-  this.url = options.url;
-  if (!this.url) {
-    var params = options.params || {};
-    params.uploadType = 'resumable';
-    this.url = this.buildUrl_(options.fileId, params, options.baseUrl);
   }
-  this.httpMethod = options.fileId ? 'PUT' : 'POST';
-};
+  this.token = options.token
+  this.onComplete = options.onComplete || noop
+  this.onProgress = options.onProgress || noop
+  this.onError = options.onError || noop
+  this.offset = options.offset || 0
+  this.chunkSize = options.chunkSize || 0
+  this.retryHandler = new RetryHandler()
+
+  this.url = options.url
+  if (!this.url) {
+    var params = options.params || {}
+    params.uploadType = 'resumable'
+    this.url = this.buildUrl_(options.fileId, params, options.baseUrl)
+  }
+  this.httpMethod = options.fileId ? 'PUT' : 'POST'
+}
 
 /**
  * Initiate the upload.
  */
-MediaUploader.prototype.upload = function() {
-  var self = this;
-  var xhr = new XMLHttpRequest();
+MediaUploader.prototype.upload = function () {
+  var xhr = new XMLHttpRequest()
 
-  xhr.open(this.httpMethod, this.url, true);
-  xhr.setRequestHeader('Authorization', 'Bearer ' + this.token);
-  xhr.setRequestHeader('Content-Type', 'application/json');
-  xhr.setRequestHeader('X-Upload-Content-Length', this.file.size);
-  xhr.setRequestHeader('X-Upload-Content-Type', this.contentType);
+  xhr.open(this.httpMethod, this.url, true)
+  xhr.setRequestHeader('Authorization', 'Bearer ' + this.token)
+  xhr.setRequestHeader('Content-Type', 'application/json')
+  xhr.setRequestHeader('X-Upload-Content-Length', this.file.size)
+  xhr.setRequestHeader('X-Upload-Content-Type', this.contentType)
 
-  xhr.onload = function(e) {
+  xhr.onload = function (e) {
     if (e.target.status < 400) {
-      var location = e.target.getResponseHeader('Location');
-      this.url = location;
-      this.sendFile_();
+      var location = e.target.getResponseHeader('Location')
+      this.url = location
+      this.sendFile_()
     } else {
-      this.onUploadError_(e);
+      this.onUploadError_(e)
     }
-  }.bind(this);
-  xhr.onerror = this.onUploadError_.bind(this);
-  xhr.send(JSON.stringify(this.metadata));
-};
+  }.bind(this)
+  xhr.onerror = this.onUploadError_.bind(this)
+  xhr.send(JSON.stringify(this.metadata))
+}
 
 /**
  * Send the actual file content.
  *
  * @private
  */
-MediaUploader.prototype.sendFile_ = function() {
-  var content = this.file;
-  var end = this.file.size;
+MediaUploader.prototype.sendFile_ = function () {
+  var content = this.file
+  var end = this.file.size
 
   if (this.offset || this.chunkSize) {
     // Only bother to slice the file if we're either resuming or uploading in chunks
     if (this.chunkSize) {
-      end = Math.min(this.offset + this.chunkSize, this.file.size);
+      end = Math.min(this.offset + this.chunkSize, this.file.size)
     }
-    content = content.slice(this.offset, end);
+    content = content.slice(this.offset, end)
   }
 
-  var xhr = new XMLHttpRequest();
-  xhr.open('PUT', this.url, true);
-  xhr.setRequestHeader('Content-Type', this.contentType);
-  xhr.setRequestHeader('Content-Range', "bytes " + this.offset + "-" + (end - 1) + "/" + this.file.size);
-  xhr.setRequestHeader('X-Upload-Content-Type', this.file.type);
+  var xhr = new XMLHttpRequest()
+  xhr.open('PUT', this.url, true)
+  xhr.setRequestHeader('Content-Type', this.contentType)
+  xhr.setRequestHeader('Content-Range', 'bytes ' + this.offset + '-' + (end - 1) + '/' + this.file.size)
+  xhr.setRequestHeader('X-Upload-Content-Type', this.file.type)
   if (xhr.upload) {
-    xhr.upload.addEventListener('progress', this.onProgress);
+    xhr.upload.addEventListener('progress', this.onProgress)
   }
-  xhr.onload = this.onContentUploadSuccess_.bind(this);
-  xhr.onerror = this.onContentUploadError_.bind(this);
-  xhr.send(content);
-};
+  xhr.onload = this.onContentUploadSuccess_.bind(this)
+  xhr.onerror = this.onContentUploadError_.bind(this)
+  xhr.send(content)
+}
 
 /**
  * Query for the state of the file for resumption.
  *
  * @private
  */
-MediaUploader.prototype.resume_ = function() {
-  var xhr = new XMLHttpRequest();
-  xhr.open('PUT', this.url, true);
-  xhr.setRequestHeader('Content-Range', "bytes */" + this.file.size);
-  xhr.setRequestHeader('X-Upload-Content-Type', this.file.type);
+MediaUploader.prototype.resume_ = function () {
+  var xhr = new XMLHttpRequest()
+  xhr.open('PUT', this.url, true)
+  xhr.setRequestHeader('Content-Range', 'bytes */' + this.file.size)
+  xhr.setRequestHeader('X-Upload-Content-Type', this.file.type)
   if (xhr.upload) {
-    xhr.upload.addEventListener('progress', this.onProgress);
+    xhr.upload.addEventListener('progress', this.onProgress)
   }
-  xhr.onload = this.onContentUploadSuccess_.bind(this);
-  xhr.onerror = this.onContentUploadError_.bind(this);
-  xhr.send();
-};
+  xhr.onload = this.onContentUploadSuccess_.bind(this)
+  xhr.onerror = this.onContentUploadError_.bind(this)
+  xhr.send()
+}
 
 /**
  * Extract the last saved range if available in the request.
  *
  * @param {XMLHttpRequest} xhr Request object
  */
-MediaUploader.prototype.extractRange_ = function(xhr) {
-  var range = xhr.getResponseHeader('Range');
+MediaUploader.prototype.extractRange_ = function (xhr) {
+  var range = xhr.getResponseHeader('Range')
   if (range) {
-    this.offset = parseInt(range.match(/\d+/g).pop(), 10) + 1;
+    this.offset = parseInt(range.match(/\d+/g).pop(), 10) + 1
   }
-};
+}
 
 /**
  * Handle successful responses for uploads. Depending on the context,
@@ -194,17 +193,17 @@ MediaUploader.prototype.extractRange_ = function(xhr) {
  * @private
  * @param {object} e XHR event
  */
-MediaUploader.prototype.onContentUploadSuccess_ = function(e) {
-  if (e.target.status == 200 || e.target.status == 201) {
-    this.onComplete(e.target.response);
-  } else if (e.target.status == 308) {
-    this.extractRange_(e.target);
-    this.retryHandler.reset();
-    this.sendFile_();
+MediaUploader.prototype.onContentUploadSuccess_ = function (e) {
+  if (e.target.status === 200 || e.target.status === 201) {
+    this.onComplete(e.target.response)
+  } else if (e.target.status === 308) {
+    this.extractRange_(e.target)
+    this.retryHandler.reset()
+    this.sendFile_()
   } else {
-    this.onContentUploadError_(e);
+    this.onContentUploadError_(e)
   }
-};
+}
 
 /**
  * Handles errors for uploads. Either retries or aborts depending
@@ -213,13 +212,13 @@ MediaUploader.prototype.onContentUploadSuccess_ = function(e) {
  * @private
  * @param {object} e XHR event
  */
-MediaUploader.prototype.onContentUploadError_ = function(e) {
+MediaUploader.prototype.onContentUploadError_ = function (e) {
   if (e.target.status && e.target.status < 500) {
-    this.onError(e.target.response);
+    this.onError(e.target.response)
   } else {
-    this.retryHandler.retry(this.resume_.bind(this));
+    this.retryHandler.retry(this.resume_.bind(this))
   }
-};
+}
 
 /**
  * Handles errors for the initial request.
@@ -227,9 +226,9 @@ MediaUploader.prototype.onContentUploadError_ = function(e) {
  * @private
  * @param {object} e XHR event
  */
-MediaUploader.prototype.onUploadError_ = function(e) {
-  this.onError(e.target.response); // TODO - Retries for initial upload
-};
+MediaUploader.prototype.onUploadError_ = function (e) {
+  this.onError(e.target.response) // TODO - Retries for initial upload
+}
 
 /**
  * Construct a query string from a hash/object
@@ -238,12 +237,12 @@ MediaUploader.prototype.onUploadError_ = function(e) {
  * @param {object} [params] Key/value pairs for query string
  * @return {string} query string
  */
-MediaUploader.prototype.buildQuery_ = function(params) {
-  params = params || {};
-  return Object.keys(params).map(function(key) {
-    return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
-  }).join('&');
-};
+MediaUploader.prototype.buildQuery_ = function (params) {
+  params = params || {}
+  return Object.keys(params).map(function (key) {
+    return encodeURIComponent(key) + '=' + encodeURIComponent(params[key])
+  }).join('&')
+}
 
 /**
  * Build the drive upload URL
@@ -253,16 +252,16 @@ MediaUploader.prototype.buildQuery_ = function(params) {
  * @param {object} [params] Query parameters
  * @return {string} URL
  */
-MediaUploader.prototype.buildUrl_ = function(id, params, baseUrl) {
-  var url = baseUrl || 'https://www.googleapis.com/upload/drive/v2/files/';
+MediaUploader.prototype.buildUrl_ = function (id, params, baseUrl) {
+  var url = baseUrl || 'https://www.googleapis.com/upload/drive/v2/files/'
   if (id) {
-    url += id;
+    url += id
   }
-  var query = this.buildQuery_(params);
+  var query = this.buildQuery_(params)
   if (query) {
-    url += '?' + query;
+    url += '?' + query
   }
-  return url;
-};
+  return url
+}
 
-window.MediaUploader = MediaUploader;
+window.MediaUploader = MediaUploader
diff --git a/public/js/history.js b/public/js/history.js
index 34b2cba..e14b80d 100644
--- a/public/js/history.js
+++ b/public/js/history.js
@@ -1,372 +1,328 @@
-import store from 'store';
-import S from 'string';
+/* eslint-env browser, jquery */
+/* global serverurl, Cookies, moment */
+
+import store from 'store'
+import S from 'string'
 
 import {
     checkIfAuth
-} from './lib/common/login';
+} from './lib/common/login'
 
 import {
     urlpath
-} from './lib/config';
+} from './lib/config'
 
-window.migrateHistoryFromTempCallback = null;
+window.migrateHistoryFromTempCallback = null
 
-migrateHistoryFromTemp();
+migrateHistoryFromTemp()
 
-function migrateHistoryFromTemp() {
-    if (url('#tempid')) {
-        $.get(`${serverurl}/temp`, {
-                tempid: url('#tempid')
-            })
-            .done(data => {
-                if (data && data.temp) {
-                    getStorageHistory(olddata => {
-                        if (!olddata || olddata.length == 0) {
-                            saveHistoryToStorage(JSON.parse(data.temp));
-                        }
-                    });
-                }
-            })
-            .always(() => {
-                let hash = location.hash.split('#')[1];
-                hash = hash.split('&');
-                for (let i = 0; i < hash.length; i++)
-                    if (hash[i].indexOf('tempid') == 0) {
-                        hash.splice(i, 1);
-                        i--;
-                    }
-                hash = hash.join('&');
-                location.hash = hash;
-                if (migrateHistoryFromTempCallback)
-                    migrateHistoryFromTempCallback();
-            });
-    }
+function migrateHistoryFromTemp () {
+  if (window.url('#tempid')) {
+    $.get(`${serverurl}/temp`, {
+      tempid: window.url('#tempid')
+    })
+    .done(data => {
+      if (data && data.temp) {
+        getStorageHistory(olddata => {
+          if (!olddata || olddata.length === 0) {
+            saveHistoryToStorage(JSON.parse(data.temp))
+          }
+        })
+      }
+    })
+    .always(() => {
+      let hash = location.hash.split('#')[1]
+      hash = hash.split('&')
+      for (let i = 0; i < hash.length; i++) {
+        if (hash[i].indexOf('tempid') === 0) {
+          hash.splice(i, 1)
+          i--
+        }
+      }
+      hash = hash.join('&')
+      location.hash = hash
+      if (window.migrateHistoryFromTempCallback) { window.migrateHistoryFromTempCallback() }
+    })
+  }
 }
 
-export function saveHistory(notehistory) {
-    checkIfAuth(
+export function saveHistory (notehistory) {
+  checkIfAuth(
         () => {
-            saveHistoryToServer(notehistory);
+          saveHistoryToServer(notehistory)
         },
         () => {
-            saveHistoryToStorage(notehistory);
+          saveHistoryToStorage(notehistory)
         }
-    );
+    )
 }
 
-function saveHistoryToStorage(notehistory) {
-    if (store.enabled)
-        store.set('notehistory', JSON.stringify(notehistory));
-    else
-        saveHistoryToCookie(notehistory);
+function saveHistoryToStorage (notehistory) {
+  if (store.enabled) { store.set('notehistory', JSON.stringify(notehistory)) } else { saveHistoryToCookie(notehistory) }
 }
 
-function saveHistoryToCookie(notehistory) {
-    Cookies.set('notehistory', notehistory, {
-        expires: 365
-    });
+function saveHistoryToCookie (notehistory) {
+  Cookies.set('notehistory', notehistory, {
+    expires: 365
+  })
 }
 
-function saveHistoryToServer(notehistory) {
+function saveHistoryToServer (notehistory) {
+  $.post(`${serverurl}/history`, {
+    history: JSON.stringify(notehistory)
+  })
+}
+
+export function saveStorageHistoryToServer (callback) {
+  const data = store.get('notehistory')
+  if (data) {
     $.post(`${serverurl}/history`, {
-        history: JSON.stringify(notehistory)
-    });
-}
-
-function saveCookieHistoryToStorage(callback) {
-    store.set('notehistory', Cookies.get('notehistory'));
-    callback();
-}
-
-export function saveStorageHistoryToServer(callback) {
-    const data = store.get('notehistory');
-    if (data) {
-        $.post(`${serverurl}/history`, {
-                history: data
-            })
+      history: data
+    })
             .done(data => {
-                callback(data);
-            });
-    }
+              callback(data)
+            })
+  }
 }
 
-function saveCookieHistoryToServer(callback) {
-    $.post(`${serverurl}/history`, {
-            history: Cookies.get('notehistory')
-        })
-        .done(data => {
-            callback(data);
-        });
-}
-
-export function clearDuplicatedHistory(notehistory) {
-    const newnotehistory = [];
-    for (let i = 0; i < notehistory.length; i++) {
-        let found = false;
-        for (let j = 0; j < newnotehistory.length; j++) {
-            const id = notehistory[i].id.replace(/\=+$/, '');
-            const newId = newnotehistory[j].id.replace(/\=+$/, '');
-            if (id == newId || notehistory[i].id == newnotehistory[j].id || !notehistory[i].id || !newnotehistory[j].id) {
-                const time = (typeof notehistory[i].time === 'number' ? moment(notehistory[i].time) : moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a'));
-                const newTime = (typeof newnotehistory[i].time === 'number' ? moment(newnotehistory[i].time) : moment(newnotehistory[i].time, 'MMMM Do YYYY, h:mm:ss a'));
-                if(time >= newTime) {
-                    newnotehistory[j] = notehistory[i];
-                }
-                found = true;
-                break;
-            }
+export function clearDuplicatedHistory (notehistory) {
+  const newnotehistory = []
+  for (let i = 0; i < notehistory.length; i++) {
+    let found = false
+    for (let j = 0; j < newnotehistory.length; j++) {
+      const id = notehistory[i].id.replace(/=+$/, '')
+      const newId = newnotehistory[j].id.replace(/=+$/, '')
+      if (id === newId || notehistory[i].id === newnotehistory[j].id || !notehistory[i].id || !newnotehistory[j].id) {
+        const time = (typeof notehistory[i].time === 'number' ? moment(notehistory[i].time) : moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a'))
+        const newTime = (typeof newnotehistory[i].time === 'number' ? moment(newnotehistory[i].time) : moment(newnotehistory[i].time, 'MMMM Do YYYY, h:mm:ss a'))
+        if (time >= newTime) {
+          newnotehistory[j] = notehistory[i]
         }
-        if (!found)
-            newnotehistory.push(notehistory[i]);
+        found = true
+        break
+      }
     }
-    return newnotehistory;
+    if (!found) { newnotehistory.push(notehistory[i]) }
+  }
+  return newnotehistory
 }
 
-function addHistory(id, text, time, tags, pinned, notehistory) {
+function addHistory (id, text, time, tags, pinned, notehistory) {
     // only add when note id exists
-    if (id) {
-      notehistory.push({
-          id,
-          text,
-          time,
-          tags,
-          pinned
-      });
-    }
-    return notehistory;
+  if (id) {
+    notehistory.push({
+      id,
+      text,
+      time,
+      tags,
+      pinned
+    })
+  }
+  return notehistory
 }
 
-export function removeHistory(id, notehistory) {
-    for (let i = 0; i < notehistory.length; i++) {
-        if (notehistory[i].id == id) {
-            notehistory.splice(i, 1);
-            i -= 1;
-        }
+export function removeHistory (id, notehistory) {
+  for (let i = 0; i < notehistory.length; i++) {
+    if (notehistory[i].id === id) {
+      notehistory.splice(i, 1)
+      i -= 1
     }
-    return notehistory;
+  }
+  return notehistory
 }
 
-//used for inner
-export function writeHistory(title, tags) {
-    checkIfAuth(
+// used for inner
+export function writeHistory (title, tags) {
+  checkIfAuth(
         () => {
             // no need to do this anymore, this will count from server-side
             // writeHistoryToServer(title, tags);
         },
         () => {
-            writeHistoryToStorage(title, tags);
+          writeHistoryToStorage(title, tags)
         }
-    );
+    )
 }
 
-function writeHistoryToServer(title, tags) {
-    $.get(`${serverurl}/history`)
-        .done(data => {
-            try {
-                if (data.history) {
-                    var notehistory = data.history;
-                } else {
-                    var notehistory = [];
-                }
-            } catch (err) {
-                var notehistory = [];
-            }
-            if (!notehistory)
-                notehistory = [];
-
-            const newnotehistory = generateHistory(title, tags, notehistory);
-            saveHistoryToServer(newnotehistory);
-        })
-        .fail((xhr, status, error) => {
-            console.error(xhr.responseText);
-        });
+function writeHistoryToCookie (title, tags) {
+  var notehistory
+  try {
+    notehistory = Cookies.getJSON('notehistory')
+  } catch (err) {
+    notehistory = []
+  }
+  if (!notehistory) { notehistory = [] }
+  const newnotehistory = generateHistory(title, tags, notehistory)
+  saveHistoryToCookie(newnotehistory)
 }
 
-function writeHistoryToCookie(title, tags) {
-    try {
-        var notehistory = Cookies.getJSON('notehistory');
-    } catch (err) {
-        var notehistory = [];
-    }
-    if (!notehistory)
-        notehistory = [];
-
-    const newnotehistory = generateHistory(title, tags, notehistory);
-    saveHistoryToCookie(newnotehistory);
-}
-
-function writeHistoryToStorage(title, tags) {
-    if (store.enabled) {
-        let data = store.get('notehistory');
-        if (data) {
-            if (typeof data == "string")
-                data = JSON.parse(data);
-            var notehistory = data;
-        } else
-            var notehistory = [];
-        if (!notehistory)
-            notehistory = [];
-
-        const newnotehistory = generateHistory(title, tags, notehistory);
-        saveHistoryToStorage(newnotehistory);
+function writeHistoryToStorage (title, tags) {
+  if (store.enabled) {
+    let data = store.get('notehistory')
+    var notehistory
+    if (data) {
+      if (typeof data === 'string') { data = JSON.parse(data) }
+      notehistory = data
     } else {
-        writeHistoryToCookie(title, tags);
+      notehistory = []
     }
+    if (!notehistory) { notehistory = [] }
+
+    const newnotehistory = generateHistory(title, tags, notehistory)
+    saveHistoryToStorage(newnotehistory)
+  } else {
+    writeHistoryToCookie(title, tags)
+  }
 }
 
 if (!Array.isArray) {
-    Array.isArray = arg => Object.prototype.toString.call(arg) === '[object Array]';
+  Array.isArray = arg => Object.prototype.toString.call(arg) === '[object Array]'
 }
 
-function renderHistory(title, tags) {
-    //console.debug(tags);
-    const id = urlpath ? location.pathname.slice(urlpath.length + 1, location.pathname.length).split('/')[1] : location.pathname.split('/')[1];
-    return {
-        id,
-        text: title,
-        time: moment().valueOf(),
-        tags
-    };
+function renderHistory (title, tags) {
+    // console.debug(tags);
+  const id = urlpath ? location.pathname.slice(urlpath.length + 1, location.pathname.length).split('/')[1] : location.pathname.split('/')[1]
+  return {
+    id,
+    text: title,
+    time: moment().valueOf(),
+    tags
+  }
 }
 
-function generateHistory(title, tags, notehistory) {
-    const info = renderHistory(title, tags);
-    //keep any pinned data
-    let pinned = false;
+function generateHistory (title, tags, notehistory) {
+  const info = renderHistory(title, tags)
+    // keep any pinned data
+  let pinned = false
+  for (let i = 0; i < notehistory.length; i++) {
+    if (notehistory[i].id === info.id && notehistory[i].pinned) {
+      pinned = true
+      break
+    }
+  }
+  notehistory = removeHistory(info.id, notehistory)
+  notehistory = addHistory(info.id, info.text, info.time, info.tags, pinned, notehistory)
+  notehistory = clearDuplicatedHistory(notehistory)
+  return notehistory
+}
+
+// used for outer
+export function getHistory (callback) {
+  checkIfAuth(
+        () => {
+          getServerHistory(callback)
+        },
+        () => {
+          getStorageHistory(callback)
+        }
+    )
+}
+
+function getServerHistory (callback) {
+  $.get(`${serverurl}/history`)
+        .done(data => {
+          if (data.history) {
+            callback(data.history)
+          }
+        })
+        .fail((xhr, status, error) => {
+          console.error(xhr.responseText)
+        })
+}
+
+function getCookieHistory (callback) {
+  callback(Cookies.getJSON('notehistory'))
+}
+
+export function getStorageHistory (callback) {
+  if (store.enabled) {
+    let data = store.get('notehistory')
+    if (data) {
+      if (typeof data === 'string') { data = JSON.parse(data) }
+      callback(data)
+    } else { getCookieHistory(callback) }
+  } else {
+    getCookieHistory(callback)
+  }
+}
+
+export function parseHistory (list, callback) {
+  checkIfAuth(
+        () => {
+          parseServerToHistory(list, callback)
+        },
+        () => {
+          parseStorageToHistory(list, callback)
+        }
+    )
+}
+
+export function parseServerToHistory (list, callback) {
+  $.get(`${serverurl}/history`)
+        .done(data => {
+          if (data.history) {
+            parseToHistory(list, data.history, callback)
+          }
+        })
+        .fail((xhr, status, error) => {
+          console.error(xhr.responseText)
+        })
+}
+
+function parseCookieToHistory (list, callback) {
+  const notehistory = Cookies.getJSON('notehistory')
+  parseToHistory(list, notehistory, callback)
+}
+
+export function parseStorageToHistory (list, callback) {
+  if (store.enabled) {
+    let data = store.get('notehistory')
+    if (data) {
+      if (typeof data === 'string') { data = JSON.parse(data) }
+      parseToHistory(list, data, callback)
+    } else { parseCookieToHistory(list, callback) }
+  } else {
+    parseCookieToHistory(list, callback)
+  }
+}
+
+function parseToHistory (list, notehistory, callback) {
+  if (!callback) return
+  else if (!list || !notehistory) callback(list, notehistory)
+  else if (notehistory && notehistory.length > 0) {
     for (let i = 0; i < notehistory.length; i++) {
-        if (notehistory[i].id == info.id && notehistory[i].pinned) {
-            pinned = true;
-            break;
-        }
-    }
-    notehistory = removeHistory(info.id, notehistory);
-    notehistory = addHistory(info.id, info.text, info.time, info.tags, pinned, notehistory);
-    notehistory = clearDuplicatedHistory(notehistory);
-    return notehistory;
-}
-
-//used for outer
-export function getHistory(callback) {
-    checkIfAuth(
-        () => {
-            getServerHistory(callback);
-        },
-        () => {
-            getStorageHistory(callback);
-        }
-    );
-}
-
-function getServerHistory(callback) {
-    $.get(`${serverurl}/history`)
-        .done(data => {
-            if (data.history) {
-                callback(data.history);
-            }
-        })
-        .fail((xhr, status, error) => {
-            console.error(xhr.responseText);
-        });
-}
-
-function getCookieHistory(callback) {
-    callback(Cookies.getJSON('notehistory'));
-}
-
-export function getStorageHistory(callback) {
-    if (store.enabled) {
-        let data = store.get('notehistory');
-        if (data) {
-            if (typeof data == "string")
-                data = JSON.parse(data);
-            callback(data);
-        } else
-            getCookieHistory(callback);
-    } else {
-        getCookieHistory(callback);
-    }
-}
-
-export function parseHistory(list, callback) {
-    checkIfAuth(
-        () => {
-            parseServerToHistory(list, callback);
-        },
-        () => {
-            parseStorageToHistory(list, callback);
-        }
-    );
-}
-
-export function parseServerToHistory(list, callback) {
-    $.get(`${serverurl}/history`)
-        .done(data => {
-            if (data.history) {
-                parseToHistory(list, data.history, callback);
-            }
-        })
-        .fail((xhr, status, error) => {
-            console.error(xhr.responseText);
-        });
-}
-
-function parseCookieToHistory(list, callback) {
-    const notehistory = Cookies.getJSON('notehistory');
-    parseToHistory(list, notehistory, callback);
-}
-
-export function parseStorageToHistory(list, callback) {
-    if (store.enabled) {
-        let data = store.get('notehistory');
-        if (data) {
-            if (typeof data == "string")
-                data = JSON.parse(data);
-            parseToHistory(list, data, callback);
-        } else
-            parseCookieToHistory(list, callback);
-    } else {
-        parseCookieToHistory(list, callback);
-    }
-}
-
-function parseToHistory(list, notehistory, callback) {
-    if (!callback) return;
-    else if (!list || !notehistory) callback(list, notehistory);
-    else if (notehistory && notehistory.length > 0) {
-        for (let i = 0; i < notehistory.length; i++) {
-            //parse time to timestamp and fromNow
-            const timestamp = (typeof notehistory[i].time === 'number' ? moment(notehistory[i].time) : moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a'));
-            notehistory[i].timestamp = timestamp.valueOf();
-            notehistory[i].fromNow = timestamp.fromNow();
-            notehistory[i].time = timestamp.format('llll');
+            // parse time to timestamp and fromNow
+      const timestamp = (typeof notehistory[i].time === 'number' ? moment(notehistory[i].time) : moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a'))
+      notehistory[i].timestamp = timestamp.valueOf()
+      notehistory[i].fromNow = timestamp.fromNow()
+      notehistory[i].time = timestamp.format('llll')
             // prevent XSS
-            notehistory[i].text = S(notehistory[i].text).escapeHTML().s;
-            notehistory[i].tags = (notehistory[i].tags && notehistory[i].tags.length > 0) ? S(notehistory[i].tags).escapeHTML().s.split(',') : [];
+      notehistory[i].text = S(notehistory[i].text).escapeHTML().s
+      notehistory[i].tags = (notehistory[i].tags && notehistory[i].tags.length > 0) ? S(notehistory[i].tags).escapeHTML().s.split(',') : []
             // add to list
-            if (notehistory[i].id && list.get('id', notehistory[i].id).length == 0)
-                list.add(notehistory[i]);
-        }
+      if (notehistory[i].id && list.get('id', notehistory[i].id).length === 0) { list.add(notehistory[i]) }
     }
-    callback(list, notehistory);
+  }
+  callback(list, notehistory)
 }
 
-export function postHistoryToServer(noteId, data, callback) {
-    $.post(`${serverurl}/history/${noteId}`, data)
+export function postHistoryToServer (noteId, data, callback) {
+  $.post(`${serverurl}/history/${noteId}`, data)
     .done(result => callback(null, result))
     .fail((xhr, status, error) => {
-        console.error(xhr.responseText);
-        return callback(error, null);
-    });
-}
-
-export function deleteServerHistory(noteId, callback) {
-    $.ajax({
-        url: `${serverurl}/history${noteId ? '/' + noteId : ""}`,
-        type: 'DELETE'
+      console.error(xhr.responseText)
+      return callback(error, null)
     })
+}
+
+export function deleteServerHistory (noteId, callback) {
+  $.ajax({
+    url: `${serverurl}/history${noteId ? '/' + noteId : ''}`,
+    type: 'DELETE'
+  })
     .done(result => callback(null, result))
     .fail((xhr, status, error) => {
-        console.error(xhr.responseText);
-        return callback(error, null);
-    });
+      console.error(xhr.responseText)
+      return callback(error, null)
+    })
 }
diff --git a/public/js/htmlExport.js b/public/js/htmlExport.js
index 1c2c5eb..1a873ac 100644
--- a/public/js/htmlExport.js
+++ b/public/js/htmlExport.js
@@ -1,6 +1,6 @@
-require('../css/github-extract.css');
-require('../css/markdown.css');
-require('../css/extra.css');
-require('../css/slide-preview.css');
-require('../css/google-font.css');
-require('../css/site.css');
+require('../css/github-extract.css')
+require('../css/markdown.css')
+require('../css/extra.css')
+require('../css/slide-preview.css')
+require('../css/google-font.css')
+require('../css/site.css')
diff --git a/public/js/index.js b/public/js/index.js
index f0c476e..e672a68 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -1,26 +1,30 @@
-/* jquery and jquery plugins */
-require('../vendor/showup/showup');
+/* eslint-env browser, jquery */
+/* global CodeMirror, Cookies, moment, editor, ui, Spinner,
+   modeType, Idle, serverurl, key, gapi, Dropbox, FilePicker
+   ot, MediaUploader, hex2rgb, num_loaded, Visibility */
 
-require('../css/index.css');
-require('../css/extra.css');
-require('../css/slide-preview.css');
-require('../css/site.css');
+require('../vendor/showup/showup')
 
-require('highlight.js/styles/github-gist.css');
+require('../css/index.css')
+require('../css/extra.css')
+require('../css/slide-preview.css')
+require('../css/site.css')
 
-var toMarkdown = require('to-markdown');
+require('highlight.js/styles/github-gist.css')
 
-var saveAs = require('file-saver').saveAs;
-var randomColor = require('randomcolor');
+var toMarkdown = require('to-markdown')
 
-var _ = require("lodash");
+var saveAs = require('file-saver').saveAs
+var randomColor = require('randomcolor')
 
-var List = require('list.js');
+var _ = require('lodash')
+
+var List = require('list.js')
 
 import {
     checkLoginStateChanged,
     setloginStateChangeEvent
-} from './lib/common/login';
+} from './lib/common/login'
 
 import {
     debug,
@@ -31,7 +35,7 @@ import {
     noteurl,
     urlpath,
     version
-} from './lib/config';
+} from './lib/config'
 
 import {
     autoLinkify,
@@ -53,14 +57,14 @@ import {
     updateLastChange,
     updateLastChangeUser,
     updateOwner
-} from './extra';
+} from './extra'
 
 import {
     clearMap,
     setupSyncAreas,
     syncScrollToEdit,
     syncScrollToView
-} from './syncscroll';
+} from './syncscroll'
 
 import {
     writeHistory,
@@ -68,4007 +72,3856 @@ import {
     getHistory,
     saveHistory,
     removeHistory
-} from './history';
+} from './history'
 
-var renderer = require('./render');
-var preventXSS = renderer.preventXSS;
+var renderer = require('./render')
+var preventXSS = renderer.preventXSS
 
-var defaultTextHeight = 20;
-var viewportMargin = 20;
-var mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault;
-var defaultEditorMode = 'gfm';
+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;
+  '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 {
-        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');
+      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()
+        var 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);
+          var _ranges = cm.listSelections()
+          var anchorIndex = window.editor.indexFromPos(_ranges[i].anchor)
+          var headIndex = window.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');
-                    }
-                }
-            }
+          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;
-var cursorMenuThrottle = 50;
-var cursorActivityDebounce = 50;
-var cursorAnimatePeriod = 100;
-var supportContainers = ['success', 'info', 'warning', 'danger'];
-var supportCodeModes = ['javascript', 'typescript', 'jsx', 'htmlmixed', 'htmlembedded', 'css', 'xml', 'clike', 'clojure', 'ruby', 'python', 'shell', 'php', 'sql', 'haskell', 'coffeescript', 'yaml', 'pug', 'lua', 'cmake', 'nginx', 'perl', 'sass', 'r', 'dockerfile', 'tiddlywiki', 'mediawiki', 'go'];
-var supportCharts = ['sequence', 'flow', 'graphviz', 'mermaid'];
+var idleTime = 300000 // 5 mins
+var updateViewDebounce = 100
+var cursorMenuThrottle = 50
+var cursorActivityDebounce = 50
+var cursorAnimatePeriod = 100
+var supportContainers = ['success', 'info', 'warning', 'danger']
+var supportCodeModes = ['javascript', 'typescript', 'jsx', 'htmlmixed', 'htmlembedded', 'css', 'xml', 'clike', 'clojure', 'ruby', 'python', 'shell', 'php', 'sql', 'haskell', 'coffeescript', 'yaml', 'pug', 'lua', 'cmake', 'nginx', 'perl', 'sass', 'r', 'dockerfile', 'tiddlywiki', 'mediawiki', 'go']
+var supportCharts = ['sequence', 'flow', 'graphviz', 'mermaid']
 var supportHeaders = [
-    {
-        text: '# h1',
-        search: '#'
-    },
-    {
-        text: '## h2',
-        search: '##'
-    },
-    {
-        text: '### h3',
-        search: '###'
-    },
-    {
-        text: '#### h4',
-        search: '####'
-    },
-    {
-        text: '##### h5',
-        search: '#####'
-    },
-    {
-        text: '###### h6',
-        search: '######'
-    },
-    {
-        text: '###### tags: `example`',
-        search: '###### tags:'
-    }
-];
+  {
+    text: '# h1',
+    search: '#'
+  },
+  {
+    text: '## h2',
+    search: '##'
+  },
+  {
+    text: '### h3',
+    search: '###'
+  },
+  {
+    text: '#### h4',
+    search: '####'
+  },
+  {
+    text: '##### h5',
+    search: '#####'
+  },
+  {
+    text: '###### h6',
+    search: '######'
+  },
+  {
+    text: '###### tags: `example`',
+    search: '###### tags:'
+  }
+]
 var supportReferrals = [
-    {
-        text: '[reference link]',
-        search: '[]'
-    },
-    {
-        text: '[reference]: https:// "title"',
-        search: '[]:'
-    },
-    {
-        text: '[^footnote link]',
-        search: '[^]'
-    },
-    {
-        text: '[^footnote reference]: https:// "title"',
-        search: '[^]:'
-    },
-    {
-        text: '^[inline footnote]',
-        search: '^[]'
-    },
-    {
-        text: '[link text][reference]',
-        search: '[][]'
-    },
-    {
-        text: '[link text](https:// "title")',
-        search: '[]()'
-    },
-    {
-        text: '![image alt][reference]',
-        search: '![][]'
-    },
-    {
-        text: '![image alt](https:// "title")',
-        search: '![]()'
-    },
-    {
-        text: '![image alt](https:// "title" =WidthxHeight)',
-        search: '![]()'
-    },
-    {
-        text: '[TOC]',
-        search: '[]'
-    }
-];
+  {
+    text: '[reference link]',
+    search: '[]'
+  },
+  {
+    text: '[reference]: https:// "title"',
+    search: '[]:'
+  },
+  {
+    text: '[^footnote link]',
+    search: '[^]'
+  },
+  {
+    text: '[^footnote reference]: https:// "title"',
+    search: '[^]:'
+  },
+  {
+    text: '^[inline footnote]',
+    search: '^[]'
+  },
+  {
+    text: '[link text][reference]',
+    search: '[][]'
+  },
+  {
+    text: '[link text](https:// "title")',
+    search: '[]()'
+  },
+  {
+    text: '![image alt][reference]',
+    search: '![][]'
+  },
+  {
+    text: '![image alt](https:// "title")',
+    search: '![]()'
+  },
+  {
+    text: '![image alt](https:// "title" =WidthxHeight)',
+    search: '![]()'
+  },
+  {
+    text: '[TOC]',
+    search: '[]'
+  }
+]
 var supportExternals = [
-    {
-        text: '{%youtube youtubeid %}',
-        search: 'youtube'
-    },
-    {
-        text: '{%vimeo vimeoid %}',
-        search: 'vimeo'
-    },
-    {
-        text: '{%gist gistid %}',
-        search: 'gist'
-    },
-    {
-        text: '{%slideshare slideshareid %}',
-        search: 'slideshare'
-    },
-    {
-        text: '{%speakerdeck speakerdeckid %}',
-        search: 'speakerdeck'
-    },
-    {
-        text: '{%pdf pdfurl %}',
-        search: 'pdf'
-    }
-];
+  {
+    text: '{%youtube youtubeid %}',
+    search: 'youtube'
+  },
+  {
+    text: '{%vimeo vimeoid %}',
+    search: 'vimeo'
+  },
+  {
+    text: '{%gist gistid %}',
+    search: 'gist'
+  },
+  {
+    text: '{%slideshare slideshareid %}',
+    search: 'slideshare'
+  },
+  {
+    text: '{%speakerdeck speakerdeckid %}',
+    search: 'speakerdeck'
+  },
+  {
+    text: '{%pdf pdfurl %}',
+    search: 'pdf'
+  }
+]
 var supportExtraTags = [
-    {
-        text: '[name tag]',
-        search: '[]',
-        command: function () {
-            return '[name=' + personalInfo.name + ']';
-        },
-    },
-    {
-        text: '[time tag]',
-        search: '[]',
-        command: function () {
-            return '[time=' + moment().format('llll') + ']';
-        },
-    },
-    {
-        text: '[my color tag]',
-        search: '[]',
-        command: function () {
-            return '[color=' + personalInfo.color + ']';
-        }
-    },
-    {
-        text: '[random color tag]',
-        search: '[]',
-        command: function () {
-            var color = randomColor();
-            return '[color=' + color + ']';
-        }
+  {
+    text: '[name tag]',
+    search: '[]',
+    command: function () {
+      return '[name=' + window.personalInfo.name + ']'
     }
-];
+  },
+  {
+    text: '[time tag]',
+    search: '[]',
+    command: function () {
+      return '[time=' + moment().format('llll') + ']'
+    }
+  },
+  {
+    text: '[my color tag]',
+    search: '[]',
+    command: function () {
+      return '[color=' + window.personalInfo.color + ']'
+    }
+  },
+  {
+    text: '[random color tag]',
+    search: '[]',
+    command: function () {
+      var color = randomColor()
+      return '[color=' + color + ']'
+    }
+  }
+]
 window.modeType = {
-    edit: {
-        name: "edit"
-    },
-    view: {
-        name: "view"
-    },
-    both: {
-        name: "both"
-    }
-};
+  edit: {
+    name: 'edit'
+  },
+  view: {
+    name: 'view'
+  },
+  both: {
+    name: 'both'
+  }
+}
 var statusType = {
-    connected: {
-        msg: "CONNECTED",
-        label: "label-warning",
-        fa: "fa-wifi"
-    },
-    online: {
-        msg: "ONLINE",
-        label: "label-primary",
-        fa: "fa-users"
-    },
-    offline: {
-        msg: "OFFLINE",
-        label: "label-danger",
-        fa: "fa-plug"
-    }
-};
-var defaultMode = modeType.view;
+  connected: {
+    msg: 'CONNECTED',
+    label: 'label-warning',
+    fa: 'fa-wifi'
+  },
+  online: {
+    msg: 'ONLINE',
+    label: 'label-primary',
+    fa: 'fa-users'
+  },
+  offline: {
+    msg: 'OFFLINE',
+    label: 'label-danger',
+    fa: 'fa-plug'
+  }
+}
+var defaultMode = modeType.view
 
-//global vars
-window.loaded = false;
-window.needRefresh = false;
-window.isDirty = false;
-window.editShown = false;
-window.visibleXS = false;
-window.visibleSM = false;
-window.visibleMD = false;
-window.visibleLG = false;
-window.isTouchDevice = 'ontouchstart' in document.documentElement;
-window.currentMode = defaultMode;
-window.currentStatus = statusType.offline;
+// global vars
+window.loaded = false
+window.needRefresh = false
+window.isDirty = false
+window.editShown = false
+window.visibleXS = false
+window.visibleSM = false
+window.visibleMD = false
+window.visibleLG = false
+window.isTouchDevice = 'ontouchstart' in document.documentElement
+window.currentMode = defaultMode
+window.currentStatus = statusType.offline
 window.lastInfo = {
-    needRestore: false,
-    cursor: null,
-    scroll: null,
-    edit: {
-        scroll: {
-            left: null,
-            top: null
-        },
-        cursor: {
-            line: null,
-            ch: null
-        },
-        selections: null
+  needRestore: false,
+  cursor: null,
+  scroll: null,
+  edit: {
+    scroll: {
+      left: null,
+      top: null
     },
-    view: {
-        scroll: {
-            left: null,
-            top: null
-        }
+    cursor: {
+      line: null,
+      ch: null
     },
-    history: null
-};
-window.personalInfo = {};
-window.onlineUsers = [];
+    selections: null
+  },
+  view: {
+    scroll: {
+      left: null,
+      top: null
+    }
+  },
+  history: null
+}
+window.personalInfo = {}
+window.onlineUsers = []
 window.fileTypes = {
-    "pl": "perl",
-    "cgi": "perl",
-    "js": "javascript",
-    "php": "php",
-    "sh": "bash",
-    "rb": "ruby",
-    "html": "html",
-    "py": "python"
-};
+  'pl': 'perl',
+  'cgi': 'perl',
+  'js': 'javascript',
+  'php': 'php',
+  'sh': 'bash',
+  'rb': 'ruby',
+  'html': 'html',
+  'py': 'python'
+}
 
-//editor settings
-var textit = document.getElementById("textit");
-if (!textit) throw new Error("There was no textit area!");
+// 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 :)"
-});
-var inlineAttach = inlineAttachment.editors.codemirror4.attach(editor);
-defaultTextHeight = parseInt($(".CodeMirror").css('line-height'));
+  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 :)"
+})
+var inlineAttach = window.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;
+var statusBarTemplate = null
+var statusBar = null
+var statusCursor = null
+var statusFile = null
+var statusIndicators = null
+var statusLength = null
+var statusTheme = null
+var statusSpellcheck = null
 
-function getStatusBarTemplate(callback) {
-    $.get(serverurl + '/views/statusbar.html', function (template) {
-        statusBarTemplate = template;
-        if (callback) callback();
-    });
+function getStatusBarTemplate (callback) {
+  $.get(serverurl + '/views/statusbar.html', function (template) {
+    statusBarTemplate = template
+    if (callback) callback()
+  })
 }
-getStatusBarTemplate();
+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"
-    });
+function addStatusBar () {
+  if (!statusBarTemplate) {
+    getStatusBarTemplate(addStatusBar)
+    return
+  }
+  statusBar = $(statusBarTemplate)
+  statusCursor = statusBar.find('.status-cursor')
+  statusFile = statusBar.find('.status-file')
+  statusIndicators = statusBar.find('.status-indicators')
+  statusBar.find('.status-indent')
+  statusBar.find('.status-keymap')
+  statusLength = statusBar.find('.status-length')
+  statusTheme = statusBar.find('.status-theme')
+  statusSpellcheck = statusBar.find('.status-spellcheck')
+  statusBar.find('.status-preferences')
+  editor.addPanel(statusBar[0], {
+    position: 'bottom'
+  })
 
-    setIndent();
-    setKeymap();
-    setTheme();
-    setSpellcheck();
-    setPreferences();
+  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);
-        }
+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);
+  }
+  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');
+  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();
+  function setType () {
+    if (editor.getOption('indentWithTabs')) {
+      Cookies.set('indent_type', 'tab', {
+        expires: 365
+      })
+      type.text('Tab Size:')
     } else {
-        Cookies.remove('preferences-override-browser-keymap');
-        resetEditorKeymapToBrowserKeymap();
+      Cookies.set('indent_type', 'space', {
+        expires: 365
+      })
+      type.text('Spaces:')
     }
-}
+  }
+  setType()
 
-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);
+  function setUnit () {
+    var unit = editor.getOption('indentUnit')
+    if (editor.getOption('indentWithTabs')) {
+      Cookies.set('tab_size', unit, {
+        expires: 365
+      })
     } else {
-        overrideBrowserKeymap.prop('checked', false);
+      Cookies.set('space_units', unit, {
+        expires: 365
+      })
     }
-    setOverrideBrowserKeymap();
+    widthLabel.text(unit)
+  }
+  setUnit()
 
-    overrideBrowserKeymap.change(function() {
-        setOverrideBrowserKeymap();
-    });
+  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')
+  })
 }
 
-var selection = null;
+function setKeymap () {
+  var cookieKeymap = Cookies.get('keymap')
+  if (cookieKeymap) { editor.setOption('keyMap', cookieKeymap) }
 
-function updateStatusBar() {
-    if (!statusBar) return;
-    var cursor = editor.getCursor();
-    var cursorText = 'Line ' + (cursor.line + 1) + ', Columns ' + (cursor.ch + 1);
-    if (selection) {
-        var anchor = selection.anchor;
-        var head = selection.head;
-        var start = head.line <= anchor.line ? head : anchor;
-        var end = head.line >= anchor.line ? head : anchor;
-        var selectionText = ' — Selected ';
-        var selectionCharCount = Math.abs(head.ch - anchor.ch);
+  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
+  /* eslint-disable camelcase */
+  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)
+  }
+  /* eslint-endable camelcase */
+}
+
+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
+  var cursor = editor.getCursor()
+  var cursorText = 'Line ' + (cursor.line + 1) + ', Columns ' + (cursor.ch + 1)
+  if (selection) {
+    var anchor = selection.anchor
+    var head = selection.head
+    var start = head.line <= anchor.line ? head : anchor
+    var end = head.line >= anchor.line ? head : anchor
+    var selectionText = ' — Selected '
+    var selectionCharCount = Math.abs(head.ch - anchor.ch)
         // borrow from brackets EditorStatusBar.js
-        if (start.line !== end.line) {
-            var lines = end.line - start.line + 1;
-            if (end.ch === 0) {
-                lines--;
-            }
-            selectionText += lines + ' lines';
-        } else if (selectionCharCount > 0)
-            selectionText += selectionCharCount + ' columns';
-        if (start.line !== end.line || selectionCharCount > 0)
-            cursorText += selectionText;
-    }
-    statusCursor.text(cursorText);
-    var fileText = ' — ' + editor.lineCount() + ' Lines';
-    statusFile.text(fileText);
-    var docLength = editor.getValue().length;
-    statusLength.text('Length ' + docLength);
-    if (docLength > (docmaxlength * 0.95)) {
-        statusLength.css('color', 'red');
-        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.');
-    } else {
-        statusLength.css('color', 'white');
-        statusLength.attr('title', 'You could write up to ' + docmaxlength + ' characters in this note.');
-    }
+    if (start.line !== end.line) {
+      var lines = end.line - start.line + 1
+      if (end.ch === 0) {
+        lines--
+      }
+      selectionText += lines + ' lines'
+    } else if (selectionCharCount > 0) { selectionText += selectionCharCount + ' columns' }
+    if (start.line !== end.line || selectionCharCount > 0) { cursorText += selectionText }
+  }
+  statusCursor.text(cursorText)
+  var fileText = ' — ' + editor.lineCount() + ' Lines'
+  statusFile.text(fileText)
+  var docLength = editor.getValue().length
+  statusLength.text('Length ' + docLength)
+  if (docLength > (docmaxlength * 0.95)) {
+    statusLength.css('color', 'red')
+    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.')
+  } else {
+    statusLength.css('color', 'white')
+    statusLength.attr('title', 'You could write up to ' + docmaxlength + ' characters in this note.')
+  }
 }
 
-//ui vars
+// 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")
+  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')
+  }
+}
 
-//page actions
+// page actions
 var opts = {
-    lines: 11, // The number of lines to draw
-    length: 20, // The length of each line
-    width: 2, // The line thickness
-    radius: 30, // The radius of the inner circle
-    corners: 0, // Corner roundness (0..1)
-    rotate: 0, // The rotation offset
-    direction: 1, // 1: clockwise, -1: counterclockwise
-    color: '#000', // #rgb or #rrggbb or array of colors
-    speed: 1.1, // Rounds per second
-    trail: 60, // Afterglow percentage
-    shadow: false, // Whether to render a shadow
-    hwaccel: true, // Whether to use hardware acceleration
-    className: 'spinner', // The CSS class to assign to the spinner
-    zIndex: 2e9, // The z-index (defaults to 2000000000)
-    top: '50%', // Top position relative to parent
-    left: '50%' // Left position relative to parent
-};
-var spinner = new Spinner(opts).spin(ui.spinner[0]);
+  lines: 11, // The number of lines to draw
+  length: 20, // The length of each line
+  width: 2, // The line thickness
+  radius: 30, // The radius of the inner circle
+  corners: 0, // Corner roundness (0..1)
+  rotate: 0, // The rotation offset
+  direction: 1, // 1: clockwise, -1: counterclockwise
+  color: '#000', // #rgb or #rrggbb or array of colors
+  speed: 1.1, // Rounds per second
+  trail: 60, // Afterglow percentage
+  shadow: false, // Whether to render a shadow
+  hwaccel: true, // Whether to use hardware acceleration
+  className: 'spinner', // The CSS class to assign to the spinner
+  zIndex: 2e9, // The z-index (defaults to 2000000000)
+  top: '50%', // Top position relative to parent
+  left: '50%' // Left position relative to parent
+}
 
-//idle
+/* eslint-disable no-unused-vars */
+var spinner = new Spinner(opts).spin(ui.spinner[0])
+/* eslint-enable no-unused-vars */
+
+// idle
 var idle = new Idle({
-    onAway: function () {
-        idle.isAway = true;
-        emitUserStatus();
-        updateOnlineStatus();
-    },
-    onAwayBack: function () {
-        idle.isAway = false;
-        emitUserStatus();
-        updateOnlineStatus();
-        setHaveUnreadChanges(false);
-        updateTitleReminder();
-    },
-    awayTimeout: idleTime
-});
+  onAway: function () {
+    idle.isAway = true
+    emitUserStatus()
+    updateOnlineStatus()
+  },
+  onAwayBack: function () {
+    idle.isAway = false
+    emitUserStatus()
+    updateOnlineStatus()
+    setHaveUnreadChanges(false)
+    updateTitleReminder()
+  },
+  awayTimeout: idleTime
+})
 ui.area.codemirror.on('touchstart', function () {
-    idle.onActive();
-});
+  idle.onActive()
+})
 
-var haveUnreadChanges = false;
+var haveUnreadChanges = false
 
-function setHaveUnreadChanges(bool) {
-    if (!loaded) return;
-    if (bool && (idle.isAway || Visibility.hidden())) {
-        haveUnreadChanges = true;
-    } else if (!bool && !idle.isAway && !Visibility.hidden()) {
-        haveUnreadChanges = false;
-    }
+function setHaveUnreadChanges (bool) {
+  if (!window.loaded) return
+  if (bool && (idle.isAway || Visibility.hidden())) {
+    haveUnreadChanges = true
+  } else if (!bool && !idle.isAway && !Visibility.hidden()) {
+    haveUnreadChanges = false
+  }
 }
 
-function updateTitleReminder() {
-    if (!loaded) return;
-    if (haveUnreadChanges) {
-        document.title = '• ' + renderTitle(ui.area.markdown);
-    } else {
-        document.title = renderTitle(ui.area.markdown);
-    }
+function updateTitleReminder () {
+  if (!window.loaded) return
+  if (haveUnreadChanges) {
+    document.title = '• ' + renderTitle(ui.area.markdown)
+  } else {
+    document.title = renderTitle(ui.area.markdown)
+  }
 }
 
-function setRefreshModal(status) {
-    $('#refreshModal').modal('show');
-    $('#refreshModal').find('.modal-body > div').hide();
-    $('#refreshModal').find('.' + status).show();
+function setRefreshModal (status) {
+  $('#refreshModal').modal('show')
+  $('#refreshModal').find('.modal-body > div').hide()
+  $('#refreshModal').find('.' + status).show()
 }
 
-function setNeedRefresh() {
-    needRefresh = true;
-    editor.setOption('readOnly', true);
-    socket.disconnect();
-    showStatus(statusType.offline);
+function setNeedRefresh () {
+  window.needRefresh = true
+  editor.setOption('readOnly', true)
+  socket.disconnect()
+  showStatus(statusType.offline)
 }
 
 setloginStateChangeEvent(function () {
-    setRefreshModal('user-state-changed');
-    setNeedRefresh();
-});
+  setRefreshModal('user-state-changed')
+  setNeedRefresh()
+})
 
-//visibility
-var wasFocus = false;
+// visibility
+var wasFocus = false
 Visibility.change(function (e, state) {
-    var hidden = Visibility.hidden();
-    if (hidden) {
-        if (editorHasFocus()) {
-            wasFocus = true;
-            editor.getInputField().blur();
-        }
-    } else {
-        if (wasFocus) {
-            if (!visibleXS) {
-                editor.focus();
-                editor.refresh();
-            }
-            wasFocus = false;
-        }
-        setHaveUnreadChanges(false);
+  var hidden = Visibility.hidden()
+  if (hidden) {
+    if (editorHasFocus()) {
+      wasFocus = true
+      editor.getInputField().blur()
     }
-    updateTitleReminder();
-});
+  } else {
+    if (wasFocus) {
+      if (!window.visibleXS) {
+        editor.focus()
+        editor.refresh()
+      }
+      wasFocus = false
+    }
+    setHaveUnreadChanges(false)
+  }
+  updateTitleReminder()
+})
 
-//when page ready
+// when page ready
 $(document).ready(function () {
-    idle.checkAway();
-    checkResponsive();
-    //if in smaller screen, we don't need advanced scrollbar
-    var scrollbarStyle;
-    if (visibleXS) {
-        scrollbarStyle = 'native';
-    } else {
-        scrollbarStyle = 'overlay';
-    }
-    if (scrollbarStyle != editor.getOption('scrollbarStyle')) {
-        editor.setOption('scrollbarStyle', scrollbarStyle);
-        clearMap();
-    }
-    checkEditorStyle();
+  idle.checkAway()
+  checkResponsive()
+    // if in smaller screen, we don't need advanced scrollbar
+  var scrollbarStyle
+  if (window.visibleXS) {
+    scrollbarStyle = 'native'
+  } else {
+    scrollbarStyle = 'overlay'
+  }
+  if (scrollbarStyle !== editor.getOption('scrollbarStyle')) {
+    editor.setOption('scrollbarStyle', scrollbarStyle)
+    clearMap()
+  }
+  checkEditorStyle()
     /* we need this only on touch devices */
-    if (isTouchDevice) {
+  if (window.isTouchDevice) {
         /* cache dom references */
-        var $body = jQuery('body');
+    var $body = jQuery('body')
 
         /* bind events */
-        $(document)
+    $(document)
             .on('focus', 'textarea, input', function () {
-                $body.addClass('fixfixed');
+              $body.addClass('fixfixed')
             })
             .on('blur', 'textarea, input', function () {
-                $body.removeClass('fixfixed');
-            });
-    }
-    //showup
-    $().showUp('.navbar', {
-        upClass: 'navbar-hide',
-        downClass: 'navbar-show'
-    });
-    //tooltip
-    $('[data-toggle="tooltip"]').tooltip();
+              $body.removeClass('fixfixed')
+            })
+  }
+    // showup
+  $().showUp('.navbar', {
+    upClass: 'navbar-hide',
+    downClass: 'navbar-show'
+  })
+    // tooltip
+  $('[data-toggle="tooltip"]').tooltip()
     // shortcuts
     // allow on all tags
-    key.filter = function (e) { return true; };
-    key('ctrl+alt+e', function (e) {
-        changeMode(modeType.edit);
-    });
-    key('ctrl+alt+v', function (e) {
-        changeMode(modeType.view);
-    });
-    key('ctrl+alt+b', function (e) {
-        changeMode(modeType.both);
-    });
+  key.filter = function (e) { return true }
+  key('ctrl+alt+e', function (e) {
+    changeMode(modeType.edit)
+  })
+  key('ctrl+alt+v', function (e) {
+    changeMode(modeType.view)
+  })
+  key('ctrl+alt+b', function (e) {
+    changeMode(modeType.both)
+  })
     // toggle-dropdown
-    $(document).on('click', '.toggle-dropdown .dropdown-menu', function (e) {
-        e.stopPropagation();
-    });
-});
-//when page resize
+  $(document).on('click', '.toggle-dropdown .dropdown-menu', function (e) {
+    e.stopPropagation()
+  })
+})
+// when page resize
 $(window).resize(function () {
-    checkLayout();
-    checkEditorStyle();
-    checkTocStyle();
-    checkCursorMenu();
-    windowResize();
-});
-//when page unload
+  checkLayout()
+  checkEditorStyle()
+  checkTocStyle()
+  checkCursorMenu()
+  windowResize()
+})
+// when page unload
 $(window).on('unload', function () {
-    //updateHistoryInner();
-});
+    // updateHistoryInner();
+})
 $(window).on('error', function () {
-    //setNeedRefresh();
-});
+    // setNeedRefresh();
+})
 
-setupSyncAreas(ui.area.codemirrorScroll, ui.area.view, ui.area.markdown);
+setupSyncAreas(ui.area.codemirrorScroll, ui.area.view, ui.area.markdown)
 
-function autoSyncscroll() {
-    if (editorHasFocus()) {
-        syncScrollToView();
-    } else {
-        syncScrollToEdit();
-    }
+function autoSyncscroll () {
+  if (editorHasFocus()) {
+    syncScrollToView()
+  } else {
+    syncScrollToEdit()
+  }
 }
 
-var windowResizeDebounce = 200;
-var windowResize = _.debounce(windowResizeInner, windowResizeDebounce);
+var windowResizeDebounce = 200
+var windowResize = _.debounce(windowResizeInner, windowResizeDebounce)
 
-function windowResizeInner(callback) {
-    checkLayout();
-    checkResponsive();
-    checkEditorStyle();
-    checkTocStyle();
-    checkCursorMenu();
-    //refresh editor
-    if (loaded) {
-        if (editor.getOption('scrollbarStyle') === 'native') {
-            setTimeout(function () {
-                clearMap();
-                autoSyncscroll();
-                updateScrollspy();
-                if (callback && typeof callback === 'function')
-                    callback();
-            }, 1);
-        } else {
+function windowResizeInner (callback) {
+  checkLayout()
+  checkResponsive()
+  checkEditorStyle()
+  checkTocStyle()
+  checkCursorMenu()
+    // refresh editor
+  if (window.loaded) {
+    if (editor.getOption('scrollbarStyle') === 'native') {
+      setTimeout(function () {
+        clearMap()
+        autoSyncscroll()
+        updateScrollspy()
+        if (callback && typeof callback === 'function') { callback() }
+      }, 1)
+    } else {
             // force it load all docs at once to prevent scroll knob blink
-            editor.setOption('viewportMargin', Infinity);
-            setTimeout(function () {
-                clearMap();
-                autoSyncscroll();
-                editor.setOption('viewportMargin', viewportMargin);
-                //add or update user cursors
-                for (var i = 0; i < onlineUsers.length; i++) {
-                    if (onlineUsers[i].id != personalInfo.id)
-                        buildCursor(onlineUsers[i]);
-                }
-                updateScrollspy();
-                if (callback && typeof callback === 'function')
-                    callback();
-            }, 1);
+      editor.setOption('viewportMargin', Infinity)
+      setTimeout(function () {
+        clearMap()
+        autoSyncscroll()
+        editor.setOption('viewportMargin', viewportMargin)
+                // add or update user cursors
+        for (var i = 0; i < window.onlineUsers.length; i++) {
+          if (window.onlineUsers[i].id !== window.personalInfo.id) { buildCursor(window.onlineUsers[i]) }
         }
+        updateScrollspy()
+        if (callback && typeof callback === 'function') { callback() }
+      }, 1)
     }
+  }
 }
 
-function checkLayout() {
-    var navbarHieght = $('.navbar').outerHeight();
-    $('body').css('padding-top', navbarHieght + 'px');
+function checkLayout () {
+  var navbarHieght = $('.navbar').outerHeight()
+  $('body').css('padding-top', navbarHieght + 'px')
 }
 
-function editorHasFocus() {
-    return $(editor.getInputField()).is(":focus");
+function editorHasFocus () {
+  return $(editor.getInputField()).is(':focus')
 }
 
-//768-792px have a gap
-function checkResponsive() {
-    visibleXS = $(".visible-xs").is(":visible");
-    visibleSM = $(".visible-sm").is(":visible");
-    visibleMD = $(".visible-md").is(":visible");
-    visibleLG = $(".visible-lg").is(":visible");
+// 768-792px have a gap
+function checkResponsive () {
+  window.visibleXS = $('.visible-xs').is(':visible')
+  window.visibleSM = $('.visible-sm').is(':visible')
+  window.visibleMD = $('.visible-md').is(':visible')
+  window.visibleLG = $('.visible-lg').is(':visible')
 
-    if (visibleXS && currentMode == modeType.both)
-        if (editorHasFocus())
-            changeMode(modeType.edit);
-        else
-            changeMode(modeType.view);
+  if (window.visibleXS && window.currentMode === modeType.both) {
+    if (editorHasFocus()) { changeMode(modeType.edit) } else { changeMode(modeType.view) }
+  }
 
-    emitUserStatus();
+  emitUserStatus()
 }
 
-var lastEditorWidth = 0;
-var previousFocusOnEditor = null;
+var lastEditorWidth = 0
+var previousFocusOnEditor = null
 
-function checkEditorStyle() {
-    var desireHeight = statusBar ? (ui.area.edit.height() - statusBar.outerHeight()) : ui.area.edit.height();
+function checkEditorStyle () {
+  var desireHeight = statusBar ? (ui.area.edit.height() - 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) {
-        ui.area.codemirrorScroll.css('height', desireHeight + 'px');
-        ui.area.codemirrorScroll.css('min-height', '');
-        checkEditorScrollbar();
-    } else if (scrollbarStyle == 'native') {
-        ui.area.codemirrorScroll.css('height', '');
-        ui.area.codemirrorScroll.css('min-height', desireHeight + 'px');
-    }
+  var scrollbarStyle = editor.getOption('scrollbarStyle')
+  if (scrollbarStyle === 'overlay' || window.currentMode === modeType.both) {
+    ui.area.codemirrorScroll.css('height', desireHeight + 'px')
+    ui.area.codemirrorScroll.css('min-height', '')
+    checkEditorScrollbar()
+  } else if (scrollbarStyle === 'native') {
+    ui.area.codemirrorScroll.css('height', '')
+    ui.area.codemirrorScroll.css('min-height', desireHeight + 'px')
+  }
     // workaround editor will have wrong doc height when editor height changed
-    editor.setSize(null, ui.area.edit.height());
-    //make editor resizable
-    if (!ui.area.resize.handle.length) {
-        ui.area.edit.resizable({
-            handles: 'e',
-            maxWidth: $(window).width() * 0.7,
-            minWidth: $(window).width() * 0.2,
-            create: function (e, ui) {
-                $(this).parent().on('resize', function (e) {
-                    e.stopPropagation();
-                });
-            },
-            start: function (e) {
-                editor.setOption('viewportMargin', Infinity);
-            },
-            resize: function (e) {
-                ui.area.resize.syncToggle.stop(true, true).show();
-                checkTocStyle();
-            },
-            stop: function (e) {
-                lastEditorWidth = ui.area.edit.width();
+  editor.setSize(null, ui.area.edit.height())
+    // make editor resizable
+  if (!ui.area.resize.handle.length) {
+    ui.area.edit.resizable({
+      handles: 'e',
+      maxWidth: $(window).width() * 0.7,
+      minWidth: $(window).width() * 0.2,
+      create: function (e, ui) {
+        $(this).parent().on('resize', function (e) {
+          e.stopPropagation()
+        })
+      },
+      start: function (e) {
+        editor.setOption('viewportMargin', Infinity)
+      },
+      resize: function (e) {
+        ui.area.resize.syncToggle.stop(true, true).show()
+        checkTocStyle()
+      },
+      stop: function (e) {
+        lastEditorWidth = ui.area.edit.width()
                 // workaround that scroll event bindings
-                preventSyncScrollToView = 2;
-                preventSyncScrollToEdit = true;
-                editor.setOption('viewportMargin', viewportMargin);
-                if (editorHasFocus()) {
-                    windowResizeInner(function () {
-                        ui.area.codemirrorScroll.scroll();
-                    });
-                } else {
-                    windowResizeInner(function () {
-                        ui.area.view.scroll();
-                    });
-                }
-                checkEditorScrollbar();
-            }
-        });
-        ui.area.resize.handle = $('.ui-resizable-handle');
-    }
-    if (!ui.area.resize.syncToggle.length) {
-        ui.area.resize.syncToggle = $('<button class="btn btn-lg btn-default ui-sync-toggle" title="Toggle sync scrolling"><i class="fa fa-link fa-fw"></i></button>');
-        ui.area.resize.syncToggle.hover(function () {
-            previousFocusOnEditor = editorHasFocus();
-        }, function () {
-            previousFocusOnEditor = null;
-        });
-        ui.area.resize.syncToggle.click(function () {
-            syncscroll = !syncscroll;
-            checkSyncToggle();
-        });
-        ui.area.resize.handle.append(ui.area.resize.syncToggle);
-        ui.area.resize.syncToggle.hide();
-        ui.area.resize.handle.hover(function () {
-            ui.area.resize.syncToggle.stop(true, true).delay(200).fadeIn(100);
-        }, function () {
-            ui.area.resize.syncToggle.stop(true, true).delay(300).fadeOut(300);
-        });
-    }
+        window.preventSyncScrollToView = 2
+        window.preventSyncScrollToEdit = true
+        editor.setOption('viewportMargin', viewportMargin)
+        if (editorHasFocus()) {
+          windowResizeInner(function () {
+            ui.area.codemirrorScroll.scroll()
+          })
+        } else {
+          windowResizeInner(function () {
+            ui.area.view.scroll()
+          })
+        }
+        checkEditorScrollbar()
+      }
+    })
+    ui.area.resize.handle = $('.ui-resizable-handle')
+  }
+  if (!ui.area.resize.syncToggle.length) {
+    ui.area.resize.syncToggle = $('<button class="btn btn-lg btn-default ui-sync-toggle" title="Toggle sync scrolling"><i class="fa fa-link fa-fw"></i></button>')
+    ui.area.resize.syncToggle.hover(function () {
+      previousFocusOnEditor = editorHasFocus()
+    }, function () {
+      previousFocusOnEditor = null
+    })
+    ui.area.resize.syncToggle.click(function () {
+      window.syncscroll = !window.syncscroll
+      checkSyncToggle()
+    })
+    ui.area.resize.handle.append(ui.area.resize.syncToggle)
+    ui.area.resize.syncToggle.hide()
+    ui.area.resize.handle.hover(function () {
+      ui.area.resize.syncToggle.stop(true, true).delay(200).fadeIn(100)
+    }, function () {
+      ui.area.resize.syncToggle.stop(true, true).delay(300).fadeOut(300)
+    })
+  }
 }
 
-function checkSyncToggle() {
-    if (syncscroll) {
-        if (previousFocusOnEditor) {
-            preventSyncScrollToView = false;
-            syncScrollToView();
-        } else {
-            preventSyncScrollToEdit = false;
-            syncScrollToEdit();
-        }
-        ui.area.resize.syncToggle.find('i').removeClass('fa-unlink').addClass('fa-link');
+function checkSyncToggle () {
+  if (window.syncscroll) {
+    if (previousFocusOnEditor) {
+      window.preventSyncScrollToView = false
+      syncScrollToView()
     } else {
-        ui.area.resize.syncToggle.find('i').removeClass('fa-link').addClass('fa-unlink');
+      window.preventSyncScrollToEdit = false
+      syncScrollToEdit()
     }
+    ui.area.resize.syncToggle.find('i').removeClass('fa-unlink').addClass('fa-link')
+  } else {
+    ui.area.resize.syncToggle.find('i').removeClass('fa-link').addClass('fa-unlink')
+  }
 }
 
 var checkEditorScrollbar = _.debounce(function () {
-    editor.operation(checkEditorScrollbarInner);
-}, 50);
+  editor.operation(checkEditorScrollbarInner)
+}, 50)
 
-function checkEditorScrollbarInner() {
+function checkEditorScrollbarInner () {
     // workaround simple scroll bar knob
     // will get wrong position when editor height changed
-    var scrollInfo = editor.getScrollInfo();
-    editor.scrollTo(null, scrollInfo.top - 1);
-    editor.scrollTo(null, scrollInfo.top);
+  var scrollInfo = editor.getScrollInfo()
+  editor.scrollTo(null, scrollInfo.top - 1)
+  editor.scrollTo(null, scrollInfo.top)
 }
 
-function checkTocStyle() {
-    //toc right
-    var paddingRight = parseFloat(ui.area.markdown.css('padding-right'));
-    var right = ($(window).width() - (ui.area.markdown.offset().left + ui.area.markdown.outerWidth() - paddingRight));
-    ui.toc.toc.css('right', right + 'px');
-    //affix toc left
-    var newbool;
-    var rightMargin = (ui.area.markdown.parent().outerWidth() - ui.area.markdown.outerWidth()) / 2;
-    //for ipad or wider device
-    if (rightMargin >= 133) {
-        newbool = true;
-        var affixLeftMargin = (ui.toc.affix.outerWidth() - ui.toc.affix.width()) / 2;
-        var left = ui.area.markdown.offset().left + ui.area.markdown.outerWidth() - affixLeftMargin;
-        ui.toc.affix.css('left', left + 'px');
-        ui.toc.affix.css('width', rightMargin + 'px');
-    } else {
-        newbool = false;
-    }
-    //toc scrollspy
-    ui.toc.toc.removeClass('scrollspy-body, scrollspy-view');
-    ui.toc.affix.removeClass('scrollspy-body, scrollspy-view');
-    if (currentMode == modeType.both) {
-        ui.toc.toc.addClass('scrollspy-view');
-        ui.toc.affix.addClass('scrollspy-view');
-    } else if (currentMode != modeType.both && !newbool) {
-        ui.toc.toc.addClass('scrollspy-body');
-        ui.toc.affix.addClass('scrollspy-body');
-    } else {
-        ui.toc.toc.addClass('scrollspy-view');
-        ui.toc.affix.addClass('scrollspy-body');
-    }
-    if (newbool != enoughForAffixToc) {
-        enoughForAffixToc = newbool;
-        generateScrollspy();
-    }
+function checkTocStyle () {
+    // toc right
+  var paddingRight = parseFloat(ui.area.markdown.css('padding-right'))
+  var right = ($(window).width() - (ui.area.markdown.offset().left + ui.area.markdown.outerWidth() - paddingRight))
+  ui.toc.toc.css('right', right + 'px')
+    // affix toc left
+  var newbool
+  var rightMargin = (ui.area.markdown.parent().outerWidth() - ui.area.markdown.outerWidth()) / 2
+    // for ipad or wider device
+  if (rightMargin >= 133) {
+    newbool = true
+    var affixLeftMargin = (ui.toc.affix.outerWidth() - ui.toc.affix.width()) / 2
+    var left = ui.area.markdown.offset().left + ui.area.markdown.outerWidth() - affixLeftMargin
+    ui.toc.affix.css('left', left + 'px')
+    ui.toc.affix.css('width', rightMargin + 'px')
+  } else {
+    newbool = false
+  }
+    // toc scrollspy
+  ui.toc.toc.removeClass('scrollspy-body, scrollspy-view')
+  ui.toc.affix.removeClass('scrollspy-body, scrollspy-view')
+  if (window.currentMode === modeType.both) {
+    ui.toc.toc.addClass('scrollspy-view')
+    ui.toc.affix.addClass('scrollspy-view')
+  } else if (window.currentMode !== modeType.both && !newbool) {
+    ui.toc.toc.addClass('scrollspy-body')
+    ui.toc.affix.addClass('scrollspy-body')
+  } else {
+    ui.toc.toc.addClass('scrollspy-view')
+    ui.toc.affix.addClass('scrollspy-body')
+  }
+  if (newbool !== enoughForAffixToc) {
+    enoughForAffixToc = newbool
+    generateScrollspy()
+  }
 }
 
-function showStatus(type, num) {
-    currentStatus = type;
-    var shortStatus = ui.toolbar.shortStatus;
-    var status = ui.toolbar.status;
-    var label = $('<span class="label"></span>');
-    var fa = $('<i class="fa"></i>');
-    var msg = "";
-    var shortMsg = "";
+function showStatus (type, num) {
+  window.currentStatus = type
+  var shortStatus = ui.toolbar.shortStatus
+  var status = ui.toolbar.status
+  var label = $('<span class="label"></span>')
+  var fa = $('<i class="fa"></i>')
+  var msg = ''
+  var shortMsg = ''
 
-    shortStatus.html("");
-    status.html("");
+  shortStatus.html('')
+  status.html('')
 
-    switch (currentStatus) {
-        case statusType.connected:
-            label.addClass(statusType.connected.label);
-            fa.addClass(statusType.connected.fa);
-            msg = statusType.connected.msg;
-            break;
-        case statusType.online:
-            label.addClass(statusType.online.label);
-            fa.addClass(statusType.online.fa);
-            shortMsg = num;
-            msg = num + " " + statusType.online.msg;
-            break;
-        case statusType.offline:
-            label.addClass(statusType.offline.label);
-            fa.addClass(statusType.offline.fa);
-            msg = statusType.offline.msg;
-            break;
-    }
+  switch (window.currentStatus) {
+    case statusType.connected:
+      label.addClass(statusType.connected.label)
+      fa.addClass(statusType.connected.fa)
+      msg = statusType.connected.msg
+      break
+    case statusType.online:
+      label.addClass(statusType.online.label)
+      fa.addClass(statusType.online.fa)
+      shortMsg = num
+      msg = num + ' ' + statusType.online.msg
+      break
+    case statusType.offline:
+      label.addClass(statusType.offline.label)
+      fa.addClass(statusType.offline.fa)
+      msg = statusType.offline.msg
+      break
+  }
 
-    label.append(fa);
-    var shortLabel = label.clone();
+  label.append(fa)
+  var shortLabel = label.clone()
 
-    shortLabel.append(" " + shortMsg);
-    shortStatus.append(shortLabel);
+  shortLabel.append(' ' + shortMsg)
+  shortStatus.append(shortLabel)
 
-    label.append(" " + msg);
-    status.append(label);
+  label.append(' ' + msg)
+  status.append(label)
 }
 
-function toggleMode() {
-    switch (currentMode) {
-        case modeType.edit:
-            changeMode(modeType.view);
-            break;
-        case modeType.view:
-            changeMode(modeType.edit);
-            break;
-        case modeType.both:
-            changeMode(modeType.view);
-            break;
-    }
+function toggleMode () {
+  switch (window.currentMode) {
+    case modeType.edit:
+      changeMode(modeType.view)
+      break
+    case modeType.view:
+      changeMode(modeType.edit)
+      break
+    case modeType.both:
+      changeMode(modeType.view)
+      break
+  }
 }
 
-var lastMode = null;
+var lastMode = null
 
-function changeMode(type) {
+function changeMode (type) {
     // lock navbar to prevent it hide after changeMode
-    lockNavbar();
-    saveInfo();
-    if (type) {
-        lastMode = currentMode;
-        currentMode = type;
-    }
-    var responsiveClass = "col-lg-6 col-md-6 col-sm-6";
-    var scrollClass = "ui-scrollable";
-    ui.area.codemirror.removeClass(scrollClass);
-    ui.area.edit.removeClass(responsiveClass);
-    ui.area.view.removeClass(scrollClass);
-    ui.area.view.removeClass(responsiveClass);
-    switch (currentMode) {
-        case modeType.edit:
-            ui.area.edit.show();
-            ui.area.view.hide();
-            if (!editShown) {
-                editor.refresh();
-                editShown = true;
-            }
-            break;
-        case modeType.view:
-            ui.area.edit.hide();
-            ui.area.view.show();
-            break;
-        case modeType.both:
-            ui.area.codemirror.addClass(scrollClass);
-            ui.area.edit.addClass(responsiveClass).show();
-            ui.area.view.addClass(scrollClass);
-            ui.area.view.show();
-            break;
-    }
+  lockNavbar()
+  saveInfo()
+  if (type) {
+    lastMode = window.currentMode
+    window.currentMode = type
+  }
+  var responsiveClass = 'col-lg-6 col-md-6 col-sm-6'
+  var scrollClass = 'ui-scrollable'
+  ui.area.codemirror.removeClass(scrollClass)
+  ui.area.edit.removeClass(responsiveClass)
+  ui.area.view.removeClass(scrollClass)
+  ui.area.view.removeClass(responsiveClass)
+  switch (window.currentMode) {
+    case modeType.edit:
+      ui.area.edit.show()
+      ui.area.view.hide()
+      if (!window.editShown) {
+        editor.refresh()
+        window.editShown = true
+      }
+      break
+    case modeType.view:
+      ui.area.edit.hide()
+      ui.area.view.show()
+      break
+    case modeType.both:
+      ui.area.codemirror.addClass(scrollClass)
+      ui.area.edit.addClass(responsiveClass).show()
+      ui.area.view.addClass(scrollClass)
+      ui.area.view.show()
+      break
+  }
     // save mode to url
-    if (history.replaceState && loaded) history.replaceState(null, "", serverurl + '/' + noteid + '?' + currentMode.name);
-    if (currentMode == modeType.view) {
-        editor.getInputField().blur();
-    }
-    if (currentMode == modeType.edit || currentMode == modeType.both) {
-        ui.toolbar.uploadImage.fadeIn();
-        //add and update status bar
-        if (!statusBar) {
-            addStatusBar();
-            updateStatusBar();
-        }
-        //work around foldGutter might not init properly
-        editor.setOption('foldGutter', false);
-        editor.setOption('foldGutter', true);
-    } else {
-        ui.toolbar.uploadImage.fadeOut();
-    }
-    if (currentMode != modeType.edit) {
-        $(document.body).css('background-color', 'white');
-        updateView();
-    } else {
-        $(document.body).css('background-color', ui.area.codemirror.css('background-color'));
-    }
-    //check resizable editor style
-    if (currentMode == modeType.both) {
-        if (lastEditorWidth > 0)
-            ui.area.edit.css('width', lastEditorWidth + 'px');
-        else
-            ui.area.edit.css('width', '');
-        ui.area.resize.handle.show();
-    } else {
-        ui.area.edit.css('width', '');
-        ui.area.resize.handle.hide();
+  if (history.replaceState && window.loaded) history.replaceState(null, '', serverurl + '/' + noteid + '?' + window.currentMode.name)
+  if (window.currentMode === modeType.view) {
+    editor.getInputField().blur()
+  }
+  if (window.currentMode === modeType.edit || window.currentMode === modeType.both) {
+    ui.toolbar.uploadImage.fadeIn()
+        // add and update status bar
+    if (!statusBar) {
+      addStatusBar()
+      updateStatusBar()
     }
+        // work around foldGutter might not init properly
+    editor.setOption('foldGutter', false)
+    editor.setOption('foldGutter', true)
+  } else {
+    ui.toolbar.uploadImage.fadeOut()
+  }
+  if (window.currentMode !== modeType.edit) {
+    $(document.body).css('background-color', 'white')
+    updateView()
+  } else {
+    $(document.body).css('background-color', ui.area.codemirror.css('background-color'))
+  }
+    // check resizable editor style
+  if (window.currentMode === modeType.both) {
+    if (lastEditorWidth > 0) { ui.area.edit.css('width', lastEditorWidth + 'px') } else { ui.area.edit.css('width', '') }
+    ui.area.resize.handle.show()
+  } else {
+    ui.area.edit.css('width', '')
+    ui.area.resize.handle.hide()
+  }
 
-    windowResizeInner();
+  windowResizeInner()
 
-    restoreInfo();
+  restoreInfo()
 
-    if (lastMode == modeType.view && currentMode == modeType.both) {
-        preventSyncScrollToView = 2;
-        syncScrollToEdit(null, true);
-    }
+  if (lastMode === modeType.view && window.currentMode === modeType.both) {
+    window.preventSyncScrollToView = 2
+    syncScrollToEdit(null, true)
+  }
 
-    if (lastMode == modeType.edit && currentMode == modeType.both) {
-        preventSyncScrollToEdit = 2;
-        syncScrollToView(null, true);
-    }
+  if (lastMode === modeType.edit && window.currentMode === modeType.both) {
+    window.preventSyncScrollToEdit = 2
+    syncScrollToView(null, true)
+  }
 
-    if (lastMode == modeType.both && currentMode != modeType.both) {
-        preventSyncScrollToView = false;
-        preventSyncScrollToEdit = false;
-    }
+  if (lastMode === modeType.both && window.currentMode !== modeType.both) {
+    window.preventSyncScrollToView = false
+    window.preventSyncScrollToEdit = false
+  }
 
-    if (lastMode != modeType.edit && currentMode == modeType.edit) {
-        editor.refresh();
-    }
+  if (lastMode !== modeType.edit && window.currentMode === modeType.edit) {
+    editor.refresh()
+  }
 
-    $(document.body).scrollspy('refresh');
-    ui.area.view.scrollspy('refresh');
+  $(document.body).scrollspy('refresh')
+  ui.area.view.scrollspy('refresh')
 
-    ui.toolbar.both.removeClass("active");
-    ui.toolbar.edit.removeClass("active");
-    ui.toolbar.view.removeClass("active");
-    var modeIcon = ui.toolbar.mode.find('i');
-    modeIcon.removeClass('fa-pencil').removeClass('fa-eye');
-    if (ui.area.edit.is(":visible") && ui.area.view.is(":visible")) { //both
-        ui.toolbar.both.addClass("active");
-        modeIcon.addClass('fa-eye');
-    } else if (ui.area.edit.is(":visible")) { //edit
-        ui.toolbar.edit.addClass("active");
-        modeIcon.addClass('fa-eye');
-    } else if (ui.area.view.is(":visible")) { //view
-        ui.toolbar.view.addClass("active");
-        modeIcon.addClass('fa-pencil');
-    }
-    unlockNavbar();
+  ui.toolbar.both.removeClass('active')
+  ui.toolbar.edit.removeClass('active')
+  ui.toolbar.view.removeClass('active')
+  var modeIcon = ui.toolbar.mode.find('i')
+  modeIcon.removeClass('fa-pencil').removeClass('fa-eye')
+  if (ui.area.edit.is(':visible') && ui.area.view.is(':visible')) { // both
+    ui.toolbar.both.addClass('active')
+    modeIcon.addClass('fa-eye')
+  } else if (ui.area.edit.is(':visible')) { // edit
+    ui.toolbar.edit.addClass('active')
+    modeIcon.addClass('fa-eye')
+  } else if (ui.area.view.is(':visible')) { // view
+    ui.toolbar.view.addClass('active')
+    modeIcon.addClass('fa-pencil')
+  }
+  unlockNavbar()
 }
 
-function lockNavbar() {
-    $('.navbar').addClass('locked');
+function lockNavbar () {
+  $('.navbar').addClass('locked')
 }
 
 var unlockNavbar = _.debounce(function () {
-    $('.navbar').removeClass('locked');
-}, 200);
+  $('.navbar').removeClass('locked')
+}, 200)
 
-function closestIndex(arr, closestTo) {
-    var closest = Math.max.apply(null, arr); //Get the highest number in arr in case it match nothing.
-    var index = 0;
-    for (var i = 0; i < arr.length; i++) { //Loop the array
-        if (arr[i] >= closestTo && arr[i] < closest) {
-            closest = arr[i]; //Check if it's higher than your number, but lower than your closest value
-            index = i;
-        }
-    }
-    return index; // return the value
-}
-
-function showMessageModal(title, header, href, text, success) {
-    var modal = $('.message-modal');
-    modal.find('.modal-title').html(title);
-    modal.find('.modal-body h5').html(header);
-    if (href)
-        modal.find('.modal-body a').attr('href', href).text(text);
-    else
-        modal.find('.modal-body a').removeAttr('href').text(text);
-    modal.find('.modal-footer button').removeClass('btn-default btn-success btn-danger')
-    if (success)
-        modal.find('.modal-footer button').addClass('btn-success');
-    else
-        modal.find('.modal-footer button').addClass('btn-danger');
-    modal.modal('show');
+function showMessageModal (title, header, href, text, success) {
+  var modal = $('.message-modal')
+  modal.find('.modal-title').html(title)
+  modal.find('.modal-body h5').html(header)
+  if (href) { modal.find('.modal-body a').attr('href', href).text(text) } else { modal.find('.modal-body a').removeAttr('href').text(text) }
+  modal.find('.modal-footer button').removeClass('btn-default btn-success btn-danger')
+  if (success) { modal.find('.modal-footer button').addClass('btn-success') } else { modal.find('.modal-footer button').addClass('btn-danger') }
+  modal.modal('show')
 }
 
 // check if dropbox app key is set and load scripts
 if (DROPBOX_APP_KEY) {
-    $('<script>')
+  $('<script>')
         .attr('type', 'text/javascript')
         .attr('src', 'https://www.dropbox.com/static/api/2/dropins.js')
         .attr('id', 'dropboxjs')
         .attr('data-app-key', DROPBOX_APP_KEY)
         .prop('async', true)
         .prop('defer', true)
-        .appendTo('body');
+        .appendTo('body')
 } else {
-    ui.toolbar.import.dropbox.hide();
-    ui.toolbar.export.dropbox.hide();
+  ui.toolbar.import.dropbox.hide()
+  ui.toolbar.export.dropbox.hide()
 }
 
 // check if google api key and client id are set and load scripts
 if (GOOGLE_API_KEY && GOOGLE_CLIENT_ID) {
-    $('<script>')
+  $('<script>')
         .attr('type', 'text/javascript')
         .attr('src', 'https://www.google.com/jsapi?callback=onGoogleAPILoaded')
         .prop('async', true)
         .prop('defer', true)
-        .appendTo('body');
+        .appendTo('body')
 } else {
-    ui.toolbar.import.googleDrive.hide();
-    ui.toolbar.export.googleDrive.hide();
+  ui.toolbar.import.googleDrive.hide()
+  ui.toolbar.export.googleDrive.hide()
 }
 
-function onGoogleAPILoaded() {
-    $('<script>')
+function onGoogleAPILoaded () {
+  $('<script>')
         .attr('type', 'text/javascript')
         .attr('src', 'https://apis.google.com/js/client:plusone.js?onload=onGoogleClientLoaded')
         .prop('async', true)
         .prop('defer', true)
-        .appendTo('body');
+        .appendTo('body')
 }
-window.onGoogleAPILoaded = onGoogleAPILoaded;
+window.onGoogleAPILoaded = onGoogleAPILoaded
 
-//button actions
-//share
-ui.toolbar.publish.attr("href", noteurl + "/publish");
+// button actions
+// share
+ui.toolbar.publish.attr('href', noteurl + '/publish')
 // extra
-//slide
-ui.toolbar.extra.slide.attr("href", noteurl + "/slide");
-//download
-//markdown
+// slide
+ui.toolbar.extra.slide.attr('href', noteurl + '/slide')
+// download
+// markdown
 ui.toolbar.download.markdown.click(function (e) {
-    e.preventDefault();
-    e.stopPropagation();
-    var filename = renderFilename(ui.area.markdown) + '.md';
-    var markdown = editor.getValue();
-    var blob = new Blob([markdown], {
-        type: "text/markdown;charset=utf-8"
-    });
-    saveAs(blob, filename, true);
-});
-//html
+  e.preventDefault()
+  e.stopPropagation()
+  var filename = renderFilename(ui.area.markdown) + '.md'
+  var markdown = editor.getValue()
+  var blob = new Blob([markdown], {
+    type: 'text/markdown;charset=utf-8'
+  })
+  saveAs(blob, filename, true)
+})
+// html
 ui.toolbar.download.html.click(function (e) {
-    e.preventDefault();
-    e.stopPropagation();
-    exportToHTML(ui.area.markdown);
-});
+  e.preventDefault()
+  e.stopPropagation()
+  exportToHTML(ui.area.markdown)
+})
 // raw html
 ui.toolbar.download.rawhtml.click(function (e) {
-    e.preventDefault();
-    e.stopPropagation();
-    exportToRawHTML(ui.area.markdown);
-});
-//pdf
-ui.toolbar.download.pdf.attr("download", "").attr("href", noteurl + "/pdf");
-//export to dropbox
+  e.preventDefault()
+  e.stopPropagation()
+  exportToRawHTML(ui.area.markdown)
+})
+// pdf
+ui.toolbar.download.pdf.attr('download', '').attr('href', noteurl + '/pdf')
+// export to dropbox
 ui.toolbar.export.dropbox.click(function () {
-    var filename = renderFilename(ui.area.markdown) + '.md';
-    var options = {
-        files: [
-            {
-                'url': noteurl + "/download",
-                'filename': filename
-            }
-        ],
-        error: function (errorMessage) {
-            console.error(errorMessage);
-        }
-    };
-    Dropbox.save(options);
-});
-function uploadToGoogleDrive(accessToken) {
-    ui.spinner.show();
-    var filename = renderFilename(ui.area.markdown) + '.md';
-    var markdown = editor.getValue();
-    var blob = new Blob([markdown], {
-        type: "text/markdown;charset=utf-8"
-    });
-    blob.name = filename;
-    var uploader = new MediaUploader({
-        file: blob,
-        token: accessToken,
-        onComplete: function (data) {
-            data = JSON.parse(data);
-            showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Complete!', data.alternateLink, 'Click here to view your file', true);
-            ui.spinner.hide();
-        },
-        onError: function (data) {
-            var modal = $('.export-modal');
-            showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Error :(', '', data, false);
-            ui.spinner.hide();
-        }
-    });
-    uploader.upload();
+  var filename = renderFilename(ui.area.markdown) + '.md'
+  var options = {
+    files: [
+      {
+        'url': noteurl + '/download',
+        'filename': filename
+      }
+    ],
+    error: function (errorMessage) {
+      console.error(errorMessage)
+    }
+  }
+  Dropbox.save(options)
+})
+function uploadToGoogleDrive (accessToken) {
+  ui.spinner.show()
+  var filename = renderFilename(ui.area.markdown) + '.md'
+  var markdown = editor.getValue()
+  var blob = new Blob([markdown], {
+    type: 'text/markdown;charset=utf-8'
+  })
+  blob.name = filename
+  var uploader = new MediaUploader({
+    file: blob,
+    token: accessToken,
+    onComplete: function (data) {
+      data = JSON.parse(data)
+      showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Complete!', data.alternateLink, 'Click here to view your file', true)
+      ui.spinner.hide()
+    },
+    onError: function (data) {
+      showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Error :(', '', data, false)
+      ui.spinner.hide()
+    }
+  })
+  uploader.upload()
 }
-function googleApiAuth(immediate, callback) {
-    gapi.auth.authorize(
-        {
-            'client_id': GOOGLE_CLIENT_ID,
-            'scope': 'https://www.googleapis.com/auth/drive.file',
-            'immediate': immediate
-        }, callback ? callback : function () { });
+function googleApiAuth (immediate, callback) {
+  gapi.auth.authorize(
+    {
+      'client_id': GOOGLE_CLIENT_ID,
+      'scope': 'https://www.googleapis.com/auth/drive.file',
+      'immediate': immediate
+    }, callback || function () { })
 }
-function onGoogleClientLoaded() {
-    googleApiAuth(true);
-    buildImportFromGoogleDrive();
+function onGoogleClientLoaded () {
+  googleApiAuth(true)
+  buildImportFromGoogleDrive()
 }
-window.onGoogleClientLoaded = onGoogleClientLoaded;
+window.onGoogleClientLoaded = onGoogleClientLoaded
 // export to google drive
 ui.toolbar.export.googleDrive.click(function (e) {
-    var token = gapi.auth.getToken();
-    if (token) {
-        uploadToGoogleDrive(token.access_token);
-    } else {
-        googleApiAuth(false, function (result) {
-            uploadToGoogleDrive(result.access_token);
-        });
-    }
-});
-//export to gist
-ui.toolbar.export.gist.attr("href", noteurl + "/gist");
-//export to snippet
-ui.toolbar.export.snippet.click(function() {
-    ui.spinner.show();
-    $.get(serverurl + '/auth/gitlab/callback/' + noteid + '/projects')
+  var token = gapi.auth.getToken()
+  if (token) {
+    uploadToGoogleDrive(token.access_token)
+  } else {
+    googleApiAuth(false, function (result) {
+      uploadToGoogleDrive(result.access_token)
+    })
+  }
+})
+// export to gist
+ui.toolbar.export.gist.attr('href', noteurl + '/gist')
+// export to snippet
+ui.toolbar.export.snippet.click(function () {
+  ui.spinner.show()
+  $.get(serverurl + '/auth/gitlab/callback/' + noteid + '/projects')
         .done(function (data) {
-            $("#snippetExportModalAccessToken").val(data.accesstoken);
-            $("#snippetExportModalBaseURL").val(data.baseURL);
-            $("#snippetExportModalLoading").hide();
-            $("#snippetExportModal").modal('toggle');
-            $("#snippetExportModalProjects").find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Projects</option>');
-            if (data.projects) {
-                data.projects.sort(function(a,b) {
-                    return (a.path_with_namespace < b.path_with_namespace) ? -1 : ((a.path_with_namespace > b.path_with_namespace) ? 1 : 0);
-                });
-                data.projects.forEach(function(project) {
-                    if (!project.snippets_enabled
-                        || (project.permissions.project_access === null && project.permissions.group_access === null)
-                        || (project.permissions.project_access !== null && project.permissions.project_access.access_level < 20))
-                    {
-                        return;
-                    }
-                    $('<option>').val(project.id).text(project.path_with_namespace).appendTo("#snippetExportModalProjects");
-                });
-                $("#snippetExportModalProjects").prop('disabled', false);
-            }
-            $("#snippetExportModalLoading").hide();
+          $('#snippetExportModalAccessToken').val(data.accesstoken)
+          $('#snippetExportModalBaseURL').val(data.baseURL)
+          $('#snippetExportModalLoading').hide()
+          $('#snippetExportModal').modal('toggle')
+          $('#snippetExportModalProjects').find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Projects</option>')
+          if (data.projects) {
+            data.projects.sort(function (a, b) {
+              return (a.path_with_namespace < b.path_with_namespace) ? -1 : ((a.path_with_namespace > b.path_with_namespace) ? 1 : 0)
+            })
+            data.projects.forEach(function (project) {
+              if (!project.snippets_enabled ||
+                        (project.permissions.project_access === null && project.permissions.group_access === null) ||
+                        (project.permissions.project_access !== null && project.permissions.project_access.access_level < 20)) {
+                return
+              }
+              $('<option>').val(project.id).text(project.path_with_namespace).appendTo('#snippetExportModalProjects')
+            })
+            $('#snippetExportModalProjects').prop('disabled', false)
+          }
+          $('#snippetExportModalLoading').hide()
         })
         .fail(function (data) {
-            showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Unable to fetch gitlab parameters :(', '', '', false);
+          showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Unable to fetch gitlab parameters :(', '', '', false)
         })
         .always(function () {
-            ui.spinner.hide();
-        });
-});
-//import from dropbox
+          ui.spinner.hide()
+        })
+})
+// import from dropbox
 ui.toolbar.import.dropbox.click(function () {
-    var options = {
-        success: function (files) {
-            ui.spinner.show();
-            var url = files[0].link;
-            importFromUrl(url);
-        },
-        linkType: "direct",
-        multiselect: false,
-        extensions: ['.md', '.html']
-    };
-    Dropbox.choose(options);
-});
+  var options = {
+    success: function (files) {
+      ui.spinner.show()
+      var url = files[0].link
+      importFromUrl(url)
+    },
+    linkType: 'direct',
+    multiselect: false,
+    extensions: ['.md', '.html']
+  }
+  Dropbox.choose(options)
+})
 // import from google drive
-var picker = null;
-function buildImportFromGoogleDrive() {
-    picker = new FilePicker({
-        apiKey: GOOGLE_API_KEY,
-        clientId: GOOGLE_CLIENT_ID,
-        buttonEl: ui.toolbar.import.googleDrive,
-        onSelect: function (file) {
-            if (file.downloadUrl) {
-                ui.spinner.show();
-                var accessToken = gapi.auth.getToken().access_token;
-                $.ajax({
-                    type: 'GET',
-                    beforeSend: function (request) {
-                        request.setRequestHeader('Authorization', 'Bearer ' + accessToken);
-                    },
-                    url: file.downloadUrl,
-                    success: function (data) {
-                        if (file.fileExtension == 'html')
-                            parseToEditor(data);
-                        else
-                            replaceAll(data);
-                    },
-                    error: function (data) {
-                        showMessageModal('<i class="fa fa-cloud-download"></i> Import from Google Drive', 'Import failed :(', '', data, false);
-                    },
-                    complete: function () {
-                        ui.spinner.hide();
-                    }
-                });
-            }
-        }
-    });
+function buildImportFromGoogleDrive () {
+  FilePicker({
+    apiKey: GOOGLE_API_KEY,
+    clientId: GOOGLE_CLIENT_ID,
+    buttonEl: ui.toolbar.import.googleDrive,
+    onSelect: function (file) {
+      if (file.downloadUrl) {
+        ui.spinner.show()
+        var accessToken = gapi.auth.getToken().access_token
+        $.ajax({
+          type: 'GET',
+          beforeSend: function (request) {
+            request.setRequestHeader('Authorization', 'Bearer ' + accessToken)
+          },
+          url: file.downloadUrl,
+          success: function (data) {
+            if (file.fileExtension === 'html') { parseToEditor(data) } else { replaceAll(data) }
+          },
+          error: function (data) {
+            showMessageModal('<i class="fa fa-cloud-download"></i> Import from Google Drive', 'Import failed :(', '', data, false)
+          },
+          complete: function () {
+            ui.spinner.hide()
+          }
+        })
+      }
+    }
+  })
 }
-//import from gist
+// import from gist
 ui.toolbar.import.gist.click(function () {
-    //na
-});
-//import from snippet
+    // na
+})
+// import from snippet
 ui.toolbar.import.snippet.click(function () {
-    ui.spinner.show();
-    $.get(serverurl + '/auth/gitlab/callback/' + noteid + '/projects')
+  ui.spinner.show()
+  $.get(serverurl + '/auth/gitlab/callback/' + noteid + '/projects')
         .done(function (data) {
-            $("#snippetImportModalAccessToken").val(data.accesstoken);
-            $("#snippetImportModalBaseURL").val(data.baseURL);
-            $("#snippetImportModalContent").prop('disabled', false);
-            $("#snippetImportModalConfirm").prop('disabled', false);
-            $("#snippetImportModalLoading").hide();
-            $("#snippetImportModal").modal('toggle');
-            $("#snippetImportModalProjects").find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Projects</option>');
-            if (data.projects) {
-                data.projects.sort(function(a,b) {
-                    return (a.path_with_namespace < b.path_with_namespace) ? -1 : ((a.path_with_namespace > b.path_with_namespace) ? 1 : 0);
-                });
-                data.projects.forEach(function(project) {
-                    if (!project.snippets_enabled
-                        || (project.permissions.project_access === null && project.permissions.group_access === null)
-                        || (project.permissions.project_access !== null && project.permissions.project_access.access_level < 20))
-                    {
-                        return;
-                    }
-                    $('<option>').val(project.id).text(project.path_with_namespace).appendTo("#snippetImportModalProjects");
-                });
-                $("#snippetImportModalProjects").prop('disabled', false);
-            }
-            $("#snippetImportModalLoading").hide();
+          $('#snippetImportModalAccessToken').val(data.accesstoken)
+          $('#snippetImportModalBaseURL').val(data.baseURL)
+          $('#snippetImportModalContent').prop('disabled', false)
+          $('#snippetImportModalConfirm').prop('disabled', false)
+          $('#snippetImportModalLoading').hide()
+          $('#snippetImportModal').modal('toggle')
+          $('#snippetImportModalProjects').find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Projects</option>')
+          if (data.projects) {
+            data.projects.sort(function (a, b) {
+              return (a.path_with_namespace < b.path_with_namespace) ? -1 : ((a.path_with_namespace > b.path_with_namespace) ? 1 : 0)
+            })
+            data.projects.forEach(function (project) {
+              if (!project.snippets_enabled ||
+                        (project.permissions.project_access === null && project.permissions.group_access === null) ||
+                        (project.permissions.project_access !== null && project.permissions.project_access.access_level < 20)) {
+                return
+              }
+              $('<option>').val(project.id).text(project.path_with_namespace).appendTo('#snippetImportModalProjects')
+            })
+            $('#snippetImportModalProjects').prop('disabled', false)
+          }
+          $('#snippetImportModalLoading').hide()
         })
         .fail(function (data) {
-            showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Unable to fetch gitlab parameters :(', '', '', false);
+          showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Unable to fetch gitlab parameters :(', '', '', false)
         })
         .always(function () {
-            ui.spinner.hide();
-        });
-});
-//import from clipboard
+          ui.spinner.hide()
+        })
+})
+// import from clipboard
 ui.toolbar.import.clipboard.click(function () {
-    //na
-});
-//upload image
+    // na
+})
+// upload image
 ui.toolbar.uploadImage.bind('change', function (e) {
-    var files = e.target.files || e.dataTransfer.files;
-    e.dataTransfer = {};
-    e.dataTransfer.files = files;
-    inlineAttach.onDrop(e);
-});
-//toc
+  var files = e.target.files || e.dataTransfer.files
+  e.dataTransfer = {}
+  e.dataTransfer.files = files
+  inlineAttach.onDrop(e)
+})
+// toc
 ui.toc.dropdown.click(function (e) {
-    e.stopPropagation();
-});
+  e.stopPropagation()
+})
 // prevent empty link change hash
 $('a[href="#"]').click(function (e) {
-    e.preventDefault();
-});
-
-//modal actions
-var revisions = [];
-var revisionViewer = null;
-var revisionInsert = [];
-var revisionDelete = [];
-var revisionInsertAnnotation = null;
-var revisionDeleteAnnotation = null;
-var revisionList = ui.modal.revision.find('.ui-revision-list');
-var revision = null;
-var revisionTime = null;
-ui.modal.revision.on('show.bs.modal', function (e) {
-    $.get(noteurl + '/revision')
-        .done(function(data) {
-            parseRevisions(data.revision);
-            initRevisionViewer();
-        })
-        .fail(function(err) {
-
-        })
-        .always(function() {
-            //na
-        });
-});
-function checkRevisionViewer() {
-    if (revisionViewer) {
-        var container = $(revisionViewer.display.wrapper).parent();
-        $(revisionViewer.display.scroller).css('height', container.height() + 'px');
-        revisionViewer.refresh();
-    }
-}
-ui.modal.revision.on('shown.bs.modal', checkRevisionViewer);
-$(window).resize(checkRevisionViewer);
-function parseRevisions(_revisions) {
-    if (_revisions.length != revisions) {
-        revisions = _revisions;
-        var lastRevision = null;
-        if (revisionList.children().length > 0) {
-            lastRevision = revisionList.find('.active').attr('data-revision-time');
-        }
-        revisionList.html('');
-        for (var i = 0; i < revisions.length; i++) {
-            var revision = revisions[i];
-            var item = $('<a href="#" class="list-group-item"></a>');
-            item.attr('data-revision-time', revision.time);
-            if (lastRevision == revision.time) item.addClass('active');
-            var itemHeading = $('<h5 class="list-group-item-heading"></h5>');
-            itemHeading.html('<i class="fa fa-clock-o"></i> ' + moment(revision.time).format('llll'));
-            var itemText = $('<p class="list-group-item-text"></p>');
-            itemText.html('<i class="fa fa-file-text"></i> Length: ' + revision.length);
-            item.append(itemHeading).append(itemText);
-            item.click(function (e) {
-                var time = $(this).attr('data-revision-time');
-                selectRevision(time);
-            });
-            revisionList.append(item);
-        }
-        if (!lastRevision) {
-            selectRevision(revisions[0].time);
-        }
-    }
-}
-function selectRevision(time) {
-    if (time == revisionTime) return;
-    $.get(noteurl + '/revision/' + time)
-        .done(function(data) {
-            revision = data;
-            revisionTime = time;
-            var lastScrollInfo = revisionViewer.getScrollInfo();
-            revisionList.children().removeClass('active');
-            revisionList.find('[data-revision-time="' + time + '"]').addClass('active');
-            var content = revision.content;
-            revisionViewer.setValue(content);
-            revisionViewer.scrollTo(null, lastScrollInfo.top);
-            revisionInsert = [];
-            revisionDelete = [];
-            // mark the text which have been insert or delete
-            if (revision.patch.length > 0) {
-                var bias = 0;
-                for (var j = 0; j < revision.patch.length; j++) {
-                    var patch = revision.patch[j];
-                    var currIndex = patch.start1 + bias;
-                    for (var i = 0; i < patch.diffs.length; i++) {
-                        var diff = patch.diffs[i];
-                        // ignore if diff only contains line breaks
-                        if ((diff[1].match(new RegExp("\n", "g")) || []).length == diff[1].length) continue;
-                        switch(diff[0]) {
-                            case 0: // retain
-                                currIndex += diff[1].length;
-                            break;
-                            case 1: // insert
-                                var prePos = revisionViewer.posFromIndex(currIndex);
-                                var postPos = revisionViewer.posFromIndex(currIndex + diff[1].length);
-                                revisionInsert.push({
-                                    from: prePos,
-                                    to: postPos
-                                });
-                                revisionViewer.markText(prePos, postPos, {
-                                    css: 'background-color: rgba(230,255,230,0.7); text-decoration: underline;'
-                                });
-                                currIndex += diff[1].length;
-                            break;
-                            case -1: // delete
-                                var prePos = revisionViewer.posFromIndex(currIndex);
-                                revisionViewer.replaceRange(diff[1], prePos);
-                                var postPos = revisionViewer.posFromIndex(currIndex + diff[1].length);
-                                revisionDelete.push({
-                                    from: prePos,
-                                    to: postPos
-                                });
-                                revisionViewer.markText(prePos, postPos, {
-                                    css: 'background-color: rgba(255,230,230,0.7); text-decoration: line-through;'
-                                });
-                                bias += diff[1].length;
-                                currIndex += diff[1].length;
-                            break;
-                        }
-                    }
-                }
-            }
-            revisionInsertAnnotation.update(revisionInsert);
-            revisionDeleteAnnotation.update(revisionDelete);
-        })
-        .fail(function(err) {
-
-        })
-        .always(function() {
-            //na
-        });
-}
-function initRevisionViewer() {
-    if (revisionViewer) return;
-    var revisionViewerTextArea = document.getElementById("revisionViewer");
-    revisionViewer = CodeMirror.fromTextArea(revisionViewerTextArea, {
-        mode: defaultEditorMode,
-        viewportMargin: viewportMargin,
-        lineNumbers: true,
-        lineWrapping: true,
-        showCursorWhenSelecting: true,
-        inputStyle: "textarea",
-        gutters: ["CodeMirror-linenumbers"],
-        flattenSpans: true,
-        addModeClass: true,
-        readOnly: true,
-        autoRefresh: true,
-        scrollbarStyle: 'overlay'
-    });
-    revisionInsertAnnotation = revisionViewer.annotateScrollbar({ className:"CodeMirror-insert-match" });
-    revisionDeleteAnnotation = revisionViewer.annotateScrollbar({ className:"CodeMirror-delete-match" });
-    checkRevisionViewer();
-}
-$('#revisionModalDownload').click(function () {
-    if (!revision) return;
-    var filename = renderFilename(ui.area.markdown) + '_' + revisionTime + '.md';
-    var blob = new Blob([revision.content], {
-        type: "text/markdown;charset=utf-8"
-    });
-    saveAs(blob, filename, true);
-});
-$('#revisionModalRevert').click(function () {
-    if (!revision) return;
-    editor.setValue(revision.content);
-    ui.modal.revision.modal('hide');
-});
-//snippet projects
-ui.modal.snippetImportProjects.change(function() {
-    var accesstoken = $("#snippetImportModalAccessToken").val(),
-        baseURL     = $("#snippetImportModalBaseURL").val(),
-        project     = $("#snippetImportModalProjects").val();
-
-    $("#snippetImportModalLoading").show();
-    $("#snippetImportModalContent").val('/projects/' + project);
-    $.get(baseURL + '/api/v3/projects/' + project + '/snippets?access_token=' + accesstoken)
-        .done(function(data) {
-            $("#snippetImportModalSnippets").find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Snippets</option>');
-            data.forEach(function(snippet) {
-                $('<option>').val(snippet.id).text(snippet.title).appendTo($("#snippetImportModalSnippets"));
-            });
-            $("#snippetImportModalLoading").hide();
-            $("#snippetImportModalSnippets").prop('disabled', false);
-        })
-        .fail(function(err) {
-
-        })
-        .always(function() {
-            //na
-        });
-});
-//snippet snippets
-ui.modal.snippetImportSnippets.change(function() {
-    var project = $("#snippetImportModalProjects").val(),
-        snippet = $("#snippetImportModalSnippets").val();
-
-    $("#snippetImportModalContent").val($("#snippetImportModalContent").val() + '/snippets/' + snippet);
+  e.preventDefault()
 })
 
-function scrollToTop() {
-    if (currentMode == modeType.both) {
-        if (editor.getScrollInfo().top != 0)
-            editor.scrollTo(0, 0);
-        else
-            ui.area.view.animate({
-                scrollTop: 0
-            }, 100, "linear");
-    } else {
-        $('body, html').stop(true, true).animate({
-            scrollTop: 0
-        }, 100, "linear");
+// modal actions
+var revisions = []
+var revisionViewer = null
+var revisionInsert = []
+var revisionDelete = []
+var revisionInsertAnnotation = null
+var revisionDeleteAnnotation = null
+var revisionList = ui.modal.revision.find('.ui-revision-list')
+var revision = null
+var revisionTime = null
+ui.modal.revision.on('show.bs.modal', function (e) {
+  $.get(noteurl + '/revision')
+        .done(function (data) {
+          parseRevisions(data.revision)
+          initRevisionViewer()
+        })
+        .fail(function (err) {
+          if (debug) {
+            console.log(err)
+          }
+        })
+        .always(function () {
+            // na
+        })
+})
+function checkRevisionViewer () {
+  if (revisionViewer) {
+    var container = $(revisionViewer.display.wrapper).parent()
+    $(revisionViewer.display.scroller).css('height', container.height() + 'px')
+    revisionViewer.refresh()
+  }
+}
+ui.modal.revision.on('shown.bs.modal', checkRevisionViewer)
+$(window).resize(checkRevisionViewer)
+function parseRevisions (_revisions) {
+  if (_revisions.length !== revisions) {
+    revisions = _revisions
+    var lastRevision = null
+    if (revisionList.children().length > 0) {
+      lastRevision = revisionList.find('.active').attr('data-revision-time')
     }
+    revisionList.html('')
+    for (var i = 0; i < revisions.length; i++) {
+      var revision = revisions[i]
+      var item = $('<a href="#" class="list-group-item"></a>')
+      item.attr('data-revision-time', revision.time)
+      if (lastRevision === revision.time) item.addClass('active')
+      var itemHeading = $('<h5 class="list-group-item-heading"></h5>')
+      itemHeading.html('<i class="fa fa-clock-o"></i> ' + moment(revision.time).format('llll'))
+      var itemText = $('<p class="list-group-item-text"></p>')
+      itemText.html('<i class="fa fa-file-text"></i> Length: ' + revision.length)
+      item.append(itemHeading).append(itemText)
+      item.click(function (e) {
+        var time = $(this).attr('data-revision-time')
+        selectRevision(time)
+      })
+      revisionList.append(item)
+    }
+    if (!lastRevision) {
+      selectRevision(revisions[0].time)
+    }
+  }
+}
+function selectRevision (time) {
+  if (time === revisionTime) return
+  $.get(noteurl + '/revision/' + time)
+        .done(function (data) {
+          revision = data
+          revisionTime = time
+          var lastScrollInfo = revisionViewer.getScrollInfo()
+          revisionList.children().removeClass('active')
+          revisionList.find('[data-revision-time="' + time + '"]').addClass('active')
+          var content = revision.content
+          revisionViewer.setValue(content)
+          revisionViewer.scrollTo(null, lastScrollInfo.top)
+          revisionInsert = []
+          revisionDelete = []
+            // mark the text which have been insert or delete
+          if (revision.patch.length > 0) {
+            var bias = 0
+            for (var j = 0; j < revision.patch.length; j++) {
+              var patch = revision.patch[j]
+              var currIndex = patch.start1 + bias
+              for (var i = 0; i < patch.diffs.length; i++) {
+                var diff = patch.diffs[i]
+                        // ignore if diff only contains line breaks
+                if ((diff[1].match(/\n/g) || []).length === diff[1].length) continue
+                var prePos
+                var postPos
+                switch (diff[0]) {
+                  case 0: // retain
+                    currIndex += diff[1].length
+                    break
+                  case 1: // insert
+                    prePos = revisionViewer.posFromIndex(currIndex)
+                    postPos = revisionViewer.posFromIndex(currIndex + diff[1].length)
+                    revisionInsert.push({
+                      from: prePos,
+                      to: postPos
+                    })
+                    revisionViewer.markText(prePos, postPos, {
+                      css: 'background-color: rgba(230,255,230,0.7); text-decoration: underline;'
+                    })
+                    currIndex += diff[1].length
+                    break
+                  case -1: // delete
+                    prePos = revisionViewer.posFromIndex(currIndex)
+                    revisionViewer.replaceRange(diff[1], prePos)
+                    postPos = revisionViewer.posFromIndex(currIndex + diff[1].length)
+                    revisionDelete.push({
+                      from: prePos,
+                      to: postPos
+                    })
+                    revisionViewer.markText(prePos, postPos, {
+                      css: 'background-color: rgba(255,230,230,0.7); text-decoration: line-through;'
+                    })
+                    bias += diff[1].length
+                    currIndex += diff[1].length
+                    break
+                }
+              }
+            }
+          }
+          revisionInsertAnnotation.update(revisionInsert)
+          revisionDeleteAnnotation.update(revisionDelete)
+        })
+        .fail(function (err) {
+          if (debug) {
+            console.log(err)
+          }
+        })
+        .always(function () {
+            // na
+        })
+}
+function initRevisionViewer () {
+  if (revisionViewer) return
+  var revisionViewerTextArea = document.getElementById('revisionViewer')
+  revisionViewer = CodeMirror.fromTextArea(revisionViewerTextArea, {
+    mode: defaultEditorMode,
+    viewportMargin: viewportMargin,
+    lineNumbers: true,
+    lineWrapping: true,
+    showCursorWhenSelecting: true,
+    inputStyle: 'textarea',
+    gutters: ['CodeMirror-linenumbers'],
+    flattenSpans: true,
+    addModeClass: true,
+    readOnly: true,
+    autoRefresh: true,
+    scrollbarStyle: 'overlay'
+  })
+  revisionInsertAnnotation = revisionViewer.annotateScrollbar({ className: 'CodeMirror-insert-match' })
+  revisionDeleteAnnotation = revisionViewer.annotateScrollbar({ className: 'CodeMirror-delete-match' })
+  checkRevisionViewer()
+}
+$('#revisionModalDownload').click(function () {
+  if (!revision) return
+  var filename = renderFilename(ui.area.markdown) + '_' + revisionTime + '.md'
+  var blob = new Blob([revision.content], {
+    type: 'text/markdown;charset=utf-8'
+  })
+  saveAs(blob, filename, true)
+})
+$('#revisionModalRevert').click(function () {
+  if (!revision) return
+  editor.setValue(revision.content)
+  ui.modal.revision.modal('hide')
+})
+// snippet projects
+ui.modal.snippetImportProjects.change(function () {
+  var accesstoken = $('#snippetImportModalAccessToken').val()
+  var baseURL = $('#snippetImportModalBaseURL').val()
+  var project = $('#snippetImportModalProjects').val()
+
+  $('#snippetImportModalLoading').show()
+  $('#snippetImportModalContent').val('/projects/' + project)
+  $.get(baseURL + '/api/v3/projects/' + project + '/snippets?access_token=' + accesstoken)
+        .done(function (data) {
+          $('#snippetImportModalSnippets').find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Snippets</option>')
+          data.forEach(function (snippet) {
+            $('<option>').val(snippet.id).text(snippet.title).appendTo($('#snippetImportModalSnippets'))
+          })
+          $('#snippetImportModalLoading').hide()
+          $('#snippetImportModalSnippets').prop('disabled', false)
+        })
+        .fail(function (err) {
+          if (debug) {
+            console.log(err)
+          }
+        })
+        .always(function () {
+            // na
+        })
+})
+// snippet snippets
+ui.modal.snippetImportSnippets.change(function () {
+  var snippet = $('#snippetImportModalSnippets').val()
+  $('#snippetImportModalContent').val($('#snippetImportModalContent').val() + '/snippets/' + snippet)
+})
+
+function scrollToTop () {
+  if (window.currentMode === modeType.both) {
+    if (editor.getScrollInfo().top !== 0) { editor.scrollTo(0, 0) } else {
+      ui.area.view.animate({
+        scrollTop: 0
+      }, 100, 'linear')
+    }
+  } else {
+    $('body, html').stop(true, true).animate({
+      scrollTop: 0
+    }, 100, 'linear')
+  }
 }
 
-function scrollToBottom() {
-    if (currentMode == modeType.both) {
-        var scrollInfo = editor.getScrollInfo();
-        var scrollHeight = scrollInfo.height;
-        if (scrollInfo.top != scrollHeight)
-            editor.scrollTo(0, scrollHeight * 2);
-        else
-            ui.area.view.animate({
-                scrollTop: ui.area.view[0].scrollHeight
-            }, 100, "linear");
-    } else {
-        $('body, html').stop(true, true).animate({
-            scrollTop: $(document.body)[0].scrollHeight
-        }, 100, "linear");
+function scrollToBottom () {
+  if (window.currentMode === modeType.both) {
+    var scrollInfo = editor.getScrollInfo()
+    var scrollHeight = scrollInfo.height
+    if (scrollInfo.top !== scrollHeight) { editor.scrollTo(0, scrollHeight * 2) } else {
+      ui.area.view.animate({
+        scrollTop: ui.area.view[0].scrollHeight
+      }, 100, 'linear')
     }
+  } else {
+    $('body, html').stop(true, true).animate({
+      scrollTop: $(document.body)[0].scrollHeight
+    }, 100, 'linear')
+  }
 }
 
-window.scrollToTop = scrollToTop;
-window.scrollToBottom = scrollToBottom;
+window.scrollToTop = scrollToTop
+window.scrollToBottom = scrollToBottom
 
-var enoughForAffixToc = true;
+var enoughForAffixToc = true
 
-//scrollspy
-function generateScrollspy() {
-    $(document.body).scrollspy({
-        target: '.scrollspy-body'
-    });
-    ui.area.view.scrollspy({
-        target: '.scrollspy-view'
-    });
-    $(document.body).scrollspy('refresh');
-    ui.area.view.scrollspy('refresh');
-    if (enoughForAffixToc) {
-        ui.toc.toc.hide();
-        ui.toc.affix.show();
-    } else {
-        ui.toc.affix.hide();
-        ui.toc.toc.show();
-    }
-    //$(document.body).scroll();
-    //ui.area.view.scroll();
+// scrollspy
+function generateScrollspy () {
+  $(document.body).scrollspy({
+    target: '.scrollspy-body'
+  })
+  ui.area.view.scrollspy({
+    target: '.scrollspy-view'
+  })
+  $(document.body).scrollspy('refresh')
+  ui.area.view.scrollspy('refresh')
+  if (enoughForAffixToc) {
+    ui.toc.toc.hide()
+    ui.toc.affix.show()
+  } else {
+    ui.toc.affix.hide()
+    ui.toc.toc.show()
+  }
+    // $(document.body).scroll();
+    // ui.area.view.scroll();
 }
 
-function updateScrollspy() {
-    var headers = ui.area.markdown.find('h1, h2, h3').toArray();
-    var headerMap = [];
-    for (var i = 0; i < headers.length; i++) {
-        headerMap.push($(headers[i]).offset().top - parseInt($(headers[i]).css('margin-top')));
-    }
-    applyScrollspyActive($(window).scrollTop(), headerMap, headers,
-        $('.scrollspy-body'), 0);
-    var offset = ui.area.view.scrollTop() - ui.area.view.offset().top;
-    applyScrollspyActive(ui.area.view.scrollTop(), headerMap, headers,
-        $('.scrollspy-view'), offset - 10);
+function updateScrollspy () {
+  var headers = ui.area.markdown.find('h1, h2, h3').toArray()
+  var headerMap = []
+  for (var i = 0; i < headers.length; i++) {
+    headerMap.push($(headers[i]).offset().top - parseInt($(headers[i]).css('margin-top')))
+  }
+  applyScrollspyActive($(window).scrollTop(), headerMap, headers,
+        $('.scrollspy-body'), 0)
+  var offset = ui.area.view.scrollTop() - ui.area.view.offset().top
+  applyScrollspyActive(ui.area.view.scrollTop(), headerMap, headers,
+        $('.scrollspy-view'), offset - 10)
 }
 
-function applyScrollspyActive(top, headerMap, headers, target, offset) {
-    var index = 0;
-    for (var i = headerMap.length - 1; i >= 0; i--) {
-        if (top >= (headerMap[i] + offset) && headerMap[i + 1] && top < (headerMap[i + 1] + offset)) {
-            index = i;
-            break;
-        }
+function applyScrollspyActive (top, headerMap, headers, target, offset) {
+  var index = 0
+  for (var i = headerMap.length - 1; i >= 0; i--) {
+    if (top >= (headerMap[i] + offset) && headerMap[i + 1] && top < (headerMap[i + 1] + offset)) {
+      index = i
+      break
     }
-    var header = $(headers[index]);
-    var active = target.find('a[href="#' + header.attr('id') + '"]');
-    active.closest('li').addClass('active').parent().closest('li').addClass('active').parent().closest('li').addClass('active');
+  }
+  var header = $(headers[index])
+  var active = target.find('a[href="#' + header.attr('id') + '"]')
+  active.closest('li').addClass('active').parent().closest('li').addClass('active').parent().closest('li').addClass('active')
 }
 
 // clipboard modal
-//fix for wrong autofocus
+// fix for wrong autofocus
 $('#clipboardModal').on('shown.bs.modal', function () {
-    $('#clipboardModal').blur();
-});
-$("#clipboardModalClear").click(function () {
-    $("#clipboardModalContent").html('');
-});
-$("#clipboardModalConfirm").click(function () {
-    var data = $("#clipboardModalContent").html();
-    if (data) {
-        parseToEditor(data);
-        $('#clipboardModal').modal('hide');
-        $("#clipboardModalContent").html('');
-    }
-});
+  $('#clipboardModal').blur()
+})
+$('#clipboardModalClear').click(function () {
+  $('#clipboardModalContent').html('')
+})
+$('#clipboardModalConfirm').click(function () {
+  var data = $('#clipboardModalContent').html()
+  if (data) {
+    parseToEditor(data)
+    $('#clipboardModal').modal('hide')
+    $('#clipboardModalContent').html('')
+  }
+})
 
 // refresh modal
 $('#refreshModalRefresh').click(function () {
-    location.reload(true);
-});
+  location.reload(true)
+})
 
 // gist import modal
-$("#gistImportModalClear").click(function () {
-    $("#gistImportModalContent").val('');
-});
-$("#gistImportModalConfirm").click(function () {
-    var gisturl = $("#gistImportModalContent").val();
-    if (!gisturl) return;
-    $('#gistImportModal').modal('hide');
-    $("#gistImportModalContent").val('');
-    if (!isValidURL(gisturl)) {
-        showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid URL :(', '', '', false);
-        return;
+$('#gistImportModalClear').click(function () {
+  $('#gistImportModalContent').val('')
+})
+$('#gistImportModalConfirm').click(function () {
+  var gisturl = $('#gistImportModalContent').val()
+  if (!gisturl) return
+  $('#gistImportModal').modal('hide')
+  $('#gistImportModalContent').val('')
+  if (!isValidURL(gisturl)) {
+    showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid URL :(', '', '', false)
+  } else {
+    var hostname = window.url('hostname', gisturl)
+    if (hostname !== 'gist.github.com') {
+      showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid Gist URL :(', '', '', false)
     } else {
-        var hostname = url('hostname', gisturl)
-        if (hostname !== 'gist.github.com') {
-            showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid Gist URL :(', '', '', false);
-        } else {
-            ui.spinner.show();
-            $.get('https://api.github.com/gists/' + url('-1', gisturl))
+      ui.spinner.show()
+      $.get('https://api.github.com/gists/' + window.url('-1', gisturl))
                 .done(function (data) {
-                    if (data.files) {
-                        var contents = "";
-                        Object.keys(data.files).forEach(function (key) {
-                            contents += key;
-                            contents += '\n---\n';
-                            contents += data.files[key].content;
-                            contents += '\n\n';
-                        });
-                        replaceAll(contents);
-                    } else {
-                        showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Unable to fetch gist files :(', '', '', false);
-                    }
+                  if (data.files) {
+                    var contents = ''
+                    Object.keys(data.files).forEach(function (key) {
+                      contents += key
+                      contents += '\n---\n'
+                      contents += data.files[key].content
+                      contents += '\n\n'
+                    })
+                    replaceAll(contents)
+                  } else {
+                    showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Unable to fetch gist files :(', '', '', false)
+                  }
                 })
                 .fail(function (data) {
-                    showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid Gist URL :(', '', JSON.stringify(data), false);
+                  showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid Gist URL :(', '', JSON.stringify(data), false)
                 })
                 .always(function () {
-                    ui.spinner.hide();
-                });
-        }
+                  ui.spinner.hide()
+                })
     }
-});
+  }
+})
 
 // snippet import modal
-$("#snippetImportModalClear").click(function () {
-    $("#snippetImportModalContent").val('');
-    $("#snippetImportModalProjects").val('init');
-    $("#snippetImportModalSnippets").val('init');
-    $("#snippetImportModalSnippets").prop('disabled', true);
-});
-$("#snippetImportModalConfirm").click(function () {
-    var snippeturl = $("#snippetImportModalContent").val();
-    if (!snippeturl) return;
-    $('#snippetImportModal').modal('hide');
-    $("#snippetImportModalContent").val('');
-    if (!/^.+\/snippets\/.+$/.test(snippeturl)) {
-        showMessageModal('<i class="fa fa-github"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', '', false);
-    } else {
-        ui.spinner.show();
-        var accessToken = '?access_token=' + $("#snippetImportModalAccessToken").val();
-        var fullURL = $("#snippetImportModalBaseURL").val() + '/api/v3' + snippeturl;
-        $.get(fullURL + accessToken)
-            .done(function(data) {
-                var content = '# ' + (data.title || "Snippet Import");
-                var fileInfo = data.file_name.split('.');
-                fileInfo[1] = (fileInfo[1]) ? fileInfo[1] : "md";
-                $.get(fullURL + '/raw' + accessToken)
+$('#snippetImportModalClear').click(function () {
+  $('#snippetImportModalContent').val('')
+  $('#snippetImportModalProjects').val('init')
+  $('#snippetImportModalSnippets').val('init')
+  $('#snippetImportModalSnippets').prop('disabled', true)
+})
+$('#snippetImportModalConfirm').click(function () {
+  var snippeturl = $('#snippetImportModalContent').val()
+  if (!snippeturl) return
+  $('#snippetImportModal').modal('hide')
+  $('#snippetImportModalContent').val('')
+  if (!/^.+\/snippets\/.+$/.test(snippeturl)) {
+    showMessageModal('<i class="fa fa-github"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', '', false)
+  } else {
+    ui.spinner.show()
+    var accessToken = '?access_token=' + $('#snippetImportModalAccessToken').val()
+    var fullURL = $('#snippetImportModalBaseURL').val() + '/api/v3' + snippeturl
+    $.get(fullURL + accessToken)
+            .done(function (data) {
+              var content = '# ' + (data.title || 'Snippet Import')
+              var fileInfo = data.file_name.split('.')
+              fileInfo[1] = (fileInfo[1]) ? fileInfo[1] : 'md'
+              $.get(fullURL + '/raw' + accessToken)
                     .done(function (raw) {
-                        if (raw) {
-                            content += "\n\n";
-                            if (fileInfo[1] != "md") {
-                                content += "```" + fileTypes[fileInfo[1]] + "\n";
-                            }
-                            content += raw;
-                            if (fileInfo[1] != "md") {
-                                content += "\n```";
-                            }
-                            replaceAll(content);
+                      if (raw) {
+                        content += '\n\n'
+                        if (fileInfo[1] !== 'md') {
+                          content += '```' + window.fileTypes[fileInfo[1]] + '\n'
                         }
+                        content += raw
+                        if (fileInfo[1] !== 'md') {
+                          content += '\n```'
+                        }
+                        replaceAll(content)
+                      }
                     })
                     .fail(function (data) {
-                        showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', JSON.stringify(data), false);
+                      showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', JSON.stringify(data), false)
                     })
                     .always(function () {
-                        ui.spinner.hide();
-                    });
+                      ui.spinner.hide()
+                    })
             })
             .fail(function (data) {
-                showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', JSON.stringify(data), false);
-            });
-    }
-});
+              showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', JSON.stringify(data), false)
+            })
+  }
+})
 
-//snippet export modal
-$("#snippetExportModalConfirm").click(function() {
-    var accesstoken = $("#snippetExportModalAccessToken").val(),
-        baseURL     = $("#snippetExportModalBaseURL").val(),
-        data        = {
-            title: $("#snippetExportModalTitle").val(),
-            file_name: $("#snippetExportModalFileName").val(),
-            code: editor.getValue(),
-            visibility_level: $("#snippetExportModalVisibility").val()
-        };
-    if (!data.title || !data.file_name || !data.code || !data.visibility_level || !$("#snippetExportModalProjects").val()) return;
-    $("#snippetExportModalLoading").show();
-    var fullURL = baseURL + '/api/v3/projects/' + $("#snippetExportModalProjects").val() + '/snippets?access_token=' + accesstoken;
-    $.post(fullURL
+// snippet export modal
+$('#snippetExportModalConfirm').click(function () {
+  var accesstoken = $('#snippetExportModalAccessToken').val()
+  var baseURL = $('#snippetExportModalBaseURL').val()
+  var data = {
+    title: $('#snippetExportModalTitle').val(),
+    file_name: $('#snippetExportModalFileName').val(),
+    code: editor.getValue(),
+    visibility_level: $('#snippetExportModalVisibility').val()
+  }
+  if (!data.title || !data.file_name || !data.code || !data.visibility_level || !$('#snippetExportModalProjects').val()) return
+  $('#snippetExportModalLoading').show()
+  var fullURL = baseURL + '/api/v3/projects/' + $('#snippetExportModalProjects').val() + '/snippets?access_token=' + accesstoken
+  $.post(fullURL
         , data
-        , function(ret) {
-            $("#snippetExportModalLoading").hide();
-            $('#snippetExportModal').modal('hide');
-            var redirect = baseURL + '/' + $("#snippetExportModalProjects option[value='" + $("#snippetExportModalProjects").val() + "']").text() + '/snippets/' + ret.id;
-            showMessageModal('<i class="fa fa-gitlab"></i> Export to Snippet', 'Export Successful!', redirect, 'View Snippet Here', true);
+        , function (ret) {
+          $('#snippetExportModalLoading').hide()
+          $('#snippetExportModal').modal('hide')
+          var redirect = baseURL + '/' + $("#snippetExportModalProjects option[value='" + $('#snippetExportModalProjects').val() + "']").text() + '/snippets/' + ret.id
+          showMessageModal('<i class="fa fa-gitlab"></i> Export to Snippet', 'Export Successful!', redirect, 'View Snippet Here', true)
         }
         , 'json'
-    );
-});
+    )
+})
 
-function parseToEditor(data) {
-    var parsed = toMarkdown(data);
-    if (parsed)
-        replaceAll(parsed);
+function parseToEditor (data) {
+  var parsed = toMarkdown(data)
+  if (parsed) { replaceAll(parsed) }
 }
 
-function replaceAll(data) {
-    editor.replaceRange(data, {
-        line: 0,
-        ch: 0
-    }, {
-            line: editor.lastLine(),
-            ch: editor.lastLine().length
-        }, '+input');
+function replaceAll (data) {
+  editor.replaceRange(data, {
+    line: 0,
+    ch: 0
+  }, {
+    line: editor.lastLine(),
+    ch: editor.lastLine().length
+  }, '+input')
 }
 
-function importFromUrl(url) {
-    //console.log(url);
-    if (!url) return;
-    if (!isValidURL(url)) {
-        showMessageModal('<i class="fa fa-cloud-download"></i> Import from URL', 'Not a valid URL :(', '', '', false);
-        return;
+function importFromUrl (url) {
+    // console.log(url);
+  if (!url) return
+  if (!isValidURL(url)) {
+    showMessageModal('<i class="fa fa-cloud-download"></i> Import from URL', 'Not a valid URL :(', '', '', false)
+    return
+  }
+  $.ajax({
+    method: 'GET',
+    url: url,
+    success: function (data) {
+      var extension = url.split('.').pop()
+      if (extension === 'html') { parseToEditor(data) } else { replaceAll(data) }
+    },
+    error: function (data) {
+      showMessageModal('<i class="fa fa-cloud-download"></i> Import from URL', 'Import failed :(', '', JSON.stringify(data), false)
+    },
+    complete: function () {
+      ui.spinner.hide()
     }
-    $.ajax({
-        method: "GET",
-        url: url,
-        success: function (data) {
-            var extension = url.split('.').pop();
-            if (extension == 'html')
-                parseToEditor(data);
-            else
-                replaceAll(data);
-        },
-        error: function (data) {
-            showMessageModal('<i class="fa fa-cloud-download"></i> Import from URL', 'Import failed :(', '', JSON.stringify(data), false);
-        },
-        complete: function () {
-            ui.spinner.hide();
-        }
-    });
+  })
 }
 
-//mode
+// mode
 ui.toolbar.mode.click(function () {
-    toggleMode();
-});
-//edit
+  toggleMode()
+})
+// edit
 ui.toolbar.edit.click(function () {
-    changeMode(modeType.edit);
-});
-//view
+  changeMode(modeType.edit)
+})
+// view
 ui.toolbar.view.click(function () {
-    changeMode(modeType.view);
-});
-//both
+  changeMode(modeType.view)
+})
+// both
 ui.toolbar.both.click(function () {
-    changeMode(modeType.both);
-});
-//permission
-//freely
+  changeMode(modeType.both)
+})
+// permission
+// freely
 ui.infobar.permission.freely.click(function () {
-    emitPermission("freely");
-});
-//editable
+  emitPermission('freely')
+})
+// editable
 ui.infobar.permission.editable.click(function () {
-    emitPermission("editable");
-});
-//locked
+  emitPermission('editable')
+})
+// locked
 ui.infobar.permission.locked.click(function () {
-    emitPermission("locked");
-});
-//private
+  emitPermission('locked')
+})
+// private
 ui.infobar.permission.private.click(function () {
-    emitPermission("private");
-});
-//limited
-ui.infobar.permission.limited.click(function() {
-    emitPermission("limited");
-});
-//protected
-ui.infobar.permission.protected.click(function() {
-    emitPermission("protected");
-});
+  emitPermission('private')
+})
+// limited
+ui.infobar.permission.limited.click(function () {
+  emitPermission('limited')
+})
+// protected
+ui.infobar.permission.protected.click(function () {
+  emitPermission('protected')
+})
 // delete note
 ui.infobar.delete.click(function () {
-    $('.delete-modal').modal('show');
-});
+  $('.delete-modal').modal('show')
+})
 $('.ui-delete-modal-confirm').click(function () {
-    socket.emit('delete');
-});
+  socket.emit('delete')
+})
 
-function emitPermission(_permission) {
-    if (_permission != permission) {
-        socket.emit('permission', _permission);
-    }
+function emitPermission (_permission) {
+  if (_permission !== permission) {
+    socket.emit('permission', _permission)
+  }
 }
 
-function updatePermission(newPermission) {
-    if (permission != newPermission) {
-        permission = newPermission;
-        if (loaded) refreshView();
-    }
-    var label = null;
-    var title = null;
-    switch (permission) {
-        case "freely":
-            label = '<i class="fa fa-leaf"></i> Freely';
-            title = "Anyone can edit";
-            break;
-        case "editable":
-            label = '<i class="fa fa-shield"></i> Editable';
-            title = "Signed people can edit";
-            break;
-        case "limited":
-            label = '<i class="fa fa-id-card"></i> Limited';
-            title = "Signed people can edit (forbid guest)"
-            break;
-        case "locked":
-            label = '<i class="fa fa-lock"></i> Locked';
-            title = "Only owner can edit";
-            break;
-        case "protected":
-            label = '<i class="fa fa-umbrella"></i> Protected';
-            title = "Only owner can edit (forbid guest)";
-            break;
-        case "private":
-            label = '<i class="fa fa-hand-stop-o"></i> Private';
-            title = "Only owner can view & edit";
-            break;
-    }
-    if (personalInfo.userid && owner && personalInfo.userid == owner) {
-        label += ' <i class="fa fa-caret-down"></i>';
-        ui.infobar.permission.label.removeClass('disabled');
-    } else {
-        ui.infobar.permission.label.addClass('disabled');
-    }
-    ui.infobar.permission.label.html(label).attr('title', title);
+function updatePermission (newPermission) {
+  if (permission !== newPermission) {
+    permission = newPermission
+    if (window.loaded) refreshView()
+  }
+  var label = null
+  var title = null
+  switch (permission) {
+    case 'freely':
+      label = '<i class="fa fa-leaf"></i> Freely'
+      title = 'Anyone can edit'
+      break
+    case 'editable':
+      label = '<i class="fa fa-shield"></i> Editable'
+      title = 'Signed people can edit'
+      break
+    case 'limited':
+      label = '<i class="fa fa-id-card"></i> Limited'
+      title = 'Signed people can edit (forbid guest)'
+      break
+    case 'locked':
+      label = '<i class="fa fa-lock"></i> Locked'
+      title = 'Only owner can edit'
+      break
+    case 'protected':
+      label = '<i class="fa fa-umbrella"></i> Protected'
+      title = 'Only owner can edit (forbid guest)'
+      break
+    case 'private':
+      label = '<i class="fa fa-hand-stop-o"></i> Private'
+      title = 'Only owner can view & edit'
+      break
+  }
+  if (window.personalInfo.userid && window.owner && window.personalInfo.userid === window.owner) {
+    label += ' <i class="fa fa-caret-down"></i>'
+    ui.infobar.permission.label.removeClass('disabled')
+  } else {
+    ui.infobar.permission.label.addClass('disabled')
+  }
+  ui.infobar.permission.label.html(label).attr('title', title)
 }
 
-function havePermission() {
-    var bool = false;
-    switch (permission) {
-        case "freely":
-            bool = true;
-            break;
-        case "editable":
-        case "limited":
-            if (!personalInfo.login) {
-                bool = false;
-            } else {
-                bool = true;
-            }
-            break;
-        case "locked":
-        case "private":
-        case "protected":
-            if (!owner || personalInfo.userid != owner) {
-                bool = false;
-            } else {
-                bool = true;
-            }
-            break;
-    }
-    return bool;
+function havePermission () {
+  var bool = false
+  switch (permission) {
+    case 'freely':
+      bool = true
+      break
+    case 'editable':
+    case 'limited':
+      if (!window.personalInfo.login) {
+        bool = false
+      } else {
+        bool = true
+      }
+      break
+    case 'locked':
+    case 'private':
+    case 'protected':
+      if (!window.owner || window.personalInfo.userid !== window.owner) {
+        bool = false
+      } else {
+        bool = true
+      }
+      break
+  }
+  return bool
 }
 // global module workaround
-window.havePermission = havePermission;
+window.havePermission = havePermission
 
-//socket.io actions
-var io = require("socket.io-client");
+// socket.io actions
+var io = require('socket.io-client')
 var socket = io.connect({
-    path: urlpath ? '/' + urlpath + '/socket.io/' : '',
-    timeout: 5000, //5 secs to timeout,
-    reconnectionAttempts: 20 // retry 20 times on connect failed
-});
-//overwrite original event for checking login state
-var on = socket.on;
+  path: urlpath ? '/' + urlpath + '/socket.io/' : '',
+  timeout: 5000, // 5 secs to timeout,
+  reconnectionAttempts: 20 // retry 20 times on connect failed
+})
+// overwrite original event for checking login state
+var on = socket.on
 socket.on = function () {
-    if (!checkLoginStateChanged() && !needRefresh)
-        return on.apply(socket, arguments);
-};
-var emit = socket.emit;
+  if (!checkLoginStateChanged() && !window.needRefresh) { return on.apply(socket, arguments) }
+}
+var emit = socket.emit
 socket.emit = function () {
-    if (!checkLoginStateChanged() && !needRefresh)
-        emit.apply(socket, arguments);
-};
+  if (!checkLoginStateChanged() && !window.needRefresh) { emit.apply(socket, arguments) }
+}
 socket.on('info', function (data) {
-    console.error(data);
-    switch (data.code) {
-        case 403:
-            location.href = serverurl + "/403";
-            break;
-        case 404:
-            location.href = serverurl + "/404";
-            break;
-        case 500:
-            location.href = serverurl + "/500";
-            break;
-    }
-});
+  console.error(data)
+  switch (data.code) {
+    case 403:
+      location.href = serverurl + '/403'
+      break
+    case 404:
+      location.href = serverurl + '/404'
+      break
+    case 500:
+      location.href = serverurl + '/500'
+      break
+  }
+})
 socket.on('error', function (data) {
-    console.error(data);
-    if (data.message && data.message.indexOf('AUTH failed') === 0)
-        location.href = serverurl + "/403";
-});
+  console.error(data)
+  if (data.message && data.message.indexOf('AUTH failed') === 0) { location.href = serverurl + '/403' }
+})
 socket.on('delete', function () {
-    if (personalInfo.login) {
-        deleteServerHistory(noteid, function (err, data) {
-            if (!err) location.href = serverurl;
-        });
-    } else {
-        getHistory(function (notehistory) {
-            var newnotehistory = removeHistory(noteid, notehistory);
-            saveHistory(newnotehistory);
-            location.href = serverurl;
-        });
-    }
-});
-var retryTimer = null;
+  if (window.personalInfo.login) {
+    deleteServerHistory(noteid, function (err, data) {
+      if (!err) location.href = serverurl
+    })
+  } else {
+    getHistory(function (notehistory) {
+      var newnotehistory = removeHistory(noteid, notehistory)
+      saveHistory(newnotehistory)
+      location.href = serverurl
+    })
+  }
+})
+var retryTimer = null
 socket.on('maintenance', function () {
-    cmClient.revision = -1;
-});
+  cmClient.revision = -1
+})
 socket.on('disconnect', function (data) {
-    showStatus(statusType.offline);
-    if (loaded) {
-        saveInfo();
-        lastInfo.history = editor.getHistory();
-    }
-    if (!editor.getOption('readOnly'))
-        editor.setOption('readOnly', true);
-    if (!retryTimer) {
-        retryTimer = setInterval(function () {
-            if (!needRefresh) socket.connect();
-        }, 1000);
-    }
-});
+  showStatus(statusType.offline)
+  if (window.loaded) {
+    saveInfo()
+    window.lastInfo.history = editor.getHistory()
+  }
+  if (!editor.getOption('readOnly')) { editor.setOption('readOnly', true) }
+  if (!retryTimer) {
+    retryTimer = setInterval(function () {
+      if (!window.needRefresh) socket.connect()
+    }, 1000)
+  }
+})
 socket.on('reconnect', function (data) {
-    //sync back any change in offline
-    emitUserStatus(true);
-    cursorActivity();
-    socket.emit('online users');
-});
+    // sync back any change in offline
+  emitUserStatus(true)
+  cursorActivity()
+  socket.emit('online users')
+})
 socket.on('connect', function (data) {
-    clearInterval(retryTimer);
-    retryTimer = null;
-    personalInfo['id'] = socket.id;
-    showStatus(statusType.connected);
-    socket.emit('version');
-});
+  clearInterval(retryTimer)
+  retryTimer = null
+  window.personalInfo['id'] = socket.id
+  showStatus(statusType.connected)
+  socket.emit('version')
+})
 socket.on('version', function (data) {
-    if (version != data.version) {
-        if (version < data.minimumCompatibleVersion) {
-            setRefreshModal('incompatible-version');
-            setNeedRefresh();
-        } else {
-            setRefreshModal('new-version');
-        }
-    }
-});
-var authors = [];
-var authorship = [];
-var authorshipMarks = {};
-var authorMarks = {}; // temp variable
-var addTextMarkers = []; // temp variable
-function updateInfo(data) {
-    //console.log(data);
-    if (data.hasOwnProperty('createtime') && createtime !== data.createtime) {
-        createtime = data.createtime;
-        updateLastChange();
-    }
-    if (data.hasOwnProperty('updatetime') && lastchangetime !== data.updatetime) {
-        lastchangetime = data.updatetime;
-        updateLastChange();
-    }
-    if (data.hasOwnProperty('owner') && owner !== data.owner) {
-        owner = data.owner;
-        ownerprofile = data.ownerprofile;
-        updateOwner();
-    }
-    if (data.hasOwnProperty('lastchangeuser') && lastchangeuser !== data.lastchangeuser) {
-        lastchangeuser = data.lastchangeuser;
-        lastchangeuserprofile = data.lastchangeuserprofile;
-        updateLastChangeUser();
-        updateOwner();
-    }
-    if (data.hasOwnProperty('authors') && authors !== data.authors) {
-        authors = data.authors;
-    }
-    if (data.hasOwnProperty('authorship') && authorship !== data.authorship) {
-        authorship = data.authorship;
-        updateAuthorship();
+  if (version !== data.version) {
+    if (version < data.minimumCompatibleVersion) {
+      setRefreshModal('incompatible-version')
+      setNeedRefresh()
+    } else {
+      setRefreshModal('new-version')
     }
+  }
+})
+var authors = []
+var authorship = []
+var authorMarks = {} // temp variable
+var addTextMarkers = [] // temp variable
+function updateInfo (data) {
+    // console.log(data);
+  if (data.hasOwnProperty('createtime') && window.createtime !== data.createtime) {
+    window.createtime = data.createtime
+    updateLastChange()
+  }
+  if (data.hasOwnProperty('updatetime') && window.lastchangetime !== data.updatetime) {
+    window.lastchangetime = data.updatetime
+    updateLastChange()
+  }
+  if (data.hasOwnProperty('owner') && window.owner !== data.owner) {
+    window.owner = data.owner
+    window.ownerprofile = data.ownerprofile
+    updateOwner()
+  }
+  if (data.hasOwnProperty('lastchangeuser') && window.lastchangeuser !== data.lastchangeuser) {
+    window.lastchangeuser = data.lastchangeuser
+    window.lastchangeuserprofile = data.lastchangeuserprofile
+    updateLastChangeUser()
+    updateOwner()
+  }
+  if (data.hasOwnProperty('authors') && authors !== data.authors) {
+    authors = data.authors
+  }
+  if (data.hasOwnProperty('authorship') && authorship !== data.authorship) {
+    authorship = data.authorship
+    updateAuthorship()
+  }
 }
 var updateAuthorship = _.debounce(function () {
-    editor.operation(updateAuthorshipInner);
-}, 50);
-function initMark() {
-    return {
-        gutter: {
-            userid: null,
-            timestamp: null
-        },
-        textmarkers: []
-    };
+  editor.operation(updateAuthorshipInner)
+}, 50)
+function initMark () {
+  return {
+    gutter: {
+      userid: null,
+      timestamp: null
+    },
+    textmarkers: []
+  }
 }
-function initMarkAndCheckGutter(mark, author, timestamp) {
-    if (!mark) mark = initMark();
-    if (!mark.gutter.userid || mark.gutter.timestamp > timestamp) {
-        mark.gutter.userid = author.userid;
-        mark.gutter.timestamp = timestamp;
-    }
-    return mark;
+function initMarkAndCheckGutter (mark, author, timestamp) {
+  if (!mark) mark = initMark()
+  if (!mark.gutter.userid || mark.gutter.timestamp > timestamp) {
+    mark.gutter.userid = author.userid
+    mark.gutter.timestamp = timestamp
+  }
+  return mark
 }
-var gutterStylePrefix = "border-left: 3px solid ";
-var gutterStylePostfix = "; height: " + defaultTextHeight + "px; margin-left: 3px;";
-var textMarkderStylePrefix = "background-image: linear-gradient(to top, ";
-var textMarkderStylePostfix = " 1px, transparent 1px);";
+var gutterStylePrefix = 'border-left: 3px solid '
+var gutterStylePostfix = '; height: ' + defaultTextHeight + 'px; margin-left: 3px;'
+var textMarkderStylePrefix = 'background-image: linear-gradient(to top, '
+var textMarkderStylePostfix = ' 1px, transparent 1px);'
 var addStyleRule = (function () {
-    var added = {};
-    var styleElement = document.createElement('style');
-    document.documentElement.getElementsByTagName('head')[0].appendChild(styleElement);
-    var styleSheet = styleElement.sheet;
+  var added = {}
+  var styleElement = document.createElement('style')
+  document.documentElement.getElementsByTagName('head')[0].appendChild(styleElement)
+  var styleSheet = styleElement.sheet
 
-    return function (css) {
-        if (added[css]) {
-            return;
-        }
-        added[css] = true;
-        styleSheet.insertRule(css, (styleSheet.cssRules || styleSheet.rules).length);
-    };
-}());
-function updateAuthorshipInner() {
+  return function (css) {
+    if (added[css]) {
+      return
+    }
+    added[css] = true
+    styleSheet.insertRule(css, (styleSheet.cssRules || styleSheet.rules).length)
+  }
+}())
+function updateAuthorshipInner () {
     // ignore when ot not synced yet
-    if (havePendingOperation()) return;
-    authorMarks = {};
-    for (var i = 0; i < authorship.length; i++) {
-        var atom = authorship[i];
-        var author = authors[atom[0]];
-        if (author) {
-            var prePos = editor.posFromIndex(atom[1]);
-            var preLine = editor.getLine(prePos.line);
-            var postPos = editor.posFromIndex(atom[2]);
-            var postLine = editor.getLine(postPos.line);
-            if (prePos.ch == 0 && postPos.ch == postLine.length) {
-                for (var j = prePos.line; j <= postPos.line; j++) {
-                    if (editor.getLine(j)) {
-                        authorMarks[j] = initMarkAndCheckGutter(authorMarks[j], author, atom[3]);
-                    }
-                }
-            } else if (postPos.line - prePos.line >= 1) {
-                var startLine = prePos.line;
-                var endLine = postPos.line;
-                if (prePos.ch == preLine.length) {
-                    startLine++;
-                } else if (prePos.ch != 0) {
-                    var mark = initMarkAndCheckGutter(authorMarks[prePos.line], author, atom[3]);
-                    var _postPos = {
-                        line: prePos.line,
-                        ch: preLine.length
-                    };
-                    if (JSON.stringify(prePos) != JSON.stringify(_postPos)) {
-                        mark.textmarkers.push({
-                            userid: author.userid,
-                            pos: [prePos, _postPos]
-                        });
-                        startLine++;
-                    }
-                    authorMarks[prePos.line] = mark;
-                }
-                if (postPos.ch == 0) {
-                    endLine--;
-                } else if (postPos.ch != postLine.length) {
-                    var mark = initMarkAndCheckGutter(authorMarks[postPos.line], author, atom[3]);
-                    var _prePos = {
-                        line: postPos.line,
-                        ch: 0
-                    };
-                    if (JSON.stringify(_prePos) != JSON.stringify(postPos)) {
-                        mark.textmarkers.push({
-                            userid: author.userid,
-                            pos: [_prePos, postPos]
-                        });
-                        endLine--;
-                    }
-                    authorMarks[postPos.line] = mark;
-                }
-                for (var j = startLine; j <= endLine; j++) {
-                    if (editor.getLine(j)) {
-                        authorMarks[j] = initMarkAndCheckGutter(authorMarks[j], author, atom[3]);
-                    }
-                }
-            } else {
-                var mark = initMarkAndCheckGutter(authorMarks[prePos.line], author, atom[3]);
-                if (JSON.stringify(prePos) != JSON.stringify(postPos)) {
-                    mark.textmarkers.push({
-                        userid: author.userid,
-                        pos: [prePos, postPos]
-                    });
-                }
-                authorMarks[prePos.line] = mark;
-            }
+  if (havePendingOperation()) return
+  authorMarks = {}
+  for (let i = 0; i < authorship.length; i++) {
+    var atom = authorship[i]
+    let author = authors[atom[0]]
+    if (author) {
+      var prePos = editor.posFromIndex(atom[1])
+      var preLine = editor.getLine(prePos.line)
+      var postPos = editor.posFromIndex(atom[2])
+      var postLine = editor.getLine(postPos.line)
+      if (prePos.ch === 0 && postPos.ch === postLine.length) {
+        for (let j = prePos.line; j <= postPos.line; j++) {
+          if (editor.getLine(j)) {
+            authorMarks[j] = initMarkAndCheckGutter(authorMarks[j], author, atom[3])
+          }
         }
+      } else if (postPos.line - prePos.line >= 1) {
+        var startLine = prePos.line
+        var endLine = postPos.line
+        if (prePos.ch === preLine.length) {
+          startLine++
+        } else if (prePos.ch !== 0) {
+          let mark = initMarkAndCheckGutter(authorMarks[prePos.line], author, atom[3])
+          var _postPos = {
+            line: prePos.line,
+            ch: preLine.length
+          }
+          if (JSON.stringify(prePos) !== JSON.stringify(_postPos)) {
+            mark.textmarkers.push({ userid: author.userid, pos: [prePos, _postPos] })
+            startLine++
+          }
+          authorMarks[prePos.line] = mark
+        }
+        if (postPos.ch === 0) {
+          endLine--
+        } else if (postPos.ch !== postLine.length) {
+          let mark = initMarkAndCheckGutter(authorMarks[postPos.line], author, atom[3])
+          var _prePos = {
+            line: postPos.line,
+            ch: 0
+          }
+          if (JSON.stringify(_prePos) !== JSON.stringify(postPos)) {
+            mark.textmarkers.push({ userid: author.userid, pos: [_prePos, postPos] })
+            endLine--
+          }
+          authorMarks[postPos.line] = mark
+        }
+        for (let j = startLine; j <= endLine; j++) {
+          if (editor.getLine(j)) {
+            authorMarks[j] = initMarkAndCheckGutter(authorMarks[j], author, atom[3])
+          }
+        }
+      } else {
+        let mark = initMarkAndCheckGutter(authorMarks[prePos.line], author, atom[3])
+        if (JSON.stringify(prePos) !== JSON.stringify(postPos)) {
+          mark.textmarkers.push({
+            userid: author.userid,
+            pos: [prePos, postPos]
+          })
+        }
+        authorMarks[prePos.line] = mark
+      }
     }
-    addTextMarkers = [];
-    editor.eachLine(iterateLine);
-    var allTextMarks = editor.getAllMarks();
-    for (var i = 0; i < allTextMarks.length; i++) {
-        var _textMarker = allTextMarks[i];
-        var pos = _textMarker.find();
-        var found = false;
-        for (var j = 0; j < addTextMarkers.length; j++) {
-            var textMarker = addTextMarkers[j];
-            var author = authors[textMarker.userid];
-            var className = 'authorship-inline-' + author.color.substr(1);
-            var obj = {
-                from: textMarker.pos[0],
-                to: textMarker.pos[1]
-            };
-            if (JSON.stringify(pos) == JSON.stringify(obj) && _textMarker.className &&
+  }
+  addTextMarkers = []
+  editor.eachLine(iterateLine)
+  var allTextMarks = editor.getAllMarks()
+  for (let i = 0; i < allTextMarks.length; i++) {
+    let _textMarker = allTextMarks[i]
+    var pos = _textMarker.find()
+    var found = false
+    for (let j = 0; j < addTextMarkers.length; j++) {
+      let textMarker = addTextMarkers[j]
+      let author = authors[textMarker.userid]
+      let className = 'authorship-inline-' + author.color.substr(1)
+      var obj = {
+        from: textMarker.pos[0],
+        to: textMarker.pos[1]
+      }
+      if (JSON.stringify(pos) === JSON.stringify(obj) && _textMarker.className &&
                 _textMarker.className.indexOf(className) > -1) {
-                addTextMarkers.splice(j, 1);
-                j--;
-                found = true;
-                break;
-            }
-        }
-        if (!found && _textMarker.className && _textMarker.className.indexOf('authorship-inline') > -1) {
-            _textMarker.clear();
-        }
+        addTextMarkers.splice(j, 1)
+        j--
+        found = true
+        break
+      }
     }
-    for (var i = 0; i < addTextMarkers.length; i++) {
-        var textMarker = addTextMarkers[i];
-        var author = authors[textMarker.userid];
-        var rgbcolor = hex2rgb(author.color);
-        var colorString = "rgba(" + rgbcolor.red + "," + rgbcolor.green + "," + rgbcolor.blue + ",0.7)";
-        var styleString = textMarkderStylePrefix + colorString + textMarkderStylePostfix;
-        var className = 'authorship-inline-' + author.color.substr(1);
-        var rule = "." + className + "{" + styleString + "}";
-        addStyleRule(rule);
-        var _textMarker = editor.markText(textMarker.pos[0], textMarker.pos[1], {
-            className: 'authorship-inline ' + className,
-            title: author.name
-        });
+    if (!found && _textMarker.className && _textMarker.className.indexOf('authorship-inline') > -1) {
+      _textMarker.clear()
     }
-    authorshipMarks = authorMarks;
+  }
+  for (let i = 0; i < addTextMarkers.length; i++) {
+    let textMarker = addTextMarkers[i]
+    let author = authors[textMarker.userid]
+    var rgbcolor = hex2rgb(author.color)
+    var colorString = 'rgba(' + rgbcolor.red + ',' + rgbcolor.green + ',' + rgbcolor.blue + ',0.7)'
+    var styleString = textMarkderStylePrefix + colorString + textMarkderStylePostfix
+    let className = 'authorship-inline-' + author.color.substr(1)
+    var rule = '.' + className + '{' + styleString + '}'
+    addStyleRule(rule)
+    editor.markText(textMarker.pos[0], textMarker.pos[1], {
+      className: 'authorship-inline ' + className,
+      title: author.name
+    })
+  }
 }
-function iterateLine(line) {
-    var lineNumber = line.lineNo();
-    var currMark = authorMarks[lineNumber];
-    var author = currMark ? authors[currMark.gutter.userid] : null;
-    if (currMark && author) {
-        var className = 'authorship-gutter-' + author.color.substr(1);
-        var gutters = line.gutterMarkers;
-        if (!gutters || !gutters['authorship-gutters'] ||
+function iterateLine (line) {
+  var lineNumber = line.lineNo()
+  var currMark = authorMarks[lineNumber]
+  var author = currMark ? authors[currMark.gutter.userid] : null
+  if (currMark && author) {
+    let className = 'authorship-gutter-' + author.color.substr(1)
+    var gutters = line.gutterMarkers
+    if (!gutters || !gutters['authorship-gutters'] ||
             !gutters['authorship-gutters'].className ||
             !gutters['authorship-gutters'].className.indexOf(className) < 0) {
-            var styleString = gutterStylePrefix + author.color + gutterStylePostfix;
-            var rule = "." + className + "{" + styleString + "}";
-            addStyleRule(rule);
-            var gutter = $('<div>', {
-                class: 'authorship-gutter ' + className,
-                title: author.name
-            });
-            editor.setGutterMarker(line, "authorship-gutters", gutter[0]);
-        }
-    } else {
-        editor.setGutterMarker(line, "authorship-gutters", null);
+      var styleString = gutterStylePrefix + author.color + gutterStylePostfix
+      var rule = '.' + className + '{' + styleString + '}'
+      addStyleRule(rule)
+      var gutter = $('<div>', {
+        class: 'authorship-gutter ' + className,
+        title: author.name
+      })
+      editor.setGutterMarker(line, 'authorship-gutters', gutter[0])
     }
-    if (currMark && currMark.textmarkers.length > 0) {
-        for (var i = 0; i < currMark.textmarkers.length; i++) {
-            var textMarker = currMark.textmarkers[i];
-            if (textMarker.userid != currMark.gutter.userid) {
-                addTextMarkers.push(textMarker);
-            }
-        }
+  } else {
+    editor.setGutterMarker(line, 'authorship-gutters', null)
+  }
+  if (currMark && currMark.textmarkers.length > 0) {
+    for (var i = 0; i < currMark.textmarkers.length; i++) {
+      let textMarker = currMark.textmarkers[i]
+      if (textMarker.userid !== currMark.gutter.userid) {
+        addTextMarkers.push(textMarker)
+      }
     }
+  }
 }
 editor.on('update', function () {
-    $('.authorship-gutter:not([data-original-title])').tooltip({
-        container: '.CodeMirror-lines',
-        placement: 'right',
-        delay: { "show": 500, "hide": 100 }
-    });
-    $('.authorship-inline:not([data-original-title])').tooltip({
-        container: '.CodeMirror-lines',
-        placement: 'bottom',
-        delay: { "show": 500, "hide": 100 }
-    });
+  $('.authorship-gutter:not([data-original-title])').tooltip({
+    container: '.CodeMirror-lines',
+    placement: 'right',
+    delay: { 'show': 500, 'hide': 100 }
+  })
+  $('.authorship-inline:not([data-original-title])').tooltip({
+    container: '.CodeMirror-lines',
+    placement: 'bottom',
+    delay: { 'show': 500, 'hide': 100 }
+  })
     // clear tooltip which described element has been removed
-    $('[id^="tooltip"]').each(function (index, element) {
-        var $ele = $(element);
-        if ($('[aria-describedby="' + $ele.attr('id') + '"]').length <= 0) $ele.remove();
-    });
-});
+  $('[id^="tooltip"]').each(function (index, element) {
+    var $ele = $(element)
+    if ($('[aria-describedby="' + $ele.attr('id') + '"]').length <= 0) $ele.remove()
+  })
+})
 socket.on('check', function (data) {
-    //console.log(data);
-    updateInfo(data);
-});
+    // console.log(data);
+  updateInfo(data)
+})
 socket.on('permission', function (data) {
-    updatePermission(data.permission);
-});
-var docmaxlength = null;
-var permission = null;
+  updatePermission(data.permission)
+})
+var docmaxlength = null
+var permission = null
 socket.on('refresh', function (data) {
-    //console.log(data);
-    docmaxlength = data.docmaxlength;
-    editor.setOption("maxLength", docmaxlength);
-    updateInfo(data);
-    updatePermission(data.permission);
-    if (!loaded) {
+    // console.log(data);
+  docmaxlength = data.docmaxlength
+  editor.setOption('maxLength', docmaxlength)
+  updateInfo(data)
+  updatePermission(data.permission)
+  if (!window.loaded) {
         // auto change mode if no content detected
-        var nocontent = editor.getValue().length <= 0;
-        if (nocontent) {
-            if (visibleXS)
-                currentMode = modeType.edit;
-            else
-                currentMode = modeType.both;
-        }
-        // parse mode from url
-        if (window.location.search.length > 0) {
-            var urlMode = modeType[window.location.search.substr(1)];
-            if (urlMode) currentMode = urlMode;
-        }
-        changeMode(currentMode);
-        if (nocontent && !visibleXS) {
-            editor.focus();
-            editor.refresh();
-        }
-        updateViewInner(); // bring up view rendering earlier
-        updateHistory(); //update history whether have content or not
-        loaded = true;
-        emitUserStatus(); //send first user status
-        updateOnlineStatus(); //update first online status
-        setTimeout(function () {
-            //work around editor not refresh or doc not fully loaded
-            windowResizeInner();
-            //work around might not scroll to hash
-            scrollToHash();
-        }, 1);
+    var nocontent = editor.getValue().length <= 0
+    if (nocontent) {
+      if (window.visibleXS) { window.currentMode = modeType.edit } else { window.currentMode = modeType.both }
     }
-    if (editor.getOption('readOnly'))
-        editor.setOption('readOnly', false);
-});
+        // parse mode from url
+    if (window.location.search.length > 0) {
+      var urlMode = modeType[window.location.search.substr(1)]
+      if (urlMode) window.currentMode = urlMode
+    }
+    changeMode(window.currentMode)
+    if (nocontent && !window.visibleXS) {
+      editor.focus()
+      editor.refresh()
+    }
+    updateViewInner() // bring up view rendering earlier
+    updateHistory() // update history whether have content or not
+    window.loaded = true
+    emitUserStatus() // send first user status
+    updateOnlineStatus() // update first online status
+    setTimeout(function () {
+            // work around editor not refresh or doc not fully loaded
+      windowResizeInner()
+            // work around might not scroll to hash
+      scrollToHash()
+    }, 1)
+  }
+  if (editor.getOption('readOnly')) { editor.setOption('readOnly', false) }
+})
 
-var EditorClient = ot.EditorClient;
-var SocketIOAdapter = ot.SocketIOAdapter;
-var CodeMirrorAdapter = ot.CodeMirrorAdapter;
-var cmClient = null;
-var synchronized_ = null;
+var EditorClient = ot.EditorClient
+var SocketIOAdapter = ot.SocketIOAdapter
+var CodeMirrorAdapter = ot.CodeMirrorAdapter
+var cmClient = null
+var synchronized_ = null
 
-function havePendingOperation() {
-    return (cmClient && cmClient.state && cmClient.state.hasOwnProperty('outstanding')) ? true : false;
+function havePendingOperation () {
+  return !!((cmClient && cmClient.state && cmClient.state.hasOwnProperty('outstanding')))
 }
 
 socket.on('doc', function (obj) {
-    var body = obj.str;
-    var bodyMismatch = editor.getValue() !== body;
-    var setDoc = !cmClient || (cmClient && (cmClient.revision === -1 || (cmClient.revision !== obj.revision && !havePendingOperation()))) || obj.force;
+  var body = obj.str
+  var bodyMismatch = editor.getValue() !== body
+  var setDoc = !cmClient || (cmClient && (cmClient.revision === -1 || (cmClient.revision !== obj.revision && !havePendingOperation()))) || obj.force
 
-    saveInfo();
-    if (setDoc && bodyMismatch) {
-        if (cmClient) cmClient.editorAdapter.ignoreNextChange = true;
-        if (body) editor.setValue(body);
-        else editor.setValue("");
-    }
+  saveInfo()
+  if (setDoc && bodyMismatch) {
+    if (cmClient) cmClient.editorAdapter.ignoreNextChange = true
+    if (body) editor.setValue(body)
+    else editor.setValue('')
+  }
 
-    if (!loaded) {
-        editor.clearHistory();
-        ui.spinner.hide();
-        ui.content.fadeIn();
-    } else {
-        //if current doc is equal to the doc before disconnect
-        if (setDoc && bodyMismatch) editor.clearHistory();
-        else if (lastInfo.history) editor.setHistory(lastInfo.history);
-        lastInfo.history = null;
-    }
+  if (!window.loaded) {
+    editor.clearHistory()
+    ui.spinner.hide()
+    ui.content.fadeIn()
+  } else {
+        // if current doc is equal to the doc before disconnect
+    if (setDoc && bodyMismatch) editor.clearHistory()
+    else if (window.lastInfo.history) editor.setHistory(window.lastInfo.history)
+    window.lastInfo.history = null
+  }
 
-    if (!cmClient) {
-        cmClient = window.cmClient = new EditorClient(
+  if (!cmClient) {
+    cmClient = window.cmClient = new EditorClient(
             obj.revision, obj.clients,
             new SocketIOAdapter(socket), new CodeMirrorAdapter(editor)
-        );
-        synchronized_ = cmClient.state;
-    } else if (setDoc) {
-        if (bodyMismatch) {
-            cmClient.undoManager.undoStack.length = 0;
-            cmClient.undoManager.redoStack.length = 0;
-        }
-        cmClient.revision = obj.revision;
-        cmClient.setState(synchronized_);
-        cmClient.initializeClientList();
-        cmClient.initializeClients(obj.clients);
-    } else if (havePendingOperation()) {
-        cmClient.serverReconnect();
+        )
+    synchronized_ = cmClient.state
+  } else if (setDoc) {
+    if (bodyMismatch) {
+      cmClient.undoManager.undoStack.length = 0
+      cmClient.undoManager.redoStack.length = 0
     }
+    cmClient.revision = obj.revision
+    cmClient.setState(synchronized_)
+    cmClient.initializeClientList()
+    cmClient.initializeClients(obj.clients)
+  } else if (havePendingOperation()) {
+    cmClient.serverReconnect()
+  }
 
-    if (setDoc && bodyMismatch) {
-        isDirty = true;
-        updateView();
-    }
+  if (setDoc && bodyMismatch) {
+    window.isDirty = true
+    updateView()
+  }
 
-    restoreInfo();
-});
+  restoreInfo()
+})
 
 socket.on('ack', function () {
-    isDirty = true;
-    updateView();
-});
+  window.isDirty = true
+  updateView()
+})
 
 socket.on('operation', function () {
-    isDirty = true;
-    updateView();
-});
+  window.isDirty = true
+  updateView()
+})
 
 socket.on('online users', function (data) {
-    if (debug)
-        console.debug(data);
-    onlineUsers = data.users;
-    updateOnlineStatus();
-    $('.CodeMirror-other-cursors').children().each(function (key, value) {
-        var found = false;
-        for (var i = 0; i < data.users.length; i++) {
-            var user = data.users[i];
-            if ($(this).attr('id') == user.id)
-                found = true;
-        }
-        if (!found)
-            $(this).stop(true).fadeOut("normal", function () {
-                $(this).remove();
-            });
-    });
+  if (debug) { console.debug(data) }
+  window.onlineUsers = data.users
+  updateOnlineStatus()
+  $('.CodeMirror-other-cursors').children().each(function (key, value) {
+    var found = false
     for (var i = 0; i < data.users.length; i++) {
-        var user = data.users[i];
-        if (user.id != socket.id)
-            buildCursor(user);
-        else
-            personalInfo = user;
+      var user = data.users[i]
+      if ($(this).attr('id') === user.id) { found = true }
     }
-});
+    if (!found) {
+      $(this).stop(true).fadeOut('normal', function () {
+        $(this).remove()
+      })
+    }
+  })
+  for (var i = 0; i < data.users.length; i++) {
+    var user = data.users[i]
+    if (user.id !== socket.id) { buildCursor(user) } else { window.personalInfo = user }
+  }
+})
 socket.on('user status', function (data) {
-    if (debug)
-        console.debug(data);
-    for (var i = 0; i < onlineUsers.length; i++) {
-        if (onlineUsers[i].id == data.id) {
-            onlineUsers[i] = data;
-        }
+  if (debug) { console.debug(data) }
+  for (var i = 0; i < window.onlineUsers.length; i++) {
+    if (window.onlineUsers[i].id === data.id) {
+      window.onlineUsers[i] = data
     }
-    updateOnlineStatus();
-    if (data.id != socket.id)
-        buildCursor(data);
-});
+  }
+  updateOnlineStatus()
+  if (data.id !== socket.id) { buildCursor(data) }
+})
 socket.on('cursor focus', function (data) {
-    if (debug)
-        console.debug(data);
-    for (var i = 0; i < onlineUsers.length; i++) {
-        if (onlineUsers[i].id == data.id) {
-            onlineUsers[i].cursor = data.cursor;
-        }
+  if (debug) { console.debug(data) }
+  for (var i = 0; i < window.onlineUsers.length; i++) {
+    if (window.onlineUsers[i].id === data.id) {
+      window.onlineUsers[i].cursor = data.cursor
     }
-    if (data.id != socket.id)
-        buildCursor(data);
-    //force show
-    var cursor = $('div[data-clientid="' + data.id + '"]');
-    if (cursor.length > 0) {
-        cursor.stop(true).fadeIn();
-    }
-});
+  }
+  if (data.id !== socket.id) { buildCursor(data) }
+    // force show
+  var cursor = $('div[data-clientid="' + data.id + '"]')
+  if (cursor.length > 0) {
+    cursor.stop(true).fadeIn()
+  }
+})
 socket.on('cursor activity', function (data) {
-    if (debug)
-        console.debug(data);
-    for (var i = 0; i < onlineUsers.length; i++) {
-        if (onlineUsers[i].id == data.id) {
-            onlineUsers[i].cursor = data.cursor;
-        }
+  if (debug) { console.debug(data) }
+  for (var i = 0; i < window.onlineUsers.length; i++) {
+    if (window.onlineUsers[i].id === data.id) {
+      window.onlineUsers[i].cursor = data.cursor
     }
-    if (data.id != socket.id)
-        buildCursor(data);
-});
+  }
+  if (data.id !== socket.id) { buildCursor(data) }
+})
 socket.on('cursor blur', function (data) {
-    if (debug)
-        console.debug(data);
-    for (var i = 0; i < onlineUsers.length; i++) {
-        if (onlineUsers[i].id == data.id) {
-            onlineUsers[i].cursor = null;
-        }
+  if (debug) { console.debug(data) }
+  for (var i = 0; i < window.onlineUsers.length; i++) {
+    if (window.onlineUsers[i].id === data.id) {
+      window.onlineUsers[i].cursor = null
     }
-    if (data.id != socket.id)
-        buildCursor(data);
-    //force hide
-    var cursor = $('div[data-clientid="' + data.id + '"]');
-    if (cursor.length > 0) {
-        cursor.stop(true).fadeOut();
-    }
-});
+  }
+  if (data.id !== socket.id) { buildCursor(data) }
+    // force hide
+  var cursor = $('div[data-clientid="' + data.id + '"]')
+  if (cursor.length > 0) {
+    cursor.stop(true).fadeOut()
+  }
+})
 
 var options = {
-    valueNames: ['id', 'name'],
-    item: '<li class="ui-user-item">\
-            <span class="id" style="display:none;"></span>\
-            <a href="#">\
-                <span class="pull-left"><i class="ui-user-icon"></i></span><span class="ui-user-name name"></span><span class="pull-right"><i class="fa fa-circle ui-user-status"></i></span>\
-            </a>\
-           </li>'
-};
-var onlineUserList = new List('online-user-list', options);
-var shortOnlineUserList = new List('short-online-user-list', options);
+  valueNames: ['id', 'name'],
+  item: '<li class="ui-user-item">' +
+            '<span class="id" style="display:none;"></span>' +
+            '<a href="#">' +
+                '<span class="pull-left"><i class="ui-user-icon"></i></span><span class="ui-user-name name"></span><span class="pull-right"><i class="fa fa-circle ui-user-status"></i></span>' +
+            '</a>' +
+           '</li>'
+}
+var onlineUserList = new List('online-user-list', options)
+var shortOnlineUserList = new List('short-online-user-list', options)
 
-function updateOnlineStatus() {
-    if (!loaded || !socket.connected) return;
-    var _onlineUsers = deduplicateOnlineUsers(onlineUsers);
-    showStatus(statusType.online, _onlineUsers.length);
-    var items = onlineUserList.items;
-    //update or remove current list items
-    for (var i = 0; i < items.length; i++) {
-        var found = false;
-        var foundindex = null;
-        for (var j = 0; j < _onlineUsers.length; j++) {
-            if (items[i].values().id == _onlineUsers[j].id) {
-                foundindex = j;
-                found = true;
-                break;
-            }
-        }
-        var id = items[i].values().id;
-        if (found) {
-            onlineUserList.get('id', id)[0].values(_onlineUsers[foundindex]);
-            shortOnlineUserList.get('id', id)[0].values(_onlineUsers[foundindex]);
-        } else {
-            onlineUserList.remove('id', id);
-            shortOnlineUserList.remove('id', id);
-        }
+function updateOnlineStatus () {
+  if (!window.loaded || !socket.connected) return
+  var _onlineUsers = deduplicateOnlineUsers(window.onlineUsers)
+  showStatus(statusType.online, _onlineUsers.length)
+  var items = onlineUserList.items
+    // update or remove current list items
+  for (let i = 0; i < items.length; i++) {
+    let found = false
+    let foundindex = null
+    for (let j = 0; j < _onlineUsers.length; j++) {
+      if (items[i].values().id === _onlineUsers[j].id) {
+        foundindex = j
+        found = true
+        break
+      }
     }
-    //add not in list items
-    for (var i = 0; i < _onlineUsers.length; i++) {
-        var found = false;
-        for (var j = 0; j < items.length; j++) {
-            if (items[j].values().id == _onlineUsers[i].id) {
-                found = true;
-                break;
-            }
-        }
-        if (!found) {
-            onlineUserList.add(_onlineUsers[i]);
-            shortOnlineUserList.add(_onlineUsers[i]);
-        }
+    let id = items[i].values().id
+    if (found) {
+      onlineUserList.get('id', id)[0].values(_onlineUsers[foundindex])
+      shortOnlineUserList.get('id', id)[0].values(_onlineUsers[foundindex])
+    } else {
+      onlineUserList.remove('id', id)
+      shortOnlineUserList.remove('id', id)
     }
-    //sorting
-    sortOnlineUserList(onlineUserList);
-    sortOnlineUserList(shortOnlineUserList);
-    //render list items
-    renderUserStatusList(onlineUserList);
-    renderUserStatusList(shortOnlineUserList);
+  }
+    // add not in list items
+  for (let i = 0; i < _onlineUsers.length; i++) {
+    let found = false
+    for (let j = 0; j < items.length; j++) {
+      if (items[j].values().id === _onlineUsers[i].id) {
+        found = true
+        break
+      }
+    }
+    if (!found) {
+      onlineUserList.add(_onlineUsers[i])
+      shortOnlineUserList.add(_onlineUsers[i])
+    }
+  }
+    // sorting
+  sortOnlineUserList(onlineUserList)
+  sortOnlineUserList(shortOnlineUserList)
+    // render list items
+  renderUserStatusList(onlineUserList)
+  renderUserStatusList(shortOnlineUserList)
 }
 
-function sortOnlineUserList(list) {
-    //sort order by isSelf, login state, idle state, alphabet name, color brightness
-    list.sort('', {
-        sortFunction: function (a, b) {
-            var usera = a.values();
-            var userb = b.values();
-            var useraIsSelf = (usera.id == personalInfo.id || (usera.login && usera.userid == personalInfo.userid));
-            var userbIsSelf = (userb.id == personalInfo.id || (userb.login && userb.userid == personalInfo.userid));
-            if (useraIsSelf && !userbIsSelf) {
-                return -1;
-            } else if (!useraIsSelf && userbIsSelf) {
-                return 1;
-            } else {
-                if (usera.login && !userb.login)
-                    return -1;
-                else if (!usera.login && userb.login)
-                    return 1;
-                else {
-                    if (!usera.idle && userb.idle)
-                        return -1;
-                    else if (usera.idle && !userb.idle)
-                        return 1;
-                    else {
-                        if (usera.name && userb.name && usera.name.toLowerCase() < userb.name.toLowerCase()) {
-                            return -1;
-                        } else if (usera.name && userb.name && usera.name.toLowerCase() > userb.name.toLowerCase()) {
-                            return 1;
-                        } else {
-                            if (usera.color && userb.color && usera.color.toLowerCase() < userb.color.toLowerCase())
-                                return -1;
-                            else if (usera.color && userb.color && usera.color.toLowerCase() > userb.color.toLowerCase())
-                                return 1;
-                            else
-                                return 0;
-                        }
-                    }
-                }
-            }
+function sortOnlineUserList (list) {
+    // sort order by isSelf, login state, idle state, alphabet name, color brightness
+  list.sort('', {
+    sortFunction: function (a, b) {
+      var usera = a.values()
+      var userb = b.values()
+      var useraIsSelf = (usera.id === window.personalInfo.id || (usera.login && usera.userid === window.personalInfo.userid))
+      var userbIsSelf = (userb.id === window.personalInfo.id || (userb.login && userb.userid === window.personalInfo.userid))
+      if (useraIsSelf && !userbIsSelf) {
+        return -1
+      } else if (!useraIsSelf && userbIsSelf) {
+        return 1
+      } else {
+        if (usera.login && !userb.login) { return -1 } else if (!usera.login && userb.login) { return 1 } else {
+          if (!usera.idle && userb.idle) { return -1 } else if (usera.idle && !userb.idle) { return 1 } else {
+            if (usera.name && userb.name && usera.name.toLowerCase() < userb.name.toLowerCase()) {
+              return -1
+            } else if (usera.name && userb.name && usera.name.toLowerCase() > userb.name.toLowerCase()) { return 1 } else { if (usera.color && userb.color && usera.color.toLowerCase() < userb.color.toLowerCase()) { return -1 } else if (usera.color && userb.color && usera.color.toLowerCase() > userb.color.toLowerCase()) { return 1 } else { return 0 } }
+          }
         }
-    });
+      }
+    }
+  })
 }
 
-function renderUserStatusList(list) {
-    var items = list.items;
-    for (var j = 0; j < items.length; j++) {
-        var item = items[j];
-        var userstatus = $(item.elm).find('.ui-user-status');
-        var usericon = $(item.elm).find('.ui-user-icon');
-        if (item.values().login && item.values().photo) {
-            usericon.css('background-image', 'url(' + item.values().photo + ')');
-            //add 1px more to right, make it feel aligned
-            usericon.css('margin-right', '6px');
-            $(item.elm).css('border-left', '4px solid ' + item.values().color);
-            usericon.css('margin-left', '-4px');
-        } else {
-            usericon.css('background-color', item.values().color);
-        }
-        userstatus.removeClass('ui-user-status-offline ui-user-status-online ui-user-status-idle');
-        if (item.values().idle)
-            userstatus.addClass('ui-user-status-idle');
-        else
-            userstatus.addClass('ui-user-status-online');
+function renderUserStatusList (list) {
+  var items = list.items
+  for (var j = 0; j < items.length; j++) {
+    var item = items[j]
+    var userstatus = $(item.elm).find('.ui-user-status')
+    var usericon = $(item.elm).find('.ui-user-icon')
+    if (item.values().login && item.values().photo) {
+      usericon.css('background-image', 'url(' + item.values().photo + ')')
+            // add 1px more to right, make it feel aligned
+      usericon.css('margin-right', '6px')
+      $(item.elm).css('border-left', '4px solid ' + item.values().color)
+      usericon.css('margin-left', '-4px')
+    } else {
+      usericon.css('background-color', item.values().color)
     }
+    userstatus.removeClass('ui-user-status-offline ui-user-status-online ui-user-status-idle')
+    if (item.values().idle) { userstatus.addClass('ui-user-status-idle') } else { userstatus.addClass('ui-user-status-online') }
+  }
 }
 
-function deduplicateOnlineUsers(list) {
-    var _onlineUsers = [];
-    for (var i = 0; i < list.length; i++) {
-        var user = $.extend({}, list[i]);
-        if (!user.userid)
-            _onlineUsers.push(user);
-        else {
-            var found = false;
-            for (var j = 0; j < _onlineUsers.length; j++) {
-                if (_onlineUsers[j].userid == user.userid) {
-                    //keep self color when login
-                    if (user.id == personalInfo.id) {
-                        _onlineUsers[j].color = user.color;
-                    }
-                    //keep idle state if any of self client not idle
-                    if (!user.idle) {
-                        _onlineUsers[j].idle = user.idle;
-                        _onlineUsers[j].color = user.color;
-                    }
-                    found = true;
-                    break;
-                }
-            }
-            if (!found)
-                _onlineUsers.push(user);
+function deduplicateOnlineUsers (list) {
+  var _onlineUsers = []
+  for (var i = 0; i < list.length; i++) {
+    var user = $.extend({}, list[i])
+    if (!user.userid) { _onlineUsers.push(user) } else {
+      var found = false
+      for (var j = 0; j < _onlineUsers.length; j++) {
+        if (_onlineUsers[j].userid === user.userid) {
+                    // keep self color when login
+          if (user.id === window.personalInfo.id) {
+            _onlineUsers[j].color = user.color
+          }
+                    // keep idle state if any of self client not idle
+          if (!user.idle) {
+            _onlineUsers[j].idle = user.idle
+            _onlineUsers[j].color = user.color
+          }
+          found = true
+          break
         }
+      }
+      if (!found) { _onlineUsers.push(user) }
     }
-    return _onlineUsers;
+  }
+  return _onlineUsers
 }
 
-var userStatusCache = null;
+var userStatusCache = null
 
-function emitUserStatus(force) {
-    if (!loaded) return;
-    var type = null;
-    if (visibleXS)
-        type = 'xs';
-    else if (visibleSM)
-        type = 'sm';
-    else if (visibleMD)
-        type = 'md';
-    else if (visibleLG)
-        type = 'lg';
+function emitUserStatus (force) {
+  if (!window.loaded) return
+  var type = null
+  if (window.visibleXS) { type = 'xs' } else if (window.visibleSM) { type = 'sm' } else if (window.visibleMD) { type = 'md' } else if (window.visibleLG) { type = 'lg' }
 
-    personalInfo['idle'] = idle.isAway;
-    personalInfo['type'] = type;
+  window.personalInfo['idle'] = idle.isAway
+  window.personalInfo['type'] = type
 
-    for (var i = 0; i < onlineUsers.length; i++) {
-        if (onlineUsers[i].id == personalInfo.id) {
-            onlineUsers[i] = personalInfo;
-        }
+  for (var i = 0; i < window.onlineUsers.length; i++) {
+    if (window.onlineUsers[i].id === window.personalInfo.id) {
+      window.onlineUsers[i] = window.personalInfo
     }
+  }
 
-    var userStatus = {
-        idle: idle.isAway,
-        type: type
-    };
+  var userStatus = {
+    idle: idle.isAway,
+    type: type
+  }
 
-    if (force || JSON.stringify(userStatus) != JSON.stringify(userStatusCache)) {
-        socket.emit('user status', userStatus);
-        userStatusCache = userStatus;
-    }
+  if (force || JSON.stringify(userStatus) !== JSON.stringify(userStatusCache)) {
+    socket.emit('user status', userStatus)
+    userStatusCache = userStatus
+  }
 }
 
-function checkCursorTag(coord, ele) {
-    if (!ele) return; // return if element not exists
+function checkCursorTag (coord, ele) {
+  if (!ele) return // return if element not exists
     // set margin
-    var tagRightMargin = 0;
-    var tagBottomMargin = 2;
+  var tagRightMargin = 0
+  var tagBottomMargin = 2
     // use sizer to get the real doc size (won't count status bar and gutters)
-    var docWidth = ui.area.codemirrorSizer.width();
-    var docHeight = ui.area.codemirrorSizer.height();
+  var docWidth = ui.area.codemirrorSizer.width()
     // get editor size (status bar not count in)
-    var editorWidth = ui.area.codemirror.width();
-    var editorHeight = ui.area.codemirror.height();
+  var editorHeight = ui.area.codemirror.height()
     // get element size
-    var width = ele.outerWidth();
-    var height = ele.outerHeight();
-    var padding = (ele.outerWidth() - ele.width()) / 2;
+  var width = ele.outerWidth()
+  var height = ele.outerHeight()
+  var padding = (ele.outerWidth() - ele.width()) / 2
     // get coord position
-    var left = coord.left;
-    var top = coord.top;
+  var left = coord.left
+  var top = coord.top
     // get doc top offset (to workaround with viewport)
-    var docTopOffset = ui.area.codemirrorSizerInner.position().top;
+  var docTopOffset = ui.area.codemirrorSizerInner.position().top
     // set offset
-    var offsetLeft = -3;
-    var offsetTop = defaultTextHeight;
+  var offsetLeft = -3
+  var offsetTop = defaultTextHeight
     // only do when have width and height
-    if (width > 0 && height > 0) {
+  if (width > 0 && height > 0) {
         // flip x when element right bound larger than doc width
-        if (left + width + offsetLeft + tagRightMargin > docWidth) {
-            offsetLeft = -(width + tagRightMargin) + padding + offsetLeft;
-        }
+    if (left + width + offsetLeft + tagRightMargin > docWidth) {
+      offsetLeft = -(width + tagRightMargin) + padding + offsetLeft
+    }
         // flip y when element bottom bound larger than doc height
         // and element top position is larger than element height
-        if (top + docTopOffset + height + offsetTop + tagBottomMargin > Math.max(editor.doc.height, editorHeight) && top + docTopOffset > height + tagBottomMargin) {
-            offsetTop = -(height);
-        }
+    if (top + docTopOffset + height + offsetTop + tagBottomMargin > Math.max(editor.doc.height, editorHeight) && top + docTopOffset > height + tagBottomMargin) {
+      offsetTop = -(height)
     }
+  }
     // set position
-    ele[0].style.left = offsetLeft + 'px';
-    ele[0].style.top = offsetTop + 'px';
+  ele[0].style.left = offsetLeft + 'px'
+  ele[0].style.top = offsetTop + 'px'
 }
 
-function buildCursor(user) {
-    if (currentMode == modeType.view) return;
-    if (!user.cursor) return;
-    var coord = editor.charCoords(user.cursor, 'windows');
-    coord.left = coord.left < 4 ? 4 : coord.left;
-    coord.top = coord.top < 0 ? 0 : coord.top;
-    var iconClass = 'fa-user';
-    switch (user.type) {
-        case 'xs':
-            iconClass = 'fa-mobile';
-            break;
-        case 'sm':
-            iconClass = 'fa-tablet';
-            break;
-        case 'md':
-            iconClass = 'fa-desktop';
-            break;
-        case 'lg':
-            iconClass = 'fa-desktop';
-            break;
-    }
-    if ($('.CodeMirror-other-cursors').length <= 0) {
-        $("<div class='CodeMirror-other-cursors'>").insertAfter('.CodeMirror-cursors');
-    }
-    if ($('div[data-clientid="' + user.id + '"]').length <= 0) {
-        var cursor = $('<div data-clientid="' + user.id + '" class="CodeMirror-other-cursor" style="display:none;"></div>');
-        cursor.attr('data-line', user.cursor.line);
-        cursor.attr('data-ch', user.cursor.ch);
-        cursor.attr('data-offset-left', 0);
-        cursor.attr('data-offset-top', 0);
+function buildCursor (user) {
+  if (window.currentMode === modeType.view) return
+  if (!user.cursor) return
+  var coord = editor.charCoords(user.cursor, 'windows')
+  coord.left = coord.left < 4 ? 4 : coord.left
+  coord.top = coord.top < 0 ? 0 : coord.top
+  var iconClass = 'fa-user'
+  switch (user.type) {
+    case 'xs':
+      iconClass = 'fa-mobile'
+      break
+    case 'sm':
+      iconClass = 'fa-tablet'
+      break
+    case 'md':
+      iconClass = 'fa-desktop'
+      break
+    case 'lg':
+      iconClass = 'fa-desktop'
+      break
+  }
+  if ($('.CodeMirror-other-cursors').length <= 0) {
+    $("<div class='CodeMirror-other-cursors'>").insertAfter('.CodeMirror-cursors')
+  }
+  if ($('div[data-clientid="' + user.id + '"]').length <= 0) {
+    let cursor = $('<div data-clientid="' + user.id + '" class="CodeMirror-other-cursor" style="display:none;"></div>')
+    cursor.attr('data-line', user.cursor.line)
+    cursor.attr('data-ch', user.cursor.ch)
+    cursor.attr('data-offset-left', 0)
+    cursor.attr('data-offset-top', 0)
 
-        var cursorbar = $('<div class="cursorbar">&nbsp;</div>');
-        cursorbar[0].style.height = defaultTextHeight + 'px';
-        cursorbar[0].style.borderLeft = '2px solid ' + user.color;
+    let cursorbar = $('<div class="cursorbar">&nbsp;</div>')
+    cursorbar[0].style.height = defaultTextHeight + 'px'
+    cursorbar[0].style.borderLeft = '2px solid ' + user.color
 
-        var icon = '<i class="fa ' + iconClass + '"></i>';
+    var icon = '<i class="fa ' + iconClass + '"></i>'
 
-        var cursortag = $('<div class="cursortag">' + icon + '&nbsp;<span class="name">' + user.name + '</span></div>');
-        //cursortag[0].style.background = color;
-        cursortag[0].style.color = user.color;
+    let cursortag = $('<div class="cursortag">' + icon + '&nbsp;<span class="name">' + user.name + '</span></div>')
+        // cursortag[0].style.background = color;
+    cursortag[0].style.color = user.color
 
-        cursor.attr('data-mode', 'hover');
-        cursortag.delay(2000).fadeOut("fast");
-        cursor.hover(
+    cursor.attr('data-mode', 'hover')
+    cursortag.delay(2000).fadeOut('fast')
+    cursor.hover(
             function () {
-                if (cursor.attr('data-mode') == 'hover')
-                    cursortag.stop(true).fadeIn("fast");
+              if (cursor.attr('data-mode') === 'hover') { cursortag.stop(true).fadeIn('fast') }
             },
             function () {
-                if (cursor.attr('data-mode') == 'hover')
-                    cursortag.stop(true).fadeOut("fast");
-            });
+              if (cursor.attr('data-mode') === 'hover') { cursortag.stop(true).fadeOut('fast') }
+            })
 
-        function switchMode(ele) {
-            if (ele.attr('data-mode') == 'state')
-                ele.attr('data-mode', 'hover');
-            else if (ele.attr('data-mode') == 'hover')
-                ele.attr('data-mode', 'state');
-        }
+    var hideCursorTagDelay = 2000
+    var hideCursorTagTimer = null
 
-        function switchTag(ele) {
-            if (ele.css('display') === 'none')
-                ele.stop(true).fadeIn("fast");
-            else
-                ele.stop(true).fadeOut("fast");
-        }
-        var hideCursorTagDelay = 2000;
-        var hideCursorTagTimer = null;
+    var switchMode = function (ele) {
+      if (ele.attr('data-mode') === 'state') { ele.attr('data-mode', 'hover') } else if (ele.attr('data-mode') === 'hover') { ele.attr('data-mode', 'state') }
+    }
 
-        function hideCursorTag() {
-            if (cursor.attr('data-mode') == 'hover')
-                cursortag.fadeOut("fast");
-        }
-        cursor.on('touchstart', function (e) {
-            var display = cursortag.css('display');
-            cursortag.stop(true).fadeIn("fast");
-            clearTimeout(hideCursorTagTimer);
-            hideCursorTagTimer = setTimeout(hideCursorTag, hideCursorTagDelay);
-            if (display === 'none') {
-                e.preventDefault();
-                e.stopPropagation();
-            }
-        });
-        cursortag.on('mousedown touchstart', function (e) {
-            if (cursor.attr('data-mode') == 'state')
-                switchTag(cursortag);
-            switchMode(cursor);
-            e.preventDefault();
-            e.stopPropagation();
-        });
+    var switchTag = function (ele) {
+      if (ele.css('display') === 'none') { ele.stop(true).fadeIn('fast') } else { ele.stop(true).fadeOut('fast') }
+    }
 
-        cursor.append(cursorbar);
-        cursor.append(cursortag);
+    var hideCursorTag = function () {
+      if (cursor.attr('data-mode') === 'hover') { cursortag.fadeOut('fast') }
+    }
+    cursor.on('touchstart', function (e) {
+      var display = cursortag.css('display')
+      cursortag.stop(true).fadeIn('fast')
+      clearTimeout(hideCursorTagTimer)
+      hideCursorTagTimer = setTimeout(hideCursorTag, hideCursorTagDelay)
+      if (display === 'none') {
+        e.preventDefault()
+        e.stopPropagation()
+      }
+    })
+    cursortag.on('mousedown touchstart', function (e) {
+      if (cursor.attr('data-mode') === 'state') { switchTag(cursortag) }
+      switchMode(cursor)
+      e.preventDefault()
+      e.stopPropagation()
+    })
 
-        cursor[0].style.left = coord.left + 'px';
-        cursor[0].style.top = coord.top + 'px';
-        $('.CodeMirror-other-cursors').append(cursor);
+    cursor.append(cursorbar)
+    cursor.append(cursortag)
 
-        if (!user.idle)
-            cursor.stop(true).fadeIn();
+    cursor[0].style.left = coord.left + 'px'
+    cursor[0].style.top = coord.top + 'px'
+    $('.CodeMirror-other-cursors').append(cursor)
 
-        checkCursorTag(coord, cursortag);
+    if (!user.idle) { cursor.stop(true).fadeIn() }
+
+    checkCursorTag(coord, cursortag)
+  } else {
+    let cursor = $('div[data-clientid="' + user.id + '"]')
+    cursor.attr('data-line', user.cursor.line)
+    cursor.attr('data-ch', user.cursor.ch)
+
+    let cursorbar = cursor.find('.cursorbar')
+    cursorbar[0].style.height = defaultTextHeight + 'px'
+    cursorbar[0].style.borderLeft = '2px solid ' + user.color
+
+    let cursortag = cursor.find('.cursortag')
+    cursortag.find('i').removeClass().addClass('fa').addClass(iconClass)
+    cursortag.find('.name').text(user.name)
+
+    if (cursor.css('display') === 'none') {
+      cursor[0].style.left = coord.left + 'px'
+      cursor[0].style.top = coord.top + 'px'
     } else {
-        var cursor = $('div[data-clientid="' + user.id + '"]');
-        var lineDiff = Math.abs(cursor.attr('data-line') - user.cursor.line);
-        cursor.attr('data-line', user.cursor.line);
-        cursor.attr('data-ch', user.cursor.ch);
-
-        var cursorbar = cursor.find('.cursorbar');
-        cursorbar[0].style.height = defaultTextHeight + 'px';
-        cursorbar[0].style.borderLeft = '2px solid ' + user.color;
-
-        var cursortag = cursor.find('.cursortag');
-        cursortag.find('i').removeClass().addClass('fa').addClass(iconClass);
-        cursortag.find(".name").text(user.name);
-
-        if (cursor.css('display') === 'none') {
-            cursor[0].style.left = coord.left + 'px';
-            cursor[0].style.top = coord.top + 'px';
-        } else {
-            cursor.animate({
-                "left": coord.left,
-                "top": coord.top
-            }, {
-                    duration: cursorAnimatePeriod,
-                    queue: false
-                });
-        }
-
-        if (user.idle && cursor.css('display') !== 'none')
-            cursor.stop(true).fadeOut();
-        else if (!user.idle && cursor.css('display') === 'none')
-            cursor.stop(true).fadeIn();
-
-        checkCursorTag(coord, cursortag);
+      cursor.animate({
+        'left': coord.left,
+        'top': coord.top
+      }, {
+        duration: cursorAnimatePeriod,
+        queue: false
+      })
     }
+
+    if (user.idle && cursor.css('display') !== 'none') { cursor.stop(true).fadeOut() } else if (!user.idle && cursor.css('display') === 'none') { cursor.stop(true).fadeIn() }
+
+    checkCursorTag(coord, cursortag)
+  }
 }
 
-//editor actions
-function removeNullByte(cm, change) {
-    var str = change.text.join("\n");
-    if (/\u0000/g.test(str) && change.update) {
-        change.update(change.from, change.to, str.replace(/\u0000/g, "").split("\n"));
-    }
+// editor actions
+function removeNullByte (cm, change) {
+  var str = change.text.join('\n')
+  if (/\u0000/g.test(str) && change.update) {
+    change.update(change.from, change.to, str.replace(/\u0000/g, '').split('\n'))
+  }
 }
-function enforceMaxLength(cm, change) {
-    var maxLength = cm.getOption("maxLength");
-    if (maxLength && change.update) {
-        var str = change.text.join("\n");
-        var delta = str.length - (cm.indexFromPos(change.to) - cm.indexFromPos(change.from));
-        if (delta <= 0) {
-            return false;
-        }
-        delta = cm.getValue().length + delta - maxLength;
-        if (delta > 0) {
-            str = str.substr(0, str.length - delta);
-            change.update(change.from, change.to, str.split("\n"));
-            return true;
-        }
+function enforceMaxLength (cm, change) {
+  var maxLength = cm.getOption('maxLength')
+  if (maxLength && change.update) {
+    var str = change.text.join('\n')
+    var delta = str.length - (cm.indexFromPos(change.to) - cm.indexFromPos(change.from))
+    if (delta <= 0) {
+      return false
     }
-    return false;
+    delta = cm.getValue().length + delta - maxLength
+    if (delta > 0) {
+      str = str.substr(0, str.length - delta)
+      change.update(change.from, change.to, str.split('\n'))
+      return true
+    }
+  }
+  return false
 }
-var ignoreEmitEvents = ['setValue', 'ignoreHistory'];
+var ignoreEmitEvents = ['setValue', 'ignoreHistory']
 editor.on('beforeChange', function (cm, change) {
-    if (debug)
-        console.debug(change);
-    removeNullByte(cm, change);
-    if (enforceMaxLength(cm, change)) {
-        $('.limit-modal').modal('show');
+  if (debug) { console.debug(change) }
+  removeNullByte(cm, change)
+  if (enforceMaxLength(cm, change)) {
+    $('.limit-modal').modal('show')
+  }
+  var isIgnoreEmitEvent = (ignoreEmitEvents.indexOf(change.origin) !== -1)
+  if (!isIgnoreEmitEvent) {
+    if (!havePermission()) {
+      change.canceled = true
+      switch (permission) {
+        case 'editable':
+          $('.signin-modal').modal('show')
+          break
+        case 'locked':
+        case 'private':
+          $('.locked-modal').modal('show')
+          break
+      }
     }
-    var isIgnoreEmitEvent = (ignoreEmitEvents.indexOf(change.origin) != -1);
-    if (!isIgnoreEmitEvent) {
-        if (!havePermission()) {
-            change.canceled = true;
-            switch (permission) {
-                case "editable":
-                    $('.signin-modal').modal('show');
-                    break;
-                case "locked":
-                case "private":
-                    $('.locked-modal').modal('show');
-                    break;
-            }
-        }
-    } else {
-        if (change.origin == 'ignoreHistory') {
-            setHaveUnreadChanges(true);
-            updateTitleReminder();
-        }
+  } else {
+    if (change.origin === 'ignoreHistory') {
+      setHaveUnreadChanges(true)
+      updateTitleReminder()
     }
-    if (cmClient && !socket.connected)
-        cmClient.editorAdapter.ignoreNextChange = true;
-});
+  }
+  if (cmClient && !socket.connected) { cmClient.editorAdapter.ignoreNextChange = true }
+})
 editor.on('cut', function () {
-    //na
-});
+    // na
+})
 editor.on('paste', function () {
-    //na
-});
+    // na
+})
 editor.on('changes', function (cm, changes) {
-    updateHistory();
-    var docLength = editor.getValue().length;
-    //workaround for big documents
-    var newViewportMargin = 20;
-    if (docLength > 20000) {
-        newViewportMargin = 1;
-    } else if (docLength > 10000) {
-        newViewportMargin = 10;
-    } else if (docLength > 5000) {
-        newViewportMargin = 15;
+  updateHistory()
+  var docLength = editor.getValue().length
+    // workaround for big documents
+  var newViewportMargin = 20
+  if (docLength > 20000) {
+    newViewportMargin = 1
+  } else if (docLength > 10000) {
+    newViewportMargin = 10
+  } else if (docLength > 5000) {
+    newViewportMargin = 15
+  }
+  if (newViewportMargin !== viewportMargin) {
+    viewportMargin = newViewportMargin
+    windowResize()
+  }
+  checkEditorScrollbar()
+  if (ui.area.codemirrorScroll[0].scrollHeight > ui.area.view[0].scrollHeight && editorHasFocus()) {
+    postUpdateEvent = function () {
+      syncScrollToView()
+      postUpdateEvent = null
     }
-    if (newViewportMargin != viewportMargin) {
-        viewportMargin = newViewportMargin;
-        windowResize();
-    }
-    checkEditorScrollbar();
-    if (ui.area.codemirrorScroll[0].scrollHeight > ui.area.view[0].scrollHeight && editorHasFocus()) {
-        postUpdateEvent = function () {
-            syncScrollToView();
-            postUpdateEvent = null;
-        };
-    }
-});
+  }
+})
 editor.on('focus', function (cm) {
-    for (var i = 0; i < onlineUsers.length; i++) {
-        if (onlineUsers[i].id == personalInfo.id) {
-            onlineUsers[i].cursor = editor.getCursor();
-        }
+  for (var i = 0; i < window.onlineUsers.length; i++) {
+    if (window.onlineUsers[i].id === window.personalInfo.id) {
+      window.onlineUsers[i].cursor = editor.getCursor()
     }
-    personalInfo['cursor'] = editor.getCursor();
-    socket.emit('cursor focus', editor.getCursor());
-});
+  }
+  window.personalInfo['cursor'] = editor.getCursor()
+  socket.emit('cursor focus', editor.getCursor())
+})
 editor.on('cursorActivity', function (cm) {
-    updateStatusBar();
-    cursorActivity();
-});
+  updateStatusBar()
+  cursorActivity()
+})
 editor.on('beforeSelectionChange', function (doc, selections) {
-    if (selections)
-        selection = selections.ranges[0];
-    else
-        selection = null;
-    updateStatusBar();
-});
+  if (selections) { selection = selections.ranges[0] } else { selection = null }
+  updateStatusBar()
+})
 
-var cursorActivity = _.debounce(cursorActivityInner, cursorActivityDebounce);
+var cursorActivity = _.debounce(cursorActivityInner, cursorActivityDebounce)
 
-function cursorActivityInner() {
-    if (editorHasFocus() && !Visibility.hidden()) {
-        for (var i = 0; i < onlineUsers.length; i++) {
-            if (onlineUsers[i].id == personalInfo.id) {
-                onlineUsers[i].cursor = editor.getCursor();
-            }
-        }
-        personalInfo['cursor'] = editor.getCursor();
-        socket.emit('cursor activity', editor.getCursor());
+function cursorActivityInner () {
+  if (editorHasFocus() && !Visibility.hidden()) {
+    for (var i = 0; i < window.onlineUsers.length; i++) {
+      if (window.onlineUsers[i].id === window.personalInfo.id) {
+        window.onlineUsers[i].cursor = editor.getCursor()
+      }
     }
+    window.personalInfo['cursor'] = editor.getCursor()
+    socket.emit('cursor activity', editor.getCursor())
+  }
 }
 editor.on('blur', function (cm) {
-    for (var i = 0; i < onlineUsers.length; i++) {
-        if (onlineUsers[i].id == personalInfo.id) {
-            onlineUsers[i].cursor = null;
-        }
+  for (var i = 0; i < window.onlineUsers.length; i++) {
+    if (window.onlineUsers[i].id === window.personalInfo.id) {
+      window.onlineUsers[i].cursor = null
     }
-    personalInfo['cursor'] = null;
-    socket.emit('cursor blur');
-});
+  }
+  window.personalInfo['cursor'] = null
+  socket.emit('cursor blur')
+})
 
-function saveInfo() {
-    var scrollbarStyle = editor.getOption('scrollbarStyle');
-    var left = $(window).scrollLeft();
-    var top = $(window).scrollTop();
-    switch (currentMode) {
-        case modeType.edit:
-            if (scrollbarStyle == 'native') {
-                lastInfo.edit.scroll.left = left;
-                lastInfo.edit.scroll.top = top;
-            } else {
-                lastInfo.edit.scroll = editor.getScrollInfo();
-            }
-            break;
-        case modeType.view:
-            lastInfo.view.scroll.left = left;
-            lastInfo.view.scroll.top = top;
-            break;
-        case modeType.both:
-            lastInfo.edit.scroll = editor.getScrollInfo();
-            lastInfo.view.scroll.left = ui.area.view.scrollLeft();
-            lastInfo.view.scroll.top = ui.area.view.scrollTop();
-            break;
-    }
-    lastInfo.edit.cursor = editor.getCursor();
-    lastInfo.edit.selections = editor.listSelections();
-    lastInfo.needRestore = true;
+function saveInfo () {
+  var scrollbarStyle = editor.getOption('scrollbarStyle')
+  var left = $(window).scrollLeft()
+  var top = $(window).scrollTop()
+  switch (window.currentMode) {
+    case modeType.edit:
+      if (scrollbarStyle === 'native') {
+        window.lastInfo.edit.scroll.left = left
+        window.lastInfo.edit.scroll.top = top
+      } else {
+        window.lastInfo.edit.scroll = editor.getScrollInfo()
+      }
+      break
+    case modeType.view:
+      window.lastInfo.view.scroll.left = left
+      window.lastInfo.view.scroll.top = top
+      break
+    case modeType.both:
+      window.lastInfo.edit.scroll = editor.getScrollInfo()
+      window.lastInfo.view.scroll.left = ui.area.view.scrollLeft()
+      window.lastInfo.view.scroll.top = ui.area.view.scrollTop()
+      break
+  }
+  window.lastInfo.edit.cursor = editor.getCursor()
+  window.lastInfo.edit.selections = editor.listSelections()
+  window.lastInfo.needRestore = true
 }
 
-function restoreInfo() {
-    var scrollbarStyle = editor.getOption('scrollbarStyle');
-    if (lastInfo.needRestore) {
-        var line = lastInfo.edit.cursor.line;
-        var ch = lastInfo.edit.cursor.ch;
-        editor.setCursor(line, ch);
-        editor.setSelections(lastInfo.edit.selections);
-        switch (currentMode) {
-            case modeType.edit:
-                if (scrollbarStyle == 'native') {
-                    $(window).scrollLeft(lastInfo.edit.scroll.left);
-                    $(window).scrollTop(lastInfo.edit.scroll.top);
-                } else {
-                    var left = lastInfo.edit.scroll.left;
-                    var top = lastInfo.edit.scroll.top;
-                    editor.scrollIntoView();
-                    editor.scrollTo(left, top);
-                }
-                break;
-            case modeType.view:
-                $(window).scrollLeft(lastInfo.view.scroll.left);
-                $(window).scrollTop(lastInfo.view.scroll.top);
-                break;
-            case modeType.both:
-                var left = lastInfo.edit.scroll.left;
-                var top = lastInfo.edit.scroll.top;
-                editor.scrollIntoView();
-                editor.scrollTo(left, top);
-                ui.area.view.scrollLeft(lastInfo.view.scroll.left);
-                ui.area.view.scrollTop(lastInfo.view.scroll.top);
-                break;
+function restoreInfo () {
+  var scrollbarStyle = editor.getOption('scrollbarStyle')
+  if (window.lastInfo.needRestore) {
+    var line = window.lastInfo.edit.cursor.line
+    var ch = window.lastInfo.edit.cursor.ch
+    editor.setCursor(line, ch)
+    editor.setSelections(window.lastInfo.edit.selections)
+    switch (window.currentMode) {
+      case modeType.edit:
+        if (scrollbarStyle === 'native') {
+          $(window).scrollLeft(window.lastInfo.edit.scroll.left)
+          $(window).scrollTop(window.lastInfo.edit.scroll.top)
+        } else {
+          let left = window.lastInfo.edit.scroll.left
+          let top = window.lastInfo.edit.scroll.top
+          editor.scrollIntoView()
+          editor.scrollTo(left, top)
         }
-
-        lastInfo.needRestore = false;
+        break
+      case modeType.view:
+        $(window).scrollLeft(window.lastInfo.view.scroll.left)
+        $(window).scrollTop(window.lastInfo.view.scroll.top)
+        break
+      case modeType.both:
+        let left = window.lastInfo.edit.scroll.left
+        let top = window.lastInfo.edit.scroll.top
+        editor.scrollIntoView()
+        editor.scrollTo(left, top)
+        ui.area.view.scrollLeft(window.lastInfo.view.scroll.left)
+        ui.area.view.scrollTop(window.lastInfo.view.scroll.top)
+        break
     }
+
+    window.lastInfo.needRestore = false
+  }
 }
 
-//view actions
-function refreshView() {
-    ui.area.markdown.html('');
-    isDirty = true;
-    updateViewInner();
+// view actions
+function refreshView () {
+  ui.area.markdown.html('')
+  window.isDirty = true
+  updateViewInner()
 }
 
 var updateView = _.debounce(function () {
-    editor.operation(updateViewInner);
-}, updateViewDebounce);
+  editor.operation(updateViewInner)
+}, updateViewDebounce)
 
-var lastResult = null;
-var postUpdateEvent = null;
+var lastResult = null
+var postUpdateEvent = null
 
-function updateViewInner() {
-    if (currentMode == modeType.edit || !isDirty) return;
-    var value = editor.getValue();
-    var lastMeta = md.meta;
-    md.meta = {};
-    delete md.metaError;
-    var rendered = md.render(value);
-    if (md.meta.type && md.meta.type === 'slide') {
-        var slideOptions = {
-            separator: '^(\r\n?|\n)---(\r\n?|\n)$',
-            verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$'
-        };
-        var slides = RevealMarkdown.slidify(editor.getValue(), slideOptions);
-        ui.area.markdown.html(slides);
-        RevealMarkdown.initialize();
-        // prevent XSS
-        ui.area.markdown.html(preventXSS(ui.area.markdown.html()));
-        ui.area.markdown.addClass('slides');
-        syncscroll = false;
-        checkSyncToggle();
-    } else {
-        if (lastMeta.type && lastMeta.type === 'slide') {
-            refreshView();
-            ui.area.markdown.removeClass('slides');
-            syncscroll = true;
-            checkSyncToggle();
-        }
-        // only render again when meta changed
-        if (JSON.stringify(md.meta) != JSON.stringify(lastMeta)) {
-            parseMeta(md, ui.area.codemirror, ui.area.markdown, $('#ui-toc'), $('#ui-toc-affix'));
-            rendered = md.render(value);
-        }
-        // prevent XSS
-        rendered = preventXSS(rendered);
-        var result = postProcess(rendered).children().toArray();
-        partialUpdate(result, lastResult, ui.area.markdown.children().toArray());
-        if (result && lastResult && result.length != lastResult.length)
-            updateDataAttrs(result, ui.area.markdown.children().toArray());
-        lastResult = $(result).clone();
+function updateViewInner () {
+  if (window.currentMode === modeType.edit || !window.isDirty) return
+  var value = editor.getValue()
+  var lastMeta = md.meta
+  md.meta = {}
+  delete md.metaError
+  var rendered = md.render(value)
+  if (md.meta.type && md.meta.type === 'slide') {
+    var slideOptions = {
+      separator: '^(\r\n?|\n)---(\r\n?|\n)$',
+      verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$'
     }
-    finishView(ui.area.markdown);
-    autoLinkify(ui.area.markdown);
-    deduplicatedHeaderId(ui.area.markdown);
-    renderTOC(ui.area.markdown);
-    generateToc('ui-toc');
-    generateToc('ui-toc-affix');
-    generateScrollspy();
-    updateScrollspy();
-    smoothHashScroll();
-    isDirty = false;
-    clearMap();
-    //buildMap();
-    updateTitleReminder();
-    if (postUpdateEvent && typeof postUpdateEvent === 'function')
-        postUpdateEvent();
+    var slides = window.RevealMarkdown.slidify(editor.getValue(), slideOptions)
+    ui.area.markdown.html(slides)
+    window.RevealMarkdown.initialize()
+        // prevent XSS
+    ui.area.markdown.html(preventXSS(ui.area.markdown.html()))
+    ui.area.markdown.addClass('slides')
+    window.syncscroll = false
+    checkSyncToggle()
+  } else {
+    if (lastMeta.type && lastMeta.type === 'slide') {
+      refreshView()
+      ui.area.markdown.removeClass('slides')
+      window.syncscroll = true
+      checkSyncToggle()
+    }
+        // only render again when meta changed
+    if (JSON.stringify(md.meta) !== JSON.stringify(lastMeta)) {
+      parseMeta(md, ui.area.codemirror, ui.area.markdown, $('#ui-toc'), $('#ui-toc-affix'))
+      rendered = md.render(value)
+    }
+        // prevent XSS
+    rendered = preventXSS(rendered)
+    var result = postProcess(rendered).children().toArray()
+    partialUpdate(result, lastResult, ui.area.markdown.children().toArray())
+    if (result && lastResult && result.length !== lastResult.length) { updateDataAttrs(result, ui.area.markdown.children().toArray()) }
+    lastResult = $(result).clone()
+  }
+  finishView(ui.area.markdown)
+  autoLinkify(ui.area.markdown)
+  deduplicatedHeaderId(ui.area.markdown)
+  renderTOC(ui.area.markdown)
+  generateToc('ui-toc')
+  generateToc('ui-toc-affix')
+  generateScrollspy()
+  updateScrollspy()
+  smoothHashScroll()
+  window.isDirty = false
+  clearMap()
+    // buildMap();
+  updateTitleReminder()
+  if (postUpdateEvent && typeof postUpdateEvent === 'function') { postUpdateEvent() }
 }
 
-var updateHistoryDebounce = 600;
+var updateHistoryDebounce = 600
 
 var updateHistory = _.debounce(updateHistoryInner, updateHistoryDebounce)
 
-function updateHistoryInner() {
-    writeHistory(renderFilename(ui.area.markdown), renderTags(ui.area.markdown));
+function updateHistoryInner () {
+  writeHistory(renderFilename(ui.area.markdown), renderTags(ui.area.markdown))
 }
 
-function updateDataAttrs(src, des) {
-    //sync data attr startline and endline
-    for (var i = 0; i < src.length; i++) {
-        copyAttribute(src[i], des[i], 'data-startline');
-        copyAttribute(src[i], des[i], 'data-endline');
+function updateDataAttrs (src, des) {
+    // sync data attr startline and endline
+  for (var i = 0; i < src.length; i++) {
+    copyAttribute(src[i], des[i], 'data-startline')
+    copyAttribute(src[i], des[i], 'data-endline')
+  }
+}
+
+function partialUpdate (src, tar, des) {
+  if (!src || src.length === 0 || !tar || tar.length === 0 || !des || des.length === 0) {
+    ui.area.markdown.html(src)
+    return
+  }
+  if (src.length === tar.length) { // same length
+    for (let i = 0; i < src.length; i++) {
+      copyAttribute(src[i], des[i], 'data-startline')
+      copyAttribute(src[i], des[i], 'data-endline')
+      var rawSrc = cloneAndRemoveDataAttr(src[i])
+      var rawTar = cloneAndRemoveDataAttr(tar[i])
+      if (rawSrc.outerHTML !== rawTar.outerHTML) {
+                // console.log(rawSrc);
+                // console.log(rawTar);
+        $(des[i]).replaceWith(src[i])
+      }
     }
-}
-
-function partialUpdate(src, tar, des) {
-    if (!src || src.length == 0 || !tar || tar.length == 0 || !des || des.length == 0) {
-        ui.area.markdown.html(src);
-        return;
+  } else { // diff length
+    var start = 0
+    // find diff start position
+    for (let i = 0; i < tar.length; i++) {
+            // copyAttribute(src[i], des[i], 'data-startline');
+            // copyAttribute(src[i], des[i], 'data-endline');
+      let rawSrc = cloneAndRemoveDataAttr(src[i])
+      let rawTar = cloneAndRemoveDataAttr(tar[i])
+      if (!rawSrc || !rawTar || rawSrc.outerHTML !== rawTar.outerHTML) {
+        start = i
+        break
+      }
     }
-    if (src.length == tar.length) { //same length
-        for (var i = 0; i < src.length; i++) {
-            copyAttribute(src[i], des[i], 'data-startline');
-            copyAttribute(src[i], des[i], 'data-endline');
-            var rawSrc = cloneAndRemoveDataAttr(src[i]);
-            var rawTar = cloneAndRemoveDataAttr(tar[i]);
-            if (rawSrc.outerHTML != rawTar.outerHTML) {
-                //console.log(rawSrc);
-                //console.log(rawTar);
-                $(des[i]).replaceWith(src[i]);
-            }
-        }
-    } else { //diff length
-        var start = 0;
-        var end = 0;
-        //find diff start position
-        for (var i = 0; i < tar.length; i++) {
-            //copyAttribute(src[i], des[i], 'data-startline');
-            //copyAttribute(src[i], des[i], 'data-endline');
-            var rawSrc = cloneAndRemoveDataAttr(src[i]);
-            var rawTar = cloneAndRemoveDataAttr(tar[i]);
-            if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) {
-                start = i;
-                break;
-            }
-        }
-        //find diff end position
-        var srcEnd = 0;
-        var tarEnd = 0;
-        for (var i = 0; i < src.length; i++) {
-            //copyAttribute(src[i], des[i], 'data-startline');
-            //copyAttribute(src[i], des[i], 'data-endline');
-            var rawSrc = cloneAndRemoveDataAttr(src[i]);
-            var rawTar = cloneAndRemoveDataAttr(tar[i]);
-            if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) {
-                start = i;
-                break;
-            }
-        }
-        //tar end
-        for (var i = 1; i <= tar.length + 1; i++) {
-            var srcLength = src.length;
-            var tarLength = tar.length;
-            //copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline');
-            //copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline');
-            var rawSrc = cloneAndRemoveDataAttr(src[srcLength - i]);
-            var rawTar = cloneAndRemoveDataAttr(tar[tarLength - i]);
-            if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) {
-                tarEnd = tar.length - i;
-                break;
-            }
-        }
-        //src end
-        for (var i = 1; i <= src.length + 1; i++) {
-            var srcLength = src.length;
-            var tarLength = tar.length;
-            //copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline');
-            //copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline');
-            var rawSrc = cloneAndRemoveDataAttr(src[srcLength - i]);
-            var rawTar = cloneAndRemoveDataAttr(tar[tarLength - i]);
-            if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) {
-                srcEnd = src.length - i;
-                break;
-            }
-        }
-        //check if tar end overlap tar start
-        var overlap = 0;
-        for (var i = start; i >= 0; i--) {
-            var rawTarStart = cloneAndRemoveDataAttr(tar[i - 1]);
-            var rawTarEnd = cloneAndRemoveDataAttr(tar[tarEnd + 1 + start - i]);
-            if (rawTarStart && rawTarEnd && rawTarStart.outerHTML == rawTarEnd.outerHTML)
-                overlap++;
-            else
-                break;
-        }
-        if (debug)
-            console.log('overlap:' + overlap);
-        //show diff content
-        if (debug) {
-            console.log('start:' + start);
-            console.log('tarEnd:' + tarEnd);
-            console.log('srcEnd:' + srcEnd);
-        }
-        tarEnd += overlap;
-        srcEnd += overlap;
-        var repeatAdd = (start - srcEnd) < (start - tarEnd);
-        var repeatDiff = Math.abs(srcEnd - tarEnd) - 1;
-        //push new elements
-        var newElements = [];
-        if (srcEnd >= start) {
-            for (var j = start; j <= srcEnd; j++) {
-                if (!src[j]) continue;
-                newElements.push(src[j].outerHTML);
-            }
-        } else if (repeatAdd) {
-            for (var j = srcEnd - repeatDiff; j <= srcEnd; j++) {
-                if (!des[j]) continue;
-                newElements.push(des[j].outerHTML);
-            }
-        }
-        //push remove elements
-        var removeElements = [];
-        if (tarEnd >= start) {
-            for (var j = start; j <= tarEnd; j++) {
-                if (!des[j]) continue;
-                removeElements.push(des[j]);
-            }
-        } else if (!repeatAdd) {
-            for (var j = start; j <= start + repeatDiff; j++) {
-                if (!des[j]) continue;
-                removeElements.push(des[j]);
-            }
-        }
-        //add elements
-        if (debug) {
-            console.log('ADD ELEMENTS');
-            console.log(newElements.join('\n'));
-        }
-        if (des[start])
-            $(newElements.join('')).insertBefore(des[start]);
-        else
-            $(newElements.join('')).insertAfter(des[start - 1]);
-        //remove elements
-        if (debug)
-            console.log('REMOVE ELEMENTS');
-        for (var j = 0; j < removeElements.length; j++) {
-            if (debug) {
-                console.log(removeElements[j].outerHTML);
-            }
-            if (removeElements[j])
-                $(removeElements[j]).remove();
-        }
+        // find diff end position
+    var srcEnd = 0
+    var tarEnd = 0
+    for (let i = 0; i < src.length; i++) {
+            // copyAttribute(src[i], des[i], 'data-startline');
+            // copyAttribute(src[i], des[i], 'data-endline');
+      let rawSrc = cloneAndRemoveDataAttr(src[i])
+      let rawTar = cloneAndRemoveDataAttr(tar[i])
+      if (!rawSrc || !rawTar || rawSrc.outerHTML !== rawTar.outerHTML) {
+        start = i
+        break
+      }
     }
+        // tar end
+    for (let i = 1; i <= tar.length + 1; i++) {
+      let srcLength = src.length
+      let tarLength = tar.length
+            // copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline');
+            // copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline');
+      let rawSrc = cloneAndRemoveDataAttr(src[srcLength - i])
+      let rawTar = cloneAndRemoveDataAttr(tar[tarLength - i])
+      if (!rawSrc || !rawTar || rawSrc.outerHTML !== rawTar.outerHTML) {
+        tarEnd = tar.length - i
+        break
+      }
+    }
+        // src end
+    for (let i = 1; i <= src.length + 1; i++) {
+      let srcLength = src.length
+      let tarLength = tar.length
+            // copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline');
+            // copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline');
+      let rawSrc = cloneAndRemoveDataAttr(src[srcLength - i])
+      let rawTar = cloneAndRemoveDataAttr(tar[tarLength - i])
+      if (!rawSrc || !rawTar || rawSrc.outerHTML !== rawTar.outerHTML) {
+        srcEnd = src.length - i
+        break
+      }
+    }
+        // check if tar end overlap tar start
+    var overlap = 0
+    for (var i = start; i >= 0; i--) {
+      var rawTarStart = cloneAndRemoveDataAttr(tar[i - 1])
+      var rawTarEnd = cloneAndRemoveDataAttr(tar[tarEnd + 1 + start - i])
+      if (rawTarStart && rawTarEnd && rawTarStart.outerHTML === rawTarEnd.outerHTML) { overlap++ } else { break }
+    }
+    if (debug) { console.log('overlap:' + overlap) }
+        // show diff content
+    if (debug) {
+      console.log('start:' + start)
+      console.log('tarEnd:' + tarEnd)
+      console.log('srcEnd:' + srcEnd)
+    }
+    tarEnd += overlap
+    srcEnd += overlap
+    var repeatAdd = (start - srcEnd) < (start - tarEnd)
+    var repeatDiff = Math.abs(srcEnd - tarEnd) - 1
+        // push new elements
+    var newElements = []
+    if (srcEnd >= start) {
+      for (let j = start; j <= srcEnd; j++) {
+        if (!src[j]) continue
+        newElements.push(src[j].outerHTML)
+      }
+    } else if (repeatAdd) {
+      for (let j = srcEnd - repeatDiff; j <= srcEnd; j++) {
+        if (!des[j]) continue
+        newElements.push(des[j].outerHTML)
+      }
+    }
+        // push remove elements
+    var removeElements = []
+    if (tarEnd >= start) {
+      for (let j = start; j <= tarEnd; j++) {
+        if (!des[j]) continue
+        removeElements.push(des[j])
+      }
+    } else if (!repeatAdd) {
+      for (let j = start; j <= start + repeatDiff; j++) {
+        if (!des[j]) continue
+        removeElements.push(des[j])
+      }
+    }
+        // add elements
+    if (debug) {
+      console.log('ADD ELEMENTS')
+      console.log(newElements.join('\n'))
+    }
+    if (des[start]) { $(newElements.join('')).insertBefore(des[start]) } else { $(newElements.join('')).insertAfter(des[start - 1]) }
+        // remove elements
+    if (debug) { console.log('REMOVE ELEMENTS') }
+    for (let j = 0; j < removeElements.length; j++) {
+      if (debug) {
+        console.log(removeElements[j].outerHTML)
+      }
+      if (removeElements[j]) { $(removeElements[j]).remove() }
+    }
+  }
 }
 
-function cloneAndRemoveDataAttr(el) {
-    if (!el) return;
-    var rawEl = $(el).clone();
-    rawEl.removeAttr('data-startline data-endline');
-    rawEl.find('[data-startline]').removeAttr('data-startline data-endline');
-    return rawEl[0];
+function cloneAndRemoveDataAttr (el) {
+  if (!el) return
+  var rawEl = $(el).clone()
+  rawEl.removeAttr('data-startline data-endline')
+  rawEl.find('[data-startline]').removeAttr('data-startline data-endline')
+  return rawEl[0]
 }
 
-function copyAttribute(src, des, attr) {
-    if (src && src.getAttribute(attr) && des)
-        des.setAttribute(attr, src.getAttribute(attr));
+function copyAttribute (src, des, attr) {
+  if (src && src.getAttribute(attr) && des) { des.setAttribute(attr, src.getAttribute(attr)) }
 }
 
 if ($('.cursor-menu').length <= 0) {
-    $("<div class='cursor-menu'>").insertAfter('.CodeMirror-cursors');
+  $("<div class='cursor-menu'>").insertAfter('.CodeMirror-cursors')
 }
 
-function reverseSortCursorMenu(dropdown) {
-    var items = dropdown.find('.textcomplete-item');
-    items.sort(function (a, b) {
-        return $(b).attr('data-index') - $(a).attr('data-index');
-    });
-    return items;
+function reverseSortCursorMenu (dropdown) {
+  var items = dropdown.find('.textcomplete-item')
+  items.sort(function (a, b) {
+    return $(b).attr('data-index') - $(a).attr('data-index')
+  })
+  return items
 }
 
-var checkCursorMenu = _.throttle(checkCursorMenuInner, cursorMenuThrottle);
+var checkCursorMenu = _.throttle(checkCursorMenuInner, cursorMenuThrottle)
 
-function checkCursorMenuInner() {
+function checkCursorMenuInner () {
     // get element
-    var dropdown = $('.cursor-menu > .dropdown-menu');
+  var dropdown = $('.cursor-menu > .dropdown-menu')
     // return if not exists
-    if (dropdown.length <= 0) return;
+  if (dropdown.length <= 0) return
     // set margin
-    var menuRightMargin = 10;
-    var menuBottomMargin = 4;
+  var menuRightMargin = 10
+  var menuBottomMargin = 4
     // use sizer to get the real doc size (won't count status bar and gutters)
-    var docWidth = ui.area.codemirrorSizer.width();
-    var docHeight = ui.area.codemirrorSizer.height();
+  var docWidth = ui.area.codemirrorSizer.width()
     // get editor size (status bar not count in)
-    var editorWidth = ui.area.codemirror.width();
-    var editorHeight = ui.area.codemirror.height();
+  var editorHeight = ui.area.codemirror.height()
     // get element size
-    var width = dropdown.outerWidth();
-    var height = dropdown.outerHeight();
+  var width = dropdown.outerWidth()
+  var height = dropdown.outerHeight()
     // get cursor
-    var cursor = editor.getCursor();
+  var cursor = editor.getCursor()
     // set element cursor data
-    if (!dropdown.hasClass('CodeMirror-other-cursor'))
-        dropdown.addClass('CodeMirror-other-cursor');
-    dropdown.attr('data-line', cursor.line);
-    dropdown.attr('data-ch', cursor.ch);
+  if (!dropdown.hasClass('CodeMirror-other-cursor')) { dropdown.addClass('CodeMirror-other-cursor') }
+  dropdown.attr('data-line', cursor.line)
+  dropdown.attr('data-ch', cursor.ch)
     // get coord position
-    var coord = editor.charCoords({
-        line: cursor.line,
-        ch: cursor.ch
-    }, 'windows');
-    var left = coord.left;
-    var top = coord.top;
+  var coord = editor.charCoords({
+    line: cursor.line,
+    ch: cursor.ch
+  }, 'windows')
+  var left = coord.left
+  var top = coord.top
     // get doc top offset (to workaround with viewport)
-    var docTopOffset = ui.area.codemirrorSizerInner.position().top;
+  var docTopOffset = ui.area.codemirrorSizerInner.position().top
     // set offset
-    var offsetLeft = 0;
-    var offsetTop = defaultTextHeight;
+  var offsetLeft = 0
+  var offsetTop = defaultTextHeight
     // set up side down
-    window.upSideDown = false;
-    var lastUpSideDown = upSideDown = false;
+  window.upSideDown = false
+  var lastUpSideDown = window.upSideDown = false
     // only do when have width and height
-    if (width > 0 && height > 0) {
+  if (width > 0 && height > 0) {
         // make element right bound not larger than doc width
-        if (left + width + offsetLeft + menuRightMargin > docWidth)
-            offsetLeft = -(left + width - docWidth + menuRightMargin);
+    if (left + width + offsetLeft + menuRightMargin > docWidth) { offsetLeft = -(left + width - docWidth + menuRightMargin) }
         // flip y when element bottom bound larger than doc height
         // and element top position is larger than element height
-        if (top + docTopOffset + height + offsetTop + menuBottomMargin > Math.max(editor.doc.height, editorHeight) && top + docTopOffset > height + menuBottomMargin) {
-            offsetTop = -(height + menuBottomMargin);
+    if (top + docTopOffset + height + offsetTop + menuBottomMargin > Math.max(editor.doc.height, editorHeight) && top + docTopOffset > height + menuBottomMargin) {
+      offsetTop = -(height + menuBottomMargin)
             // reverse sort menu because upSideDown
-            dropdown.html(reverseSortCursorMenu(dropdown));
-            upSideDown = true;
-        }
-        var textCompleteDropdown = $(editor.getInputField()).data('textComplete').dropdown;
-        lastUpSideDown = textCompleteDropdown.upSideDown;
-        textCompleteDropdown.upSideDown = upSideDown;
+      dropdown.html(reverseSortCursorMenu(dropdown))
+      window.upSideDown = true
     }
+    var textCompleteDropdown = $(editor.getInputField()).data('textComplete').dropdown
+    lastUpSideDown = textCompleteDropdown.upSideDown
+    textCompleteDropdown.upSideDown = window.upSideDown
+  }
     // make menu scroll top only if upSideDown changed
-    if (upSideDown !== lastUpSideDown)
-        dropdown.scrollTop(dropdown[0].scrollHeight);
+  if (window.upSideDown !== lastUpSideDown) { dropdown.scrollTop(dropdown[0].scrollHeight) }
     // set element offset data
-    dropdown.attr('data-offset-left', offsetLeft);
-    dropdown.attr('data-offset-top', offsetTop);
+  dropdown.attr('data-offset-left', offsetLeft)
+  dropdown.attr('data-offset-top', offsetTop)
     // set position
-    dropdown[0].style.left = left + offsetLeft + 'px';
-    dropdown[0].style.top = top + offsetTop + 'px';
+  dropdown[0].style.left = left + offsetLeft + 'px'
+  dropdown[0].style.top = top + offsetTop + 'px'
 }
 
-function checkInIndentCode() {
+function checkInIndentCode () {
     // if line starts with tab or four spaces is a code block
-    var line = editor.getLine(editor.getCursor().line);
-    var isIndentCode = ((line.substr(0, 4) === '    ') || (line.substr(0, 1) === '\t'));
-    return isIndentCode;
+  var line = editor.getLine(editor.getCursor().line)
+  var isIndentCode = ((line.substr(0, 4) === '    ') || (line.substr(0, 1) === '\t'))
+  return isIndentCode
 }
 
-var isInCode = false;
+var isInCode = false
 
-function checkInCode() {
-    isInCode = checkAbove(matchInCode) || checkInIndentCode();
+function checkInCode () {
+  isInCode = checkAbove(matchInCode) || checkInIndentCode()
 }
 
-function checkAbove(method) {
-    var cursor = editor.getCursor();
-    var text = [];
-    for (var i = 0; i < cursor.line; i++) //contain current line
-        text.push(editor.getLine(i));
-    text = text.join('\n') + '\n' + editor.getLine(cursor.line).slice(0, cursor.ch);
-    //console.log(text);
-    return method(text);
+function checkAbove (method) {
+  var cursor = editor.getCursor()
+  var text = []
+  for (var i = 0; i < cursor.line; i++) {  // contain current line
+    text.push(editor.getLine(i))
+  }
+  text = text.join('\n') + '\n' + editor.getLine(cursor.line).slice(0, cursor.ch)
+    // console.log(text);
+  return method(text)
 }
 
-function checkBelow(method) {
-    var cursor = editor.getCursor();
-    var count = editor.lineCount();
-    var text = [];
-    for (var i = cursor.line + 1; i < count; i++) //contain current line
-        text.push(editor.getLine(i));
-    text = editor.getLine(cursor.line).slice(cursor.ch) + '\n' + text.join('\n');
-    //console.log(text);
-    return method(text);
+function checkBelow (method) {
+  var cursor = editor.getCursor()
+  var count = editor.lineCount()
+  var text = []
+  for (var i = cursor.line + 1; i < count; i++) { // contain current line
+    text.push(editor.getLine(i))
+  }
+  text = editor.getLine(cursor.line).slice(cursor.ch) + '\n' + text.join('\n')
+    // console.log(text);
+  return method(text)
 }
 
-function matchInCode(text) {
-    var match;
-    match = text.match(/`{3,}/g);
+function matchInCode (text) {
+  var match
+  match = text.match(/`{3,}/g)
+  if (match && match.length % 2) {
+    return true
+  } else {
+    match = text.match(/`/g)
     if (match && match.length % 2) {
-        return true;
+      return true
     } else {
-        match = text.match(/`/g);
-        if (match && match.length % 2) {
-            return true;
-        } else {
-            return false;
-        }
+      return false
     }
+  }
 }
 
-var isInContainer = false;
-var isInContainerSyntax = false;
+var isInContainer = false
+var isInContainerSyntax = false
 
-function checkInContainer() {
-    isInContainer = checkAbove(matchInContainer) && !checkInIndentCode();
+function checkInContainer () {
+  isInContainer = checkAbove(matchInContainer) && !checkInIndentCode()
 }
 
-function checkInContainerSyntax() {
+function checkInContainerSyntax () {
     // if line starts with :::, it's in container syntax
-    var line = editor.getLine(editor.getCursor().line);
-    isInContainerSyntax = (line.substr(0, 3) === ':::');
+  var line = editor.getLine(editor.getCursor().line)
+  isInContainerSyntax = (line.substr(0, 3) === ':::')
 }
 
-function matchInContainer(text) {
-    var match;
-    match = text.match(/:{3,}/g);
-    if (match && match.length % 2) {
-        return true;
-    } else {
-        return false;
-    }
+function matchInContainer (text) {
+  var match
+  match = text.match(/:{3,}/g)
+  if (match && match.length % 2) {
+    return true
+  } else {
+    return false
+  }
 }
 
 $(editor.getInputField())
     .textcomplete([
-        { // emoji strategy
-            match: /(^|\n|\s)\B:([\-+\w]*)$/,
-            search: function (term, callback) {
-                var line = editor.getLine(editor.getCursor().line);
-                term = line.match(this.match)[2];
-                var list = [];
-                $.map(emojify.emojiNames, function (emoji) {
-                    if (emoji.indexOf(term) === 0) //match at first character
-                        list.push(emoji);
-                });
-                $.map(emojify.emojiNames, function (emoji) {
-                    if (emoji.indexOf(term) !== -1) //match inside the word
-                        list.push(emoji);
-                });
-                callback(list);
-            },
-            template: function (value) {
-                return '<img class="emoji" src="' + serverurl + '/build/emojify.js/dist/images/basic/' + value + '.png"></img> ' + value;
-            },
-            replace: function (value) {
-                return '$1:' + value + ': ';
-            },
-            index: 1,
-            context: function (text) {
-                checkInCode();
-                checkInContainer();
-                checkInContainerSyntax();
-                return !isInCode && !isInContainerSyntax;
+      { // emoji strategy
+        match: /(^|\n|\s)\B:([-+\w]*)$/,
+        search: function (term, callback) {
+          var line = editor.getLine(editor.getCursor().line)
+          term = line.match(this.match)[2]
+          var list = []
+          $.map(window.emojify.emojiNames, function (emoji) {
+            if (emoji.indexOf(term) === 0) { // match at first character
+              list.push(emoji)
             }
+          })
+          $.map(window.emojify.emojiNames, function (emoji) {
+            if (emoji.indexOf(term) !== -1) { // match inside the word
+              list.push(emoji)
+            }
+          })
+          callback(list)
         },
-        { // Code block language strategy
-            langs: supportCodeModes,
-            charts: supportCharts,
-            match: /(^|\n)```(\w+)$/,
-            search: function (term, callback) {
-                var line = editor.getLine(editor.getCursor().line);
-                term = line.match(this.match)[2];
-                var list = [];
-                $.map(this.langs, function (lang) {
-                    if (lang.indexOf(term) === 0 && lang !== term)
-                        list.push(lang);
-                });
-                $.map(this.charts, function (chart) {
-                    if (chart.indexOf(term) === 0 && chart !== term)
-                        list.push(chart);
-                });
-                callback(list);
-            },
-            replace: function (lang) {
-                var ending = '';
-                if (!checkBelow(matchInCode)) {
-                    ending = '\n\n```';
-                }
-                if (this.langs.indexOf(lang) !== -1)
-                    return '$1```' + lang + '=' + ending;
-                else if (this.charts.indexOf(lang) !== -1)
-                    return '$1```' + lang + ending;
-            },
-            done: function () {
-                var cursor = editor.getCursor();
-                var text = [];
-                text.push(editor.getLine(cursor.line - 1));
-                text.push(editor.getLine(cursor.line));
-                text = text.join('\n');
-                //console.log(text);
-                if (text == '\n```')
-                    editor.doc.cm.execCommand("goLineUp");
-            },
-            context: function (text) {
-                return isInCode;
-            }
+        template: function (value) {
+          return '<img class="emoji" src="' + serverurl + '/build/emojify.js/dist/images/basic/' + value + '.png"></img> ' + value
         },
-        { // Container strategy
-            containers: supportContainers,
-            match: /(^|\n):::(\s*)(\w*)$/,
-            search: function (term, callback) {
-                var line = editor.getLine(editor.getCursor().line);
-                term = line.match(this.match)[3].trim();
-                var list = [];
-                $.map(this.containers, function (container) {
-                    if (container.indexOf(term) === 0 && container !== term)
-                        list.push(container);
-                });
-                callback(list);
-            },
-            replace: function (lang) {
-                var ending = '';
-                if (!checkBelow(matchInContainer)) {
-                    ending = '\n\n:::';
-                }
-                if (this.containers.indexOf(lang) !== -1)
-                    return '$1:::$2' + lang + ending;
-            },
-            done: function () {
-                var cursor = editor.getCursor();
-                var text = [];
-                text.push(editor.getLine(cursor.line - 1));
-                text.push(editor.getLine(cursor.line));
-                text = text.join('\n');
-                //console.log(text);
-                if (text == '\n:::')
-                    editor.doc.cm.execCommand("goLineUp");
-            },
-            context: function (text) {
-                return !isInCode && isInContainer;
-            }
+        replace: function (value) {
+          return '$1:' + value + ': '
         },
-        { //header
-            match: /(?:^|\n)(\s{0,3})(#{1,6}\w*)$/,
-            search: function (term, callback) {
-                callback($.map(supportHeaders, function (header) {
-                    return header.search.indexOf(term) === 0 ? header.text : null;
-                }));
-            },
-            replace: function (value) {
-                return '$1' + value;
-            },
-            context: function (text) {
-                return !isInCode;
-            }
-        },
-        { //extra tags for blockquote
-            match: /(?:^|\n|\s)(\>.*|\s|)((\^|)\[(\^|)\](\[\]|\(\)|\:|)\s*\w*)$/,
-            search: function (term, callback) {
-                var line = editor.getLine(editor.getCursor().line);
-                var quote = line.match(this.match)[1].trim();
-                var list = [];
-                if (quote.indexOf('>') == 0) {
-                    $.map(supportExtraTags, function (extratag) {
-                        if (extratag.search.indexOf(term) === 0)
-                            list.push(extratag.command());
-                    });
-                }
-                $.map(supportReferrals, function (referral) {
-                    if (referral.search.indexOf(term) === 0)
-                        list.push(referral.text);
-                })
-                callback(list);
-            },
-            replace: function (value) {
-                return '$1' + value;
-            },
-            context: function (text) {
-                return !isInCode;
-            }
-        },
-        { //extra tags for list
-            match: /(^[>\s]*[\-\+\*]\s(?:\[[x ]\]|.*))(\[\])(\w*)$/,
-            search: function (term, callback) {
-                var list = [];
-                $.map(supportExtraTags, function (extratag) {
-                    if (extratag.search.indexOf(term) === 0)
-                        list.push(extratag.command());
-                });
-                $.map(supportReferrals, function (referral) {
-                    if (referral.search.indexOf(term) === 0)
-                        list.push(referral.text);
-                })
-                callback(list);
-            },
-            replace: function (value) {
-                return '$1' + value;
-            },
-            context: function (text) {
-                return !isInCode;
-            }
-        },
-        { //referral
-            match: /(^\s*|\n|\s{2})((\[\]|\[\]\[\]|\[\]\(\)|\!|\!\[\]|\!\[\]\[\]|\!\[\]\(\))\s*\w*)$/,
-            search: function (term, callback) {
-                callback($.map(supportReferrals, function (referral) {
-                    return referral.search.indexOf(term) === 0 ? referral.text : null;
-                }));
-            },
-            replace: function (value) {
-                return '$1' + value;
-            },
-            context: function (text) {
-                return !isInCode;
-            }
-        },
-        { //externals
-            match: /(^|\n|\s)\{\}(\w*)$/,
-            search: function (term, callback) {
-                callback($.map(supportExternals, function (external) {
-                    return external.search.indexOf(term) === 0 ? external.text : null;
-                }));
-            },
-            replace: function (value) {
-                return '$1' + value;
-            },
-            context: function (text) {
-                return !isInCode;
-            }
+        index: 1,
+        context: function (text) {
+          checkInCode()
+          checkInContainer()
+          checkInContainerSyntax()
+          return !isInCode && !isInContainerSyntax
         }
+      },
+      { // Code block language strategy
+        langs: supportCodeModes,
+        charts: supportCharts,
+        match: /(^|\n)```(\w+)$/,
+        search: function (term, callback) {
+          var line = editor.getLine(editor.getCursor().line)
+          term = line.match(this.match)[2]
+          var list = []
+          $.map(this.langs, function (lang) {
+            if (lang.indexOf(term) === 0 && lang !== term) { list.push(lang) }
+          })
+          $.map(this.charts, function (chart) {
+            if (chart.indexOf(term) === 0 && chart !== term) { list.push(chart) }
+          })
+          callback(list)
+        },
+        replace: function (lang) {
+          var ending = ''
+          if (!checkBelow(matchInCode)) {
+            ending = '\n\n```'
+          }
+          if (this.langs.indexOf(lang) !== -1) { return '$1```' + lang + '=' + ending } else if (this.charts.indexOf(lang) !== -1) { return '$1```' + lang + ending }
+        },
+        done: function () {
+          var cursor = editor.getCursor()
+          var text = []
+          text.push(editor.getLine(cursor.line - 1))
+          text.push(editor.getLine(cursor.line))
+          text = text.join('\n')
+                // console.log(text);
+          if (text === '\n```') { editor.doc.cm.execCommand('goLineUp') }
+        },
+        context: function (text) {
+          return isInCode
+        }
+      },
+      { // Container strategy
+        containers: supportContainers,
+        match: /(^|\n):::(\s*)(\w*)$/,
+        search: function (term, callback) {
+          var line = editor.getLine(editor.getCursor().line)
+          term = line.match(this.match)[3].trim()
+          var list = []
+          $.map(this.containers, function (container) {
+            if (container.indexOf(term) === 0 && container !== term) { list.push(container) }
+          })
+          callback(list)
+        },
+        replace: function (lang) {
+          var ending = ''
+          if (!checkBelow(matchInContainer)) {
+            ending = '\n\n:::'
+          }
+          if (this.containers.indexOf(lang) !== -1) { return '$1:::$2' + lang + ending }
+        },
+        done: function () {
+          var cursor = editor.getCursor()
+          var text = []
+          text.push(editor.getLine(cursor.line - 1))
+          text.push(editor.getLine(cursor.line))
+          text = text.join('\n')
+                // console.log(text);
+          if (text === '\n:::') { editor.doc.cm.execCommand('goLineUp') }
+        },
+        context: function (text) {
+          return !isInCode && isInContainer
+        }
+      },
+      { // header
+        match: /(?:^|\n)(\s{0,3})(#{1,6}\w*)$/,
+        search: function (term, callback) {
+          callback($.map(supportHeaders, function (header) {
+            return header.search.indexOf(term) === 0 ? header.text : null
+          }))
+        },
+        replace: function (value) {
+          return '$1' + value
+        },
+        context: function (text) {
+          return !isInCode
+        }
+      },
+      { // extra tags for blockquote
+        match: /(?:^|\n|\s)(>.*|\s|)((\^|)\[(\^|)\](\[\]|\(\)|:|)\s*\w*)$/,
+        search: function (term, callback) {
+          var line = editor.getLine(editor.getCursor().line)
+          var quote = line.match(this.match)[1].trim()
+          var list = []
+          if (quote.indexOf('>') === 0) {
+            $.map(supportExtraTags, function (extratag) {
+              if (extratag.search.indexOf(term) === 0) { list.push(extratag.command()) }
+            })
+          }
+          $.map(supportReferrals, function (referral) {
+            if (referral.search.indexOf(term) === 0) { list.push(referral.text) }
+          })
+          callback(list)
+        },
+        replace: function (value) {
+          return '$1' + value
+        },
+        context: function (text) {
+          return !isInCode
+        }
+      },
+      { // extra tags for list
+        match: /(^[>\s]*[-+*]\s(?:\[[x ]\]|.*))(\[\])(\w*)$/,
+        search: function (term, callback) {
+          var list = []
+          $.map(supportExtraTags, function (extratag) {
+            if (extratag.search.indexOf(term) === 0) { list.push(extratag.command()) }
+          })
+          $.map(supportReferrals, function (referral) {
+            if (referral.search.indexOf(term) === 0) { list.push(referral.text) }
+          })
+          callback(list)
+        },
+        replace: function (value) {
+          return '$1' + value
+        },
+        context: function (text) {
+          return !isInCode
+        }
+      },
+      { // referral
+        match: /(^\s*|\n|\s{2})((\[\]|\[\]\[\]|\[\]\(\)|!|!\[\]|!\[\]\[\]|!\[\]\(\))\s*\w*)$/,
+        search: function (term, callback) {
+          callback($.map(supportReferrals, function (referral) {
+            return referral.search.indexOf(term) === 0 ? referral.text : null
+          }))
+        },
+        replace: function (value) {
+          return '$1' + value
+        },
+        context: function (text) {
+          return !isInCode
+        }
+      },
+      { // externals
+        match: /(^|\n|\s)\{\}(\w*)$/,
+        search: function (term, callback) {
+          callback($.map(supportExternals, function (external) {
+            return external.search.indexOf(term) === 0 ? external.text : null
+          }))
+        },
+        replace: function (value) {
+          return '$1' + value
+        },
+        context: function (text) {
+          return !isInCode
+        }
+      }
     ], {
-        appendTo: $('.cursor-menu')
+      appendTo: $('.cursor-menu')
     })
     .on({
-        'textComplete:beforeSearch': function (e) {
-            //NA
-        },
-        'textComplete:afterSearch': function (e) {
-            checkCursorMenu();
-        },
-        'textComplete:select': function (e, value, strategy) {
-            //NA
-        },
-        'textComplete:show': function (e) {
-            $(this).data('autocompleting', true);
-            editor.setOption("extraKeys", {
-                "Up": function () {
-                    return false;
-                },
-                "Right": function () {
-                    editor.doc.cm.execCommand("goCharRight");
-                },
-                "Down": function () {
-                    return false;
-                },
-                "Left": function () {
-                    editor.doc.cm.execCommand("goCharLeft");
-                },
-                "Enter": function () {
-                    return false;
-                },
-                "Backspace": function () {
-                    editor.doc.cm.execCommand("delCharBefore");
-                }
-            });
-        },
-        'textComplete:hide': function (e) {
-            $(this).data('autocompleting', false);
-            editor.setOption("extraKeys", defaultExtraKeys);
-        }
-    });
+      'textComplete:beforeSearch': function (e) {
+            // NA
+      },
+      'textComplete:afterSearch': function (e) {
+        checkCursorMenu()
+      },
+      'textComplete:select': function (e, value, strategy) {
+            // NA
+      },
+      'textComplete:show': function (e) {
+        $(this).data('autocompleting', true)
+        editor.setOption('extraKeys', {
+          'Up': function () {
+            return false
+          },
+          'Right': function () {
+            editor.doc.cm.execCommand('goCharRight')
+          },
+          'Down': function () {
+            return false
+          },
+          'Left': function () {
+            editor.doc.cm.execCommand('goCharLeft')
+          },
+          'Enter': function () {
+            return false
+          },
+          'Backspace': function () {
+            editor.doc.cm.execCommand('delCharBefore')
+          }
+        })
+      },
+      'textComplete:hide': function (e) {
+        $(this).data('autocompleting', false)
+        editor.setOption('extraKeys', defaultExtraKeys)
+      }
+    })
diff --git a/public/js/lib/common/login.js b/public/js/lib/common/login.js
index 58fa55c..18cd377 100644
--- a/public/js/lib/common/login.js
+++ b/public/js/lib/common/login.js
@@ -1,89 +1,92 @@
-import { serverurl } from '../config';
+/* eslint-env browser, jquery */
+/* global Cookies */
 
-let checkAuth = false;
-let profile = null;
-let lastLoginState = getLoginState();
-let lastUserId = getUserId();
-var loginStateChangeEvent = null;
+import { serverurl } from '../config'
 
-export function setloginStateChangeEvent(func) {
-    loginStateChangeEvent = func;
+let checkAuth = false
+let profile = null
+let lastLoginState = getLoginState()
+let lastUserId = getUserId()
+var loginStateChangeEvent = null
+
+export function setloginStateChangeEvent (func) {
+  loginStateChangeEvent = func
 }
 
-export function resetCheckAuth() {
-    checkAuth = false;
+export function resetCheckAuth () {
+  checkAuth = false
 }
 
-export function setLoginState(bool, id) {
-    Cookies.set('loginstate', bool, {
-        expires: 365
-    });
-    if (id) {
-        Cookies.set('userid', id, {
-            expires: 365
-        });
-    } else {
-        Cookies.remove('userid');
-    }
-    lastLoginState = bool;
-    lastUserId = id;
-    checkLoginStateChanged();
+export function setLoginState (bool, id) {
+  Cookies.set('loginstate', bool, {
+    expires: 365
+  })
+  if (id) {
+    Cookies.set('userid', id, {
+      expires: 365
+    })
+  } else {
+    Cookies.remove('userid')
+  }
+  lastLoginState = bool
+  lastUserId = id
+  checkLoginStateChanged()
 }
 
-export function checkLoginStateChanged() {
-    if (getLoginState() != lastLoginState || getUserId() != lastUserId) {
-        if (loginStateChangeEvent) setTimeout(loginStateChangeEvent, 100);
-        return true;
-    } else {
-        return false;
-    }
+export function checkLoginStateChanged () {
+  if (getLoginState() !== lastLoginState || getUserId() !== lastUserId) {
+    if (loginStateChangeEvent) setTimeout(loginStateChangeEvent, 100)
+    return true
+  } else {
+    return false
+  }
 }
 
-export function getLoginState() {
-    const state = Cookies.get('loginstate');
-    return state === "true" || state === true;
+export function getLoginState () {
+  const state = Cookies.get('loginstate')
+  return state === 'true' || state === true
 }
 
-export function getUserId() {
-    return Cookies.get('userid');
+export function getUserId () {
+  return Cookies.get('userid')
 }
 
-export function clearLoginState() {
-    Cookies.remove('loginstate');
+export function clearLoginState () {
+  Cookies.remove('loginstate')
 }
 
-export function checkIfAuth(yesCallback, noCallback) {
-    const cookieLoginState = getLoginState();
-    if (checkLoginStateChanged()) checkAuth = false;
-    if (!checkAuth || typeof cookieLoginState == 'undefined') {
-        $.get(`${serverurl}/me`)
+export function checkIfAuth (yesCallback, noCallback) {
+  const cookieLoginState = getLoginState()
+  if (checkLoginStateChanged()) checkAuth = false
+  if (!checkAuth || typeof cookieLoginState === 'undefined') {
+    $.get(`${serverurl}/me`)
             .done(data => {
-                if (data && data.status == 'ok') {
-                    profile = data;
-                    yesCallback(profile);
-                    setLoginState(true, data.id);
-                } else {
-                    noCallback();
-                    setLoginState(false);
-                }
+              if (data && data.status === 'ok') {
+                profile = data
+                yesCallback(profile)
+                setLoginState(true, data.id)
+              } else {
+                noCallback()
+                setLoginState(false)
+              }
             })
             .fail(() => {
-                noCallback();
+              noCallback()
             })
             .always(() => {
-                checkAuth = true;
-            });
-    } else if (cookieLoginState) {
-        yesCallback(profile);
-    } else {
-        noCallback();
-    }
+              checkAuth = true
+            })
+  } else if (cookieLoginState) {
+    yesCallback(profile)
+  } else {
+    noCallback()
+  }
 }
 
 export default {
-    checkAuth,
-    profile,
-    lastLoginState,
-    lastUserId,
-    loginStateChangeEvent
-};
+  checkAuth,
+  profile,
+  lastLoginState,
+  lastUserId,
+  loginStateChangeEvent
+}
diff --git a/public/js/lib/config/index.js b/public/js/lib/config/index.js
index 2b73679..1ea7a7a 100644
--- a/public/js/lib/config/index.js
+++ b/public/js/lib/config/index.js
@@ -1,19 +1,19 @@
-import configJson from '../../../../config.json'; // root path json config
+import configJson from '../../../../config.json' // root path json config
 
-const config = 'production' === process.env.NODE_ENV ? configJson.production : configJson.development;
+const config = process.env.NODE_ENV === 'production' ? configJson.production : configJson.development
 
-export const GOOGLE_API_KEY = (config.google && config.google.apiKey) || '';
-export const GOOGLE_CLIENT_ID = (config.google && config.google.clientID) || '';
-export const DROPBOX_APP_KEY = (config.dropbox && config.dropbox.appKey) || '';
+export const GOOGLE_API_KEY = (config.google && config.google.apiKey) || ''
+export const GOOGLE_CLIENT_ID = (config.google && config.google.clientID) || ''
+export const DROPBOX_APP_KEY = (config.dropbox && config.dropbox.appKey) || ''
 
-export const domain = config.domain || ''; // domain name
-export const urlpath = config.urlpath || ''; // sub url path, like: www.example.com/<urlpath>
-export const debug = config.debug || false;
+export const domain = config.domain || '' // domain name
+export const urlpath = config.urlpath || '' // sub url path, like: www.example.com/<urlpath>
+export const debug = config.debug || false
 
-export const port = window.location.port;
-export const serverurl = `${window.location.protocol}//${domain ? domain : window.location.hostname}${port ? ':' + port : ''}${urlpath ? '/' + urlpath : ''}`;
-window.serverurl = serverurl;
-export const noteid = urlpath ? window.location.pathname.slice(urlpath.length + 1, window.location.pathname.length).split('/')[1] : window.location.pathname.split('/')[1];
-export const noteurl = `${serverurl}/${noteid}`;
+export const port = window.location.port
+export const serverurl = `${window.location.protocol}//${domain || window.location.hostname}${port ? ':' + port : ''}${urlpath ? '/' + urlpath : ''}`
+window.serverurl = serverurl
+export const noteid = urlpath ? window.location.pathname.slice(urlpath.length + 1, window.location.pathname.length).split('/')[1] : window.location.pathname.split('/')[1]
+export const noteurl = `${serverurl}/${noteid}`
 
-export const version = '0.5.0';
+export const version = '0.5.0'
diff --git a/public/js/locale.js b/public/js/locale.js
index e6d11cd..2a2c181 100644
--- a/public/js/locale.js
+++ b/public/js/locale.js
@@ -1,26 +1,28 @@
-var lang = "en";
-var userLang = navigator.language || navigator.userLanguage;
-var userLangCode = userLang.split('-')[0];
-var userCountryCode = userLang.split('-')[1];
-var locale = $('.ui-locale');
-var supportLangs = [];
-$(".ui-locale option").each(function() {
-    supportLangs.push($(this).val());
-});
+/* eslint-env browser, jquery */
+/* global Cookies */
+
+var lang = 'en'
+var userLang = navigator.language || navigator.userLanguage
+var userLangCode = userLang.split('-')[0]
+var locale = $('.ui-locale')
+var supportLangs = []
+$('.ui-locale option').each(function () {
+  supportLangs.push($(this).val())
+})
 if (Cookies.get('locale')) {
-    lang = Cookies.get('locale');
+  lang = Cookies.get('locale')
 } else if (supportLangs.indexOf(userLang) !== -1) {
-    lang = supportLangs[supportLangs.indexOf(userLang)];
+  lang = supportLangs[supportLangs.indexOf(userLang)]
 } else if (supportLangs.indexOf(userLangCode) !== -1) {
-    lang = supportLangs[supportLangs.indexOf(userLangCode)];
+  lang = supportLangs[supportLangs.indexOf(userLangCode)]
 }
 
-locale.val(lang);
-$('select.ui-locale option[value="' + lang + '"]').attr('selected','selected');
+locale.val(lang)
+$('select.ui-locale option[value="' + lang + '"]').attr('selected', 'selected')
 
-locale.change(function() {
-    Cookies.set('locale', $(this).val(), {
-        expires: 365
-    });
-    window.location.reload();
-});
+locale.change(function () {
+  Cookies.set('locale', $(this).val(), {
+    expires: 365
+  })
+  window.location.reload()
+})
diff --git a/public/js/pretty.js b/public/js/pretty.js
index 18d0dc0..718941a 100644
--- a/public/js/pretty.js
+++ b/public/js/pretty.js
@@ -1,8 +1,11 @@
-require('../css/extra.css');
-require('../css/slide-preview.css');
-require('../css/site.css');
+/* eslint-env browser, jquery */
+/* global refreshView */
 
-require('highlight.js/styles/github-gist.css');
+require('../css/extra.css')
+require('../css/slide-preview.css')
+require('../css/site.css')
+
+require('highlight.js/styles/github-gist.css')
 
 import {
     autoLinkify,
@@ -16,126 +19,126 @@ import {
     scrollToHash,
     smoothHashScroll,
     updateLastChange
-} from './extra';
+} from './extra'
 
-import { preventXSS } from './render';
+import { preventXSS } from './render'
 
-const markdown = $("#doc.markdown-body");
-const text = markdown.text();
-const lastMeta = md.meta;
-md.meta = {};
-delete md.metaError;
-let rendered = md.render(text);
+const markdown = $('#doc.markdown-body')
+const text = markdown.text()
+const lastMeta = md.meta
+md.meta = {}
+delete md.metaError
+let rendered = md.render(text)
 if (md.meta.type && md.meta.type === 'slide') {
-    const slideOptions = {
-        separator: '^(\r\n?|\n)---(\r\n?|\n)$',
-        verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$'
-    };
-    const slides = RevealMarkdown.slidify(text, slideOptions);
-    markdown.html(slides);
-    RevealMarkdown.initialize();
+  const slideOptions = {
+    separator: '^(\r\n?|\n)---(\r\n?|\n)$',
+    verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$'
+  }
+  const slides = window.RevealMarkdown.slidify(text, slideOptions)
+  markdown.html(slides)
+  window.RevealMarkdown.initialize()
     // prevent XSS
-    markdown.html(preventXSS(markdown.html()));
-    markdown.addClass('slides');
+  markdown.html(preventXSS(markdown.html()))
+  markdown.addClass('slides')
 } else {
-    if (lastMeta.type && lastMeta.type === 'slide') {
-        refreshView();
-        markdown.removeClass('slides');
-    }
+  if (lastMeta.type && lastMeta.type === 'slide') {
+    refreshView()
+    markdown.removeClass('slides')
+  }
     // only render again when meta changed
-    if (JSON.stringify(md.meta) != JSON.stringify(lastMeta)) {
-        parseMeta(md, null, markdown, $('#ui-toc'), $('#ui-toc-affix'));
-        rendered = md.render(text);
-    }
+  if (JSON.stringify(md.meta) !== JSON.stringify(lastMeta)) {
+    parseMeta(md, null, markdown, $('#ui-toc'), $('#ui-toc-affix'))
+    rendered = md.render(text)
+  }
     // prevent XSS
-    rendered = preventXSS(rendered);
-    const result = postProcess(rendered);
-    markdown.html(result.html());
+  rendered = preventXSS(rendered)
+  const result = postProcess(rendered)
+  markdown.html(result.html())
 }
-$(document.body).show();
+$(document.body).show()
 
-finishView(markdown);
-autoLinkify(markdown);
-deduplicatedHeaderId(markdown);
-renderTOC(markdown);
-generateToc('ui-toc');
-generateToc('ui-toc-affix');
-smoothHashScroll();
-createtime = lastchangeui.time.attr('data-createtime');
-lastchangetime = lastchangeui.time.attr('data-updatetime');
-updateLastChange();
+finishView(markdown)
+autoLinkify(markdown)
+deduplicatedHeaderId(markdown)
+renderTOC(markdown)
+generateToc('ui-toc')
+generateToc('ui-toc-affix')
+smoothHashScroll()
+window.createtime = window.lastchangeui.time.attr('data-createtime')
+window.lastchangetime = window.lastchangeui.time.attr('data-updatetime')
+updateLastChange()
 
-const url = window.location.pathname;
-$('.ui-edit').attr('href', `${url}/edit`);
-const toc = $('.ui-toc');
-const tocAffix = $('.ui-affix-toc');
-const tocDropdown = $('.ui-toc-dropdown');
-//toc
+const url = window.location.pathname
+$('.ui-edit').attr('href', `${url}/edit`)
+const toc = $('.ui-toc')
+const tocAffix = $('.ui-affix-toc')
+const tocDropdown = $('.ui-toc-dropdown')
+// toc
 tocDropdown.click(e => {
-    e.stopPropagation();
-});
+  e.stopPropagation()
+})
 
-let enoughForAffixToc = true;
+let enoughForAffixToc = true
 
-function generateScrollspy() {
-    $(document.body).scrollspy({
-        target: ''
-    });
-    $(document.body).scrollspy('refresh');
-    if (enoughForAffixToc) {
-        toc.hide();
-        tocAffix.show();
-    } else {
-        tocAffix.hide();
-        toc.show();
-    }
-    $(document.body).scroll();
+function generateScrollspy () {
+  $(document.body).scrollspy({
+    target: ''
+  })
+  $(document.body).scrollspy('refresh')
+  if (enoughForAffixToc) {
+    toc.hide()
+    tocAffix.show()
+  } else {
+    tocAffix.hide()
+    toc.show()
+  }
+  $(document.body).scroll()
 }
 
-function windowResize() {
-    //toc right
-    const paddingRight = parseFloat(markdown.css('padding-right'));
-    const right = ($(window).width() - (markdown.offset().left + markdown.outerWidth() - paddingRight));
-    toc.css('right', `${right}px`);
-    //affix toc left
-    let newbool;
-    const rightMargin = (markdown.parent().outerWidth() - markdown.outerWidth()) / 2;
-    //for ipad or wider device
-    if (rightMargin >= 133) {
-        newbool = true;
-        const affixLeftMargin = (tocAffix.outerWidth() - tocAffix.width()) / 2;
-        const left = markdown.offset().left + markdown.outerWidth() - affixLeftMargin;
-        tocAffix.css('left', `${left}px`);
-    } else {
-        newbool = false;
-    }
-    if (newbool != enoughForAffixToc) {
-        enoughForAffixToc = newbool;
-        generateScrollspy();
-    }
+function windowResize () {
+    // toc right
+  const paddingRight = parseFloat(markdown.css('padding-right'))
+  const right = ($(window).width() - (markdown.offset().left + markdown.outerWidth() - paddingRight))
+  toc.css('right', `${right}px`)
+    // affix toc left
+  let newbool
+  const rightMargin = (markdown.parent().outerWidth() - markdown.outerWidth()) / 2
+    // for ipad or wider device
+  if (rightMargin >= 133) {
+    newbool = true
+    const affixLeftMargin = (tocAffix.outerWidth() - tocAffix.width()) / 2
+    const left = markdown.offset().left + markdown.outerWidth() - affixLeftMargin
+    tocAffix.css('left', `${left}px`)
+  } else {
+    newbool = false
+  }
+  if (newbool !== enoughForAffixToc) {
+    enoughForAffixToc = newbool
+    generateScrollspy()
+  }
 }
 $(window).resize(() => {
-    windowResize();
-});
+  windowResize()
+})
 $(document).ready(() => {
-    windowResize();
-    generateScrollspy();
-    setTimeout(scrollToHash, 0);
-    //tooltip
-    $('[data-toggle="tooltip"]').tooltip();
-});
+  windowResize()
+  generateScrollspy()
+  setTimeout(scrollToHash, 0)
+    // tooltip
+  $('[data-toggle="tooltip"]').tooltip()
+})
 
-export function scrollToTop() {
-    $('body, html').stop(true, true).animate({
-        scrollTop: 0
-    }, 100, "linear");
+export function scrollToTop () {
+  $('body, html').stop(true, true).animate({
+    scrollTop: 0
+  }, 100, 'linear')
 }
 
-export function scrollToBottom() {
-    $('body, html').stop(true, true).animate({
-        scrollTop: $(document.body)[0].scrollHeight
-    }, 100, "linear");
+export function scrollToBottom () {
+  $('body, html').stop(true, true).animate({
+    scrollTop: $(document.body)[0].scrollHeight
+  }, 100, 'linear')
 }
 
-window.scrollToTop = scrollToTop;
-window.scrollToBottom = scrollToBottom;
+window.scrollToTop = scrollToTop
+window.scrollToBottom = scrollToBottom
diff --git a/public/js/render.js b/public/js/render.js
index 5d6d0aa..61663a4 100644
--- a/public/js/render.js
+++ b/public/js/render.js
@@ -1,62 +1,64 @@
+/* eslint-env browser, jquery */
+/* global filterXSS */
 // allow some attributes
-var whiteListAttr = ['id', 'class', 'style'];
-window.whiteListAttr = whiteListAttr;
+var whiteListAttr = ['id', 'class', 'style']
+window.whiteListAttr = whiteListAttr
 // allow link starts with '.', '/' and custom protocol with '://'
-var linkRegex = /^([\w|-]+:\/\/)|^([\.|\/])+/;
+var linkRegex = /^([\w|-]+:\/\/)|^([.|/])+/
 // allow data uri, from https://gist.github.com/bgrins/6194623
-var dataUriRegex = /^\s*data:([a-z]+\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)?)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@\/?%\s]*)\s*$/i;
+var dataUriRegex = /^\s*data:([a-z]+\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)?)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@/?%\s]*)\s*$/i
 // custom white list
-var whiteList = filterXSS.whiteList;
+var whiteList = filterXSS.whiteList
 // allow ol specify start number
-whiteList['ol'] = ['start'];
+whiteList['ol'] = ['start']
 // allow li specify value number
-whiteList['li'] = ['value'];
+whiteList['li'] = ['value']
 // allow style tag
-whiteList['style'] = [];
+whiteList['style'] = []
 // allow kbd tag
-whiteList['kbd'] = [];
+whiteList['kbd'] = []
 // allow ifram tag with some safe attributes
-whiteList['iframe'] = ['allowfullscreen', 'name', 'referrerpolicy', 'sandbox', 'src', 'srcdoc', 'width', 'height'];
+whiteList['iframe'] = ['allowfullscreen', 'name', 'referrerpolicy', 'sandbox', 'src', 'srcdoc', 'width', 'height']
 // allow summary tag
-whiteList['summary'] = [];
+whiteList['summary'] = []
 
 var filterXSSOptions = {
-    allowCommentTag: true,
-    whiteList: whiteList,
-    escapeHtml: function (html) {
+  allowCommentTag: true,
+  whiteList: whiteList,
+  escapeHtml: function (html) {
         // allow html comment in multiple lines
-        return html.replace(/<(.*?)>/g, '&lt;$1&gt;');
-    },
-    onIgnoreTag: function (tag, html, options) {
+    return html.replace(/<(.*?)>/g, '&lt;$1&gt;')
+  },
+  onIgnoreTag: function (tag, html, options) {
         // allow comment tag
-        if (tag == "!--") {
+    if (tag === '!--') {
             // do not filter its attributes
-            return html;
-        }
-    },
-    onTagAttr: function (tag, name, value, isWhiteAttr) {
-        // allow href and src that match linkRegex
-        if (isWhiteAttr && (name === 'href' || name === 'src') && linkRegex.test(value)) {
-            return name + '="' + filterXSS.escapeAttrValue(value) + '"';
-        }
-        // allow data uri in img src
-        if (isWhiteAttr && (tag == "img" && name === 'src') && dataUriRegex.test(value)) {
-            return name + '="' + filterXSS.escapeAttrValue(value) + '"';
-        }
-    },
-    onIgnoreTagAttr: function (tag, name, value, isWhiteAttr) {
-        // allow attr start with 'data-' or in the whiteListAttr
-        if (name.substr(0, 5) === 'data-' || whiteListAttr.indexOf(name) !== -1) {
-            // escape its value using built-in escapeAttrValue function
-            return name + '="' + filterXSS.escapeAttrValue(value) + '"';
-        }
+      return html
     }
-};
-
-function preventXSS(html) {
-    return filterXSS(html, filterXSSOptions);
+  },
+  onTagAttr: function (tag, name, value, isWhiteAttr) {
+        // allow href and src that match linkRegex
+    if (isWhiteAttr && (name === 'href' || name === 'src') && linkRegex.test(value)) {
+      return name + '="' + filterXSS.escapeAttrValue(value) + '"'
+    }
+        // allow data uri in img src
+    if (isWhiteAttr && (tag === 'img' && name === 'src') && dataUriRegex.test(value)) {
+      return name + '="' + filterXSS.escapeAttrValue(value) + '"'
+    }
+  },
+  onIgnoreTagAttr: function (tag, name, value, isWhiteAttr) {
+        // allow attr start with 'data-' or in the whiteListAttr
+    if (name.substr(0, 5) === 'data-' || window.whiteListAttr.indexOf(name) !== -1) {
+            // escape its value using built-in escapeAttrValue function
+      return name + '="' + filterXSS.escapeAttrValue(value) + '"'
+    }
+  }
 }
-window.preventXSS = preventXSS;
+
+function preventXSS (html) {
+  return filterXSS(html, filterXSSOptions)
+}
+window.preventXSS = preventXSS
 
 module.exports = {
   preventXSS: preventXSS
diff --git a/public/js/reveal-markdown.js b/public/js/reveal-markdown.js
index 3c3e1f5..eca148d 100755
--- a/public/js/reveal-markdown.js
+++ b/public/js/reveal-markdown.js
@@ -1,396 +1,355 @@
+/* eslint-env browser, jquery */
+
+import { preventXSS } from './render'
+import { md } from './extra'
+
 /**
  * The reveal.js markdown plugin. Handles parsing of
  * markdown inside of presentations as well as loading
  * of external markdown documents.
  */
-(function( root, factory ) {
-	if( typeof exports === 'object' ) {
-		module.exports = factory();
-	}
-	else {
-		// Browser globals (root is window)
-		root.RevealMarkdown = factory();
-		root.RevealMarkdown.initialize();
-	}
-}( this, function() {
-	
-	var DEFAULT_SLIDE_SEPARATOR = '^\r?\n---\r?\n$',
-		DEFAULT_NOTES_SEPARATOR = 'note:',
-		DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\\.element\\\s*?(.+?)$',
-		DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\\.slide:\\\s*?(\\\S.+?)$';
-
-	var SCRIPT_END_PLACEHOLDER = '__SCRIPT_END__';
-
-
-	/**
-	 * Retrieves the markdown contents of a slide section
-	 * element. Normalizes leading tabs/whitespace.
-	 */
-	function getMarkdownFromSlide( section ) {
-
-		var template = section.querySelector( 'script' );
-
-		// strip leading whitespace so it isn't evaluated as code
-		var text = ( template || section ).textContent;
-
-		// restore script end tags
-		text = text.replace( new RegExp( SCRIPT_END_PLACEHOLDER, 'g' ), '</script>' );
-
-		var leadingWs = text.match( /^\n?(\s*)/ )[1].length,
-			leadingTabs = text.match( /^\n?(\t*)/ )[1].length;
-
-		if( leadingTabs > 0 ) {
-			text = text.replace( new RegExp('\\n?\\t{' + leadingTabs + '}','g'), '\n' );
-		}
-		else if( leadingWs > 1 ) {
-			text = text.replace( new RegExp('\\n? {' + leadingWs + '}', 'g'), '\n' );
-		}
-
-		return text;
-
-	}
-
-	/**
-	 * Given a markdown slide section element, this will
-	 * return all arguments that aren't related to markdown
-	 * parsing. Used to forward any other user-defined arguments
-	 * to the output markdown slide.
-	 */
-	function getForwardedAttributes( section ) {
-
-		var attributes = section.attributes;
-		var result = [];
-
-		for( var i = 0, len = attributes.length; i < len; i++ ) {
-			var name = attributes[i].name,
-				value = attributes[i].value;
-
-			// disregard attributes that are used for markdown loading/parsing
-			if( /data\-(markdown|separator|vertical|notes)/gi.test( name ) ) continue;
-
-			if( value ) {
-				result.push( name + '="' + value + '"' );
-			}
-			else {
-				result.push( name );
-			}
-		}
-
-		return result.join( ' ' );
-
-	}
-
-	/**
-	 * Inspects the given options and fills out default
-	 * values for what's not defined.
-	 */
-	function getSlidifyOptions( options ) {
-
-		options = options || {};
-		options.separator = options.separator || DEFAULT_SLIDE_SEPARATOR;
-		options.notesSeparator = options.notesSeparator || DEFAULT_NOTES_SEPARATOR;
-		options.attributes = options.attributes || '';
-
-		return options;
-
-	}
-
-	/**
-	 * Helper function for constructing a markdown slide.
-	 */
-	function createMarkdownSlide( content, options ) {
-
-		options = getSlidifyOptions( options );
-
-		var notesMatch = content.split( new RegExp( options.notesSeparator, 'mgi' ) );
-
-		if( notesMatch.length === 2 ) {
-			content = notesMatch[0] + '<aside class="notes" data-markdown>' + notesMatch[1].trim() + '</aside>';
-		}
-
-		// prevent script end tags in the content from interfering
-		// with parsing
-		content = content.replace( /<\/script>/g, SCRIPT_END_PLACEHOLDER );
-
-		return '<script type="text/template">' + content + '</script>';
-
-	}
-
-	/**
-	 * Parses a data string into multiple slides based
-	 * on the passed in separator arguments.
-	 */
-	function slidify( markdown, options ) {
-
-		options = getSlidifyOptions( options );
-
-		var separatorRegex = new RegExp( options.separator + ( options.verticalSeparator ? '|' + options.verticalSeparator : '' ), 'mg' ),
-			horizontalSeparatorRegex = new RegExp( options.separator );
-
-		var matches,
-			lastIndex = 0,
-			isHorizontal,
-			wasHorizontal = true,
-			content,
-			sectionStack = [];
-
-		// iterate until all blocks between separators are stacked up
-		while( matches = separatorRegex.exec( markdown ) ) {
-			notes = null;
-
-			// determine direction (horizontal by default)
-			isHorizontal = horizontalSeparatorRegex.test( matches[0] );
-
-			if( !isHorizontal && wasHorizontal ) {
-				// create vertical stack
-				sectionStack.push( [] );
-			}
-
-			// pluck slide content from markdown input
-			content = markdown.substring( lastIndex, matches.index );
-
-			if( isHorizontal && wasHorizontal ) {
-				// add to horizontal stack
-				sectionStack.push( content );
-			}
-			else {
-				// add to vertical stack
-				sectionStack[sectionStack.length-1].push( content );
-			}
-
-			lastIndex = separatorRegex.lastIndex;
-			wasHorizontal = isHorizontal;
-		}
-
-		// add the remaining slide
-		( wasHorizontal ? sectionStack : sectionStack[sectionStack.length-1] ).push( markdown.substring( lastIndex ) );
-
-		var markdownSections = '';
-
-		// flatten the hierarchical stack, and insert <section data-markdown> tags
-		for( var i = 0, len = sectionStack.length; i < len; i++ ) {
-			// vertical
-			if( sectionStack[i] instanceof Array ) {
-				markdownSections += '<section '+ options.attributes +'>';
-
-				sectionStack[i].forEach( function( child ) {
-					markdownSections += '<section data-markdown>' +  createMarkdownSlide( child, options ) + '</section>';
-				} );
-
-				markdownSections += '</section>';
-			}
-			else {
-				markdownSections += '<section '+ options.attributes +' data-markdown>' + createMarkdownSlide( sectionStack[i], options ) + '</section>';
-			}
-		}
-
-		return markdownSections;
-
-	}
-
-	/**
-	 * Parses any current data-markdown slides, splits
-	 * multi-slide markdown into separate sections and
-	 * handles loading of external markdown.
-	 */
-	function processSlides() {
-
-		var sections = document.querySelectorAll( '[data-markdown]'),
-			section;
-
-		for( var i = 0, len = sections.length; i < len; i++ ) {
-
-			section = sections[i];
-
-			if( section.getAttribute( 'data-markdown' ).length ) {
-
-				var xhr = new XMLHttpRequest(),
-					url = section.getAttribute( 'data-markdown' );
-
-				datacharset = section.getAttribute( 'data-charset' );
-
-				// see https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute#Notes
-				if( datacharset != null && datacharset != '' ) {
-					xhr.overrideMimeType( 'text/html; charset=' + datacharset );
-				}
-
-				xhr.onreadystatechange = function() {
-					if( xhr.readyState === 4 ) {
-						// file protocol yields status code 0 (useful for local debug, mobile applications etc.)
-						if ( ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status === 0 ) {
-
-							section.outerHTML = slidify( xhr.responseText, {
-								separator: section.getAttribute( 'data-separator' ),
-								verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
-								notesSeparator: section.getAttribute( 'data-separator-notes' ),
-								attributes: getForwardedAttributes( section )
-							});
-
-						}
-						else {
-
-							section.outerHTML = '<section data-state="alert">' +
-								'ERROR: The attempt to fetch ' + url + ' failed with HTTP status ' + xhr.status + '.' +
-								'Check your browser\'s JavaScript console for more details.' +
-								'<p>Remember that you need to serve the presentation HTML from a HTTP server.</p>' +
-								'</section>';
-
-						}
-					}
-				};
-
-				xhr.open( 'GET', url, false );
-
-				try {
-					xhr.send();
-				}
-				catch ( e ) {
-					alert( 'Failed to get the Markdown file ' + url + '. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ' + e );
-				}
-
-			}
-			else if( section.getAttribute( 'data-separator' ) || section.getAttribute( 'data-separator-vertical' ) || section.getAttribute( 'data-separator-notes' ) ) {
-
-				section.outerHTML = slidify( getMarkdownFromSlide( section ), {
-					separator: section.getAttribute( 'data-separator' ),
-					verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
-					notesSeparator: section.getAttribute( 'data-separator-notes' ),
-					attributes: getForwardedAttributes( section )
-				});
-
-			}
-			else {
-				section.innerHTML = createMarkdownSlide( getMarkdownFromSlide( section ) );
-			}
-		}
-
-	}
-
-	/**
-	 * Check if a node value has the attributes pattern.
-	 * If yes, extract it and add that value as one or several attributes
-	 * the the terget element.
-	 *
-	 * You need Cache Killer on Chrome to see the effect on any FOM transformation
-	 * directly on refresh (F5)
-	 * http://stackoverflow.com/questions/5690269/disabling-chrome-cache-for-website-development/7000899#answer-11786277
-	 */
-	function addAttributeInElement( node, elementTarget, separator ) {
-
-		var mardownClassesInElementsRegex = new RegExp( separator, 'mg' );
-		var mardownClassRegex = new RegExp( "([^\"= ]+?)=\"([^\"=]+?)\"", 'mg' );
-		var nodeValue = node.nodeValue;
-		if( matches = mardownClassesInElementsRegex.exec( nodeValue ) ) {
-
-			var classes = matches[1];
-			nodeValue = nodeValue.substring( 0, matches.index ) + nodeValue.substring( mardownClassesInElementsRegex.lastIndex );
-			node.nodeValue = nodeValue;
-			while( matchesClass = mardownClassRegex.exec( classes ) ) {
-				var name = matchesClass[1];
-				var value = matchesClass[2];
-				if (name.substr(0, 5) === 'data-' || whiteListAttr.indexOf(name) !== -1)
-					elementTarget.setAttribute( name, filterXSS.escapeAttrValue(value) );
-			}
-			return true;
-		}
-		return false;
-	}
-
-	/**
-	 * Add attributes to the parent element of a text node,
-	 * or the element of an attribute node.
-	 */
-	function addAttributes( section, element, previousElement, separatorElementAttributes, separatorSectionAttributes ) {
-
-		if ( element != null && element.childNodes != undefined && element.childNodes.length > 0 ) {
-			previousParentElement = element;
-			for( var i = 0; i < element.childNodes.length; i++ ) {
-				childElement = element.childNodes[i];
-				if ( i > 0 ) {
-					j = i - 1;
-					while ( j >= 0 ) {
-						aPreviousChildElement = element.childNodes[j];
-						if ( typeof aPreviousChildElement.setAttribute == 'function' && aPreviousChildElement.tagName != "BR" ) {
-							previousParentElement = aPreviousChildElement;
-							break;
-						}
-						j = j - 1;
-					}
-				}
-				parentSection = section;
-				if( childElement.nodeName ==  "section" ) {
-					parentSection = childElement ;
-					previousParentElement = childElement ;
-				}
-				if ( typeof childElement.setAttribute == 'function' || childElement.nodeType == Node.COMMENT_NODE ) {
-					addAttributes( parentSection, childElement, previousParentElement, separatorElementAttributes, separatorSectionAttributes );
-				}
-			}
-		}
-
-		if ( element.nodeType == Node.COMMENT_NODE ) {
-			if ( addAttributeInElement( element, previousElement, separatorElementAttributes ) == false ) {
-				addAttributeInElement( element, section, separatorSectionAttributes );
-			}
-		}
-	}
-
-	/**
-	 * Converts any current data-markdown slides in the
-	 * DOM to HTML.
-	 */
-	function convertSlides() {
-
-		var sections = document.querySelectorAll( '[data-markdown]');
-
-		for( var i = 0, len = sections.length; i < len; i++ ) {
-
-			var section = sections[i];
-
-			// Only parse the same slide once
-			if( !section.getAttribute( 'data-markdown-parsed' ) ) {
-
-				section.setAttribute( 'data-markdown-parsed', true )
-
-				var notes = section.querySelector( 'aside.notes' );
-				var markdown = getMarkdownFromSlide( section );
-
-                var rendered = md.render(markdown);
-				rendered = preventXSS(rendered);
-				var result = postProcess(rendered);
-				section.innerHTML = result[0].outerHTML;
-				addAttributes( 	section, section, null, section.getAttribute( 'data-element-attributes' ) ||
-								section.parentNode.getAttribute( 'data-element-attributes' ) ||
-								DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR,
-								section.getAttribute( 'data-attributes' ) ||
-								section.parentNode.getAttribute( 'data-attributes' ) ||
-								DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR);
-
-				// If there were notes, we need to re-add them after
-				// having overwritten the section's HTML
-				if( notes ) {
-					section.appendChild( notes );
-				}
-
-			}
-
-		}
-
-	}
-
-	// API
-	return {
-
-		initialize: function() {
-			processSlides();
-			convertSlides();
-		},
-
-		// TODO: Do these belong in the API?
-		processSlides: processSlides,
-		convertSlides: convertSlides,
-		slidify: slidify
-
-	};
-
-}));
+(function (root, factory) {
+  if (typeof exports === 'object') {
+    module.exports = factory()
+  } else {
+    // Browser globals (root is window)
+    root.RevealMarkdown = factory()
+    root.RevealMarkdown.initialize()
+  }
+}(this, function () {
+  var DEFAULT_SLIDE_SEPARATOR = '^\r?\n---\r?\n$'
+  var DEFAULT_NOTES_SEPARATOR = 'note:'
+  var DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\.element\\s*?(.+?)$'
+  var DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\.slide:\\s*?(\\S.+?)$'
+
+  var SCRIPT_END_PLACEHOLDER = '__SCRIPT_END__'
+
+  /**
+   * Retrieves the markdown contents of a slide section
+   * element. Normalizes leading tabs/whitespace.
+   */
+  function getMarkdownFromSlide (section) {
+    var template = section.querySelector('script')
+
+    // strip leading whitespace so it isn't evaluated as code
+    var text = (template || section).textContent
+
+    // restore script end tags
+    text = text.replace(new RegExp(SCRIPT_END_PLACEHOLDER, 'g'), '</script>')
+
+    var leadingWs = text.match(/^\n?(\s*)/)[1].length
+    var leadingTabs = text.match(/^\n?(\t*)/)[1].length
+
+    if (leadingTabs > 0) {
+      text = text.replace(new RegExp('\\n?\\t{' + leadingTabs + '}', 'g'), '\n')
+    } else if (leadingWs > 1) {
+      text = text.replace(new RegExp('\\n? {' + leadingWs + '}', 'g'), '\n')
+    }
+
+    return text
+  }
+
+  /**
+   * Given a markdown slide section element, this will
+   * return all arguments that aren't related to markdown
+   * parsing. Used to forward any other user-defined arguments
+   * to the output markdown slide.
+   */
+  function getForwardedAttributes (section) {
+    var attributes = section.attributes
+    var result = []
+
+    for (var i = 0, len = attributes.length; i < len; i++) {
+      var name = attributes[i].name
+      var value = attributes[i].value
+
+      // disregard attributes that are used for markdown loading/parsing
+      if (/data-(markdown|separator|vertical|notes)/gi.test(name)) continue
+
+      if (value) {
+        result.push(name + '="' + value + '"')
+      } else {
+        result.push(name)
+      }
+    }
+
+    return result.join(' ')
+  }
+
+  /**
+   * Inspects the given options and fills out default
+   * values for what's not defined.
+   */
+  function getSlidifyOptions (options) {
+    options = options || {}
+    options.separator = options.separator || DEFAULT_SLIDE_SEPARATOR
+    options.notesSeparator = options.notesSeparator || DEFAULT_NOTES_SEPARATOR
+    options.attributes = options.attributes || ''
+
+    return options
+  }
+
+  /**
+   * Helper function for constructing a markdown slide.
+   */
+  function createMarkdownSlide (content, options) {
+    options = getSlidifyOptions(options)
+
+    var notesMatch = content.split(new RegExp(options.notesSeparator, 'mgi'))
+
+    if (notesMatch.length === 2) {
+      content = notesMatch[0] + '<aside class="notes" data-markdown>' + notesMatch[1].trim() + '</aside>'
+    }
+
+    // prevent script end tags in the content from interfering
+    // with parsing
+    content = content.replace(/<\/script>/g, SCRIPT_END_PLACEHOLDER)
+
+    return '<script type="text/template">' + content + '</script>'
+  }
+
+  /**
+   * Parses a data string into multiple slides based
+   * on the passed in separator arguments.
+   */
+  function slidify (markdown, options) {
+    options = getSlidifyOptions(options)
+
+    var separatorRegex = new RegExp(options.separator + (options.verticalSeparator ? '|' + options.verticalSeparator : ''), 'mg')
+    var horizontalSeparatorRegex = new RegExp(options.separator)
+
+    var matches
+    var lastIndex = 0
+    var isHorizontal
+    var wasHorizontal = true
+    var content
+    var sectionStack = []
+
+    // iterate until all blocks between separators are stacked up
+    while ((matches = separatorRegex.exec(markdown)) !== null) {
+      // determine direction (horizontal by default)
+      isHorizontal = horizontalSeparatorRegex.test(matches[0])
+
+      if (!isHorizontal && wasHorizontal) {
+        // create vertical stack
+        sectionStack.push([])
+      }
+
+      // pluck slide content from markdown input
+      content = markdown.substring(lastIndex, matches.index)
+
+      if (isHorizontal && wasHorizontal) {
+        // add to horizontal stack
+        sectionStack.push(content)
+      } else {
+        // add to vertical stack
+        sectionStack[sectionStack.length - 1].push(content)
+      }
+
+      lastIndex = separatorRegex.lastIndex
+      wasHorizontal = isHorizontal
+    }
+
+    // add the remaining slide
+    (wasHorizontal ? sectionStack : sectionStack[sectionStack.length - 1]).push(markdown.substring(lastIndex))
+
+    var markdownSections = ''
+
+    // flatten the hierarchical stack, and insert <section data-markdown> tags
+    for (var i = 0, len = sectionStack.length; i < len; i++) {
+      // vertical
+      if (sectionStack[i] instanceof Array) {
+        markdownSections += '<section ' + options.attributes + '>'
+
+        sectionStack[i].forEach(function (child) {
+          markdownSections += '<section data-markdown>' + createMarkdownSlide(child, options) + '</section>'
+        })
+
+        markdownSections += '</section>'
+      } else {
+        markdownSections += '<section ' + options.attributes + ' data-markdown>' + createMarkdownSlide(sectionStack[i], options) + '</section>'
+      }
+    }
+
+    return markdownSections
+  }
+
+  /**
+   * Parses any current data-markdown slides, splits
+   * multi-slide markdown into separate sections and
+   * handles loading of external markdown.
+   */
+  function processSlides () {
+    var sections = document.querySelectorAll('[data-markdown]')
+    var section
+
+    for (var i = 0, len = sections.length; i < len; i++) {
+      section = sections[i]
+
+      if (section.getAttribute('data-markdown').length) {
+        var xhr = new XMLHttpRequest()
+        var url = section.getAttribute('data-markdown')
+
+        var datacharset = section.getAttribute('data-charset')
+
+        // see https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute#Notes
+        if (datacharset !== null && datacharset !== '') {
+          xhr.overrideMimeType('text/html; charset=' + datacharset)
+        }
+
+        xhr.onreadystatechange = function () {
+          if (xhr.readyState === 4) {
+            // file protocol yields status code 0 (useful for local debug, mobile applications etc.)
+            if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 0) {
+              section.outerHTML = slidify(xhr.responseText, {
+                separator: section.getAttribute('data-separator'),
+                verticalSeparator: section.getAttribute('data-separator-vertical'),
+                notesSeparator: section.getAttribute('data-separator-notes'),
+                attributes: getForwardedAttributes(section)
+              })
+            } else {
+              section.outerHTML = '<section data-state="alert">' +
+              'ERROR: The attempt to fetch ' + url + ' failed with HTTP status ' + xhr.status + '.' +
+              'Check your browser\'s JavaScript console for more details.' +
+              '<p>Remember that you need to serve the presentation HTML from a HTTP server.</p>' +
+              '</section>'
+            }
+          }
+        }
+
+        xhr.open('GET', url, false)
+
+        try {
+          xhr.send()
+        } catch (e) {
+          alert('Failed to get the Markdown file ' + url + '. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ' + e)
+        }
+      } else if (section.getAttribute('data-separator') || section.getAttribute('data-separator-vertical') || section.getAttribute('data-separator-notes')) {
+        section.outerHTML = slidify(getMarkdownFromSlide(section), {
+          separator: section.getAttribute('data-separator'),
+          verticalSeparator: section.getAttribute('data-separator-vertical'),
+          notesSeparator: section.getAttribute('data-separator-notes'),
+          attributes: getForwardedAttributes(section)
+        })
+      } else {
+        section.innerHTML = createMarkdownSlide(getMarkdownFromSlide(section))
+      }
+    }
+  }
+
+  /**
+   * Check if a node value has the attributes pattern.
+   * If yes, extract it and add that value as one or several attributes
+   * the the terget element.
+   *
+   * You need Cache Killer on Chrome to see the effect on any FOM transformation
+   * directly on refresh (F5)
+   * http://stackoverflow.com/questions/5690269/disabling-chrome-cache-for-website-development/7000899#answer-11786277
+   */
+  function addAttributeInElement (node, elementTarget, separator) {
+    var mardownClassesInElementsRegex = new RegExp(separator, 'mg')
+    var mardownClassRegex = new RegExp('([^"= ]+?)="([^"=]+?)"', 'mg')
+    var nodeValue = node.nodeValue
+    var matches
+    var matchesClass
+    if ((matches = mardownClassesInElementsRegex.exec(nodeValue))) {
+      var classes = matches[1]
+      nodeValue = nodeValue.substring(0, matches.index) + nodeValue.substring(mardownClassesInElementsRegex.lastIndex)
+      node.nodeValue = nodeValue
+      while ((matchesClass = mardownClassRegex.exec(classes))) {
+        var name = matchesClass[1]
+        var value = matchesClass[2]
+        if (name.substr(0, 5) === 'data-' || window.whiteListAttr.indexOf(name) !== -1) { elementTarget.setAttribute(name, window.filterXSS.escapeAttrValue(value)) }
+      }
+      return true
+    }
+    return false
+  }
+
+  /**
+   * Add attributes to the parent element of a text node,
+   * or the element of an attribute node.
+   */
+  function addAttributes (section, element, previousElement, separatorElementAttributes, separatorSectionAttributes) {
+    if (element != null && element.childNodes !== undefined && element.childNodes.length > 0) {
+      var previousParentElement = element
+      for (var i = 0; i < element.childNodes.length; i++) {
+        var childElement = element.childNodes[i]
+        if (i > 0) {
+          let j = i - 1
+          while (j >= 0) {
+            var aPreviousChildElement = element.childNodes[j]
+            if (typeof aPreviousChildElement.setAttribute === 'function' && aPreviousChildElement.tagName !== 'BR') {
+              previousParentElement = aPreviousChildElement
+              break
+            }
+            j = j - 1
+          }
+        }
+        var parentSection = section
+        if (childElement.nodeName === 'section') {
+          parentSection = childElement
+          previousParentElement = childElement
+        }
+        if (typeof childElement.setAttribute === 'function' || childElement.nodeType === Node.COMMENT_NODE) {
+          addAttributes(parentSection, childElement, previousParentElement, separatorElementAttributes, separatorSectionAttributes)
+        }
+      }
+    }
+
+    if (element.nodeType === Node.COMMENT_NODE) {
+      if (addAttributeInElement(element, previousElement, separatorElementAttributes) === false) {
+        addAttributeInElement(element, section, separatorSectionAttributes)
+      }
+    }
+  }
+
+  /**
+   * Converts any current data-markdown slides in the
+   * DOM to HTML.
+   */
+  function convertSlides () {
+    var sections = document.querySelectorAll('[data-markdown]')
+
+    for (var i = 0, len = sections.length; i < len; i++) {
+      var section = sections[i]
+
+      // Only parse the same slide once
+      if (!section.getAttribute('data-markdown-parsed')) {
+        section.setAttribute('data-markdown-parsed', true)
+
+        var notes = section.querySelector('aside.notes')
+        var markdown = getMarkdownFromSlide(section)
+
+        var rendered = md.render(markdown)
+        rendered = preventXSS(rendered)
+        var result = window.postProcess(rendered)
+        section.innerHTML = result[0].outerHTML
+        addAttributes(section, section, null, section.getAttribute('data-element-attributes') ||
+        section.parentNode.getAttribute('data-element-attributes') ||
+        DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR,
+        section.getAttribute('data-attributes') ||
+        section.parentNode.getAttribute('data-attributes') ||
+        DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR)
+
+        // If there were notes, we need to re-add them after
+        // having overwritten the section's HTML
+        if (notes) {
+          section.appendChild(notes)
+        }
+      }
+    }
+  }
+
+  // API
+  return {
+    initialize: function () {
+      processSlides()
+      convertSlides()
+    },
+    // TODO: Do these belong in the API?
+    processSlides: processSlides,
+    convertSlides: convertSlides,
+    slidify: slidify
+  }
+}))
diff --git a/public/js/slide.js b/public/js/slide.js
index 63cf64c..e743bb5 100644
--- a/public/js/slide.js
+++ b/public/js/slide.js
@@ -1,138 +1,139 @@
-require('../css/extra.css');
-require('../css/site.css');
+/* eslint-env browser, jquery */
+/* global serverurl, Reveal */
 
-import { md, updateLastChange, finishView } from './extra';
+require('../css/extra.css')
+require('../css/site.css')
 
-import { preventXSS } from './render';
+import { md, updateLastChange, finishView } from './extra'
 
-const body = $(".slides").text();
+const body = $('.slides').text()
 
-createtime = lastchangeui.time.attr('data-createtime');
-lastchangetime = lastchangeui.time.attr('data-updatetime');
-updateLastChange();
-const url = window.location.pathname;
-$('.ui-edit').attr('href', `${url}/edit`);
+window.createtime = window.lastchangeui.time.attr('data-createtime')
+window.lastchangetime = window.lastchangeui.time.attr('data-updatetime')
+updateLastChange()
+const url = window.location.pathname
+$('.ui-edit').attr('href', `${url}/edit`)
 
 $(document).ready(() => {
-    //tooltip
-    $('[data-toggle="tooltip"]').tooltip();
-});
+    // tooltip
+  $('[data-toggle="tooltip"]').tooltip()
+})
 
-function extend() {
-    const target = {};
+function extend () {
+  const target = {}
 
-    for (const source of arguments) {
-        for (const key in source) {
-            if (source.hasOwnProperty(key)) {
-                target[key] = source[key];
-            }
-        }
+  for (const source of arguments) {
+    for (const key in source) {
+      if (source.hasOwnProperty(key)) {
+        target[key] = source[key]
+      }
     }
+  }
 
-    return target;
+  return target
 }
 
 // Optional libraries used to extend on reveal.js
 const deps = [{
-    src: `${serverurl}/build/reveal.js/lib/js/classList.js`,
-    condition() {
-        return !document.body.classList;
-    }
+  src: `${serverurl}/build/reveal.js/lib/js/classList.js`,
+  condition () {
+    return !document.body.classList
+  }
 }, {
-    src: `${serverurl}/js/reveal-markdown.js`,
-    callback() {
-        const slideOptions = {
-            separator: '^(\r\n?|\n)---(\r\n?|\n)$',
-            verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$'
-        };
-        const slides = RevealMarkdown.slidify(body, slideOptions);
-        $(".slides").html(slides);
-        RevealMarkdown.initialize();
-        $(".slides").show();
+  src: `${serverurl}/js/reveal-markdown.js`,
+  callback () {
+    const slideOptions = {
+      separator: '^(\r\n?|\n)---(\r\n?|\n)$',
+      verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$'
     }
+    const slides = window.RevealMarkdown.slidify(body, slideOptions)
+    $('.slides').html(slides)
+    window.RevealMarkdown.initialize()
+    $('.slides').show()
+  }
 }, {
-    src: `${serverurl}/build/reveal.js/plugin/notes/notes.js`,
-    async: true,
-    condition() {
-        return !!document.body.classList;
-    }
-}];
+  src: `${serverurl}/build/reveal.js/plugin/notes/notes.js`,
+  async: true,
+  condition () {
+    return !!document.body.classList
+  }
+}]
 
 // default options to init reveal.js
 const defaultOptions = {
-    controls: true,
-    progress: true,
-    slideNumber: true,
-    history: true,
-    center: true,
-    transition: 'none',
-    dependencies: deps
-};
+  controls: true,
+  progress: true,
+  slideNumber: true,
+  history: true,
+  center: true,
+  transition: 'none',
+  dependencies: deps
+}
 
 // options from yaml meta
-const meta = JSON.parse($("#meta").text());
-var options = meta.slideOptions || {};
+const meta = JSON.parse($('#meta').text())
+var options = meta.slideOptions || {}
 
-const view = $('.reveal');
+const view = $('.reveal')
 
-//text language
-if (meta.lang && typeof meta.lang == "string") {
-    view.attr('lang', meta.lang);
+// text language
+if (meta.lang && typeof meta.lang === 'string') {
+  view.attr('lang', meta.lang)
 } else {
-    view.removeAttr('lang');
+  view.removeAttr('lang')
 }
-//text direction
-if (meta.dir && typeof meta.dir == "string" && meta.dir == "rtl") {
-    options.rtl = true;
+// text direction
+if (meta.dir && typeof meta.dir === 'string' && meta.dir === 'rtl') {
+  options.rtl = true
 } else {
-    options.rtl = false;
+  options.rtl = false
 }
-//breaks
+// breaks
 if (typeof meta.breaks === 'boolean' && !meta.breaks) {
-    md.options.breaks = false;
+  md.options.breaks = false
 } else {
-    md.options.breaks = true;
+  md.options.breaks = true
 }
 
 // options from URL query string
-const queryOptions = Reveal.getQueryHash() || {};
+const queryOptions = Reveal.getQueryHash() || {}
 
-var options = extend(defaultOptions, options, queryOptions);
-Reveal.initialize(options);
+options = extend(defaultOptions, options, queryOptions)
+Reveal.initialize(options)
 
 window.viewAjaxCallback = () => {
-    Reveal.layout();
-};
+  Reveal.layout()
+}
 
-function renderSlide(event) {
-    if (window.location.search.match( /print-pdf/gi )) {
-        const slides = $('.slides');
-        var title = document.title;
-        finishView(slides);
-        document.title = title;
-        Reveal.layout();
-    } else {
-        const markdown = $(event.currentSlide);
-        if (!markdown.attr('data-rendered')) {
-            var title = document.title;
-            finishView(markdown);
-            markdown.attr('data-rendered', 'true');
-            document.title = title;
-            Reveal.layout();
-        }
+function renderSlide (event) {
+  if (window.location.search.match(/print-pdf/gi)) {
+    const slides = $('.slides')
+    let title = document.title
+    finishView(slides)
+    document.title = title
+    Reveal.layout()
+  } else {
+    const markdown = $(event.currentSlide)
+    if (!markdown.attr('data-rendered')) {
+      let title = document.title
+      finishView(markdown)
+      markdown.attr('data-rendered', 'true')
+      document.title = title
+      Reveal.layout()
     }
+  }
 }
 
 Reveal.addEventListener('ready', event => {
-    renderSlide(event);
-    const markdown = $(event.currentSlide);
+  renderSlide(event)
+  const markdown = $(event.currentSlide)
     // force browser redraw
-    setTimeout(() => {
-        markdown.hide().show(0);
-    }, 0);
-});
-Reveal.addEventListener('slidechanged', renderSlide);
+  setTimeout(() => {
+    markdown.hide().show(0)
+  }, 0)
+})
+Reveal.addEventListener('slidechanged', renderSlide)
 
-const isMacLike = navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i) ? true : false;
+const isMacLike = !!navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i)
 
-if (!isMacLike) $('.container').addClass('hidescrollbar');
+if (!isMacLike) $('.container').addClass('hidescrollbar')
diff --git a/public/js/syncscroll.js b/public/js/syncscroll.js
index c969317..c227f83 100644
--- a/public/js/syncscroll.js
+++ b/public/js/syncscroll.js
@@ -1,365 +1,367 @@
+/* eslint-env browser, jquery */
+/* global _ */
 // Inject line numbers for sync scroll.
 
-import markdownitContainer from 'markdown-it-container';
+import markdownitContainer from 'markdown-it-container'
 
-import { md } from './extra';
+import { md } from './extra'
 
-function addPart(tokens, idx) {
-    if (tokens[idx].map && tokens[idx].level === 0) {
-        const startline = tokens[idx].map[0] + 1;
-        const endline = tokens[idx].map[1];
-        tokens[idx].attrJoin('class', 'part');
-        tokens[idx].attrJoin('data-startline', startline);
-        tokens[idx].attrJoin('data-endline', endline);
-    }
+function addPart (tokens, idx) {
+  if (tokens[idx].map && tokens[idx].level === 0) {
+    const startline = tokens[idx].map[0] + 1
+    const endline = tokens[idx].map[1]
+    tokens[idx].attrJoin('class', 'part')
+    tokens[idx].attrJoin('data-startline', startline)
+    tokens[idx].attrJoin('data-endline', endline)
+  }
 }
 
 md.renderer.rules.blockquote_open = function (tokens, idx, options, env, self) {
-    tokens[idx].attrJoin('class', 'raw');
-    addPart(tokens, idx);
-    return self.renderToken(...arguments);
-};
+  tokens[idx].attrJoin('class', 'raw')
+  addPart(tokens, idx)
+  return self.renderToken(...arguments)
+}
 md.renderer.rules.table_open = function (tokens, idx, options, env, self) {
-    addPart(tokens, idx);
-    return self.renderToken(...arguments);
-};
+  addPart(tokens, idx)
+  return self.renderToken(...arguments)
+}
 md.renderer.rules.bullet_list_open = function (tokens, idx, options, env, self) {
-    addPart(tokens, idx);
-    return self.renderToken(...arguments);
-};
+  addPart(tokens, idx)
+  return self.renderToken(...arguments)
+}
 md.renderer.rules.list_item_open = function (tokens, idx, options, env, self) {
-    tokens[idx].attrJoin('class', 'raw');
-    if (tokens[idx].map) {
-        const startline = tokens[idx].map[0] + 1;
-        const endline = tokens[idx].map[1];
-        tokens[idx].attrJoin('data-startline', startline);
-        tokens[idx].attrJoin('data-endline', endline);
-    }
-    return self.renderToken(...arguments);
-};
+  tokens[idx].attrJoin('class', 'raw')
+  if (tokens[idx].map) {
+    const startline = tokens[idx].map[0] + 1
+    const endline = tokens[idx].map[1]
+    tokens[idx].attrJoin('data-startline', startline)
+    tokens[idx].attrJoin('data-endline', endline)
+  }
+  return self.renderToken(...arguments)
+}
 md.renderer.rules.ordered_list_open = function (tokens, idx, options, env, self) {
-    addPart(tokens, idx);
-    return self.renderToken(...arguments);
-};
+  addPart(tokens, idx)
+  return self.renderToken(...arguments)
+}
 md.renderer.rules.link_open = function (tokens, idx, options, env, self) {
-    addPart(tokens, idx);
-    return self.renderToken(...arguments);
-};
+  addPart(tokens, idx)
+  return self.renderToken(...arguments)
+}
 md.renderer.rules.paragraph_open = function (tokens, idx, options, env, self) {
-    addPart(tokens, idx);
-    return self.renderToken(...arguments);
-};
+  addPart(tokens, idx)
+  return self.renderToken(...arguments)
+}
 md.renderer.rules.heading_open = function (tokens, idx, options, env, self) {
-    tokens[idx].attrJoin('class', 'raw');
-    addPart(tokens, idx);
-    return self.renderToken(...arguments);
-};
+  tokens[idx].attrJoin('class', 'raw')
+  addPart(tokens, idx)
+  return self.renderToken(...arguments)
+}
 md.renderer.rules.fence = (tokens, idx, options, env, self) => {
-    const token = tokens[idx];
-    const info = token.info ? md.utils.unescapeAll(token.info).trim() : '';
-    let langName = '';
-    let highlighted;
+  const token = tokens[idx]
+  const info = token.info ? md.utils.unescapeAll(token.info).trim() : ''
+  let langName = ''
+  let highlighted
 
-    if (info) {
-        langName = info.split(/\s+/g)[0];
-        if (/\!$/.test(info)) token.attrJoin('class', 'wrap');
-        token.attrJoin('class', options.langPrefix + langName.replace(/\=$|\=\d+$|\=\+$|\!$|\=\!/, ''));
-        token.attrJoin('class', 'hljs');
-        token.attrJoin('class', 'raw');
-    }
+  if (info) {
+    langName = info.split(/\s+/g)[0]
+    if (/!$/.test(info)) token.attrJoin('class', 'wrap')
+    token.attrJoin('class', options.langPrefix + langName.replace(/=$|=\d+$|=\+$|!$|=!/, ''))
+    token.attrJoin('class', 'hljs')
+    token.attrJoin('class', 'raw')
+  }
 
-    if (options.highlight) {
-        highlighted = options.highlight(token.content, langName) || md.utils.escapeHtml(token.content);
-    } else {
-        highlighted = md.utils.escapeHtml(token.content);
-    }
+  if (options.highlight) {
+    highlighted = options.highlight(token.content, langName) || md.utils.escapeHtml(token.content)
+  } else {
+    highlighted = md.utils.escapeHtml(token.content)
+  }
 
-    if (highlighted.indexOf('<pre') === 0) {
-        return `${highlighted}\n`;
-    }
+  if (highlighted.indexOf('<pre') === 0) {
+    return `${highlighted}\n`
+  }
 
-    if (tokens[idx].map && tokens[idx].level === 0) {
-        const startline = tokens[idx].map[0] + 1;
-        const endline = tokens[idx].map[1];
-        return `<pre class="part" data-startline="${startline}" data-endline="${endline}"><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n`;
-    }
+  if (tokens[idx].map && tokens[idx].level === 0) {
+    const startline = tokens[idx].map[0] + 1
+    const endline = tokens[idx].map[1]
+    return `<pre class="part" data-startline="${startline}" data-endline="${endline}"><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n`
+  }
 
-    return `<pre><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n`;
-};
+  return `<pre><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n`
+}
 md.renderer.rules.code_block = (tokens, idx, options, env, self) => {
-    if (tokens[idx].map && tokens[idx].level === 0) {
-        const startline = tokens[idx].map[0] + 1;
-        const endline = tokens[idx].map[1];
-        return `<pre class="part" data-startline="${startline}" data-endline="${endline}"><code>${md.utils.escapeHtml(tokens[idx].content)}</code></pre>\n`;
-    }
-    return `<pre><code>${md.utils.escapeHtml(tokens[idx].content)}</code></pre>\n`;
-};
-function renderContainer(tokens, idx, options, env, self) {
-    tokens[idx].attrJoin('role', 'alert');
-    tokens[idx].attrJoin('class', 'alert');
-    tokens[idx].attrJoin('class', `alert-${tokens[idx].info.trim()}`);
-    addPart(tokens, idx);
-    return self.renderToken(...arguments);
+  if (tokens[idx].map && tokens[idx].level === 0) {
+    const startline = tokens[idx].map[0] + 1
+    const endline = tokens[idx].map[1]
+    return `<pre class="part" data-startline="${startline}" data-endline="${endline}"><code>${md.utils.escapeHtml(tokens[idx].content)}</code></pre>\n`
+  }
+  return `<pre><code>${md.utils.escapeHtml(tokens[idx].content)}</code></pre>\n`
+}
+function renderContainer (tokens, idx, options, env, self) {
+  tokens[idx].attrJoin('role', 'alert')
+  tokens[idx].attrJoin('class', 'alert')
+  tokens[idx].attrJoin('class', `alert-${tokens[idx].info.trim()}`)
+  addPart(tokens, idx)
+  return self.renderToken(...arguments)
 }
 
-md.use(markdownitContainer, 'success', { render: renderContainer });
-md.use(markdownitContainer, 'info', { render: renderContainer });
-md.use(markdownitContainer, 'warning', { render: renderContainer });
-md.use(markdownitContainer, 'danger', { render: renderContainer });
+md.use(markdownitContainer, 'success', { render: renderContainer })
+md.use(markdownitContainer, 'info', { render: renderContainer })
+md.use(markdownitContainer, 'warning', { render: renderContainer })
+md.use(markdownitContainer, 'danger', { render: renderContainer })
 
 // FIXME: expose syncscroll to window
-window.syncscroll = true;
+window.syncscroll = true
 
-window.preventSyncScrollToEdit = false;
-window.preventSyncScrollToView = false;
+window.preventSyncScrollToEdit = false
+window.preventSyncScrollToView = false
 
-const editScrollThrottle = 5;
-const viewScrollThrottle = 5;
-const buildMapThrottle = 100;
+const editScrollThrottle = 5
+const viewScrollThrottle = 5
+const buildMapThrottle = 100
 
-let viewScrolling = false;
-let editScrolling = false;
+let viewScrolling = false
+let editScrolling = false
 
-let editArea = null;
-let viewArea = null;
-let markdownArea = null;
+let editArea = null
+let viewArea = null
+let markdownArea = null
 
-export function setupSyncAreas(edit, view, markdown) {
-    editArea = edit;
-    viewArea = view;
-    markdownArea = markdown;
-    editArea.on('scroll', _.throttle(syncScrollToView, editScrollThrottle));
-    viewArea.on('scroll', _.throttle(syncScrollToEdit, viewScrollThrottle));
+export function setupSyncAreas (edit, view, markdown) {
+  editArea = edit
+  viewArea = view
+  markdownArea = markdown
+  editArea.on('scroll', _.throttle(syncScrollToView, editScrollThrottle))
+  viewArea.on('scroll', _.throttle(syncScrollToEdit, viewScrollThrottle))
 }
 
-let scrollMap, lineHeightMap, viewTop, viewBottom;
+let scrollMap, lineHeightMap, viewTop, viewBottom
 
-export function clearMap() {
-    scrollMap = null;
-    lineHeightMap = null;
-    viewTop = null;
-    viewBottom = null;
+export function clearMap () {
+  scrollMap = null
+  lineHeightMap = null
+  viewTop = null
+  viewBottom = null
 }
-window.viewAjaxCallback = clearMap;
+window.viewAjaxCallback = clearMap
 
-const buildMap = _.throttle(buildMapInner, buildMapThrottle);
+const buildMap = _.throttle(buildMapInner, buildMapThrottle)
 
 // Build offsets for each line (lines can be wrapped)
 // That's a bit dirty to process each line everytime, but ok for demo.
 // Optimizations are required only for big texts.
-function buildMapInner(callback) {
-    if (!viewArea || !markdownArea) return;
-    let i, offset, nonEmptyList, pos, a, b, _lineHeightMap, linesCount, acc, _scrollMap;
+function buildMapInner (callback) {
+  if (!viewArea || !markdownArea) return
+  let i, offset, nonEmptyList, pos, a, b, _lineHeightMap, linesCount, acc, _scrollMap
 
-    offset = viewArea.scrollTop() - viewArea.offset().top;
-    _scrollMap = [];
-    nonEmptyList = [];
-    _lineHeightMap = [];
-    viewTop = 0;
-    viewBottom = viewArea[0].scrollHeight - viewArea.height();
+  offset = viewArea.scrollTop() - viewArea.offset().top
+  _scrollMap = []
+  nonEmptyList = []
+  _lineHeightMap = []
+  viewTop = 0
+  viewBottom = viewArea[0].scrollHeight - viewArea.height()
 
-    acc = 0;
-    const lines = editor.getValue().split('\n');
-    const lineHeight = editor.defaultTextHeight();
-    for (i = 0; i < lines.length; i++) {
-        const str = lines[i];
+  acc = 0
+  const lines = window.editor.getValue().split('\n')
+  const lineHeight = window.editor.defaultTextHeight()
+  for (i = 0; i < lines.length; i++) {
+    const str = lines[i]
 
-        _lineHeightMap.push(acc);
+    _lineHeightMap.push(acc)
 
-        if (str.length === 0) {
-            acc++;
-            continue;
-        }
-
-        const h = editor.heightAtLine(i + 1) - editor.heightAtLine(i);
-        acc += Math.round(h / lineHeight);
-    }
-    _lineHeightMap.push(acc);
-    linesCount = acc;
-
-    for (i = 0; i < linesCount; i++) {
-        _scrollMap.push(-1);
+    if (str.length === 0) {
+      acc++
+      continue
     }
 
-    nonEmptyList.push(0);
+    const h = window.editor.heightAtLine(i + 1) - window.editor.heightAtLine(i)
+    acc += Math.round(h / lineHeight)
+  }
+  _lineHeightMap.push(acc)
+  linesCount = acc
+
+  for (i = 0; i < linesCount; i++) {
+    _scrollMap.push(-1)
+  }
+
+  nonEmptyList.push(0)
     // make the first line go top
-    _scrollMap[0] = viewTop;
+  _scrollMap[0] = viewTop
 
-    const parts = markdownArea.find('.part').toArray();
-    for (i = 0; i < parts.length; i++) {
-        const $el = $(parts[i]);
-        let t = $el.attr('data-startline') - 1;
-        if (t === '') {
-            return;
-        }
-        t = _lineHeightMap[t];
-        if (t !== 0 && t !== nonEmptyList[nonEmptyList.length - 1]) {
-            nonEmptyList.push(t);
-        }
-        _scrollMap[t] = Math.round($el.offset().top + offset - 10);
+  const parts = markdownArea.find('.part').toArray()
+  for (i = 0; i < parts.length; i++) {
+    const $el = $(parts[i])
+    let t = $el.attr('data-startline') - 1
+    if (t === '') {
+      return
+    }
+    t = _lineHeightMap[t]
+    if (t !== 0 && t !== nonEmptyList[nonEmptyList.length - 1]) {
+      nonEmptyList.push(t)
+    }
+    _scrollMap[t] = Math.round($el.offset().top + offset - 10)
+  }
+
+  nonEmptyList.push(linesCount)
+  _scrollMap[linesCount] = viewArea[0].scrollHeight
+
+  pos = 0
+  for (i = 1; i < linesCount; i++) {
+    if (_scrollMap[i] !== -1) {
+      pos++
+      continue
     }
 
-    nonEmptyList.push(linesCount);
-    _scrollMap[linesCount] = viewArea[0].scrollHeight;
+    a = nonEmptyList[pos]
+    b = nonEmptyList[pos + 1]
+    _scrollMap[i] = Math.round((_scrollMap[b] * (i - a) + _scrollMap[a] * (b - i)) / (b - a))
+  }
 
-    pos = 0;
-    for (i = 1; i < linesCount; i++) {
-        if (_scrollMap[i] !== -1) {
-            pos++;
-            continue;
-        }
+  _scrollMap[0] = 0
 
-        a = nonEmptyList[pos];
-        b = nonEmptyList[pos + 1];
-        _scrollMap[i] = Math.round((_scrollMap[b] * (i - a) + _scrollMap[a] * (b - i)) / (b - a));
-    }
+  scrollMap = _scrollMap
+  lineHeightMap = _lineHeightMap
 
-    _scrollMap[0] = 0;
-
-    scrollMap = _scrollMap;
-    lineHeightMap = _lineHeightMap;
-
-    if (loaded && callback) callback();
+  if (window.loaded && callback) callback()
 }
 
 // sync view scroll progress to edit
-let viewScrollingTimer = null;
+let viewScrollingTimer = null
 
-export function syncScrollToEdit(event, preventAnimate) {
-    if (currentMode != modeType.both || !syncscroll || !editArea) return;
-    if (preventSyncScrollToEdit) {
-        if (typeof preventSyncScrollToEdit === 'number') {
-            preventSyncScrollToEdit--;
-        } else {
-            preventSyncScrollToEdit = false;
-        }
-        return;
-    }
-    if (!scrollMap || !lineHeightMap) {
-        buildMap(() => {
-            syncScrollToEdit(event, preventAnimate);
-        });
-        return;
-    }
-    if (editScrolling) return;
-
-    const scrollTop = viewArea[0].scrollTop;
-    let lineIndex = 0;
-    for (var i = 0, l = scrollMap.length; i < l; i++) {
-        if (scrollMap[i] > scrollTop) {
-            break;
-        } else {
-            lineIndex = i;
-        }
-    }
-    let lineNo = 0;
-    let lineDiff = 0;
-    for (var i = 0, l = lineHeightMap.length; i < l; i++) {
-        if (lineHeightMap[i] > lineIndex) {
-            break;
-        } else {
-            lineNo = lineHeightMap[i];
-            lineDiff = lineHeightMap[i + 1] - lineNo;
-        }
-    }
-
-    let posTo = 0;
-    let topDiffPercent = 0;
-    let posToNextDiff = 0;
-    const scrollInfo = editor.getScrollInfo();
-    const textHeight = editor.defaultTextHeight();
-    const preLastLineHeight = scrollInfo.height - scrollInfo.clientHeight - textHeight;
-    const preLastLineNo = Math.round(preLastLineHeight / textHeight);
-    const preLastLinePos = scrollMap[preLastLineNo];
-
-    if (scrollInfo.height > scrollInfo.clientHeight && scrollTop >= preLastLinePos) {
-        posTo = preLastLineHeight;
-        topDiffPercent = (scrollTop - preLastLinePos) / (viewBottom - preLastLinePos);
-        posToNextDiff = textHeight * topDiffPercent;
-        posTo += Math.ceil(posToNextDiff);
+export function syncScrollToEdit (event, preventAnimate) {
+  if (window.currentMode !== window.modeType.both || !window.syncscroll || !editArea) return
+  if (window.preventSyncScrollToEdit) {
+    if (typeof window.preventSyncScrollToEdit === 'number') {
+      window.preventSyncScrollToEdit--
     } else {
-        posTo = lineNo * textHeight;
-        topDiffPercent = (scrollTop - scrollMap[lineNo]) / (scrollMap[lineNo + lineDiff] - scrollMap[lineNo]);
-        posToNextDiff = textHeight * lineDiff * topDiffPercent;
-        posTo += Math.ceil(posToNextDiff);
+      window.preventSyncScrollToEdit = false
     }
+    return
+  }
+  if (!scrollMap || !lineHeightMap) {
+    buildMap(() => {
+      syncScrollToEdit(event, preventAnimate)
+    })
+    return
+  }
+  if (editScrolling) return
 
-    if (preventAnimate) {
-        editArea.scrollTop(posTo);
+  const scrollTop = viewArea[0].scrollTop
+  let lineIndex = 0
+  for (let i = 0, l = scrollMap.length; i < l; i++) {
+    if (scrollMap[i] > scrollTop) {
+      break
     } else {
-        const posDiff = Math.abs(scrollInfo.top - posTo);
-        var duration = posDiff / 50;
-        duration = duration >= 100 ? duration : 100;
-        editArea.stop(true, true).animate({
-            scrollTop: posTo
-        }, duration, "linear");
+      lineIndex = i
     }
+  }
+  let lineNo = 0
+  let lineDiff = 0
+  for (let i = 0, l = lineHeightMap.length; i < l; i++) {
+    if (lineHeightMap[i] > lineIndex) {
+      break
+    } else {
+      lineNo = lineHeightMap[i]
+      lineDiff = lineHeightMap[i + 1] - lineNo
+    }
+  }
 
-    viewScrolling = true;
-    clearTimeout(viewScrollingTimer);
-    viewScrollingTimer = setTimeout(viewScrollingTimeoutInner, duration * 1.5);
+  let posTo = 0
+  let topDiffPercent = 0
+  let posToNextDiff = 0
+  const scrollInfo = window.editor.getScrollInfo()
+  const textHeight = window.editor.defaultTextHeight()
+  const preLastLineHeight = scrollInfo.height - scrollInfo.clientHeight - textHeight
+  const preLastLineNo = Math.round(preLastLineHeight / textHeight)
+  const preLastLinePos = scrollMap[preLastLineNo]
+
+  if (scrollInfo.height > scrollInfo.clientHeight && scrollTop >= preLastLinePos) {
+    posTo = preLastLineHeight
+    topDiffPercent = (scrollTop - preLastLinePos) / (viewBottom - preLastLinePos)
+    posToNextDiff = textHeight * topDiffPercent
+    posTo += Math.ceil(posToNextDiff)
+  } else {
+    posTo = lineNo * textHeight
+    topDiffPercent = (scrollTop - scrollMap[lineNo]) / (scrollMap[lineNo + lineDiff] - scrollMap[lineNo])
+    posToNextDiff = textHeight * lineDiff * topDiffPercent
+    posTo += Math.ceil(posToNextDiff)
+  }
+
+  if (preventAnimate) {
+    editArea.scrollTop(posTo)
+  } else {
+    const posDiff = Math.abs(scrollInfo.top - posTo)
+    var duration = posDiff / 50
+    duration = duration >= 100 ? duration : 100
+    editArea.stop(true, true).animate({
+      scrollTop: posTo
+    }, duration, 'linear')
+  }
+
+  viewScrolling = true
+  clearTimeout(viewScrollingTimer)
+  viewScrollingTimer = setTimeout(viewScrollingTimeoutInner, duration * 1.5)
 }
 
-function viewScrollingTimeoutInner() {
-    viewScrolling = false;
+function viewScrollingTimeoutInner () {
+  viewScrolling = false
 }
 
 // sync edit scroll progress to view
-let editScrollingTimer = null;
+let editScrollingTimer = null
 
-export function syncScrollToView(event, preventAnimate) {
-    if (currentMode != modeType.both || !syncscroll || !viewArea) return;
-    if (preventSyncScrollToView) {
-        if (typeof preventSyncScrollToView === 'number') {
-            preventSyncScrollToView--;
-        } else {
-            preventSyncScrollToView = false;
-        }
-        return;
+export function syncScrollToView (event, preventAnimate) {
+  if (window.currentMode !== window.modeType.both || !window.syncscroll || !viewArea) return
+  if (window.preventSyncScrollToView) {
+    if (typeof preventSyncScrollToView === 'number') {
+      window.preventSyncScrollToView--
+    } else {
+      window.preventSyncScrollToView = false
     }
-    if (!scrollMap || !lineHeightMap) {
-        buildMap(() => {
-            syncScrollToView(event, preventAnimate);
-        });
-        return;
-    }
-    if (viewScrolling) return;
+    return
+  }
+  if (!scrollMap || !lineHeightMap) {
+    buildMap(() => {
+      syncScrollToView(event, preventAnimate)
+    })
+    return
+  }
+  if (viewScrolling) return
 
-    let lineNo, posTo;
-    let topDiffPercent, posToNextDiff;
-    const scrollInfo = editor.getScrollInfo();
-    const textHeight = editor.defaultTextHeight();
-    lineNo = Math.floor(scrollInfo.top / textHeight);
+  let lineNo, posTo
+  let topDiffPercent, posToNextDiff
+  const scrollInfo = window.editor.getScrollInfo()
+  const textHeight = window.editor.defaultTextHeight()
+  lineNo = Math.floor(scrollInfo.top / textHeight)
     // if reach the last line, will start lerp to the bottom
-    const diffToBottom = (scrollInfo.top + scrollInfo.clientHeight) - (scrollInfo.height - textHeight);
-    if (scrollInfo.height > scrollInfo.clientHeight && diffToBottom > 0) {
-        topDiffPercent = diffToBottom / textHeight;
-        posTo = scrollMap[lineNo + 1];
-        posToNextDiff = (viewBottom - posTo) * topDiffPercent;
-        posTo += Math.floor(posToNextDiff);
-    } else {
-        topDiffPercent = (scrollInfo.top % textHeight) / textHeight;
-        posTo = scrollMap[lineNo];
-        posToNextDiff = (scrollMap[lineNo + 1] - posTo) * topDiffPercent;
-        posTo += Math.floor(posToNextDiff);
-    }
+  const diffToBottom = (scrollInfo.top + scrollInfo.clientHeight) - (scrollInfo.height - textHeight)
+  if (scrollInfo.height > scrollInfo.clientHeight && diffToBottom > 0) {
+    topDiffPercent = diffToBottom / textHeight
+    posTo = scrollMap[lineNo + 1]
+    posToNextDiff = (viewBottom - posTo) * topDiffPercent
+    posTo += Math.floor(posToNextDiff)
+  } else {
+    topDiffPercent = (scrollInfo.top % textHeight) / textHeight
+    posTo = scrollMap[lineNo]
+    posToNextDiff = (scrollMap[lineNo + 1] - posTo) * topDiffPercent
+    posTo += Math.floor(posToNextDiff)
+  }
 
-    if (preventAnimate) {
-        viewArea.scrollTop(posTo);
-    } else {
-        const posDiff = Math.abs(viewArea.scrollTop() - posTo);
-        var duration = posDiff / 50;
-        duration = duration >= 100 ? duration : 100;
-        viewArea.stop(true, true).animate({
-            scrollTop: posTo
-        }, duration, "linear");
-    }
+  if (preventAnimate) {
+    viewArea.scrollTop(posTo)
+  } else {
+    const posDiff = Math.abs(viewArea.scrollTop() - posTo)
+    var duration = posDiff / 50
+    duration = duration >= 100 ? duration : 100
+    viewArea.stop(true, true).animate({
+      scrollTop: posTo
+    }, duration, 'linear')
+  }
 
-    editScrolling = true;
-    clearTimeout(editScrollingTimer);
-    editScrollingTimer = setTimeout(editScrollingTimeoutInner, duration * 1.5);
+  editScrolling = true
+  clearTimeout(editScrollingTimer)
+  editScrollingTimer = setTimeout(editScrollingTimeoutInner, duration * 1.5)
 }
 
-function editScrollingTimeoutInner() {
-    editScrolling = false;
+function editScrollingTimeoutInner () {
+  editScrolling = false
 }
diff --git a/public/vendor/md-toc.js b/public/vendor/md-toc.js
index 200275a..f93f792 100755
--- a/public/vendor/md-toc.js
+++ b/public/vendor/md-toc.js
@@ -1,129 +1,123 @@
+/* eslint-env browser, jquery */
 /**
  * md-toc.js v1.0.2
  * https://github.com/yijian166/md-toc.js
  */
 
 (function (window) {
-    function Toc(id, options) {
-        this.el = document.getElementById(id);
-        if (!this.el) return;
-        this.options = options || {};
-        this.tocLevel = parseInt(options.level) || 0;
-        this.tocClass = options['class'] || 'toc';
-        this.ulClass = options['ulClass'];
-        this.tocTop = parseInt(options.top) || 0;
-        this.elChilds = this.el.children;
-        this.process = options['process'];
-        if (!this.elChilds.length) return;
-        this._init();
+  function Toc (id, options) {
+    this.el = document.getElementById(id)
+    if (!this.el) return
+    this.options = options || {}
+    this.tocLevel = parseInt(options.level) || 0
+    this.tocClass = options['class'] || 'toc'
+    this.ulClass = options['ulClass']
+    this.tocTop = parseInt(options.top) || 0
+    this.elChilds = this.el.children
+    this.process = options['process']
+    if (!this.elChilds.length) return
+    this._init()
+  }
+
+  Toc.prototype._init = function () {
+    this._collectTitleElements()
+    this._createTocContent()
+    this._showToc()
+  }
+
+  Toc.prototype._collectTitleElements = function () {
+    this._elTitlesNames = []
+    this.elTitleElements = []
+    for (var i = 1; i < 7; i++) {
+      if (this.el.getElementsByTagName('h' + i).length) {
+        this._elTitlesNames.push('h' + i)
+      }
     }
 
-    Toc.prototype._init = function () {
-        this._collectTitleElements();
-        this._createTocContent();
-        this._showToc();
-    };
+    this._elTitlesNames.length = this._elTitlesNames.length > this.tocLevel ? this.tocLevel : this._elTitlesNames.length
 
-    Toc.prototype._collectTitleElements = function () {
-        this._elTitlesNames = [],
-            this.elTitleElements = [];
-        for (var i = 1; i < 7; i++) {
-            if (this.el.getElementsByTagName('h' + i).length) {
-                this._elTitlesNames.push('h' + i);
+    for (var j = 0; j < this.elChilds.length; j++) {
+      this._elChildName = this.elChilds[j].tagName.toLowerCase()
+      if (this._elTitlesNames.toString().match(this._elChildName)) {
+        this.elTitleElements.push(this.elChilds[j])
+      }
+    }
+  }
+
+  Toc.prototype._createTocContent = function () {
+    this._elTitleElementsLen = this.elTitleElements.length
+    if (!this._elTitleElementsLen) return
+    this.tocContent = ''
+    this._tempLists = []
+
+    for (var i = 0; i < this._elTitleElementsLen; i++) {
+      var j = i + 1
+      this._elTitleElement = this.elTitleElements[i]
+      this._elTitleElementName = this._elTitleElement.tagName
+      this._elTitleElementText = (typeof this.process === 'function' ? this.process(this._elTitleElement) : this._elTitleElement.innerHTML).replace(/<(?:.|\n)*?>/gm, '')
+      var id = this._elTitleElement.getAttribute('id')
+      if (!id) {
+        this._elTitleElement.setAttribute('id', 'tip' + i)
+        id = '#tip' + i
+      } else {
+        id = '#' + id
+      }
+
+      this.tocContent += '<li><a href="' + id + '">' + this._elTitleElementText + '</a>'
+
+      if (j !== this._elTitleElementsLen) {
+        this._elNextTitleElementName = this.elTitleElements[j].tagName
+        if (this._elTitleElementName !== this._elNextTitleElementName) {
+          var checkColse = false
+          var y = 1
+          for (var t = this._tempLists.length - 1; t >= 0; t--) {
+            if (this._tempLists[t].tagName === this._elNextTitleElementName) {
+              checkColse = true
+              break
             }
-        }
-
-        this._elTitlesNames.length = this._elTitlesNames.length > this.tocLevel ? this.tocLevel : this._elTitlesNames.length;
-
-        for (var j = 0; j < this.elChilds.length; j++) {
-            this._elChildName = this.elChilds[j].tagName.toLowerCase();
-            if (this._elTitlesNames.toString().match(this._elChildName)) {
-                this.elTitleElements.push(this.elChilds[j]);
-            }
-        }
-    };
-
-    Toc.prototype._createTocContent = function () {
-        this._elTitleElementsLen = this.elTitleElements.length;
-        if (!this._elTitleElementsLen) return;
-        this.tocContent = '';
-        this._tempLists = [];
-
-        var url = location.origin + location.pathname;
-        for (var i = 0; i < this._elTitleElementsLen; i++) {
-            var j = i + 1;
-            this._elTitleElement = this.elTitleElements[i];
-            this._elTitleElementName = this._elTitleElement.tagName;
-            this._elTitleElementText = (typeof this.process === 'function' ? this.process(this._elTitleElement) : this._elTitleElement.innerHTML).replace(/<(?:.|\n)*?>/gm, '');
-            var id = this._elTitleElement.getAttribute('id');
-            if (!id) {
-                this._elTitleElement.setAttribute('id', 'tip' + i);
-                id = '#tip' + i;
-            } else {
-                id = '#' + id;
-            }
-
-            this.tocContent += '<li><a href="' + id + '">' + this._elTitleElementText + '</a>';
-
-            if (j != this._elTitleElementsLen) {
-                this._elNextTitleElementName = this.elTitleElements[j].tagName;
-                if (this._elTitleElementName != this._elNextTitleElementName) {
-                    var checkColse = false,
-                        y = 1;
-                    for (var t = this._tempLists.length - 1; t >= 0; t--) {
-                        if (this._tempLists[t].tagName == this._elNextTitleElementName) {
-                            checkColse = true;
-                            break;
-                        }
-                        y++;
-                    }
-                    if (checkColse) {
-                        this.tocContent += new Array(y + 1).join('</li></ul>');
-                        this._tempLists.length = this._tempLists.length - y;
-                    } else {
-                        this._tempLists.push(this._elTitleElement);
-                        if (this.ulClass)
-                            this.tocContent += '<ul class="' + this.ulClass + '">';
-                        else
-                            this.tocContent += '<ul>';
-                    }
-                } else {
-                    this.tocContent += '</li>';
-                }
-            } else {
-                if (this._tempLists.length) {
-                    this.tocContent += new Array(this._tempLists.length + 1).join('</li></ul>');
-                } else {
-                    this.tocContent += '</li>';
-                }
-            }
-        }
-        if (this.ulClass)
-            this.tocContent = '<ul class="' + this.ulClass + '">' + this.tocContent + '</ul>';
-        else
-            this.tocContent = '<ul>' + this.tocContent + '</ul>';
-    };
-
-    Toc.prototype._showToc = function () {
-        this.toc = document.createElement('div');
-        this.toc.innerHTML = this.tocContent;
-        this.toc.setAttribute('class', this.tocClass);
-        if (!this.options.targetId) {
-            this.el.appendChild(this.toc);
+            y++
+          }
+          if (checkColse) {
+            this.tocContent += new Array(y + 1).join('</li></ul>')
+            this._tempLists.length = this._tempLists.length - y
+          } else {
+            this._tempLists.push(this._elTitleElement)
+            if (this.ulClass) { this.tocContent += '<ul class="' + this.ulClass + '">' } else { this.tocContent += '<ul>' }
+          }
         } else {
-            document.getElementById(this.options.targetId).appendChild(this.toc);
+          this.tocContent += '</li>'
         }
-        var self = this;
-        if (this.tocTop > -1) {
-            window.onscroll = function () {
-                var t = document.documentElement.scrollTop || document.body.scrollTop;
-                if (t < self.tocTop) {
-                    self.toc.setAttribute('style', 'position:absolute;top:' + self.tocTop + 'px;');
-                } else {
-                    self.toc.setAttribute('style', 'position:fixed;top:10px;');
-                }
-            }
+      } else {
+        if (this._tempLists.length) {
+          this.tocContent += new Array(this._tempLists.length + 1).join('</li></ul>')
+        } else {
+          this.tocContent += '</li>'
         }
-    };
-    window.Toc = Toc;
-})(window);
\ No newline at end of file
+      }
+    }
+    if (this.ulClass) { this.tocContent = '<ul class="' + this.ulClass + '">' + this.tocContent + '</ul>' } else { this.tocContent = '<ul>' + this.tocContent + '</ul>' }
+  }
+
+  Toc.prototype._showToc = function () {
+    this.toc = document.createElement('div')
+    this.toc.innerHTML = this.tocContent
+    this.toc.setAttribute('class', this.tocClass)
+    if (!this.options.targetId) {
+      this.el.appendChild(this.toc)
+    } else {
+      document.getElementById(this.options.targetId).appendChild(this.toc)
+    }
+    var self = this
+    if (this.tocTop > -1) {
+      window.onscroll = function () {
+        var t = document.documentElement.scrollTop || document.body.scrollTop
+        if (t < self.tocTop) {
+          self.toc.setAttribute('style', 'position:absolute;top:' + self.tocTop + 'px;')
+        } else {
+          self.toc.setAttribute('style', 'position:fixed;top:10px;')
+        }
+      }
+    }
+  }
+  window.Toc = Toc
+})(window)
diff --git a/webpack.config.js b/webpack.config.js
index 236490b..f9f0a1c 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,33 +1,33 @@
-var baseConfig = require('./webpackBaseConfig');
-var ExtractTextPlugin = require("extract-text-webpack-plugin");
-var path = require('path');
+var baseConfig = require('./webpackBaseConfig')
+var ExtractTextPlugin = require('extract-text-webpack-plugin')
+var path = require('path')
 
 module.exports = [Object.assign({}, baseConfig, {
-    plugins: baseConfig.plugins.concat([
-        new ExtractTextPlugin("[name].css")
-    ])
+  plugins: baseConfig.plugins.concat([
+    new ExtractTextPlugin('[name].css')
+  ])
 }), {
-    entry: {
-        htmlExport: path.join(__dirname, 'public/js/htmlExport.js')
-    },
-    module: {
-        loaders: [{
-            test: /\.css$/,
-            loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
-        }, {
-            test: /\.scss$/,
-            loader: ExtractTextPlugin.extract('style-loader', 'sass-loader')
-        }, {
-            test: /\.less$/,
-            loader: ExtractTextPlugin.extract('style-loader', 'less-loader')
-        }]
-    },
-    output: {
-        path: path.join(__dirname, 'public/build'),
-        publicPath: '/build/',
-        filename: '[name].js'
-    },
-    plugins: [
-        new ExtractTextPlugin("html.min.css")
-    ]
-}];
+  entry: {
+    htmlExport: path.join(__dirname, 'public/js/htmlExport.js')
+  },
+  module: {
+    loaders: [{
+      test: /\.css$/,
+      loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
+    }, {
+      test: /\.scss$/,
+      loader: ExtractTextPlugin.extract('style-loader', 'sass-loader')
+    }, {
+      test: /\.less$/,
+      loader: ExtractTextPlugin.extract('style-loader', 'less-loader')
+    }]
+  },
+  output: {
+    path: path.join(__dirname, 'public/build'),
+    publicPath: '/build/',
+    filename: '[name].js'
+  },
+  plugins: [
+    new ExtractTextPlugin('html.min.css')
+  ]
+}]
diff --git a/webpack.production.js b/webpack.production.js
index 7c690d2..7b42843 100644
--- a/webpack.production.js
+++ b/webpack.production.js
@@ -1,63 +1,63 @@
-var baseConfig = require('./webpackBaseConfig');
-var webpack = require('webpack');
-var path = require('path');
-var ExtractTextPlugin = require("extract-text-webpack-plugin");
-var OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
-var ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
+var baseConfig = require('./webpackBaseConfig')
+var webpack = require('webpack')
+var path = require('path')
+var ExtractTextPlugin = require('extract-text-webpack-plugin')
+var OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
+var ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
 
 module.exports = [Object.assign({}, baseConfig, {
-    plugins: baseConfig.plugins.concat([
-        new webpack.DefinePlugin({
-            'process.env': {
-                'NODE_ENV': JSON.stringify('production')
-            }
-        }),
-        new ParallelUglifyPlugin({
-            uglifyJS: {
-                compress: {
-                    warnings: false
-                },
-                mangle: false,
-                sourceMap: false
-            }
-        }),
-        new ExtractTextPlugin("[name].[hash].css")
-    ]),
+  plugins: baseConfig.plugins.concat([
+    new webpack.DefinePlugin({
+      'process.env': {
+        'NODE_ENV': JSON.stringify('production')
+      }
+    }),
+    new ParallelUglifyPlugin({
+      uglifyJS: {
+        compress: {
+          warnings: false
+        },
+        mangle: false,
+        sourceMap: false
+      }
+    }),
+    new ExtractTextPlugin('[name].[hash].css')
+  ]),
 
-    output: {
-        path: path.join(__dirname, 'public/build'),
-        publicPath: '/build/',
-        filename: '[id].[name].[hash].js',
-        baseUrl: '<%- url %>'
-    }
+  output: {
+    path: path.join(__dirname, 'public/build'),
+    publicPath: '/build/',
+    filename: '[id].[name].[hash].js',
+    baseUrl: '<%- url %>'
+  }
 }), {
-    entry: {
-        htmlExport: path.join(__dirname, 'public/js/htmlExport.js')
-    },
-    module: {
-        loaders: [{
-            test: /\.css$/,
-            loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
-        }, {
-            test: /\.scss$/,
-            loader: ExtractTextPlugin.extract('style-loader', 'sass-loader')
-        }, {
-            test: /\.less$/,
-            loader: ExtractTextPlugin.extract('style-loader', 'less-loader')
-        }]
-    },
-    output: {
-        path: path.join(__dirname, 'public/build'),
-        publicPath: '/build/',
-        filename: '[name].js'
-    },
-    plugins: [
-        new webpack.DefinePlugin({
-            'process.env': {
-                'NODE_ENV': JSON.stringify('production')
-            }
-        }),
-        new ExtractTextPlugin("html.min.css"),
-        new OptimizeCssAssetsPlugin()
-    ]
-}];
+  entry: {
+    htmlExport: path.join(__dirname, 'public/js/htmlExport.js')
+  },
+  module: {
+    loaders: [{
+      test: /\.css$/,
+      loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
+    }, {
+      test: /\.scss$/,
+      loader: ExtractTextPlugin.extract('style-loader', 'sass-loader')
+    }, {
+      test: /\.less$/,
+      loader: ExtractTextPlugin.extract('style-loader', 'less-loader')
+    }]
+  },
+  output: {
+    path: path.join(__dirname, 'public/build'),
+    publicPath: '/build/',
+    filename: '[name].js'
+  },
+  plugins: [
+    new webpack.DefinePlugin({
+      'process.env': {
+        'NODE_ENV': JSON.stringify('production')
+      }
+    }),
+    new ExtractTextPlugin('html.min.css'),
+    new OptimizeCssAssetsPlugin()
+  ]
+}]
diff --git a/webpackBaseConfig.js b/webpackBaseConfig.js
index 419149c..8279005 100644
--- a/webpackBaseConfig.js
+++ b/webpackBaseConfig.js
@@ -1,423 +1,423 @@
-var webpack = require('webpack');
-var path = require('path');
-var ExtractTextPlugin = require("extract-text-webpack-plugin");
-var HtmlWebpackPlugin = require('html-webpack-plugin');
-var CopyWebpackPlugin = require('copy-webpack-plugin');
+var webpack = require('webpack')
+var path = require('path')
+var ExtractTextPlugin = require('extract-text-webpack-plugin')
+var HtmlWebpackPlugin = require('html-webpack-plugin')
+var CopyWebpackPlugin = require('copy-webpack-plugin')
 
 module.exports = {
-    plugins: [
-        new webpack.ProvidePlugin({
-            Visibility: "visibilityjs",
-            Cookies: "js-cookie",
-            key: "keymaster",
-            $: "jquery",
-            jQuery: "jquery",
-            "window.jQuery": "jquery",
-            "moment": "moment",
-            "Handlebars": "handlebars"
-        }),
-        new webpack.optimize.OccurrenceOrderPlugin(true),
-        new webpack.optimize.CommonsChunkPlugin({
-            names: ["cover", "index", "pretty", "slide", "vendor"],
-            children: true,
-            async: true,
-            filename: '[name].js',
-            minChunks: Infinity
-        }),
-        new HtmlWebpackPlugin({
-            template: 'public/views/includes/header.ejs',
-            chunks: ['font', 'index-styles', 'index'],
-            filename: path.join(__dirname, 'public/views/build/index-header.ejs'),
-            inject: false
-        }),
-        new HtmlWebpackPlugin({
-            template: 'public/views/includes/header.ejs',
-            chunks: ['font-pack', 'index-styles-pack', 'index-styles', 'index'],
-            filename: path.join(__dirname, 'public/views/build/index-pack-header.ejs'),
-            inject: false
-        }),
-        new HtmlWebpackPlugin({
-            template: 'public/views/includes/scripts.ejs',
-            chunks: ['index'],
-            filename: path.join(__dirname, 'public/views/build/index-scripts.ejs'),
-            inject: false
-        }),
-        new HtmlWebpackPlugin({
-            template: 'public/views/includes/scripts.ejs',
-            chunks: ['common', 'index-pack'],
-            filename: path.join(__dirname, 'public/views/build/index-pack-scripts.ejs'),
-            inject: false
-        }),
-        new HtmlWebpackPlugin({
-            template: 'public/views/includes/header.ejs',
-            chunks: ['font', 'cover'],
-            filename: path.join(__dirname, 'public/views/build/cover-header.ejs'),
-            inject: false
-        }),
-        new HtmlWebpackPlugin({
-            template: 'public/views/includes/header.ejs',
-            chunks: ['font-pack', 'cover-styles-pack', 'cover'],
-            filename: path.join(__dirname, 'public/views/build/cover-pack-header.ejs'),
-            inject: false
-        }),
-        new HtmlWebpackPlugin({
-            template: 'public/views/includes/scripts.ejs',
-            chunks: ['cover'],
-            filename: path.join(__dirname, 'public/views/build/cover-scripts.ejs'),
-            inject: false
-        }),
-        new HtmlWebpackPlugin({
-            template: 'public/views/includes/scripts.ejs',
-            chunks: ['common', 'cover-pack'],
-            filename: path.join(__dirname, 'public/views/build/cover-pack-scripts.ejs'),
-            inject: false
-        }),
-        new HtmlWebpackPlugin({
-            template: 'public/views/includes/header.ejs',
-            chunks: ['font', 'pretty-styles', 'pretty'],
-            filename: path.join(__dirname, 'public/views/build/pretty-header.ejs'),
-            inject: false
-        }),
-        new HtmlWebpackPlugin({
-            template: 'public/views/includes/header.ejs',
-            chunks: ['font-pack', 'pretty-styles-pack', 'pretty-styles', 'pretty'],
-            filename: path.join(__dirname, 'public/views/build/pretty-pack-header.ejs'),
-            inject: false
-        }),
-        new HtmlWebpackPlugin({
-            template: 'public/views/includes/scripts.ejs',
-            chunks: ['pretty'],
-            filename: path.join(__dirname, 'public/views/build/pretty-scripts.ejs'),
-            inject: false
-        }),
-        new HtmlWebpackPlugin({
-            template: 'public/views/includes/scripts.ejs',
-            chunks: ['common', 'pretty-pack'],
-            filename: path.join(__dirname, 'public/views/build/pretty-pack-scripts.ejs'),
-            inject: false
-        }),
-        new HtmlWebpackPlugin({
-            template: 'public/views/includes/header.ejs',
-            chunks: ['font', 'slide-styles', 'slide'],
-            filename: path.join(__dirname, 'public/views/build/slide-header.ejs'),
-            inject: false
-        }),
-        new HtmlWebpackPlugin({
-            template: 'public/views/includes/header.ejs',
-            chunks: ['font-pack', 'slide-styles-pack', 'slide-styles', 'slide'],
-            filename: path.join(__dirname, 'public/views/build/slide-pack-header.ejs'),
-            inject: false
-        }),
-        new HtmlWebpackPlugin({
-            template: 'public/views/includes/scripts.ejs',
-            chunks: ['slide'],
-            filename: path.join(__dirname, 'public/views/build/slide-scripts.ejs'),
-            inject: false
-        }),
-        new HtmlWebpackPlugin({
-            template: 'public/views/includes/scripts.ejs',
-            chunks: ['slide-pack'],
-            filename: path.join(__dirname, 'public/views/build/slide-pack-scripts.ejs'),
-            inject: false
-        }),
-        new CopyWebpackPlugin([
-            {
-                context: path.join(__dirname, 'node_modules/mathjax'),
-                from: {
-                    glob: '**/*',
-                    dot: false
-                },
-                to: 'MathJax/'
-            },
-            {
-                context: path.join(__dirname, 'node_modules/emojify.js'),
-                from: {
-                    glob: '**/*',
-                    dot: false
-                },
-                to: 'emojify.js/'
-            },
-            {
-                context: path.join(__dirname, 'node_modules/reveal.js'),
-                from: {
-                    glob: '**/*',
-                    dot: false
-                },
-                to: 'reveal.js/'
-            }
-        ])
+  plugins: [
+    new webpack.ProvidePlugin({
+      Visibility: 'visibilityjs',
+      Cookies: 'js-cookie',
+      key: 'keymaster',
+      $: 'jquery',
+      jQuery: 'jquery',
+      'window.jQuery': 'jquery',
+      'moment': 'moment',
+      'Handlebars': 'handlebars'
+    }),
+    new webpack.optimize.OccurrenceOrderPlugin(true),
+    new webpack.optimize.CommonsChunkPlugin({
+      names: ['cover', 'index', 'pretty', 'slide', 'vendor'],
+      children: true,
+      async: true,
+      filename: '[name].js',
+      minChunks: Infinity
+    }),
+    new HtmlWebpackPlugin({
+      template: 'public/views/includes/header.ejs',
+      chunks: ['font', 'index-styles', 'index'],
+      filename: path.join(__dirname, 'public/views/build/index-header.ejs'),
+      inject: false
+    }),
+    new HtmlWebpackPlugin({
+      template: 'public/views/includes/header.ejs',
+      chunks: ['font-pack', 'index-styles-pack', 'index-styles', 'index'],
+      filename: path.join(__dirname, 'public/views/build/index-pack-header.ejs'),
+      inject: false
+    }),
+    new HtmlWebpackPlugin({
+      template: 'public/views/includes/scripts.ejs',
+      chunks: ['index'],
+      filename: path.join(__dirname, 'public/views/build/index-scripts.ejs'),
+      inject: false
+    }),
+    new HtmlWebpackPlugin({
+      template: 'public/views/includes/scripts.ejs',
+      chunks: ['common', 'index-pack'],
+      filename: path.join(__dirname, 'public/views/build/index-pack-scripts.ejs'),
+      inject: false
+    }),
+    new HtmlWebpackPlugin({
+      template: 'public/views/includes/header.ejs',
+      chunks: ['font', 'cover'],
+      filename: path.join(__dirname, 'public/views/build/cover-header.ejs'),
+      inject: false
+    }),
+    new HtmlWebpackPlugin({
+      template: 'public/views/includes/header.ejs',
+      chunks: ['font-pack', 'cover-styles-pack', 'cover'],
+      filename: path.join(__dirname, 'public/views/build/cover-pack-header.ejs'),
+      inject: false
+    }),
+    new HtmlWebpackPlugin({
+      template: 'public/views/includes/scripts.ejs',
+      chunks: ['cover'],
+      filename: path.join(__dirname, 'public/views/build/cover-scripts.ejs'),
+      inject: false
+    }),
+    new HtmlWebpackPlugin({
+      template: 'public/views/includes/scripts.ejs',
+      chunks: ['common', 'cover-pack'],
+      filename: path.join(__dirname, 'public/views/build/cover-pack-scripts.ejs'),
+      inject: false
+    }),
+    new HtmlWebpackPlugin({
+      template: 'public/views/includes/header.ejs',
+      chunks: ['font', 'pretty-styles', 'pretty'],
+      filename: path.join(__dirname, 'public/views/build/pretty-header.ejs'),
+      inject: false
+    }),
+    new HtmlWebpackPlugin({
+      template: 'public/views/includes/header.ejs',
+      chunks: ['font-pack', 'pretty-styles-pack', 'pretty-styles', 'pretty'],
+      filename: path.join(__dirname, 'public/views/build/pretty-pack-header.ejs'),
+      inject: false
+    }),
+    new HtmlWebpackPlugin({
+      template: 'public/views/includes/scripts.ejs',
+      chunks: ['pretty'],
+      filename: path.join(__dirname, 'public/views/build/pretty-scripts.ejs'),
+      inject: false
+    }),
+    new HtmlWebpackPlugin({
+      template: 'public/views/includes/scripts.ejs',
+      chunks: ['common', 'pretty-pack'],
+      filename: path.join(__dirname, 'public/views/build/pretty-pack-scripts.ejs'),
+      inject: false
+    }),
+    new HtmlWebpackPlugin({
+      template: 'public/views/includes/header.ejs',
+      chunks: ['font', 'slide-styles', 'slide'],
+      filename: path.join(__dirname, 'public/views/build/slide-header.ejs'),
+      inject: false
+    }),
+    new HtmlWebpackPlugin({
+      template: 'public/views/includes/header.ejs',
+      chunks: ['font-pack', 'slide-styles-pack', 'slide-styles', 'slide'],
+      filename: path.join(__dirname, 'public/views/build/slide-pack-header.ejs'),
+      inject: false
+    }),
+    new HtmlWebpackPlugin({
+      template: 'public/views/includes/scripts.ejs',
+      chunks: ['slide'],
+      filename: path.join(__dirname, 'public/views/build/slide-scripts.ejs'),
+      inject: false
+    }),
+    new HtmlWebpackPlugin({
+      template: 'public/views/includes/scripts.ejs',
+      chunks: ['slide-pack'],
+      filename: path.join(__dirname, 'public/views/build/slide-pack-scripts.ejs'),
+      inject: false
+    }),
+    new CopyWebpackPlugin([
+      {
+        context: path.join(__dirname, 'node_modules/mathjax'),
+        from: {
+          glob: '**/*',
+          dot: false
+        },
+        to: 'MathJax/'
+      },
+      {
+        context: path.join(__dirname, 'node_modules/emojify.js'),
+        from: {
+          glob: '**/*',
+          dot: false
+        },
+        to: 'emojify.js/'
+      },
+      {
+        context: path.join(__dirname, 'node_modules/reveal.js'),
+        from: {
+          glob: '**/*',
+          dot: false
+        },
+        to: 'reveal.js/'
+      }
+    ])
+  ],
+
+  entry: {
+    font: path.join(__dirname, 'public/css/google-font.css'),
+    'font-pack': path.join(__dirname, 'public/css/font.css'),
+    common: [
+      'expose?jQuery!expose?$!jquery',
+      'velocity-animate',
+      'imports?$=jquery!jquery-mousewheel',
+      'bootstrap'
     ],
+    cover: [
+      'babel-polyfill',
+      path.join(__dirname, 'public/js/cover.js')
+    ],
+    'cover-styles-pack': [
+      path.join(__dirname, 'node_modules/bootstrap/dist/css/bootstrap.min.css'),
+      path.join(__dirname, 'node_modules/font-awesome/css/font-awesome.min.css'),
+      path.join(__dirname, 'public/css/bootstrap-social.css'),
+      path.join(__dirname, 'node_modules/select2/select2.css'),
+      path.join(__dirname, 'node_modules/select2/select2-bootstrap.css')
+    ],
+    'cover-pack': [
+      'babel-polyfill',
+      'bootstrap-validator',
+      'script!listPagnation',
+      'expose?select2!select2',
+      'expose?moment!moment',
+      'script!js-url',
+      path.join(__dirname, 'public/js/cover.js')
+    ],
+    index: [
+      'babel-polyfill',
+      'script!jquery-ui-resizable',
+      'script!js-url',
+      'expose?filterXSS!xss',
+      'script!Idle.Js',
+      'expose?LZString!lz-string',
+      'script!codemirror',
+      'script!inlineAttachment',
+      'script!jqueryTextcomplete',
+      'script!codemirrorSpellChecker',
+      'script!codemirrorInlineAttachment',
+      'script!ot',
+      'flowchart.js',
+      'js-sequence-diagrams',
+      'expose?RevealMarkdown!reveal-markdown',
+      path.join(__dirname, 'public/js/google-drive-upload.js'),
+      path.join(__dirname, 'public/js/google-drive-picker.js'),
+      path.join(__dirname, 'public/js/index.js')
+    ],
+    'index-styles': [
+      path.join(__dirname, 'public/vendor/jquery-ui/jquery-ui.min.css'),
+      path.join(__dirname, 'public/vendor/codemirror-spell-checker/spell-checker.min.css'),
+      path.join(__dirname, 'node_modules/codemirror/lib/codemirror.css'),
+      path.join(__dirname, 'node_modules/codemirror/addon/fold/foldgutter.css'),
+      path.join(__dirname, 'node_modules/codemirror/addon/display/fullscreen.css'),
+      path.join(__dirname, 'node_modules/codemirror/addon/dialog/dialog.css'),
+      path.join(__dirname, 'node_modules/codemirror/addon/scroll/simplescrollbars.css'),
+      path.join(__dirname, 'node_modules/codemirror/addon/search/matchesonscrollbar.css'),
+      path.join(__dirname, 'node_modules/codemirror/theme/monokai.css'),
+      path.join(__dirname, 'node_modules/codemirror/theme/one-dark.css'),
+      path.join(__dirname, 'node_modules/codemirror/mode/tiddlywiki/tiddlywiki.css'),
+      path.join(__dirname, 'node_modules/codemirror/mode/mediawiki/mediawiki.css'),
+      path.join(__dirname, 'public/css/github-extract.css'),
+      path.join(__dirname, 'public/vendor/showup/showup.css'),
+      path.join(__dirname, 'public/css/mermaid.css'),
+      path.join(__dirname, 'public/css/markdown.css'),
+      path.join(__dirname, 'public/css/slide-preview.css')
+    ],
+    'index-styles-pack': [
+      path.join(__dirname, 'node_modules/bootstrap/dist/css/bootstrap.min.css'),
+      path.join(__dirname, 'node_modules/font-awesome/css/font-awesome.min.css'),
+      path.join(__dirname, 'public/css/bootstrap-social.css'),
+      path.join(__dirname, 'node_modules/ionicons/css/ionicons.min.css'),
+      path.join(__dirname, 'node_modules/octicons/octicons/octicons.css')
+    ],
+    'index-pack': [
+      'babel-polyfill',
+      'expose?Spinner!spin.js',
+      'script!jquery-ui-resizable',
+      'bootstrap-validator',
+      'expose?jsyaml!js-yaml',
+      'script!mermaid',
+      'expose?moment!moment',
+      'script!js-url',
+      'script!handlebars',
+      'expose?hljs!highlight.js',
+      'expose?emojify!emojify.js',
+      'expose?filterXSS!xss',
+      'script!Idle.Js',
+      'script!gist-embed',
+      'expose?LZString!lz-string',
+      'script!codemirror',
+      'script!inlineAttachment',
+      'script!jqueryTextcomplete',
+      'script!codemirrorSpellChecker',
+      'script!codemirrorInlineAttachment',
+      'script!ot',
+      'flowchart.js',
+      'js-sequence-diagrams',
+      'expose?Viz!viz.js',
+      'expose?io!socket.io-client',
+      'expose?RevealMarkdown!reveal-markdown',
+      path.join(__dirname, 'public/js/google-drive-upload.js'),
+      path.join(__dirname, 'public/js/google-drive-picker.js'),
+      path.join(__dirname, 'public/js/index.js')
+    ],
+    pretty: [
+      'babel-polyfill',
+      'expose?filterXSS!xss',
+      'flowchart.js',
+      'js-sequence-diagrams',
+      'expose?RevealMarkdown!reveal-markdown',
+      path.join(__dirname, 'public/js/pretty.js')
+    ],
+    'pretty-styles': [
+      path.join(__dirname, 'public/css/github-extract.css'),
+      path.join(__dirname, 'public/css/mermaid.css'),
+      path.join(__dirname, 'public/css/markdown.css'),
+      path.join(__dirname, 'public/css/slide-preview.css')
+    ],
+    'pretty-styles-pack': [
+      path.join(__dirname, 'node_modules/bootstrap/dist/css/bootstrap.min.css'),
+      path.join(__dirname, 'node_modules/font-awesome/css/font-awesome.min.css'),
+      path.join(__dirname, 'node_modules/ionicons/css/ionicons.min.css'),
+      path.join(__dirname, 'node_modules/octicons/octicons/octicons.css')
+    ],
+    'pretty-pack': [
+      'babel-polyfill',
+      'expose?jsyaml!js-yaml',
+      'script!mermaid',
+      'expose?moment!moment',
+      'script!handlebars',
+      'expose?hljs!highlight.js',
+      'expose?emojify!emojify.js',
+      'expose?filterXSS!xss',
+      'script!gist-embed',
+      'flowchart.js',
+      'js-sequence-diagrams',
+      'expose?Viz!viz.js',
+      'expose?RevealMarkdown!reveal-markdown',
+      path.join(__dirname, 'public/js/pretty.js')
+    ],
+    slide: [
+      'babel-polyfill',
+      'bootstrap-tooltip',
+      'expose?filterXSS!xss',
+      'flowchart.js',
+      'js-sequence-diagrams',
+      'expose?RevealMarkdown!reveal-markdown',
+      path.join(__dirname, 'public/js/slide.js')
+    ],
+    'slide-styles': [
+      path.join(__dirname, 'public/vendor/bootstrap/tooltip.min.css'),
+      path.join(__dirname, 'public/css/github-extract.css'),
+      path.join(__dirname, 'public/css/mermaid.css'),
+      path.join(__dirname, 'public/css/markdown.css')
+    ],
+    'slide-styles-pack': [
+      path.join(__dirname, 'node_modules/font-awesome/css/font-awesome.min.css'),
+      path.join(__dirname, 'node_modules/ionicons/css/ionicons.min.css'),
+      path.join(__dirname, 'node_modules/octicons/octicons/octicons.css')
+    ],
+    'slide-pack': [
+      'babel-polyfill',
+      'expose?jQuery!expose?$!jquery',
+      'velocity-animate',
+      'imports?$=jquery!jquery-mousewheel',
+      'bootstrap-tooltip',
+      'expose?jsyaml!js-yaml',
+      'script!mermaid',
+      'expose?moment!moment',
+      'script!handlebars',
+      'expose?hljs!highlight.js',
+      'expose?emojify!emojify.js',
+      'expose?filterXSS!xss',
+      'script!gist-embed',
+      'flowchart.js',
+      'js-sequence-diagrams',
+      'expose?Viz!viz.js',
+      'headjs',
+      'expose?Reveal!reveal.js',
+      'expose?RevealMarkdown!reveal-markdown',
+      path.join(__dirname, 'public/js/slide.js')
+    ]
+  },
 
-    entry: {
-        font: path.join(__dirname, 'public/css/google-font.css'),
-        "font-pack": path.join(__dirname, 'public/css/font.css'),
-        common: [
-            "expose?jQuery!expose?$!jquery",
-            "velocity-animate",
-            "imports?$=jquery!jquery-mousewheel",
-            "bootstrap"
-        ],
-        cover: [
-            "babel-polyfill",
-            path.join(__dirname, 'public/js/cover.js')
-        ],
-        "cover-styles-pack": [
-            path.join(__dirname, 'node_modules/bootstrap/dist/css/bootstrap.min.css'),
-            path.join(__dirname, 'node_modules/font-awesome/css/font-awesome.min.css'),
-            path.join(__dirname, 'public/css/bootstrap-social.css'),
-            path.join(__dirname, 'node_modules/select2/select2.css'),
-            path.join(__dirname, 'node_modules/select2/select2-bootstrap.css'),
-        ],
-        "cover-pack": [
-            "babel-polyfill",
-            "bootstrap-validator",
-            "script!listPagnation",
-            "expose?select2!select2",
-            "expose?moment!moment",
-            "script!js-url",
-            path.join(__dirname, 'public/js/cover.js')
-        ],
-        index: [
-            "babel-polyfill",
-            "script!jquery-ui-resizable",
-            "script!js-url",
-            "expose?filterXSS!xss",
-            "script!Idle.Js",
-            "expose?LZString!lz-string",
-            "script!codemirror",
-            "script!inlineAttachment",
-            "script!jqueryTextcomplete",
-            "script!codemirrorSpellChecker",
-            "script!codemirrorInlineAttachment",
-            "script!ot",
-            "flowchart.js",
-            "js-sequence-diagrams",
-            "expose?RevealMarkdown!reveal-markdown",
-            path.join(__dirname, 'public/js/google-drive-upload.js'),
-            path.join(__dirname, 'public/js/google-drive-picker.js'),
-            path.join(__dirname, 'public/js/index.js')
-        ],
-        "index-styles": [
-            path.join(__dirname, 'public/vendor/jquery-ui/jquery-ui.min.css'),
-            path.join(__dirname, 'public/vendor/codemirror-spell-checker/spell-checker.min.css'),
-            path.join(__dirname, 'node_modules/codemirror/lib/codemirror.css'),
-            path.join(__dirname, 'node_modules/codemirror/addon/fold/foldgutter.css'),
-            path.join(__dirname, 'node_modules/codemirror/addon/display/fullscreen.css'),
-            path.join(__dirname, 'node_modules/codemirror/addon/dialog/dialog.css'),
-            path.join(__dirname, 'node_modules/codemirror/addon/scroll/simplescrollbars.css'),
-            path.join(__dirname, 'node_modules/codemirror/addon/search/matchesonscrollbar.css'),
-            path.join(__dirname, 'node_modules/codemirror/theme/monokai.css'),
-            path.join(__dirname, 'node_modules/codemirror/theme/one-dark.css'),
-            path.join(__dirname, 'node_modules/codemirror/mode/tiddlywiki/tiddlywiki.css'),
-            path.join(__dirname, 'node_modules/codemirror/mode/mediawiki/mediawiki.css'),
-            path.join(__dirname, 'public/css/github-extract.css'),
-            path.join(__dirname, 'public/vendor/showup/showup.css'),
-            path.join(__dirname, 'public/css/mermaid.css'),
-            path.join(__dirname, 'public/css/markdown.css'),
-            path.join(__dirname, 'public/css/slide-preview.css')
-        ],
-        "index-styles-pack": [
-            path.join(__dirname, 'node_modules/bootstrap/dist/css/bootstrap.min.css'),
-            path.join(__dirname, 'node_modules/font-awesome/css/font-awesome.min.css'),
-            path.join(__dirname, 'public/css/bootstrap-social.css'),
-            path.join(__dirname, 'node_modules/ionicons/css/ionicons.min.css'),
-            path.join(__dirname, 'node_modules/octicons/octicons/octicons.css')
-        ],
-        "index-pack": [
-            "babel-polyfill",
-            "expose?Spinner!spin.js",
-            "script!jquery-ui-resizable",
-            "bootstrap-validator",
-            "expose?jsyaml!js-yaml",
-            "script!mermaid",
-            "expose?moment!moment",
-            "script!js-url",
-            "script!handlebars",
-            "expose?hljs!highlight.js",
-            "expose?emojify!emojify.js",
-            "expose?filterXSS!xss",
-            "script!Idle.Js",
-            "script!gist-embed",
-            "expose?LZString!lz-string",
-            "script!codemirror",
-            "script!inlineAttachment",
-            "script!jqueryTextcomplete",
-            "script!codemirrorSpellChecker",
-            "script!codemirrorInlineAttachment",
-            "script!ot",
-            "flowchart.js",
-            "js-sequence-diagrams",
-            "expose?Viz!viz.js",
-            "expose?io!socket.io-client",
-            "expose?RevealMarkdown!reveal-markdown",
-            path.join(__dirname, 'public/js/google-drive-upload.js'),
-            path.join(__dirname, 'public/js/google-drive-picker.js'),
-            path.join(__dirname, 'public/js/index.js')
-        ],
-        pretty: [
-            "babel-polyfill",
-            "expose?filterXSS!xss",
-            "flowchart.js",
-            "js-sequence-diagrams",
-            "expose?RevealMarkdown!reveal-markdown",
-            path.join(__dirname, 'public/js/pretty.js')
-        ],
-        "pretty-styles": [
-            path.join(__dirname, 'public/css/github-extract.css'),
-            path.join(__dirname, 'public/css/mermaid.css'),
-            path.join(__dirname, 'public/css/markdown.css'),
-            path.join(__dirname, 'public/css/slide-preview.css')
-        ],
-        "pretty-styles-pack": [
-            path.join(__dirname, 'node_modules/bootstrap/dist/css/bootstrap.min.css'),
-            path.join(__dirname, 'node_modules/font-awesome/css/font-awesome.min.css'),
-            path.join(__dirname, 'node_modules/ionicons/css/ionicons.min.css'),
-            path.join(__dirname, 'node_modules/octicons/octicons/octicons.css')
-        ],
-        "pretty-pack": [
-            "babel-polyfill",
-            "expose?jsyaml!js-yaml",
-            "script!mermaid",
-            "expose?moment!moment",
-            "script!handlebars",
-            "expose?hljs!highlight.js",
-            "expose?emojify!emojify.js",
-            "expose?filterXSS!xss",
-            "script!gist-embed",
-            "flowchart.js",
-            "js-sequence-diagrams",
-            "expose?Viz!viz.js",
-            "expose?RevealMarkdown!reveal-markdown",
-            path.join(__dirname, 'public/js/pretty.js')
-        ],
-        slide: [
-            "babel-polyfill",
-            "bootstrap-tooltip",
-            "expose?filterXSS!xss",
-            "flowchart.js",
-            "js-sequence-diagrams",
-            "expose?RevealMarkdown!reveal-markdown",
-            path.join(__dirname, 'public/js/slide.js')
-        ],
-        "slide-styles": [
-            path.join(__dirname, 'public/vendor/bootstrap/tooltip.min.css'),
-            path.join(__dirname, 'public/css/github-extract.css'),
-            path.join(__dirname, 'public/css/mermaid.css'),
-            path.join(__dirname, 'public/css/markdown.css')
-        ],
-        "slide-styles-pack": [
-            path.join(__dirname, 'node_modules/font-awesome/css/font-awesome.min.css'),
-            path.join(__dirname, 'node_modules/ionicons/css/ionicons.min.css'),
-            path.join(__dirname, 'node_modules/octicons/octicons/octicons.css')
-        ],
-        "slide-pack": [
-            "babel-polyfill",
-            "expose?jQuery!expose?$!jquery",
-            "velocity-animate",
-            "imports?$=jquery!jquery-mousewheel",
-            "bootstrap-tooltip",
-            "expose?jsyaml!js-yaml",
-            "script!mermaid",
-            "expose?moment!moment",
-            "script!handlebars",
-            "expose?hljs!highlight.js",
-            "expose?emojify!emojify.js",
-            "expose?filterXSS!xss",
-            "script!gist-embed",
-            "flowchart.js",
-            "js-sequence-diagrams",
-            "expose?Viz!viz.js",
-            "headjs",
-            "expose?Reveal!reveal.js",
-            "expose?RevealMarkdown!reveal-markdown",
-            path.join(__dirname, 'public/js/slide.js')
-        ]
-    },
+  output: {
+    path: path.join(__dirname, 'public/build'),
+    publicPath: '/build/',
+    filename: '[name].js',
+    baseUrl: '<%- url %>'
+  },
 
-    output: {
-        path: path.join(__dirname, 'public/build'),
-        publicPath: '/build/',
-        filename: '[name].js',
-        baseUrl: '<%- url %>'
-    },
-
-    resolve: {
-        modulesDirectories: [
-            path.resolve(__dirname, 'src'),
-            path.resolve(__dirname, 'node_modules')
-        ],
-        extensions: ["", ".js"],
-        alias: {
-            codemirror: path.join(__dirname, 'node_modules/codemirror/codemirror.min.js'),
-            inlineAttachment: path.join(__dirname, 'public/vendor/inlineAttachment/inline-attachment.js'),
-            jqueryTextcomplete: path.join(__dirname, 'public/vendor/jquery-textcomplete/jquery.textcomplete.js'),
-            codemirrorSpellChecker: path.join(__dirname, 'public/vendor/codemirror-spell-checker/spell-checker.min.js'),
-            codemirrorInlineAttachment: path.join(__dirname, 'public/vendor/inlineAttachment/codemirror.inline-attachment.js'),
-            ot: path.join(__dirname, 'public/vendor/ot/ot.min.js'),
-            listPagnation: path.join(__dirname, 'node_modules/list.pagination.js/dist/list.pagination.min.js'),
-            mermaid: path.join(__dirname, 'node_modules/mermaid/dist/mermaid.min.js'),
-            handlebars: path.join(__dirname, 'node_modules/handlebars/dist/handlebars.min.js'),
-            "jquery-ui-resizable": path.join(__dirname, 'public/vendor/jquery-ui/jquery-ui.min.js'),
-            "gist-embed": path.join(__dirname, 'node_modules/gist-embed/gist-embed.min.js'),
-            "bootstrap-tooltip": path.join(__dirname, 'public/vendor/bootstrap/tooltip.min.js'),
-            "headjs": path.join(__dirname, 'node_modules/reveal.js/lib/js/head.min.js'),
-            "reveal-markdown": path.join(__dirname, 'public/js/reveal-markdown.js')
-        }
-    },
-
-    externals: {
-        "viz.js": "Viz",
-        "socket.io-client": "io",
-        "lodash": "_",
-        "jquery": "$",
-        "moment": "moment",
-        "handlebars": "Handlebars",
-        "highlight.js": "hljs",
-        "select2": "select2"
-    },
-
-    module: {
-        loaders: [{
-            test: /\.json$/,
-            loader: 'json-loader'
-        }, {
-            test: /\.js$/,
-            loader: 'babel',
-            exclude: [/node_modules/, /public\/vendor/]
-        }, {
-            test: /\.css$/,
-            loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
-        }, {
-            test: /\.scss$/,
-            loader: ExtractTextPlugin.extract('style-loader', 'sass-loader')
-        }, {
-            test: /\.less$/,
-            loader: ExtractTextPlugin.extract('style-loader', 'less-loader')
-        }, {
-            test: require.resolve("js-sequence-diagrams"),
-            loader: "imports?Raphael=raphael"
-        }, {
-            test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
-            loader: "file"
-        }, {
-            test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
-            loader: "url?prefix=font/&limit=5000"
-        }, {
-            test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
-            loader: "url?limit=10000&mimetype=application/octet-stream"
-        }, {
-            test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
-            loader: "url?limit=10000&mimetype=image/svg+xml"
-        }, {
-            test: /\.png(\?v=\d+\.\d+\.\d+)?$/,
-            loader: "url?limit=10000&mimetype=image/png"
-        }, {
-            test: /\.gif(\?v=\d+\.\d+\.\d+)?$/,
-            loader: "url?limit=10000&mimetype=image/gif"
-        }]
-    },
-
-    node: {
-        fs: "empty"
+  resolve: {
+    modulesDirectories: [
+      path.resolve(__dirname, 'src'),
+      path.resolve(__dirname, 'node_modules')
+    ],
+    extensions: ['', '.js'],
+    alias: {
+      codemirror: path.join(__dirname, 'node_modules/codemirror/codemirror.min.js'),
+      inlineAttachment: path.join(__dirname, 'public/vendor/inlineAttachment/inline-attachment.js'),
+      jqueryTextcomplete: path.join(__dirname, 'public/vendor/jquery-textcomplete/jquery.textcomplete.js'),
+      codemirrorSpellChecker: path.join(__dirname, 'public/vendor/codemirror-spell-checker/spell-checker.min.js'),
+      codemirrorInlineAttachment: path.join(__dirname, 'public/vendor/inlineAttachment/codemirror.inline-attachment.js'),
+      ot: path.join(__dirname, 'public/vendor/ot/ot.min.js'),
+      listPagnation: path.join(__dirname, 'node_modules/list.pagination.js/dist/list.pagination.min.js'),
+      mermaid: path.join(__dirname, 'node_modules/mermaid/dist/mermaid.min.js'),
+      handlebars: path.join(__dirname, 'node_modules/handlebars/dist/handlebars.min.js'),
+      'jquery-ui-resizable': path.join(__dirname, 'public/vendor/jquery-ui/jquery-ui.min.js'),
+      'gist-embed': path.join(__dirname, 'node_modules/gist-embed/gist-embed.min.js'),
+      'bootstrap-tooltip': path.join(__dirname, 'public/vendor/bootstrap/tooltip.min.js'),
+      'headjs': path.join(__dirname, 'node_modules/reveal.js/lib/js/head.min.js'),
+      'reveal-markdown': path.join(__dirname, 'public/js/reveal-markdown.js')
     }
-};
+  },
+
+  externals: {
+    'viz.js': 'Viz',
+    'socket.io-client': 'io',
+    'lodash': '_',
+    'jquery': '$',
+    'moment': 'moment',
+    'handlebars': 'Handlebars',
+    'highlight.js': 'hljs',
+    'select2': 'select2'
+  },
+
+  module: {
+    loaders: [{
+      test: /\.json$/,
+      loader: 'json-loader'
+    }, {
+      test: /\.js$/,
+      loader: 'babel',
+      exclude: [/node_modules/, /public\/vendor/]
+    }, {
+      test: /\.css$/,
+      loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
+    }, {
+      test: /\.scss$/,
+      loader: ExtractTextPlugin.extract('style-loader', 'sass-loader')
+    }, {
+      test: /\.less$/,
+      loader: ExtractTextPlugin.extract('style-loader', 'less-loader')
+    }, {
+      test: require.resolve('js-sequence-diagrams'),
+      loader: 'imports?Raphael=raphael'
+    }, {
+      test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
+      loader: 'file'
+    }, {
+      test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
+      loader: 'url?prefix=font/&limit=5000'
+    }, {
+      test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
+      loader: 'url?limit=10000&mimetype=application/octet-stream'
+    }, {
+      test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
+      loader: 'url?limit=10000&mimetype=image/svg+xml'
+    }, {
+      test: /\.png(\?v=\d+\.\d+\.\d+)?$/,
+      loader: 'url?limit=10000&mimetype=image/png'
+    }, {
+      test: /\.gif(\?v=\d+\.\d+\.\d+)?$/,
+      loader: 'url?limit=10000&mimetype=image/gif'
+    }]
+  },
+
+  node: {
+    fs: 'empty'
+  }
+}