/* jquery and jquery plugins */ require('../vendor/showup/showup'); require('prismjs/themes/prism.css'); require('highlight.js/styles/github-gist.css'); require('prismjs'); require('prismjs/components/prism-wiki'); var toMarkdown = require('to-markdown'); var saveAs = require('file-saver').saveAs; require('js-url'); 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 serverurl = common.serverurl; 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 syncScroll = require('./syncscroll'); var setupSyncAreas = syncScroll.setupSyncAreas; var clearMap = syncScroll.clearMap; var syncScrollToEdit = syncScroll.syncScrollToEdit; var syncScrollToView = syncScroll.syncScrollToView; require('./pretty'); var extra = require('./extra'); var md = extra.md; var createtime = extra.createtime; var updateLastChange = extra.updateLastChange; var postProcess = extra.postProcess; var finishView = extra.finishView; var lastchangetime = extra.lastchangetime; var autoLinkify = extra.autoLinkify; var generateToc = extra.generateToc; var smoothHashScroll = extra.smoothHashScroll; var lastchangeuser = extra.lastchangeuser; var deduplicatedHeaderId = extra.deduplicatedHeaderId; var renderTOC = extra.renderTOC; var renderTitle = extra.renderTitle; var renderFilename = extra.renderFilename; var scrollToHash = extra.scrollToHash; var owner = extra.owner; var updateLastChangeUser = extra.updateLastChangeUser; var updateOwner = extra.updateOwner; var historyModule = require('./history'); var writeHistory = historyModule.writeHistory; var deleteServerHistory = historyModule.deleteServerHistory; 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: '![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' } ]; 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 var 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) { $('