/* 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('../vendor/showup/showup') require('../css/index.css') require('../css/extra.css') require('../css/slide-preview.css') require('../css/site.css') require('highlight.js/styles/github-gist.css') var toMarkdown = require('to-markdown') var saveAs = require('file-saver').saveAs var randomColor = require('randomcolor') var _ = require('lodash') var List = require('list.js') import { checkLoginStateChanged, setloginStateChangeEvent } from './lib/common/login' import { debug, DROPBOX_APP_KEY, GOOGLE_API_KEY, GOOGLE_CLIENT_ID, noteid, noteurl, urlpath, version } from './lib/config' import { autoLinkify, deduplicatedHeaderId, exportToHTML, exportToRawHTML, finishView, generateToc, isValidURL, md, parseMeta, postProcess, renderFilename, renderTOC, renderTags, renderTitle, scrollToHash, smoothHashScroll, updateLastChange, updateLastChangeUser, updateOwner } from './extra' import { clearMap, setupSyncAreas, syncScrollToEdit, syncScrollToView } from './syncscroll' import { writeHistory, deleteServerHistory, getHistory, saveHistory, removeHistory } from './history' 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() 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 = 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') } } } } } } 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:' } ] 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=' + 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' } } 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 }, 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' } // 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 = window.inlineAttachment.editors.codemirror4.attach(editor) defaultTextHeight = parseInt($('.CodeMirror').css('line-height')) 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() }) } 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') 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() } function setIndent () { var cookieIndentType = Cookies.get('indent_type') var cookieTabSize = parseInt(Cookies.get('tab_size')) var cookieSpaceUnits = parseInt(Cookies.get('space_units')) if (cookieIndentType) { if (cookieIndentType === 'tab') { editor.setOption('indentWithTabs', true) if (cookieTabSize) { editor.setOption('indentUnit', cookieTabSize) } } else if (cookieIndentType === 'space') { editor.setOption('indentWithTabs', false) if (cookieSpaceUnits) { editor.setOption('indentUnit', cookieSpaceUnits) } } } if (cookieTabSize) { editor.setOption('tabSize', cookieTabSize) } var type = statusIndicators.find('.indent-type') var widthLabel = statusIndicators.find('.indent-width-label') var widthInput = statusIndicators.find('.indent-width-input') function setType () { if (editor.getOption('indentWithTabs')) { Cookies.set('indent_type', 'tab', { expires: 365 }) type.text('Tab Size:') } else { Cookies.set('indent_type', 'space', { expires: 365 }) type.text('Spaces:') } } setType() function setUnit () { var unit = editor.getOption('indentUnit') if (editor.getOption('indentWithTabs')) { Cookies.set('tab_size', unit, { expires: 365 }) } else { Cookies.set('space_units', unit, { expires: 365 }) } widthLabel.text(unit) } setUnit() type.click(function () { if (editor.getOption('indentWithTabs')) { editor.setOption('indentWithTabs', false) cookieSpaceUnits = parseInt(Cookies.get('space_units')) if (cookieSpaceUnits) { editor.setOption('indentUnit', cookieSpaceUnits) } } else { editor.setOption('indentWithTabs', true) cookieTabSize = parseInt(Cookies.get('tab_size')) if (cookieTabSize) { editor.setOption('indentUnit', cookieTabSize) editor.setOption('tabSize', cookieTabSize) } } setType() setUnit() }) widthLabel.click(function () { if (widthLabel.is(':visible')) { widthLabel.addClass('hidden') widthInput.removeClass('hidden') widthInput.val(editor.getOption('indentUnit')) widthInput.select() } else { widthLabel.removeClass('hidden') widthInput.addClass('hidden') } }) widthInput.on('change', function () { var val = parseInt(widthInput.val()) if (!val) val = editor.getOption('indentUnit') if (val < 1) val = 1 else if (val > 10) val = 10 if (editor.getOption('indentWithTabs')) { editor.setOption('tabSize', val) } editor.setOption('indentUnit', val) setUnit() }) widthInput.on('blur', function () { widthLabel.removeClass('hidden') widthInput.addClass('hidden') }) } function setKeymap () { var cookieKeymap = Cookies.get('keymap') if (cookieKeymap) { editor.setOption('keyMap', cookieKeymap) } var label = statusIndicators.find('.ui-keymap-label') var sublime = statusIndicators.find('.ui-keymap-sublime') var emacs = statusIndicators.find('.ui-keymap-emacs') var vim = statusIndicators.find('.ui-keymap-vim') function setKeymapLabel () { var keymap = editor.getOption('keyMap') Cookies.set('keymap', keymap, { expires: 365 }) label.text(keymap) restoreOverrideEditorKeymap() setOverrideBrowserKeymap() } setKeymapLabel() sublime.click(function () { editor.setOption('keyMap', 'sublime') setKeymapLabel() }) emacs.click(function () { editor.setOption('keyMap', 'emacs') setKeymapLabel() }) vim.click(function () { editor.setOption('keyMap', 'vim') setKeymapLabel() }) } function setTheme () { var cookieTheme = Cookies.get('theme') if (cookieTheme) { editor.setOption('theme', cookieTheme) } var themeToggle = statusTheme.find('.ui-theme-toggle') themeToggle.click(function () { var theme = editor.getOption('theme') if (theme === 'one-dark') { theme = 'default' } else { theme = 'one-dark' } editor.setOption('theme', theme) Cookies.set('theme', theme, { expires: 365 }) checkTheme() }) function checkTheme () { var theme = editor.getOption('theme') if (theme === 'one-dark') { themeToggle.removeClass('active') } else { themeToggle.addClass('active') } } checkTheme() } function setSpellcheck () { var cookieSpellcheck = Cookies.get('spellcheck') if (cookieSpellcheck) { var mode = null if (cookieSpellcheck === 'true' || cookieSpellcheck === true) { mode = 'spell-checker' } else { mode = defaultEditorMode } if (mode && mode !== editor.getOption('mode')) { editor.setOption('mode', mode) } } var spellcheckToggle = statusSpellcheck.find('.ui-spellcheck-toggle') spellcheckToggle.click(function () { var mode = editor.getOption('mode') if (mode === defaultEditorMode) { mode = 'spell-checker' } else { mode = defaultEditorMode } if (mode && mode !== editor.getOption('mode')) { editor.setOption('mode', mode) } Cookies.set('spellcheck', (mode === 'spell-checker'), { expires: 365 }) checkSpellcheck() }) function checkSpellcheck () { var mode = editor.getOption('mode') if (mode === defaultEditorMode) { spellcheckToggle.removeClass('active') } else { spellcheckToggle.addClass('active') } } checkSpellcheck() // workaround spellcheck might not activate beacuse the ajax loading /* 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.') } } // ui vars window.ui = { spinner: $('.ui-spinner'), content: $('.ui-content'), toolbar: { shortStatus: $('.ui-short-status'), status: $('.ui-status'), new: $('.ui-new'), publish: $('.ui-publish'), extra: { revision: $('.ui-extra-revision'), slide: $('.ui-extra-slide') }, download: { markdown: $('.ui-download-markdown'), html: $('.ui-download-html'), rawhtml: $('.ui-download-raw-html'), pdf: $('.ui-download-pdf-beta') }, export: { dropbox: $('.ui-save-dropbox'), googleDrive: $('.ui-save-google-drive'), gist: $('.ui-save-gist'), snippet: $('.ui-save-snippet') }, import: { dropbox: $('.ui-import-dropbox'), googleDrive: $('.ui-import-google-drive'), gist: $('.ui-import-gist'), snippet: $('.ui-import-snippet'), clipboard: $('.ui-import-clipboard') }, mode: $('.ui-mode'), edit: $('.ui-edit'), view: $('.ui-view'), both: $('.ui-both'), uploadImage: $('.ui-upload-image') }, infobar: { lastchange: $('.ui-lastchange'), lastchangeuser: $('.ui-lastchangeuser'), nolastchangeuser: $('.ui-no-lastchangeuser'), permission: { permission: $('.ui-permission'), label: $('.ui-permission-label'), freely: $('.ui-permission-freely'), editable: $('.ui-permission-editable'), locked: $('.ui-permission-locked'), private: $('.ui-permission-private'), limited: $('.ui-permission-limited'), protected: $('.ui-permission-protected') }, delete: $('.ui-delete-note') }, toc: { toc: $('.ui-toc'), affix: $('.ui-affix-toc'), label: $('.ui-toc-label'), dropdown: $('.ui-toc-dropdown') }, area: { edit: $('.ui-edit-area'), view: $('.ui-view-area'), codemirror: $('.ui-edit-area .CodeMirror'), codemirrorScroll: $('.ui-edit-area .CodeMirror .CodeMirror-scroll'), codemirrorSizer: $('.ui-edit-area .CodeMirror .CodeMirror-sizer'), codemirrorSizerInner: $('.ui-edit-area .CodeMirror .CodeMirror-sizer > div'), markdown: $('.ui-view-area .markdown-body'), resize: { handle: $('.ui-resizable-handle'), syncToggle: $('.ui-sync-toggle') } }, modal: { snippetImportProjects: $('#snippetImportModalProjects'), snippetImportSnippets: $('#snippetImportModalSnippets'), revision: $('#revisionModal') } } // 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 } /* 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 }) ui.area.codemirror.on('touchstart', function () { idle.onActive() }) var 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 (!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 setNeedRefresh () { window.needRefresh = true editor.setOption('readOnly', true) socket.disconnect() showStatus(statusType.offline) } setloginStateChangeEvent(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 (!window.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 (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 (window.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) }) // toggle-dropdown $(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 $(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 (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 < 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 editorHasFocus () { return $(editor.getInputField()).is(':focus') } // 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 (window.visibleXS && window.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' || 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() // workaround that scroll event bindings 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 = $('') 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 (window.syncscroll) { if (previousFocusOnEditor) { window.preventSyncScrollToView = false syncScrollToView() } else { 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) 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) } 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) { window.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 (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() shortLabel.append(' ' + shortMsg) shortStatus.append(shortLabel) label.append(' ' + msg) status.append(label) } 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 function changeMode (type) { // lock navbar to prevent it hide after changeMode 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 && 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() restoreInfo() if (lastMode === modeType.view && window.currentMode === modeType.both) { window.preventSyncScrollToView = 2 syncScrollToEdit(null, true) } if (lastMode === modeType.edit && window.currentMode === modeType.both) { window.preventSyncScrollToEdit = 2 syncScrollToView(null, true) } if (lastMode === modeType.both && window.currentMode !== modeType.both) { window.preventSyncScrollToView = false window.preventSyncScrollToEdit = false } if (lastMode !== modeType.edit && window.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 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) { $('