/* jquery and jquery plugins */
require('../vendor/showup/showup');
require('../css/index.css');
require('../css/extra.css');
require('../css/slide-preview.css');
require('../css/site.css');
var toMarkdown = require('to-markdown');
var saveAs = require('file-saver').saveAs;
var url = require('js-url');
var randomColor = require('randomcolor');
var _ = require("lodash");
var List = require('list.js');
var common = require('./common.js');
var urlpath = common.urlpath;
var noteid = common.noteid;
var debug = common.debug;
var version = common.version;
var GOOGLE_API_KEY = common.GOOGLE_API_KEY;
var GOOGLE_CLIENT_ID = common.GOOGLE_CLIENT_ID;
var DROPBOX_APP_KEY = common.DROPBOX_APP_KEY;
var noteurl = common.noteurl;
var checkLoginStateChanged = common.checkLoginStateChanged;
var extra = require('./extra');
var md = extra.md;
var updateLastChange = extra.updateLastChange;
var postProcess = extra.postProcess;
var finishView = extra.finishView;
var autoLinkify = extra.autoLinkify;
var generateToc = extra.generateToc;
var smoothHashScroll = extra.smoothHashScroll;
var deduplicatedHeaderId = extra.deduplicatedHeaderId;
var renderTOC = extra.renderTOC;
var renderTitle = extra.renderTitle;
var renderFilename = extra.renderFilename;
var renderTags = extra.renderTags;
var isValidURL = extra.isValidURL;
var scrollToHash = extra.scrollToHash;
var updateLastChangeUser = extra.updateLastChangeUser;
var updateOwner = extra.updateOwner;
var parseMeta = extra.parseMeta;
var exportToHTML = extra.exportToHTML;
var exportToRawHTML = extra.exportToRawHTML;
var syncScroll = require('./syncscroll');
var setupSyncAreas = syncScroll.setupSyncAreas;
var clearMap = syncScroll.clearMap;
var syncScrollToEdit = syncScroll.syncScrollToEdit;
var syncScrollToView = syncScroll.syncScrollToView;
var historyModule = require('./history');
var writeHistory = historyModule.writeHistory;
var deleteServerHistory = historyModule.deleteServerHistory;
var getHistory = historyModule.getHistory;
var saveHistory = historyModule.saveHistory;
var removeHistory = historyModule.removeHistory;
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 defaultExtraKeys = {
    "F10": function (cm) {
        cm.setOption("fullScreen", !cm.getOption("fullScreen"));
    },
    "Esc": function (cm) {
        if (cm.getOption('keyMap').substr(0, 3) === 'vim') return CodeMirror.Pass;
        else if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
    },
    "Cmd-S": function () {
        return false;
    },
    "Ctrl-S": function () {
        return false;
    },
    "Enter": "newlineAndIndentContinueMarkdownList",
    "Tab": function (cm) {
        var tab = '\t';
        var spaces = Array(parseInt(cm.getOption("indentUnit")) + 1).join(" ");
        //auto indent whole line when in list or blockquote
        var cursor = cm.getCursor();
        var line = cm.getLine(cursor.line);
        var regex = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))/;
        var match;
        var multiple = cm.getSelection().split('\n').length > 1 || cm.getSelections().length > 1;
        if (multiple) {
            cm.execCommand('defaultTab');
        } else if ((match = regex.exec(line)) !== null) {
            var ch = match[1].length;
            var pos = {
                line: cursor.line,
                ch: ch
            };
            if (cm.getOption('indentWithTabs'))
                cm.replaceRange(tab, pos, pos, '+input');
            else
                cm.replaceRange(spaces, pos, pos, '+input');
        } else {
            if (cm.getOption('indentWithTabs'))
                cm.execCommand('defaultTab');
            else {
                cm.replaceSelection(spaces);
            }
        }
    },
    "Cmd-Left": "goLineLeftSmart",
    "Cmd-Right": "goLineRight",
    "Ctrl-C": function (cm) {
        if (!mac && cm.getOption('keyMap').substr(0, 3) === 'vim') document.execCommand("copy");
        else return CodeMirror.Pass;
    },
    "Ctrl-*": function (cm) {
        wrapTextWith(cm, '*');
    },
    "Shift-Ctrl-8": function (cm) {
        wrapTextWith(cm, '*');
    },
    "Ctrl-_": function (cm) {
        wrapTextWith(cm, '_');
    },
    "Shift-Ctrl--": function (cm) {
        wrapTextWith(cm, '_');
    },
    "Ctrl-~": function (cm) {
        wrapTextWith(cm, '~');
    },
    "Shift-Ctrl-`": function (cm) {
        wrapTextWith(cm, '~');
    },
    "Ctrl-^": function (cm) {
        wrapTextWith(cm, '^');
    },
    "Shift-Ctrl-6": function (cm) {
        wrapTextWith(cm, '^');
    },
    "Ctrl-+": function (cm) {
        wrapTextWith(cm, '+');
    },
    "Shift-Ctrl-=": function (cm) {
        wrapTextWith(cm, '+');
    },
    "Ctrl-=": function (cm) {
        wrapTextWith(cm, '=');
    },
    "Shift-Ctrl-Backspace": function (cm) {
        wrapTextWith(cm, 'Backspace');
    }
};
var wrapSymbols = ['*', '_', '~', '^', '+', '='];
function wrapTextWith(cm, symbol) {
    if (!cm.getSelection()) {
        return CodeMirror.Pass;
    } else {
        var ranges = cm.listSelections();
        for (var i = 0; i < ranges.length; i++) {
            var range = ranges[i];
            if (!range.empty()) {
                var from = range.from(), to = range.to();
                if (symbol !== 'Backspace') {
                    cm.replaceRange(symbol, to, to, '+input');
                    cm.replaceRange(symbol, from, from, '+input');
                    // workaround selection range not correct after add symbol
                    var _ranges = cm.listSelections();
                    var anchorIndex = editor.indexFromPos(_ranges[i].anchor);
                    var headIndex = editor.indexFromPos(_ranges[i].head);
                    if (anchorIndex > headIndex) {
                        _ranges[i].anchor.ch--;
                    } else {
                        _ranges[i].head.ch--;
                    }
                    cm.setSelections(_ranges);
                } else {
                    var preEndPos = {
                        line: to.line,
                        ch: to.ch + 1
                    };
                    var preText = cm.getRange(to, preEndPos);
                    var preIndex = wrapSymbols.indexOf(preText);
                    var postEndPos = {
                        line: from.line,
                        ch: from.ch - 1
                    };
                    var postText = cm.getRange(postEndPos, from);
                    var postIndex = wrapSymbols.indexOf(postText);
                    // check if surround symbol are list in array and matched
                    if (preIndex > -1 && postIndex > -1 && preIndex === postIndex) {
                        cm.replaceRange("", to, preEndPos, '+delete');
                        cm.replaceRange("", postEndPos, from, '+delete');
                    }
                }
            }
        }
    }
}
var idleTime = 300000; //5 mins
var updateViewDebounce = 100;
var cursorMenuThrottle = 50;
var cursorActivityDebounce = 50;
var cursorAnimatePeriod = 100;
var supportContainers = ['success', 'info', 'warning', 'danger'];
var supportCodeModes = ['javascript', 'htmlmixed', 'htmlembedded', 'css', 'xml', 'clike', 'clojure', 'ruby', 'python', 'shell', 'php', 'sql', 'coffeescript', 'yaml', 'pug', 'lua', 'cmake', 'nginx', 'perl', 'sass', 'r', 'dockerfile', 'tiddlywiki', 'mediawiki'];
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:'
    }
];
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: '',
        search: '![]()'
    },
    {
        text: '',
        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'
    }
];
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 + ']';
        }
    }
];
window.modeType = {
    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;
//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
        }
    },
    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"
};
//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,
    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'));
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;
function getStatusBarTemplate(callback) {
    $.get(serverurl + '/views/statusbar.html', function (template) {
        statusBarTemplate = template;
        if (callback) callback();
    });
}
getStatusBarTemplate();
function addStatusBar() {
    if (!statusBarTemplate) {
        getStatusBarTemplate(addStatusBar);
        return;
    }
    statusBar = $(statusBarTemplate);
    statusCursor = statusBar.find('.status-cursor');
    statusFile = statusBar.find('.status-file');
    statusIndicators = statusBar.find('.status-indicators');
    statusIndent = statusBar.find('.status-indent');
    statusKeymap = statusBar.find('.status-keymap');
    statusLength = statusBar.find('.status-length');
    statusTheme = statusBar.find('.status-theme');
    statusSpellcheck = statusBar.find('.status-spellcheck');
    statusPanel = editor.addPanel(statusBar[0], {
        position: "bottom"
    });
    setIndent();
    setKeymap();
    setTheme();
    setSpellcheck();
}
function setIndent() {
    var cookieIndentType = Cookies.get('indent_type');
    var cookieTabSize = parseInt(Cookies.get('tab_size'));
    var cookieSpaceUnits = parseInt(Cookies.get('space_units'));
    if (cookieIndentType) {
        if (cookieIndentType == 'tab') {
            editor.setOption('indentWithTabs', true);
            if (cookieTabSize)
                editor.setOption('indentUnit', cookieTabSize);
        } else if (cookieIndentType == 'space') {
            editor.setOption('indentWithTabs', false);
            if (cookieSpaceUnits)
                editor.setOption('indentUnit', cookieSpaceUnits);
        }
    }
    if (cookieTabSize)
        editor.setOption('tabSize', cookieTabSize);
    var type = statusIndicators.find('.indent-type');
    var widthLabel = statusIndicators.find('.indent-width-label');
    var widthInput = statusIndicators.find('.indent-width-input');
    function setType() {
        if (editor.getOption('indentWithTabs')) {
            Cookies.set('indent_type', 'tab', {
                expires: 365
            });
            type.text('Tab Size:');
        } else {
            Cookies.set('indent_type', 'space', {
                expires: 365
            });
            type.text('Spaces:');
        }
    }
    setType();
    function setUnit() {
        var unit = editor.getOption('indentUnit');
        if (editor.getOption('indentWithTabs')) {
            Cookies.set('tab_size', unit, {
                expires: 365
            });
        } else {
            Cookies.set('space_units', unit, {
                expires: 365
            });
        }
        widthLabel.text(unit);
    }
    setUnit();
    type.click(function () {
        if (editor.getOption('indentWithTabs')) {
            editor.setOption('indentWithTabs', false);
            cookieSpaceUnits = parseInt(Cookies.get('space_units'));
            if (cookieSpaceUnits)
                editor.setOption('indentUnit', cookieSpaceUnits)
        } else {
            editor.setOption('indentWithTabs', true);
            cookieTabSize = parseInt(Cookies.get('tab_size'));
            if (cookieTabSize) {
                editor.setOption('indentUnit', cookieTabSize);
                editor.setOption('tabSize', cookieTabSize);
            }
        }
        setType();
        setUnit();
    });
    widthLabel.click(function () {
        if (widthLabel.is(':visible')) {
            widthLabel.addClass('hidden');
            widthInput.removeClass('hidden');
            widthInput.val(editor.getOption('indentUnit'));
            widthInput.select();
        } else {
            widthLabel.removeClass('hidden');
            widthInput.addClass('hidden');
        }
    });
    widthInput.on('change', function () {
        var val = parseInt(widthInput.val());
        if (!val) val = editor.getOption('indentUnit');
        if (val < 1) val = 1;
        else if (val > 10) val = 10;
        if (editor.getOption('indentWithTabs')) {
            editor.setOption('tabSize', val);
        }
        editor.setOption('indentUnit', val);
        setUnit();
    });
    widthInput.on('blur', function () {
        widthLabel.removeClass('hidden');
        widthInput.addClass('hidden');
    });
}
function setKeymap() {
    var cookieKeymap = Cookies.get('keymap');
    if (cookieKeymap)
        editor.setOption('keyMap', cookieKeymap);
    var label = statusIndicators.find('.ui-keymap-label');
    var sublime = statusIndicators.find('.ui-keymap-sublime');
    var emacs = statusIndicators.find('.ui-keymap-emacs');
    var vim = statusIndicators.find('.ui-keymap-vim');
    function setKeymapLabel() {
        var keymap = editor.getOption('keyMap');
        Cookies.set('keymap', keymap, {
            expires: 365
        });
        label.text(keymap);
    }
    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 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.');
    }
}
//ui vars
window.ui = {
    spinner: $(".ui-spinner"),
    content: $(".ui-content"),
    toolbar: {
        shortStatus: $(".ui-short-status"),
        status: $(".ui-status"),
        new: $(".ui-new"),
        publish: $(".ui-publish"),
        extra: {
            revision: $(".ui-extra-revision"),
            slide: $(".ui-extra-slide")
        },
        download: {
            markdown: $(".ui-download-markdown"),
            html: $(".ui-download-html"),
            rawhtml: $(".ui-download-raw-html"),
            pdf: $(".ui-download-pdf-beta"),
        },
        export: {
            dropbox: $(".ui-save-dropbox"),
            googleDrive: $(".ui-save-google-drive"),
            gist: $(".ui-save-gist"),
            snippet: $(".ui-save-snippet")
        },
        import: {
            dropbox: $(".ui-import-dropbox"),
            googleDrive: $(".ui-import-google-drive"),
            gist: $(".ui-import-gist"),
            snippet: $(".ui-import-snippet"),
            clipboard: $(".ui-import-clipboard")
        },
        mode: $(".ui-mode"),
        edit: $(".ui-edit"),
        view: $(".ui-view"),
        both: $(".ui-both"),
        uploadImage: $(".ui-upload-image")
    },
    infobar: {
        lastchange: $(".ui-lastchange"),
        lastchangeuser: $(".ui-lastchangeuser"),
        nolastchangeuser: $(".ui-no-lastchangeuser"),
        permission: {
            permission: $(".ui-permission"),
            label: $(".ui-permission-label"),
            freely: $(".ui-permission-freely"),
            editable: $(".ui-permission-editable"),
            locked: $(".ui-permission-locked"),
            private: $(".ui-permission-private")
        },
        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
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]);
//idle
var idle = new Idle({
    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();
});
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 updateTitleReminder() {
    if (!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 setNeedRefresh() {
    needRefresh = true;
    editor.setOption('readOnly', true);
    socket.disconnect();
    showStatus(statusType.offline);
}
loginStateChangeEvent = function () {
    setRefreshModal('user-state-changed');
    setNeedRefresh();
};
//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);
    }
    updateTitleReminder();
});
//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();
    /* we need this only on touch devices */
    if (isTouchDevice) {
        /* cache dom references */
        var $body = jQuery('body');
        /* bind events */
        $(document)
            .on('focus', 'textarea, input', function () {
                $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();
    // 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);
    });
});
//when page resize
$(window).resize(function () {
    checkLayout();
    checkEditorStyle();
    checkTocStyle();
    checkCursorMenu();
    windowResize();
});
//when page unload
$(window).on('unload', function () {
    //updateHistoryInner();
});
$(window).on('error', function () {
    //setNeedRefresh();
});
setupSyncAreas(ui.area.codemirrorScroll, ui.area.view, ui.area.markdown);
function autoSyncscroll() {
    if (editorHasFocus()) {
        syncScrollToView();
    } else {
        syncScrollToEdit();
    }
}
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 {
            // 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);
        }
    }
}
function checkLayout() {
    var navbarHieght = $('.navbar').outerHeight();
    $('body').css('padding-top', navbarHieght + 'px');
}
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");
    if (visibleXS && currentMode == modeType.both)
        if (editorHasFocus())
            changeMode(modeType.edit);
        else
            changeMode(modeType.view);
    emitUserStatus();
}
var lastEditorWidth = 0;
var previousFocusOnEditor = null;
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');
    }
    // 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();
                // 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 = $('');
        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);
        });
    }
}
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');
    } else {
        ui.area.resize.syncToggle.find('i').removeClass('fa-link').addClass('fa-unlink');
    }
}
function checkEditorScrollbar() {
    // 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);
}
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 showStatus(type, num) {
    currentStatus = type;
    var shortStatus = ui.toolbar.shortStatus;
    var status = ui.toolbar.status;
    var label = $('');
    var fa = $('');
    var msg = "";
    var shortMsg = "";
    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;
    }
    label.append(fa);
    var shortLabel = label.clone();
    shortLabel.append(" " + shortMsg);
    shortStatus.append(shortLabel);
    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;
    }
}
var lastMode = null;
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;
    }
    // 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();
    }
    windowResizeInner();
    restoreInfo();
    if (lastMode == modeType.view && currentMode == modeType.both) {
        preventSyncScrollToView = 2;
        syncScrollToEdit(null, true);
    }
    if (lastMode == modeType.edit && currentMode == modeType.both) {
        preventSyncScrollToEdit = 2;
        syncScrollToView(null, true);
    }
    if (lastMode == modeType.both && currentMode != modeType.both) {
        preventSyncScrollToView = false;
        preventSyncScrollToEdit = false;
    }
    if (lastMode != modeType.edit && currentMode == modeType.edit) {
        editor.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();
}
function lockNavbar() {
    $('.navbar').addClass('locked');
}
var unlockNavbar = _.debounce(function () {
    $('.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');
}
// check if dropbox app key is set and load scripts
if (DROPBOX_APP_KEY) {
    $('