diff options
author | Yukai Huang | 2017-03-13 21:32:50 +0800 |
---|---|---|
committer | Yukai Huang | 2017-03-13 21:32:50 +0800 |
commit | 93e41f3a40394f061ca81c8ce119c85611173621 (patch) | |
tree | 20a81f7e77263e05b3d28304f72f4f224d931b0e /public/js/index.js | |
parent | 9b513f619fe74a579fadd807f86bd38f1049c56b (diff) | |
parent | edb1b4aa0a72ac8b0215211c9dbc54156c3ff91f (diff) |
Merge branch 'master' into cm-refactor
Diffstat (limited to '')
-rw-r--r-- | public/js/index.js | 6087 |
1 files changed, 2980 insertions, 3107 deletions
diff --git a/public/js/index.js b/public/js/index.js index 0d4da4d0..7764fb58 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -1,26 +1,30 @@ -/* jquery and jquery plugins */ -require('../vendor/showup/showup'); +/* eslint-env browser, jquery */ +/* global CodeMirror, Cookies, moment, editor, ui, Spinner, + modeType, Idle, serverurl, key, gapi, Dropbox, FilePicker + ot, MediaUploader, hex2rgb, num_loaded, Visibility */ -require('../css/index.css'); -require('../css/extra.css'); -require('../css/slide-preview.css'); -require('../css/site.css'); +require('../vendor/showup/showup') -require('highlight.js/styles/github-gist.css'); +require('../css/index.css') +require('../css/extra.css') +require('../css/slide-preview.css') +require('../css/site.css') -var toMarkdown = require('to-markdown'); +require('highlight.js/styles/github-gist.css') -var saveAs = require('file-saver').saveAs; -var randomColor = require('randomcolor'); +var toMarkdown = require('to-markdown') -var _ = require("lodash"); +var saveAs = require('file-saver').saveAs +var randomColor = require('randomcolor') -var List = require('list.js'); +var _ = require('lodash') + +var List = require('list.js') import { checkLoginStateChanged, setloginStateChangeEvent -} from './lib/common/login'; +} from './lib/common/login' import { debug, @@ -31,7 +35,7 @@ import { noteurl, urlpath, version -} from './lib/config'; +} from './lib/config' import { autoLinkify, @@ -53,14 +57,14 @@ import { updateLastChange, updateLastChangeUser, updateOwner -} from './extra'; +} from './extra' import { clearMap, setupSyncAreas, syncScrollToEdit, syncScrollToView -} from './syncscroll'; +} from './syncscroll' import { writeHistory, @@ -68,3469 +72,3338 @@ import { getHistory, saveHistory, removeHistory -} from './history'; +} from './history' -var renderer = require('./render'); -var preventXSS = renderer.preventXSS; +var renderer = require('./render') +var preventXSS = renderer.preventXSS -import Editor from './lib/editor'; +import Editor from './lib/editor' -import getUIElements from './lib/editor/ui-elements'; +import getUIElements from './lib/editor/ui-elements' -var defaultTextHeight = 20; -var viewportMargin = 20; +var defaultTextHeight = 20 +var viewportMargin = 20 -var idleTime = 300000; //5 mins -var updateViewDebounce = 100; -var cursorMenuThrottle = 50; -var cursorActivityDebounce = 50; -var cursorAnimatePeriod = 100; -var supportContainers = ['success', 'info', 'warning', 'danger']; -var supportCodeModes = ['javascript', 'typescript', 'jsx', 'htmlmixed', 'htmlembedded', 'css', 'xml', 'clike', 'clojure', 'ruby', 'python', 'shell', 'php', 'sql', 'haskell', 'coffeescript', 'yaml', 'pug', 'lua', 'cmake', 'nginx', 'perl', 'sass', 'r', 'dockerfile', 'tiddlywiki', 'mediawiki', 'go']; -var supportCharts = ['sequence', 'flow', 'graphviz', 'mermaid']; +var idleTime = 300000 // 5 mins +var updateViewDebounce = 100 +var cursorMenuThrottle = 50 +var cursorActivityDebounce = 50 +var cursorAnimatePeriod = 100 +var supportContainers = ['success', 'info', 'warning', 'danger'] +var supportCodeModes = ['javascript', 'typescript', 'jsx', 'htmlmixed', 'htmlembedded', 'css', 'xml', 'clike', 'clojure', 'ruby', 'python', 'shell', 'php', 'sql', 'haskell', 'coffeescript', 'yaml', 'pug', 'lua', 'cmake', 'nginx', 'perl', 'sass', 'r', 'dockerfile', 'tiddlywiki', 'mediawiki', 'go'] +var supportCharts = ['sequence', 'flow', 'graphviz', 'mermaid'] var supportHeaders = [ - { - text: '# h1', - search: '#' - }, - { - text: '## h2', - search: '##' - }, - { - text: '### h3', - search: '###' - }, - { - text: '#### h4', - search: '####' - }, - { - text: '##### h5', - search: '#####' - }, - { - text: '###### h6', - search: '######' - }, - { - text: '###### tags: `example`', - search: '###### tags:' - } -]; + { + text: '# h1', + search: '#' + }, + { + text: '## h2', + search: '##' + }, + { + text: '### h3', + search: '###' + }, + { + text: '#### h4', + search: '####' + }, + { + text: '##### h5', + search: '#####' + }, + { + text: '###### h6', + search: '######' + }, + { + text: '###### tags: `example`', + search: '###### tags:' + } +] var supportReferrals = [ - { - text: '[reference link]', - search: '[]' - }, - { - text: '[reference]: https:// "title"', - search: '[]:' - }, - { - text: '[^footnote link]', - search: '[^]' - }, - { - text: '[^footnote reference]: https:// "title"', - search: '[^]:' - }, - { - text: '^[inline footnote]', - search: '^[]' - }, - { - text: '[link text][reference]', - search: '[][]' - }, - { - text: '[link text](https:// "title")', - search: '[]()' - }, - { - text: '![image alt][reference]', - search: '![][]' - }, - { - text: '![image alt](https:// "title")', - search: '![]()' - }, - { - text: '![image alt](https:// "title" =WidthxHeight)', - search: '![]()' - }, - { - text: '[TOC]', - search: '[]' - } -]; + { + text: '[reference link]', + search: '[]' + }, + { + text: '[reference]: https:// "title"', + search: '[]:' + }, + { + text: '[^footnote link]', + search: '[^]' + }, + { + text: '[^footnote reference]: https:// "title"', + search: '[^]:' + }, + { + text: '^[inline footnote]', + search: '^[]' + }, + { + text: '[link text][reference]', + search: '[][]' + }, + { + text: '[link text](https:// "title")', + search: '[]()' + }, + { + text: '![image alt][reference]', + search: '![][]' + }, + { + text: '![image alt](https:// "title")', + search: '![]()' + }, + { + text: '![image alt](https:// "title" =WidthxHeight)', + search: '![]()' + }, + { + text: '[TOC]', + search: '[]' + } +] var supportExternals = [ - { - text: '{%youtube youtubeid %}', - search: 'youtube' - }, - { - text: '{%vimeo vimeoid %}', - search: 'vimeo' - }, - { - text: '{%gist gistid %}', - search: 'gist' - }, - { - text: '{%slideshare slideshareid %}', - search: 'slideshare' - }, - { - text: '{%speakerdeck speakerdeckid %}', - search: 'speakerdeck' - }, - { - text: '{%pdf pdfurl %}', - search: 'pdf' - } -]; + { + text: '{%youtube youtubeid %}', + search: 'youtube' + }, + { + text: '{%vimeo vimeoid %}', + search: 'vimeo' + }, + { + text: '{%gist gistid %}', + search: 'gist' + }, + { + text: '{%slideshare slideshareid %}', + search: 'slideshare' + }, + { + text: '{%speakerdeck speakerdeckid %}', + search: 'speakerdeck' + }, + { + text: '{%pdf pdfurl %}', + search: 'pdf' + } +] var supportExtraTags = [ - { - text: '[name tag]', - search: '[]', - command: function () { - return '[name=' + personalInfo.name + ']'; - }, - }, - { - text: '[time tag]', - search: '[]', - command: function () { - return '[time=' + moment().format('llll') + ']'; - }, - }, - { - text: '[my color tag]', - search: '[]', - command: function () { - return '[color=' + personalInfo.color + ']'; - } - }, - { - text: '[random color tag]', - search: '[]', - command: function () { - var color = randomColor(); - return '[color=' + color + ']'; - } - } -]; + { + text: '[name tag]', + search: '[]', + command: function () { + return '[name=' + window.personalInfo.name + ']' + } + }, + { + text: '[time tag]', + search: '[]', + command: function () { + return '[time=' + moment().format('llll') + ']' + } + }, + { + text: '[my color tag]', + search: '[]', + command: function () { + return '[color=' + window.personalInfo.color + ']' + } + }, + { + text: '[random color tag]', + search: '[]', + command: function () { + var color = randomColor() + return '[color=' + color + ']' + } + } +] window.modeType = { - edit: { - name: "edit" - }, - view: { - name: "view" - }, - both: { - name: "both" - } -}; + edit: { + name: 'edit' + }, + view: { + name: 'view' + }, + both: { + name: 'both' + } +} var statusType = { - connected: { - msg: "CONNECTED", - label: "label-warning", - fa: "fa-wifi" - }, - online: { - msg: "ONLINE", - label: "label-primary", - fa: "fa-users" - }, - offline: { - msg: "OFFLINE", - label: "label-danger", - fa: "fa-plug" - } -}; -var defaultMode = modeType.view; - -//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; + 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 + needRestore: false, + cursor: null, + scroll: null, + edit: { + scroll: { + left: null, + top: null }, - view: { - scroll: { - left: null, - top: null - } + cursor: { + line: null, + ch: null }, - history: null -}; -window.personalInfo = {}; -window.onlineUsers = []; + selections: null + }, + view: { + scroll: { + left: null, + top: null + } + }, + history: null +} +window.personalInfo = {} +window.onlineUsers = [] window.fileTypes = { - "pl": "perl", - "cgi": "perl", - "js": "javascript", - "php": "php", - "sh": "bash", - "rb": "ruby", - "html": "html", - "py": "python" -}; + 'pl': 'perl', + 'cgi': 'perl', + 'js': 'javascript', + 'php': 'php', + 'sh': 'bash', + 'rb': 'ruby', + 'html': 'html', + 'py': 'python' +} // editor settings -var textit = document.getElementById("textit"); +var textit = document.getElementById('textit') if (!textit) { - throw new Error("There was no textit area!"); + throw new Error('There was no textit area!') } -const editorInstance = new Editor(); -var editor = editorInstance.init(textit); +const editorInstance = new Editor() +var editor = editorInstance.init(textit) // TODO: global referncing in jquery-textcomplete patch -window.editor = editor; - -var inlineAttach = inlineAttachment.editors.codemirror4.attach(editor); -defaultTextHeight = parseInt($(".CodeMirror").css('line-height')); - -var selection = null; - -function updateStatusBar() { - if (!editorInstance.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; - } - editorInstance.statusCursor.text(cursorText); - var fileText = ' — ' + editor.lineCount() + ' Lines'; - editorInstance.statusFile.text(fileText); - var docLength = editor.getValue().length; - editorInstance.statusLength.text('Length ' + docLength); - if (docLength > (docmaxlength * 0.95)) { - editorInstance.statusLength.css('color', 'red'); - editorInstance.statusLength.attr('title', 'Your almost reach note max length limit.'); - } else if (docLength > (docmaxlength * 0.8)) { - editorInstance.statusLength.css('color', 'orange'); - editorInstance.statusLength.attr('title', 'You nearly fill the note, consider to make more pieces.'); - } else { - editorInstance.statusLength.css('color', 'white'); - editorInstance.statusLength.attr('title', 'You could write up to ' + docmaxlength + ' characters in this note.'); - } +window.editor = editor + +var inlineAttach = inlineAttachment.editors.codemirror4.attach(editor) +defaultTextHeight = parseInt($('.CodeMirror').css('line-height')) + +var selection = null + +function updateStatusBar () { + if (!editorInstance.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 + } + } + editorInstance.statusCursor.text(cursorText) + var fileText = ' — ' + editor.lineCount() + ' Lines' + editorInstance.statusFile.text(fileText) + var docLength = editor.getValue().length + editorInstance.statusLength.text('Length ' + docLength) + if (docLength > (docmaxlength * 0.95)) { + editorInstance.statusLength.css('color', 'red') + editorInstance.statusLength.attr('title', 'Your almost reach note max length limit.') + } else if (docLength > (docmaxlength * 0.8)) { + editorInstance.statusLength.css('color', 'orange') + editorInstance.statusLength.attr('title', 'You nearly fill the note, consider to make more pieces.') + } else { + editorInstance.statusLength.css('color', 'white') + editorInstance.statusLength.attr('title', 'You could write up to ' + docmaxlength + ' characters in this note.') + } } // initalize ui reference -const ui = getUIElements(); +const ui = getUIElements() -//page actions +// page actions var opts = { - lines: 11, // The number of lines to draw - length: 20, // The length of each line - width: 2, // The line thickness - radius: 30, // The radius of the inner circle - corners: 0, // Corner roundness (0..1) - rotate: 0, // The rotation offset - direction: 1, // 1: clockwise, -1: counterclockwise - color: '#000', // #rgb or #rrggbb or array of colors - speed: 1.1, // Rounds per second - trail: 60, // Afterglow percentage - shadow: false, // Whether to render a shadow - hwaccel: true, // Whether to use hardware acceleration - className: 'spinner', // The CSS class to assign to the spinner - zIndex: 2e9, // The z-index (defaults to 2000000000) - top: '50%', // Top position relative to parent - left: '50%' // Left position relative to parent -}; -var spinner = new Spinner(opts).spin(ui.spinner[0]); - -//idle + 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 -}); + onAway: function () { + idle.isAway = true + emitUserStatus() + updateOnlineStatus() + }, + onAwayBack: function () { + idle.isAway = false + emitUserStatus() + updateOnlineStatus() + setHaveUnreadChanges(false) + updateTitleReminder() + }, + awayTimeout: idleTime +}) ui.area.codemirror.on('touchstart', function () { - idle.onActive(); -}); + idle.onActive() +}) -var haveUnreadChanges = false; +var haveUnreadChanges = false -function setHaveUnreadChanges(bool) { - if (!loaded) return; - if (bool && (idle.isAway || Visibility.hidden())) { - haveUnreadChanges = true; - } else if (!bool && !idle.isAway && !Visibility.hidden()) { - haveUnreadChanges = false; - } +function setHaveUnreadChanges (bool) { + if (!window.loaded) return + if (bool && (idle.isAway || Visibility.hidden())) { + haveUnreadChanges = true + } else if (!bool && !idle.isAway && !Visibility.hidden()) { + haveUnreadChanges = false + } } -function updateTitleReminder() { - if (!loaded) return; - if (haveUnreadChanges) { - document.title = '• ' + renderTitle(ui.area.markdown); - } else { - document.title = renderTitle(ui.area.markdown); - } +function updateTitleReminder () { + if (!window.loaded) return + if (haveUnreadChanges) { + document.title = '• ' + renderTitle(ui.area.markdown) + } else { + document.title = renderTitle(ui.area.markdown) + } } -function setRefreshModal(status) { - $('#refreshModal').modal('show'); - $('#refreshModal').find('.modal-body > div').hide(); - $('#refreshModal').find('.' + status).show(); +function setRefreshModal (status) { + $('#refreshModal').modal('show') + $('#refreshModal').find('.modal-body > div').hide() + $('#refreshModal').find('.' + status).show() } -function setNeedRefresh() { - needRefresh = true; - editor.setOption('readOnly', true); - socket.disconnect(); - showStatus(statusType.offline); +function setNeedRefresh () { + window.needRefresh = true + editor.setOption('readOnly', true) + socket.disconnect() + showStatus(statusType.offline) } setloginStateChangeEvent(function () { - setRefreshModal('user-state-changed'); - setNeedRefresh(); -}); + setRefreshModal('user-state-changed') + setNeedRefresh() +}) -//visibility -var wasFocus = false; +// visibility +var wasFocus = false Visibility.change(function (e, state) { - var hidden = Visibility.hidden(); - if (hidden) { - if (editorHasFocus()) { - wasFocus = true; - editor.getInputField().blur(); - } - } else { - if (wasFocus) { - if (!visibleXS) { - editor.focus(); - editor.refresh(); - } - wasFocus = false; - } - setHaveUnreadChanges(false); - } - updateTitleReminder(); -}); + 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 +// when page ready $(document).ready(function () { - idle.checkAway(); - checkResponsive(); - //if in smaller screen, we don't need advanced scrollbar - var scrollbarStyle; - if (visibleXS) { - scrollbarStyle = 'native'; - } else { - scrollbarStyle = 'overlay'; - } - if (scrollbarStyle != editor.getOption('scrollbarStyle')) { - editor.setOption('scrollbarStyle', scrollbarStyle); - clearMap(); - } - checkEditorStyle(); + idle.checkAway() + checkResponsive() + // if in smaller screen, we don't need advanced scrollbar + var scrollbarStyle + if (window.visibleXS) { + scrollbarStyle = 'native' + } else { + scrollbarStyle = 'overlay' + } + if (scrollbarStyle !== editor.getOption('scrollbarStyle')) { + editor.setOption('scrollbarStyle', scrollbarStyle) + clearMap() + } + checkEditorStyle() /* we need this only on touch devices */ - if (isTouchDevice) { + if (window.isTouchDevice) { /* cache dom references */ - var $body = jQuery('body'); + var $body = jQuery('body') /* bind events */ - $(document) + $(document) .on('focus', 'textarea, input', function () { - $body.addClass('fixfixed'); + $body.addClass('fixfixed') }) .on('blur', 'textarea, input', function () { - $body.removeClass('fixfixed'); - }); - } - //showup - $().showUp('.navbar', { - upClass: 'navbar-hide', - downClass: 'navbar-show' - }); - //tooltip - $('[data-toggle="tooltip"]').tooltip(); + $body.removeClass('fixfixed') + }) + } + // showup + $().showUp('.navbar', { + upClass: 'navbar-hide', + downClass: 'navbar-show' + }) + // tooltip + $('[data-toggle="tooltip"]').tooltip() // shortcuts // allow on all tags - key.filter = function (e) { return true; }; - key('ctrl+alt+e', function (e) { - changeMode(modeType.edit); - }); - key('ctrl+alt+v', function (e) { - changeMode(modeType.view); - }); - key('ctrl+alt+b', function (e) { - changeMode(modeType.both); - }); + key.filter = function (e) { return true } + key('ctrl+alt+e', function (e) { + changeMode(modeType.edit) + }) + key('ctrl+alt+v', function (e) { + changeMode(modeType.view) + }) + key('ctrl+alt+b', function (e) { + changeMode(modeType.both) + }) // toggle-dropdown - $(document).on('click', '.toggle-dropdown .dropdown-menu', function (e) { - e.stopPropagation(); - }); -}); -//when page resize + $(document).on('click', '.toggle-dropdown .dropdown-menu', function (e) { + e.stopPropagation() + }) +}) +// when page resize $(window).resize(function () { - checkLayout(); - checkEditorStyle(); - checkTocStyle(); - checkCursorMenu(); - windowResize(); -}); -//when page unload + checkLayout() + checkEditorStyle() + checkTocStyle() + checkCursorMenu() + windowResize() +}) +// when page unload $(window).on('unload', function () { - //updateHistoryInner(); -}); + // updateHistoryInner(); +}) $(window).on('error', function () { - //setNeedRefresh(); -}); - -setupSyncAreas(ui.area.codemirrorScroll, ui.area.view, ui.area.markdown); + // setNeedRefresh(); +}) -function autoSyncscroll() { - if (editorHasFocus()) { - syncScrollToView(); +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 { - 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); + editor.setOption('viewportMargin', Infinity) + setTimeout(function () { + clearMap() + autoSyncscroll() + editor.setOption('viewportMargin', viewportMargin) + // add or update user cursors + for (var i = 0; i < window.onlineUsers.length; i++) { + if (window.onlineUsers[i].id !== window.personalInfo.id) { buildCursor(window.onlineUsers[i]) } } + updateScrollspy() + if (callback && typeof callback === 'function') { callback() } + }, 1) } + } } -function checkLayout() { - var navbarHieght = $('.navbar').outerHeight(); - $('body').css('padding-top', navbarHieght + 'px'); +function checkLayout () { + var navbarHieght = $('.navbar').outerHeight() + $('body').css('padding-top', navbarHieght + 'px') } -function editorHasFocus() { - return $(editor.getInputField()).is(":focus"); +function editorHasFocus () { + return $(editor.getInputField()).is(':focus') } -//768-792px have a gap -function checkResponsive() { - visibleXS = $(".visible-xs").is(":visible"); - visibleSM = $(".visible-sm").is(":visible"); - visibleMD = $(".visible-md").is(":visible"); - visibleLG = $(".visible-lg").is(":visible"); +// 768-792px have a gap +function checkResponsive () { + window.visibleXS = $('.visible-xs').is(':visible') + window.visibleSM = $('.visible-sm').is(':visible') + window.visibleMD = $('.visible-md').is(':visible') + window.visibleLG = $('.visible-lg').is(':visible') - if (visibleXS && currentMode == modeType.both) - if (editorHasFocus()) - changeMode(modeType.edit); - else - changeMode(modeType.view); + if (window.visibleXS && window.currentMode === modeType.both) { + if (editorHasFocus()) { changeMode(modeType.edit) } else { changeMode(modeType.view) } + } - emitUserStatus(); + emitUserStatus() } -var lastEditorWidth = 0; -var previousFocusOnEditor = null; +var lastEditorWidth = 0 +var previousFocusOnEditor = null -function checkEditorStyle() { - var desireHeight = editorInstance.statusBar ? (ui.area.edit.height() - editorInstance.statusBar.outerHeight()) : ui.area.edit.height(); +function checkEditorStyle () { + var desireHeight = editorInstance.statusBar ? (ui.area.edit.height() - editorInstance.statusBar.outerHeight()) : ui.area.edit.height() // set editor height and min height based on scrollbar style and mode - var scrollbarStyle = editor.getOption('scrollbarStyle'); - if (scrollbarStyle == 'overlay' || currentMode == modeType.both) { - ui.area.codemirrorScroll.css('height', desireHeight + 'px'); - ui.area.codemirrorScroll.css('min-height', ''); - checkEditorScrollbar(); - } else if (scrollbarStyle == 'native') { - ui.area.codemirrorScroll.css('height', ''); - ui.area.codemirrorScroll.css('min-height', desireHeight + 'px'); - } + var scrollbarStyle = editor.getOption('scrollbarStyle') + if (scrollbarStyle === 'overlay' || window.currentMode === modeType.both) { + ui.area.codemirrorScroll.css('height', desireHeight + 'px') + ui.area.codemirrorScroll.css('min-height', '') + checkEditorScrollbar() + } else if (scrollbarStyle === 'native') { + ui.area.codemirrorScroll.css('height', '') + ui.area.codemirrorScroll.css('min-height', desireHeight + 'px') + } // workaround editor will have wrong doc height when editor height changed - editor.setSize(null, ui.area.edit.height()); - //make editor resizable - if (!ui.area.resize.handle.length) { - ui.area.edit.resizable({ - handles: 'e', - maxWidth: $(window).width() * 0.7, - minWidth: $(window).width() * 0.2, - create: function (e, ui) { - $(this).parent().on('resize', function (e) { - e.stopPropagation(); - }); - }, - start: function (e) { - editor.setOption('viewportMargin', Infinity); - }, - resize: function (e) { - ui.area.resize.syncToggle.stop(true, true).show(); - checkTocStyle(); - }, - stop: function (e) { - lastEditorWidth = ui.area.edit.width(); + editor.setSize(null, ui.area.edit.height()) + // make editor resizable + if (!ui.area.resize.handle.length) { + ui.area.edit.resizable({ + handles: 'e', + maxWidth: $(window).width() * 0.7, + minWidth: $(window).width() * 0.2, + create: function (e, ui) { + $(this).parent().on('resize', function (e) { + e.stopPropagation() + }) + }, + start: function (e) { + editor.setOption('viewportMargin', Infinity) + }, + resize: function (e) { + ui.area.resize.syncToggle.stop(true, true).show() + checkTocStyle() + }, + stop: function (e) { + lastEditorWidth = ui.area.edit.width() // workaround that scroll event bindings - preventSyncScrollToView = 2; - preventSyncScrollToEdit = true; - editor.setOption('viewportMargin', viewportMargin); - if (editorHasFocus()) { - windowResizeInner(function () { - ui.area.codemirrorScroll.scroll(); - }); - } else { - windowResizeInner(function () { - ui.area.view.scroll(); - }); - } - checkEditorScrollbar(); - } - }); - ui.area.resize.handle = $('.ui-resizable-handle'); - } - if (!ui.area.resize.syncToggle.length) { - ui.area.resize.syncToggle = $('<button class="btn btn-lg btn-default ui-sync-toggle" title="Toggle sync scrolling"><i class="fa fa-link fa-fw"></i></button>'); - ui.area.resize.syncToggle.hover(function () { - previousFocusOnEditor = editorHasFocus(); - }, function () { - previousFocusOnEditor = null; - }); - ui.area.resize.syncToggle.click(function () { - syncscroll = !syncscroll; - checkSyncToggle(); - }); - ui.area.resize.handle.append(ui.area.resize.syncToggle); - ui.area.resize.syncToggle.hide(); - ui.area.resize.handle.hover(function () { - ui.area.resize.syncToggle.stop(true, true).delay(200).fadeIn(100); - }, function () { - ui.area.resize.syncToggle.stop(true, true).delay(300).fadeOut(300); - }); - } -} - -function checkSyncToggle() { - if (syncscroll) { - if (previousFocusOnEditor) { - preventSyncScrollToView = false; - syncScrollToView(); + window.preventSyncScrollToView = 2 + window.preventSyncScrollToEdit = true + editor.setOption('viewportMargin', viewportMargin) + if (editorHasFocus()) { + windowResizeInner(function () { + ui.area.codemirrorScroll.scroll() + }) } else { - preventSyncScrollToEdit = false; - syncScrollToEdit(); + windowResizeInner(function () { + ui.area.view.scroll() + }) } - ui.area.resize.syncToggle.find('i').removeClass('fa-unlink').addClass('fa-link'); + checkEditorScrollbar() + } + }) + ui.area.resize.handle = $('.ui-resizable-handle') + } + if (!ui.area.resize.syncToggle.length) { + ui.area.resize.syncToggle = $('<button class="btn btn-lg btn-default ui-sync-toggle" title="Toggle sync scrolling"><i class="fa fa-link fa-fw"></i></button>') + ui.area.resize.syncToggle.hover(function () { + previousFocusOnEditor = editorHasFocus() + }, function () { + previousFocusOnEditor = null + }) + ui.area.resize.syncToggle.click(function () { + window.syncscroll = !window.syncscroll + checkSyncToggle() + }) + ui.area.resize.handle.append(ui.area.resize.syncToggle) + ui.area.resize.syncToggle.hide() + ui.area.resize.handle.hover(function () { + ui.area.resize.syncToggle.stop(true, true).delay(200).fadeIn(100) + }, function () { + ui.area.resize.syncToggle.stop(true, true).delay(300).fadeOut(300) + }) + } +} + +function checkSyncToggle () { + if (window.syncscroll) { + if (previousFocusOnEditor) { + window.preventSyncScrollToView = false + syncScrollToView() } else { - ui.area.resize.syncToggle.find('i').removeClass('fa-link').addClass('fa-unlink'); + window.preventSyncScrollToEdit = false + syncScrollToEdit() } + ui.area.resize.syncToggle.find('i').removeClass('fa-unlink').addClass('fa-link') + } else { + ui.area.resize.syncToggle.find('i').removeClass('fa-link').addClass('fa-unlink') + } } var checkEditorScrollbar = _.debounce(function () { - editor.operation(checkEditorScrollbarInner); -}, 50); + editor.operation(checkEditorScrollbarInner) +}, 50) -function checkEditorScrollbarInner() { +function checkEditorScrollbarInner () { // workaround simple scroll bar knob // will get wrong position when editor height changed - var scrollInfo = editor.getScrollInfo(); - editor.scrollTo(null, scrollInfo.top - 1); - editor.scrollTo(null, scrollInfo.top); -} - -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 = $('<span class="label"></span>'); - var fa = $('<i class="fa"></i>'); - 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) { + 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 = $('<span class="label"></span>') + var fa = $('<i class="fa"></i>') + 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 = currentMode; - currentMode = type; - } - var responsiveClass = "col-lg-6 col-md-6 col-sm-6"; - var scrollClass = "ui-scrollable"; - ui.area.codemirror.removeClass(scrollClass); - ui.area.edit.removeClass(responsiveClass); - ui.area.view.removeClass(scrollClass); - ui.area.view.removeClass(responsiveClass); - switch (currentMode) { - case modeType.edit: - ui.area.edit.show(); - ui.area.view.hide(); - if (!editShown) { - editor.refresh(); - editShown = true; - } - break; - case modeType.view: - ui.area.edit.hide(); - ui.area.view.show(); - break; - case modeType.both: - ui.area.codemirror.addClass(scrollClass); - ui.area.edit.addClass(responsiveClass).show(); - ui.area.view.addClass(scrollClass); - ui.area.view.show(); - break; - } + lockNavbar() + saveInfo() + if (type) { + lastMode = window.currentMode + window.currentMode = type + } + var responsiveClass = 'col-lg-6 col-md-6 col-sm-6' + var scrollClass = 'ui-scrollable' + ui.area.codemirror.removeClass(scrollClass) + ui.area.edit.removeClass(responsiveClass) + ui.area.view.removeClass(scrollClass) + ui.area.view.removeClass(responsiveClass) + switch (window.currentMode) { + case modeType.edit: + ui.area.edit.show() + ui.area.view.hide() + if (!window.editShown) { + editor.refresh() + window.editShown = true + } + break + case modeType.view: + ui.area.edit.hide() + ui.area.view.show() + break + case modeType.both: + ui.area.codemirror.addClass(scrollClass) + ui.area.edit.addClass(responsiveClass).show() + ui.area.view.addClass(scrollClass) + ui.area.view.show() + break + } // save mode to url - if (history.replaceState && loaded) history.replaceState(null, "", serverurl + '/' + noteid + '?' + currentMode.name); - if (currentMode == modeType.view) { - editor.getInputField().blur(); - } - if (currentMode == modeType.edit || currentMode == modeType.both) { - ui.toolbar.uploadImage.fadeIn(); - //add and update status bar - if (!editorInstance.statusBar) { - editorInstance.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(); + 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 (!editorInstance.statusBar) { + editorInstance.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 { - $(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'); + 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 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 -} + $('.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'); +function showMessageModal (title, header, href, text, success) { + var modal = $('.message-modal') + modal.find('.modal-title').html(title) + modal.find('.modal-body h5').html(header) + if (href) { modal.find('.modal-body a').attr('href', href).text(text) } else { modal.find('.modal-body a').removeAttr('href').text(text) } + modal.find('.modal-footer button').removeClass('btn-default btn-success btn-danger') + if (success) { modal.find('.modal-footer button').addClass('btn-success') } else { modal.find('.modal-footer button').addClass('btn-danger') } + modal.modal('show') } // check if dropbox app key is set and load scripts if (DROPBOX_APP_KEY) { - $('<script>') + $('<script>') .attr('type', 'text/javascript') .attr('src', 'https://www.dropbox.com/static/api/2/dropins.js') .attr('id', 'dropboxjs') .attr('data-app-key', DROPBOX_APP_KEY) .prop('async', true) .prop('defer', true) - .appendTo('body'); + .appendTo('body') } else { - ui.toolbar.import.dropbox.hide(); - ui.toolbar.export.dropbox.hide(); + ui.toolbar.import.dropbox.hide() + ui.toolbar.export.dropbox.hide() } // check if google api key and client id are set and load scripts if (GOOGLE_API_KEY && GOOGLE_CLIENT_ID) { - $('<script>') + $('<script>') .attr('type', 'text/javascript') .attr('src', 'https://www.google.com/jsapi?callback=onGoogleAPILoaded') .prop('async', true) .prop('defer', true) - .appendTo('body'); + .appendTo('body') } else { - ui.toolbar.import.googleDrive.hide(); - ui.toolbar.export.googleDrive.hide(); + ui.toolbar.import.googleDrive.hide() + ui.toolbar.export.googleDrive.hide() } -function onGoogleAPILoaded() { - $('<script>') +function onGoogleAPILoaded () { + $('<script>') .attr('type', 'text/javascript') .attr('src', 'https://apis.google.com/js/client:plusone.js?onload=onGoogleClientLoaded') .prop('async', true) .prop('defer', true) - .appendTo('body'); + .appendTo('body') } -window.onGoogleAPILoaded = onGoogleAPILoaded; +window.onGoogleAPILoaded = onGoogleAPILoaded -//button actions -//share -ui.toolbar.publish.attr("href", noteurl + "/publish"); +// button actions +// share +ui.toolbar.publish.attr('href', noteurl + '/publish') // extra -//slide -ui.toolbar.extra.slide.attr("href", noteurl + "/slide"); -//download -//markdown +// slide +ui.toolbar.extra.slide.attr('href', noteurl + '/slide') +// download +// markdown ui.toolbar.download.markdown.click(function (e) { - e.preventDefault(); - e.stopPropagation(); - var filename = renderFilename(ui.area.markdown) + '.md'; - var markdown = editor.getValue(); - var blob = new Blob([markdown], { - type: "text/markdown;charset=utf-8" - }); - saveAs(blob, filename, true); -}); -//html + e.preventDefault() + e.stopPropagation() + var filename = renderFilename(ui.area.markdown) + '.md' + var markdown = editor.getValue() + var blob = new Blob([markdown], { + type: 'text/markdown;charset=utf-8' + }) + saveAs(blob, filename, true) +}) +// html ui.toolbar.download.html.click(function (e) { - e.preventDefault(); - e.stopPropagation(); - exportToHTML(ui.area.markdown); -}); + e.preventDefault() + e.stopPropagation() + exportToHTML(ui.area.markdown) +}) // raw html ui.toolbar.download.rawhtml.click(function (e) { - e.preventDefault(); - e.stopPropagation(); - exportToRawHTML(ui.area.markdown); -}); -//pdf -ui.toolbar.download.pdf.attr("download", "").attr("href", noteurl + "/pdf"); -//export to dropbox + e.preventDefault() + e.stopPropagation() + exportToRawHTML(ui.area.markdown) +}) +// pdf +ui.toolbar.download.pdf.attr('download', '').attr('href', noteurl + '/pdf') +// export to dropbox ui.toolbar.export.dropbox.click(function () { - var filename = renderFilename(ui.area.markdown) + '.md'; - var options = { - files: [ - { - 'url': noteurl + "/download", - 'filename': filename - } - ], - error: function (errorMessage) { - console.error(errorMessage); - } - }; - Dropbox.save(options); -}); -function uploadToGoogleDrive(accessToken) { - ui.spinner.show(); - var filename = renderFilename(ui.area.markdown) + '.md'; - var markdown = editor.getValue(); - var blob = new Blob([markdown], { - type: "text/markdown;charset=utf-8" - }); - blob.name = filename; - var uploader = new MediaUploader({ - file: blob, - token: accessToken, - onComplete: function (data) { - data = JSON.parse(data); - showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Complete!', data.alternateLink, 'Click here to view your file', true); - ui.spinner.hide(); - }, - onError: function (data) { - var modal = $('.export-modal'); - showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Error :(', '', data, false); - ui.spinner.hide(); - } - }); - uploader.upload(); -} -function googleApiAuth(immediate, callback) { - gapi.auth.authorize( - { - 'client_id': GOOGLE_CLIENT_ID, - 'scope': 'https://www.googleapis.com/auth/drive.file', - 'immediate': immediate - }, callback ? callback : function () { }); -} -function onGoogleClientLoaded() { - googleApiAuth(true); - buildImportFromGoogleDrive(); -} -window.onGoogleClientLoaded = onGoogleClientLoaded; + var filename = renderFilename(ui.area.markdown) + '.md' + var options = { + files: [ + { + 'url': noteurl + '/download', + 'filename': filename + } + ], + error: function (errorMessage) { + console.error(errorMessage) + } + } + Dropbox.save(options) +}) +function uploadToGoogleDrive (accessToken) { + ui.spinner.show() + var filename = renderFilename(ui.area.markdown) + '.md' + var markdown = editor.getValue() + var blob = new Blob([markdown], { + type: 'text/markdown;charset=utf-8' + }) + blob.name = filename + var uploader = new MediaUploader({ + file: blob, + token: accessToken, + onComplete: function (data) { + data = JSON.parse(data) + showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Complete!', data.alternateLink, 'Click here to view your file', true) + ui.spinner.hide() + }, + onError: function (data) { + showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Error :(', '', data, false) + ui.spinner.hide() + } + }) + uploader.upload() +} +function googleApiAuth (immediate, callback) { + gapi.auth.authorize( + { + 'client_id': GOOGLE_CLIENT_ID, + 'scope': 'https://www.googleapis.com/auth/drive.file', + 'immediate': immediate + }, callback || function () { }) +} +function onGoogleClientLoaded () { + googleApiAuth(true) + buildImportFromGoogleDrive() +} +window.onGoogleClientLoaded = onGoogleClientLoaded // export to google drive ui.toolbar.export.googleDrive.click(function (e) { - var token = gapi.auth.getToken(); - if (token) { - uploadToGoogleDrive(token.access_token); - } else { - googleApiAuth(false, function (result) { - uploadToGoogleDrive(result.access_token); - }); - } -}); -//export to gist -ui.toolbar.export.gist.attr("href", noteurl + "/gist"); -//export to snippet -ui.toolbar.export.snippet.click(function() { - ui.spinner.show(); - $.get(serverurl + '/auth/gitlab/callback/' + noteid + '/projects') + var token = gapi.auth.getToken() + if (token) { + uploadToGoogleDrive(token.access_token) + } else { + googleApiAuth(false, function (result) { + uploadToGoogleDrive(result.access_token) + }) + } +}) +// export to gist +ui.toolbar.export.gist.attr('href', noteurl + '/gist') +// export to snippet +ui.toolbar.export.snippet.click(function () { + ui.spinner.show() + $.get(serverurl + '/auth/gitlab/callback/' + noteid + '/projects') .done(function (data) { - $("#snippetExportModalAccessToken").val(data.accesstoken); - $("#snippetExportModalBaseURL").val(data.baseURL); - $("#snippetExportModalLoading").hide(); - $("#snippetExportModal").modal('toggle'); - $("#snippetExportModalProjects").find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Projects</option>'); - if (data.projects) { - data.projects.sort(function(a,b) { - return (a.path_with_namespace < b.path_with_namespace) ? -1 : ((a.path_with_namespace > b.path_with_namespace) ? 1 : 0); - }); - data.projects.forEach(function(project) { - if (!project.snippets_enabled - || (project.permissions.project_access === null && project.permissions.group_access === null) - || (project.permissions.project_access !== null && project.permissions.project_access.access_level < 20)) - { - return; - } - $('<option>').val(project.id).text(project.path_with_namespace).appendTo("#snippetExportModalProjects"); - }); - $("#snippetExportModalProjects").prop('disabled', false); - } - $("#snippetExportModalLoading").hide(); + $('#snippetExportModalAccessToken').val(data.accesstoken) + $('#snippetExportModalBaseURL').val(data.baseURL) + $('#snippetExportModalLoading').hide() + $('#snippetExportModal').modal('toggle') + $('#snippetExportModalProjects').find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Projects</option>') + if (data.projects) { + data.projects.sort(function (a, b) { + return (a.path_with_namespace < b.path_with_namespace) ? -1 : ((a.path_with_namespace > b.path_with_namespace) ? 1 : 0) + }) + data.projects.forEach(function (project) { + if (!project.snippets_enabled || + (project.permissions.project_access === null && project.permissions.group_access === null) || + (project.permissions.project_access !== null && project.permissions.project_access.access_level < 20)) { + return + } + $('<option>').val(project.id).text(project.path_with_namespace).appendTo('#snippetExportModalProjects') + }) + $('#snippetExportModalProjects').prop('disabled', false) + } + $('#snippetExportModalLoading').hide() }) .fail(function (data) { - showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Unable to fetch gitlab parameters :(', '', '', false); + showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Unable to fetch gitlab parameters :(', '', '', false) }) .always(function () { - ui.spinner.hide(); - }); -}); -//import from dropbox + ui.spinner.hide() + }) +}) +// import from dropbox ui.toolbar.import.dropbox.click(function () { - var options = { - success: function (files) { - ui.spinner.show(); - var url = files[0].link; - importFromUrl(url); - }, - linkType: "direct", - multiselect: false, - extensions: ['.md', '.html'] - }; - Dropbox.choose(options); -}); + var options = { + success: function (files) { + ui.spinner.show() + var url = files[0].link + importFromUrl(url) + }, + linkType: 'direct', + multiselect: false, + extensions: ['.md', '.html'] + } + Dropbox.choose(options) +}) // import from google drive -var picker = null; -function buildImportFromGoogleDrive() { - picker = new FilePicker({ - apiKey: GOOGLE_API_KEY, - clientId: GOOGLE_CLIENT_ID, - buttonEl: ui.toolbar.import.googleDrive, - onSelect: function (file) { - if (file.downloadUrl) { - ui.spinner.show(); - var accessToken = gapi.auth.getToken().access_token; - $.ajax({ - type: 'GET', - beforeSend: function (request) { - request.setRequestHeader('Authorization', 'Bearer ' + accessToken); - }, - url: file.downloadUrl, - success: function (data) { - if (file.fileExtension == 'html') - parseToEditor(data); - else - replaceAll(data); - }, - error: function (data) { - showMessageModal('<i class="fa fa-cloud-download"></i> Import from Google Drive', 'Import failed :(', '', data, false); - }, - complete: function () { - ui.spinner.hide(); - } - }); - } - } - }); +function buildImportFromGoogleDrive () { + FilePicker({ + apiKey: GOOGLE_API_KEY, + clientId: GOOGLE_CLIENT_ID, + buttonEl: ui.toolbar.import.googleDrive, + onSelect: function (file) { + if (file.downloadUrl) { + ui.spinner.show() + var accessToken = gapi.auth.getToken().access_token + $.ajax({ + type: 'GET', + beforeSend: function (request) { + request.setRequestHeader('Authorization', 'Bearer ' + accessToken) + }, + url: file.downloadUrl, + success: function (data) { + if (file.fileExtension === 'html') { parseToEditor(data) } else { replaceAll(data) } + }, + error: function (data) { + showMessageModal('<i class="fa fa-cloud-download"></i> Import from Google Drive', 'Import failed :(', '', data, false) + }, + complete: function () { + ui.spinner.hide() + } + }) + } + } + }) } -//import from gist +// import from gist ui.toolbar.import.gist.click(function () { - //na -}); -//import from snippet + // na +}) +// import from snippet ui.toolbar.import.snippet.click(function () { - ui.spinner.show(); - $.get(serverurl + '/auth/gitlab/callback/' + noteid + '/projects') + ui.spinner.show() + $.get(serverurl + '/auth/gitlab/callback/' + noteid + '/projects') .done(function (data) { - $("#snippetImportModalAccessToken").val(data.accesstoken); - $("#snippetImportModalBaseURL").val(data.baseURL); - $("#snippetImportModalContent").prop('disabled', false); - $("#snippetImportModalConfirm").prop('disabled', false); - $("#snippetImportModalLoading").hide(); - $("#snippetImportModal").modal('toggle'); - $("#snippetImportModalProjects").find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Projects</option>'); - if (data.projects) { - data.projects.sort(function(a,b) { - return (a.path_with_namespace < b.path_with_namespace) ? -1 : ((a.path_with_namespace > b.path_with_namespace) ? 1 : 0); - }); - data.projects.forEach(function(project) { - if (!project.snippets_enabled - || (project.permissions.project_access === null && project.permissions.group_access === null) - || (project.permissions.project_access !== null && project.permissions.project_access.access_level < 20)) - { - return; - } - $('<option>').val(project.id).text(project.path_with_namespace).appendTo("#snippetImportModalProjects"); - }); - $("#snippetImportModalProjects").prop('disabled', false); - } - $("#snippetImportModalLoading").hide(); + $('#snippetImportModalAccessToken').val(data.accesstoken) + $('#snippetImportModalBaseURL').val(data.baseURL) + $('#snippetImportModalContent').prop('disabled', false) + $('#snippetImportModalConfirm').prop('disabled', false) + $('#snippetImportModalLoading').hide() + $('#snippetImportModal').modal('toggle') + $('#snippetImportModalProjects').find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Projects</option>') + if (data.projects) { + data.projects.sort(function (a, b) { + return (a.path_with_namespace < b.path_with_namespace) ? -1 : ((a.path_with_namespace > b.path_with_namespace) ? 1 : 0) + }) + data.projects.forEach(function (project) { + if (!project.snippets_enabled || + (project.permissions.project_access === null && project.permissions.group_access === null) || + (project.permissions.project_access !== null && project.permissions.project_access.access_level < 20)) { + return + } + $('<option>').val(project.id).text(project.path_with_namespace).appendTo('#snippetImportModalProjects') + }) + $('#snippetImportModalProjects').prop('disabled', false) + } + $('#snippetImportModalLoading').hide() }) .fail(function (data) { - showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Unable to fetch gitlab parameters :(', '', '', false); + showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Unable to fetch gitlab parameters :(', '', '', false) }) .always(function () { - ui.spinner.hide(); - }); -}); -//import from clipboard + ui.spinner.hide() + }) +}) +// import from clipboard ui.toolbar.import.clipboard.click(function () { - //na -}); -//upload image + // na +}) +// upload image ui.toolbar.uploadImage.bind('change', function (e) { - var files = e.target.files || e.dataTransfer.files; - e.dataTransfer = {}; - e.dataTransfer.files = files; - inlineAttach.onDrop(e); -}); -//toc + var files = e.target.files || e.dataTransfer.files + e.dataTransfer = {} + e.dataTransfer.files = files + inlineAttach.onDrop(e) +}) +// toc ui.toc.dropdown.click(function (e) { - e.stopPropagation(); -}); + e.stopPropagation() +}) // prevent empty link change hash $('a[href="#"]').click(function (e) { - e.preventDefault(); -}); - -//modal actions -var revisions = []; -var revisionViewer = null; -var revisionInsert = []; -var revisionDelete = []; -var revisionInsertAnnotation = null; -var revisionDeleteAnnotation = null; -var revisionList = ui.modal.revision.find('.ui-revision-list'); -var revision = null; -var revisionTime = null; + e.preventDefault() +}) + +// modal actions +var revisions = [] +var revisionViewer = null +var revisionInsert = [] +var revisionDelete = [] +var revisionInsertAnnotation = null +var revisionDeleteAnnotation = null +var revisionList = ui.modal.revision.find('.ui-revision-list') +var revision = null +var revisionTime = null ui.modal.revision.on('show.bs.modal', function (e) { - $.get(noteurl + '/revision') - .done(function(data) { - parseRevisions(data.revision); - initRevisionViewer(); + $.get(noteurl + '/revision') + .done(function (data) { + parseRevisions(data.revision) + initRevisionViewer() }) - .fail(function(err) { - + .fail(function (err) { + if (debug) { + console.log(err) + } }) - .always(function() { - //na - }); -}); -function checkRevisionViewer() { - if (revisionViewer) { - var container = $(revisionViewer.display.wrapper).parent(); - $(revisionViewer.display.scroller).css('height', container.height() + 'px'); - revisionViewer.refresh(); - } -} -ui.modal.revision.on('shown.bs.modal', checkRevisionViewer); -$(window).resize(checkRevisionViewer); -function parseRevisions(_revisions) { - if (_revisions.length != revisions) { - revisions = _revisions; - var lastRevision = null; - if (revisionList.children().length > 0) { - lastRevision = revisionList.find('.active').attr('data-revision-time'); - } - revisionList.html(''); - for (var i = 0; i < revisions.length; i++) { - var revision = revisions[i]; - var item = $('<a href="#" class="list-group-item"></a>'); - item.attr('data-revision-time', revision.time); - if (lastRevision == revision.time) item.addClass('active'); - var itemHeading = $('<h5 class="list-group-item-heading"></h5>'); - itemHeading.html('<i class="fa fa-clock-o"></i> ' + moment(revision.time).format('llll')); - var itemText = $('<p class="list-group-item-text"></p>'); - itemText.html('<i class="fa fa-file-text"></i> Length: ' + revision.length); - item.append(itemHeading).append(itemText); - item.click(function (e) { - var time = $(this).attr('data-revision-time'); - selectRevision(time); - }); - revisionList.append(item); - } - if (!lastRevision) { - selectRevision(revisions[0].time); - } - } -} -function selectRevision(time) { - if (time == revisionTime) return; - $.get(noteurl + '/revision/' + time) - .done(function(data) { - revision = data; - revisionTime = time; - var lastScrollInfo = revisionViewer.getScrollInfo(); - revisionList.children().removeClass('active'); - revisionList.find('[data-revision-time="' + time + '"]').addClass('active'); - var content = revision.content; - revisionViewer.setValue(content); - revisionViewer.scrollTo(null, lastScrollInfo.top); - revisionInsert = []; - revisionDelete = []; + .always(function () { + // na + }) +}) +function checkRevisionViewer () { + if (revisionViewer) { + var container = $(revisionViewer.display.wrapper).parent() + $(revisionViewer.display.scroller).css('height', container.height() + 'px') + revisionViewer.refresh() + } +} +ui.modal.revision.on('shown.bs.modal', checkRevisionViewer) +$(window).resize(checkRevisionViewer) +function parseRevisions (_revisions) { + if (_revisions.length !== revisions) { + revisions = _revisions + var lastRevision = null + if (revisionList.children().length > 0) { + lastRevision = revisionList.find('.active').attr('data-revision-time') + } + revisionList.html('') + for (var i = 0; i < revisions.length; i++) { + var revision = revisions[i] + var item = $('<a href="#" class="list-group-item"></a>') + item.attr('data-revision-time', revision.time) + if (lastRevision === revision.time) item.addClass('active') + var itemHeading = $('<h5 class="list-group-item-heading"></h5>') + itemHeading.html('<i class="fa fa-clock-o"></i> ' + moment(revision.time).format('llll')) + var itemText = $('<p class="list-group-item-text"></p>') + itemText.html('<i class="fa fa-file-text"></i> Length: ' + revision.length) + item.append(itemHeading).append(itemText) + item.click(function (e) { + var time = $(this).attr('data-revision-time') + selectRevision(time) + }) + revisionList.append(item) + } + if (!lastRevision) { + selectRevision(revisions[0].time) + } + } +} +function selectRevision (time) { + if (time === revisionTime) return + $.get(noteurl + '/revision/' + time) + .done(function (data) { + revision = data + revisionTime = time + var lastScrollInfo = revisionViewer.getScrollInfo() + revisionList.children().removeClass('active') + revisionList.find('[data-revision-time="' + time + '"]').addClass('active') + var content = revision.content + revisionViewer.setValue(content) + revisionViewer.scrollTo(null, lastScrollInfo.top) + revisionInsert = [] + revisionDelete = [] // mark the text which have been insert or delete - if (revision.patch.length > 0) { - var bias = 0; - for (var j = 0; j < revision.patch.length; j++) { - var patch = revision.patch[j]; - var currIndex = patch.start1 + bias; - for (var i = 0; i < patch.diffs.length; i++) { - var diff = patch.diffs[i]; + if (revision.patch.length > 0) { + var bias = 0 + for (var j = 0; j < revision.patch.length; j++) { + var patch = revision.patch[j] + var currIndex = patch.start1 + bias + for (var i = 0; i < patch.diffs.length; i++) { + var diff = patch.diffs[i] // ignore if diff only contains line breaks - if ((diff[1].match(new RegExp("\n", "g")) || []).length == diff[1].length) continue; - switch(diff[0]) { - case 0: // retain - currIndex += diff[1].length; - break; - case 1: // insert - var prePos = revisionViewer.posFromIndex(currIndex); - var postPos = revisionViewer.posFromIndex(currIndex + diff[1].length); - revisionInsert.push({ - from: prePos, - to: postPos - }); - revisionViewer.markText(prePos, postPos, { - css: 'background-color: rgba(230,255,230,0.7); text-decoration: underline;' - }); - currIndex += diff[1].length; - break; - case -1: // delete - var prePos = revisionViewer.posFromIndex(currIndex); - revisionViewer.replaceRange(diff[1], prePos); - var postPos = revisionViewer.posFromIndex(currIndex + diff[1].length); - revisionDelete.push({ - from: prePos, - to: postPos - }); - revisionViewer.markText(prePos, postPos, { - css: 'background-color: rgba(255,230,230,0.7); text-decoration: line-through;' - }); - bias += diff[1].length; - currIndex += diff[1].length; - break; - } - } + if ((diff[1].match(/\n/g) || []).length === diff[1].length) continue + var prePos + var postPos + switch (diff[0]) { + case 0: // retain + currIndex += diff[1].length + break + case 1: // insert + prePos = revisionViewer.posFromIndex(currIndex) + postPos = revisionViewer.posFromIndex(currIndex + diff[1].length) + revisionInsert.push({ + from: prePos, + to: postPos + }) + revisionViewer.markText(prePos, postPos, { + css: 'background-color: rgba(230,255,230,0.7); text-decoration: underline;' + }) + currIndex += diff[1].length + break + case -1: // delete + prePos = revisionViewer.posFromIndex(currIndex) + revisionViewer.replaceRange(diff[1], prePos) + postPos = revisionViewer.posFromIndex(currIndex + diff[1].length) + revisionDelete.push({ + from: prePos, + to: postPos + }) + revisionViewer.markText(prePos, postPos, { + css: 'background-color: rgba(255,230,230,0.7); text-decoration: line-through;' + }) + bias += diff[1].length + currIndex += diff[1].length + break } + } } - revisionInsertAnnotation.update(revisionInsert); - revisionDeleteAnnotation.update(revisionDelete); + } + revisionInsertAnnotation.update(revisionInsert) + revisionDeleteAnnotation.update(revisionDelete) }) - .fail(function(err) { - + .fail(function (err) { + if (debug) { + console.log(err) + } + }) + .always(function () { + // na }) - .always(function() { - //na - }); -} -function initRevisionViewer() { - if (revisionViewer) return; - var revisionViewerTextArea = document.getElementById("revisionViewer"); - revisionViewer = CodeMirror.fromTextArea(revisionViewerTextArea, { - mode: defaultEditorMode, - viewportMargin: viewportMargin, - lineNumbers: true, - lineWrapping: true, - showCursorWhenSelecting: true, - inputStyle: "textarea", - gutters: ["CodeMirror-linenumbers"], - flattenSpans: true, - addModeClass: true, - readOnly: true, - autoRefresh: true, - scrollbarStyle: 'overlay' - }); - revisionInsertAnnotation = revisionViewer.annotateScrollbar({ className:"CodeMirror-insert-match" }); - revisionDeleteAnnotation = revisionViewer.annotateScrollbar({ className:"CodeMirror-delete-match" }); - checkRevisionViewer(); +} +function initRevisionViewer () { + if (revisionViewer) return + var revisionViewerTextArea = document.getElementById('revisionViewer') + revisionViewer = CodeMirror.fromTextArea(revisionViewerTextArea, { + mode: defaultEditorMode, + viewportMargin: viewportMargin, + lineNumbers: true, + lineWrapping: true, + showCursorWhenSelecting: true, + inputStyle: 'textarea', + gutters: ['CodeMirror-linenumbers'], + flattenSpans: true, + addModeClass: true, + readOnly: true, + autoRefresh: true, + scrollbarStyle: 'overlay' + }) + revisionInsertAnnotation = revisionViewer.annotateScrollbar({ className: 'CodeMirror-insert-match' }) + revisionDeleteAnnotation = revisionViewer.annotateScrollbar({ className: 'CodeMirror-delete-match' }) + checkRevisionViewer() } $('#revisionModalDownload').click(function () { - if (!revision) return; - var filename = renderFilename(ui.area.markdown) + '_' + revisionTime + '.md'; - var blob = new Blob([revision.content], { - type: "text/markdown;charset=utf-8" - }); - saveAs(blob, filename, true); -}); + if (!revision) return + var filename = renderFilename(ui.area.markdown) + '_' + revisionTime + '.md' + var blob = new Blob([revision.content], { + type: 'text/markdown;charset=utf-8' + }) + saveAs(blob, filename, true) +}) $('#revisionModalRevert').click(function () { - if (!revision) return; - editor.setValue(revision.content); - ui.modal.revision.modal('hide'); -}); -//snippet projects -ui.modal.snippetImportProjects.change(function() { - var accesstoken = $("#snippetImportModalAccessToken").val(), - baseURL = $("#snippetImportModalBaseURL").val(), - project = $("#snippetImportModalProjects").val(); - - $("#snippetImportModalLoading").show(); - $("#snippetImportModalContent").val('/projects/' + project); - $.get(baseURL + '/api/v3/projects/' + project + '/snippets?access_token=' + accesstoken) - .done(function(data) { - $("#snippetImportModalSnippets").find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Snippets</option>'); - data.forEach(function(snippet) { - $('<option>').val(snippet.id).text(snippet.title).appendTo($("#snippetImportModalSnippets")); - }); - $("#snippetImportModalLoading").hide(); - $("#snippetImportModalSnippets").prop('disabled', false); + if (!revision) return + editor.setValue(revision.content) + ui.modal.revision.modal('hide') +}) +// snippet projects +ui.modal.snippetImportProjects.change(function () { + var accesstoken = $('#snippetImportModalAccessToken').val() + var baseURL = $('#snippetImportModalBaseURL').val() + var project = $('#snippetImportModalProjects').val() + + $('#snippetImportModalLoading').show() + $('#snippetImportModalContent').val('/projects/' + project) + $.get(baseURL + '/api/v3/projects/' + project + '/snippets?access_token=' + accesstoken) + .done(function (data) { + $('#snippetImportModalSnippets').find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Snippets</option>') + data.forEach(function (snippet) { + $('<option>').val(snippet.id).text(snippet.title).appendTo($('#snippetImportModalSnippets')) + }) + $('#snippetImportModalLoading').hide() + $('#snippetImportModalSnippets').prop('disabled', false) }) - .fail(function(err) { - + .fail(function (err) { + if (debug) { + console.log(err) + } }) - .always(function() { - //na - }); -}); -//snippet snippets -ui.modal.snippetImportSnippets.change(function() { - var project = $("#snippetImportModalProjects").val(), - snippet = $("#snippetImportModalSnippets").val(); - - $("#snippetImportModalContent").val($("#snippetImportModalContent").val() + '/snippets/' + snippet); -}) - -function scrollToTop() { - if (currentMode == modeType.both) { - if (editor.getScrollInfo().top != 0) - editor.scrollTo(0, 0); - else - ui.area.view.animate({ - scrollTop: 0 - }, 100, "linear"); - } else { - $('body, html').stop(true, true).animate({ - scrollTop: 0 - }, 100, "linear"); - } -} - -function scrollToBottom() { - if (currentMode == modeType.both) { - var scrollInfo = editor.getScrollInfo(); - var scrollHeight = scrollInfo.height; - if (scrollInfo.top != scrollHeight) - editor.scrollTo(0, scrollHeight * 2); - else - ui.area.view.animate({ - scrollTop: ui.area.view[0].scrollHeight - }, 100, "linear"); - } else { - $('body, html').stop(true, true).animate({ - scrollTop: $(document.body)[0].scrollHeight - }, 100, "linear"); - } -} - -window.scrollToTop = scrollToTop; -window.scrollToBottom = scrollToBottom; - -var enoughForAffixToc = true; - -//scrollspy -function generateScrollspy() { - $(document.body).scrollspy({ - target: '.scrollspy-body' - }); - ui.area.view.scrollspy({ - target: '.scrollspy-view' - }); - $(document.body).scrollspy('refresh'); - ui.area.view.scrollspy('refresh'); - if (enoughForAffixToc) { - ui.toc.toc.hide(); - ui.toc.affix.show(); - } else { - ui.toc.affix.hide(); - ui.toc.toc.show(); - } - //$(document.body).scroll(); - //ui.area.view.scroll(); -} - -function updateScrollspy() { - var headers = ui.area.markdown.find('h1, h2, h3').toArray(); - var headerMap = []; - for (var i = 0; i < headers.length; i++) { - headerMap.push($(headers[i]).offset().top - parseInt($(headers[i]).css('margin-top'))); - } - applyScrollspyActive($(window).scrollTop(), headerMap, headers, - $('.scrollspy-body'), 0); - var offset = ui.area.view.scrollTop() - ui.area.view.offset().top; - applyScrollspyActive(ui.area.view.scrollTop(), headerMap, headers, - $('.scrollspy-view'), offset - 10); -} + .always(function () { + // na + }) +}) +// snippet snippets +ui.modal.snippetImportSnippets.change(function () { + var snippet = $('#snippetImportModalSnippets').val() + $('#snippetImportModalContent').val($('#snippetImportModalContent').val() + '/snippets/' + snippet) +}) -function applyScrollspyActive(top, headerMap, headers, target, offset) { - var index = 0; - for (var i = headerMap.length - 1; i >= 0; i--) { - if (top >= (headerMap[i] + offset) && headerMap[i + 1] && top < (headerMap[i + 1] + offset)) { - index = i; - break; - } - } - var header = $(headers[index]); - var active = target.find('a[href="#' + header.attr('id') + '"]'); - active.closest('li').addClass('active').parent().closest('li').addClass('active').parent().closest('li').addClass('active'); +function scrollToTop () { + if (window.currentMode === modeType.both) { + if (editor.getScrollInfo().top !== 0) { editor.scrollTo(0, 0) } else { + ui.area.view.animate({ + scrollTop: 0 + }, 100, 'linear') + } + } else { + $('body, html').stop(true, true).animate({ + scrollTop: 0 + }, 100, 'linear') + } +} + +function scrollToBottom () { + if (window.currentMode === modeType.both) { + var scrollInfo = editor.getScrollInfo() + var scrollHeight = scrollInfo.height + if (scrollInfo.top !== scrollHeight) { editor.scrollTo(0, scrollHeight * 2) } else { + ui.area.view.animate({ + scrollTop: ui.area.view[0].scrollHeight + }, 100, 'linear') + } + } else { + $('body, html').stop(true, true).animate({ + scrollTop: $(document.body)[0].scrollHeight + }, 100, 'linear') + } +} + +window.scrollToTop = scrollToTop +window.scrollToBottom = scrollToBottom + +var enoughForAffixToc = true + +// scrollspy +function generateScrollspy () { + $(document.body).scrollspy({ + target: '.scrollspy-body' + }) + ui.area.view.scrollspy({ + target: '.scrollspy-view' + }) + $(document.body).scrollspy('refresh') + ui.area.view.scrollspy('refresh') + if (enoughForAffixToc) { + ui.toc.toc.hide() + ui.toc.affix.show() + } else { + ui.toc.affix.hide() + ui.toc.toc.show() + } + // $(document.body).scroll(); + // ui.area.view.scroll(); +} + +function updateScrollspy () { + var headers = ui.area.markdown.find('h1, h2, h3').toArray() + var headerMap = [] + for (var i = 0; i < headers.length; i++) { + headerMap.push($(headers[i]).offset().top - parseInt($(headers[i]).css('margin-top'))) + } + applyScrollspyActive($(window).scrollTop(), headerMap, headers, + $('.scrollspy-body'), 0) + var offset = ui.area.view.scrollTop() - ui.area.view.offset().top + applyScrollspyActive(ui.area.view.scrollTop(), headerMap, headers, + $('.scrollspy-view'), offset - 10) +} + +function applyScrollspyActive (top, headerMap, headers, target, offset) { + var index = 0 + for (var i = headerMap.length - 1; i >= 0; i--) { + if (top >= (headerMap[i] + offset) && headerMap[i + 1] && top < (headerMap[i + 1] + offset)) { + index = i + break + } + } + var header = $(headers[index]) + var active = target.find('a[href="#' + header.attr('id') + '"]') + active.closest('li').addClass('active').parent().closest('li').addClass('active').parent().closest('li').addClass('active') } // clipboard modal -//fix for wrong autofocus +// fix for wrong autofocus $('#clipboardModal').on('shown.bs.modal', function () { - $('#clipboardModal').blur(); -}); -$("#clipboardModalClear").click(function () { - $("#clipboardModalContent").html(''); -}); -$("#clipboardModalConfirm").click(function () { - var data = $("#clipboardModalContent").html(); - if (data) { - parseToEditor(data); - $('#clipboardModal').modal('hide'); - $("#clipboardModalContent").html(''); - } -}); + $('#clipboardModal').blur() +}) +$('#clipboardModalClear').click(function () { + $('#clipboardModalContent').html('') +}) +$('#clipboardModalConfirm').click(function () { + var data = $('#clipboardModalContent').html() + if (data) { + parseToEditor(data) + $('#clipboardModal').modal('hide') + $('#clipboardModalContent').html('') + } +}) // refresh modal $('#refreshModalRefresh').click(function () { - location.reload(true); -}); + location.reload(true) +}) // gist import modal -$("#gistImportModalClear").click(function () { - $("#gistImportModalContent").val(''); -}); -$("#gistImportModalConfirm").click(function () { - var gisturl = $("#gistImportModalContent").val(); - if (!gisturl) return; - $('#gistImportModal').modal('hide'); - $("#gistImportModalContent").val(''); - if (!isValidURL(gisturl)) { - showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid URL :(', '', '', false); - return; +$('#gistImportModalClear').click(function () { + $('#gistImportModalContent').val('') +}) +$('#gistImportModalConfirm').click(function () { + var gisturl = $('#gistImportModalContent').val() + if (!gisturl) return + $('#gistImportModal').modal('hide') + $('#gistImportModalContent').val('') + if (!isValidURL(gisturl)) { + showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid URL :(', '', '', false) + } else { + var hostname = window.url('hostname', gisturl) + if (hostname !== 'gist.github.com') { + showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid Gist URL :(', '', '', false) } else { - var hostname = url('hostname', gisturl) - if (hostname !== 'gist.github.com') { - showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid Gist URL :(', '', '', false); - } else { - ui.spinner.show(); - $.get('https://api.github.com/gists/' + url('-1', gisturl)) + ui.spinner.show() + $.get('https://api.github.com/gists/' + window.url('-1', gisturl)) .done(function (data) { - if (data.files) { - var contents = ""; - Object.keys(data.files).forEach(function (key) { - contents += key; - contents += '\n---\n'; - contents += data.files[key].content; - contents += '\n\n'; - }); - replaceAll(contents); - } else { - showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Unable to fetch gist files :(', '', '', false); - } + if (data.files) { + var contents = '' + Object.keys(data.files).forEach(function (key) { + contents += key + contents += '\n---\n' + contents += data.files[key].content + contents += '\n\n' + }) + replaceAll(contents) + } else { + showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Unable to fetch gist files :(', '', '', false) + } }) .fail(function (data) { - showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid Gist URL :(', '', JSON.stringify(data), false); + showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid Gist URL :(', '', JSON.stringify(data), false) }) .always(function () { - ui.spinner.hide(); - }); - } + ui.spinner.hide() + }) } -}); + } +}) // snippet import modal -$("#snippetImportModalClear").click(function () { - $("#snippetImportModalContent").val(''); - $("#snippetImportModalProjects").val('init'); - $("#snippetImportModalSnippets").val('init'); - $("#snippetImportModalSnippets").prop('disabled', true); -}); -$("#snippetImportModalConfirm").click(function () { - var snippeturl = $("#snippetImportModalContent").val(); - if (!snippeturl) return; - $('#snippetImportModal').modal('hide'); - $("#snippetImportModalContent").val(''); - if (!/^.+\/snippets\/.+$/.test(snippeturl)) { - showMessageModal('<i class="fa fa-github"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', '', false); - } else { - ui.spinner.show(); - var accessToken = '?access_token=' + $("#snippetImportModalAccessToken").val(); - var fullURL = $("#snippetImportModalBaseURL").val() + '/api/v3' + snippeturl; - $.get(fullURL + accessToken) - .done(function(data) { - var content = '# ' + (data.title || "Snippet Import"); - var fileInfo = data.file_name.split('.'); - fileInfo[1] = (fileInfo[1]) ? fileInfo[1] : "md"; - $.get(fullURL + '/raw' + accessToken) +$('#snippetImportModalClear').click(function () { + $('#snippetImportModalContent').val('') + $('#snippetImportModalProjects').val('init') + $('#snippetImportModalSnippets').val('init') + $('#snippetImportModalSnippets').prop('disabled', true) +}) +$('#snippetImportModalConfirm').click(function () { + var snippeturl = $('#snippetImportModalContent').val() + if (!snippeturl) return + $('#snippetImportModal').modal('hide') + $('#snippetImportModalContent').val('') + if (!/^.+\/snippets\/.+$/.test(snippeturl)) { + showMessageModal('<i class="fa fa-github"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', '', false) + } else { + ui.spinner.show() + var accessToken = '?access_token=' + $('#snippetImportModalAccessToken').val() + var fullURL = $('#snippetImportModalBaseURL').val() + '/api/v3' + snippeturl + $.get(fullURL + accessToken) + .done(function (data) { + var content = '# ' + (data.title || 'Snippet Import') + var fileInfo = data.file_name.split('.') + fileInfo[1] = (fileInfo[1]) ? fileInfo[1] : 'md' + $.get(fullURL + '/raw' + accessToken) .done(function (raw) { - if (raw) { - content += "\n\n"; - if (fileInfo[1] != "md") { - content += "```" + fileTypes[fileInfo[1]] + "\n"; - } - content += raw; - if (fileInfo[1] != "md") { - content += "\n```"; - } - replaceAll(content); + if (raw) { + content += '\n\n' + if (fileInfo[1] !== 'md') { + content += '```' + window.fileTypes[fileInfo[1]] + '\n' } + content += raw + if (fileInfo[1] !== 'md') { + content += '\n```' + } + replaceAll(content) + } }) .fail(function (data) { - showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', JSON.stringify(data), false); + showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', JSON.stringify(data), false) }) .always(function () { - ui.spinner.hide(); - }); + ui.spinner.hide() + }) }) .fail(function (data) { - showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', JSON.stringify(data), false); - }); - } -}); - -//snippet export modal -$("#snippetExportModalConfirm").click(function() { - var accesstoken = $("#snippetExportModalAccessToken").val(), - baseURL = $("#snippetExportModalBaseURL").val(), - data = { - title: $("#snippetExportModalTitle").val(), - file_name: $("#snippetExportModalFileName").val(), - code: editor.getValue(), - visibility_level: $("#snippetExportModalVisibility").val() - }; - if (!data.title || !data.file_name || !data.code || !data.visibility_level || !$("#snippetExportModalProjects").val()) return; - $("#snippetExportModalLoading").show(); - var fullURL = baseURL + '/api/v3/projects/' + $("#snippetExportModalProjects").val() + '/snippets?access_token=' + accesstoken; - $.post(fullURL + showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', JSON.stringify(data), false) + }) + } +}) + +// snippet export modal +$('#snippetExportModalConfirm').click(function () { + var accesstoken = $('#snippetExportModalAccessToken').val() + var baseURL = $('#snippetExportModalBaseURL').val() + var data = { + title: $('#snippetExportModalTitle').val(), + file_name: $('#snippetExportModalFileName').val(), + code: editor.getValue(), + visibility_level: $('#snippetExportModalVisibility').val() + } + if (!data.title || !data.file_name || !data.code || !data.visibility_level || !$('#snippetExportModalProjects').val()) return + $('#snippetExportModalLoading').show() + var fullURL = baseURL + '/api/v3/projects/' + $('#snippetExportModalProjects').val() + '/snippets?access_token=' + accesstoken + $.post(fullURL , data - , function(ret) { - $("#snippetExportModalLoading").hide(); - $('#snippetExportModal').modal('hide'); - var redirect = baseURL + '/' + $("#snippetExportModalProjects option[value='" + $("#snippetExportModalProjects").val() + "']").text() + '/snippets/' + ret.id; - showMessageModal('<i class="fa fa-gitlab"></i> Export to Snippet', 'Export Successful!', redirect, 'View Snippet Here', true); + , function (ret) { + $('#snippetExportModalLoading').hide() + $('#snippetExportModal').modal('hide') + var redirect = baseURL + '/' + $("#snippetExportModalProjects option[value='" + $('#snippetExportModalProjects').val() + "']").text() + '/snippets/' + ret.id + showMessageModal('<i class="fa fa-gitlab"></i> Export to Snippet', 'Export Successful!', redirect, 'View Snippet Here', true) } , 'json' - ); -}); - -function parseToEditor(data) { - var parsed = toMarkdown(data); - if (parsed) - replaceAll(parsed); -} - -function replaceAll(data) { - editor.replaceRange(data, { - line: 0, - ch: 0 - }, { - line: editor.lastLine(), - ch: editor.lastLine().length - }, '+input'); -} - -function importFromUrl(url) { - //console.log(url); - if (!url) return; - if (!isValidURL(url)) { - showMessageModal('<i class="fa fa-cloud-download"></i> Import from URL', 'Not a valid URL :(', '', '', false); - return; - } - $.ajax({ - method: "GET", - url: url, - success: function (data) { - var extension = url.split('.').pop(); - if (extension == 'html') - parseToEditor(data); - else - replaceAll(data); - }, - error: function (data) { - showMessageModal('<i class="fa fa-cloud-download"></i> Import from URL', 'Import failed :(', '', JSON.stringify(data), false); - }, - complete: function () { - ui.spinner.hide(); - } - }); + ) +}) + +function parseToEditor (data) { + var parsed = toMarkdown(data) + if (parsed) { replaceAll(parsed) } +} + +function replaceAll (data) { + editor.replaceRange(data, { + line: 0, + ch: 0 + }, { + line: editor.lastLine(), + ch: editor.lastLine().length + }, '+input') +} + +function importFromUrl (url) { + // console.log(url); + if (!url) return + if (!isValidURL(url)) { + showMessageModal('<i class="fa fa-cloud-download"></i> Import from URL', 'Not a valid URL :(', '', '', false) + return + } + $.ajax({ + method: 'GET', + url: url, + success: function (data) { + var extension = url.split('.').pop() + if (extension === 'html') { parseToEditor(data) } else { replaceAll(data) } + }, + error: function (data) { + showMessageModal('<i class="fa fa-cloud-download"></i> Import from URL', 'Import failed :(', '', JSON.stringify(data), false) + }, + complete: function () { + ui.spinner.hide() + } + }) } -//mode +// mode ui.toolbar.mode.click(function () { - toggleMode(); -}); -//edit + toggleMode() +}) +// edit ui.toolbar.edit.click(function () { - changeMode(modeType.edit); -}); -//view + changeMode(modeType.edit) +}) +// view ui.toolbar.view.click(function () { - changeMode(modeType.view); -}); -//both + changeMode(modeType.view) +}) +// both ui.toolbar.both.click(function () { - changeMode(modeType.both); -}); -//permission -//freely + changeMode(modeType.both) +}) +// permission +// freely ui.infobar.permission.freely.click(function () { - emitPermission("freely"); -}); -//editable + emitPermission('freely') +}) +// editable ui.infobar.permission.editable.click(function () { - emitPermission("editable"); -}); -//locked + emitPermission('editable') +}) +// locked ui.infobar.permission.locked.click(function () { - emitPermission("locked"); -}); -//private + emitPermission('locked') +}) +// private ui.infobar.permission.private.click(function () { - emitPermission("private"); -}); -//limited -ui.infobar.permission.limited.click(function() { - emitPermission("limited"); -}); -//protected -ui.infobar.permission.protected.click(function() { - emitPermission("protected"); -}); + emitPermission('private') +}) +// limited +ui.infobar.permission.limited.click(function () { + emitPermission('limited') +}) +// protected +ui.infobar.permission.protected.click(function () { + emitPermission('protected') +}) // delete note ui.infobar.delete.click(function () { - $('.delete-modal').modal('show'); -}); + $('.delete-modal').modal('show') +}) $('.ui-delete-modal-confirm').click(function () { - socket.emit('delete'); -}); - -function emitPermission(_permission) { - if (_permission != permission) { - socket.emit('permission', _permission); - } -} - -function updatePermission(newPermission) { - if (permission != newPermission) { - permission = newPermission; - if (loaded) refreshView(); - } - var label = null; - var title = null; - switch (permission) { - case "freely": - label = '<i class="fa fa-leaf"></i> Freely'; - title = "Anyone can edit"; - break; - case "editable": - label = '<i class="fa fa-shield"></i> Editable'; - title = "Signed people can edit"; - break; - case "limited": - label = '<i class="fa fa-id-card"></i> Limited'; - title = "Signed people can edit (forbid guest)" - break; - case "locked": - label = '<i class="fa fa-lock"></i> Locked'; - title = "Only owner can edit"; - break; - case "protected": - label = '<i class="fa fa-umbrella"></i> Protected'; - title = "Only owner can edit (forbid guest)"; - break; - case "private": - label = '<i class="fa fa-hand-stop-o"></i> Private'; - title = "Only owner can view & edit"; - break; - } - if (personalInfo.userid && owner && personalInfo.userid == owner) { - label += ' <i class="fa fa-caret-down"></i>'; - ui.infobar.permission.label.removeClass('disabled'); - } else { - ui.infobar.permission.label.addClass('disabled'); - } - ui.infobar.permission.label.html(label).attr('title', title); -} - -function havePermission() { - var bool = false; - switch (permission) { - case "freely": - bool = true; - break; - case "editable": - case "limited": - if (!personalInfo.login) { - bool = false; - } else { - bool = true; - } - break; - case "locked": - case "private": - case "protected": - if (!owner || personalInfo.userid != owner) { - bool = false; - } else { - bool = true; - } - break; - } - return bool; + socket.emit('delete') +}) + +function emitPermission (_permission) { + if (_permission !== permission) { + socket.emit('permission', _permission) + } +} + +function updatePermission (newPermission) { + if (permission !== newPermission) { + permission = newPermission + if (window.loaded) refreshView() + } + var label = null + var title = null + switch (permission) { + case 'freely': + label = '<i class="fa fa-leaf"></i> Freely' + title = 'Anyone can edit' + break + case 'editable': + label = '<i class="fa fa-shield"></i> Editable' + title = 'Signed people can edit' + break + case 'limited': + label = '<i class="fa fa-id-card"></i> Limited' + title = 'Signed people can edit (forbid guest)' + break + case 'locked': + label = '<i class="fa fa-lock"></i> Locked' + title = 'Only owner can edit' + break + case 'protected': + label = '<i class="fa fa-umbrella"></i> Protected' + title = 'Only owner can edit (forbid guest)' + break + case 'private': + label = '<i class="fa fa-hand-stop-o"></i> Private' + title = 'Only owner can view & edit' + break + } + if (window.personalInfo.userid && window.owner && window.personalInfo.userid === window.owner) { + label += ' <i class="fa fa-caret-down"></i>' + ui.infobar.permission.label.removeClass('disabled') + } else { + ui.infobar.permission.label.addClass('disabled') + } + ui.infobar.permission.label.html(label).attr('title', title) +} + +function havePermission () { + var bool = false + switch (permission) { + case 'freely': + bool = true + break + case 'editable': + case 'limited': + if (!window.personalInfo.login) { + bool = false + } else { + bool = true + } + break + case 'locked': + case 'private': + case 'protected': + if (!window.owner || window.personalInfo.userid !== window.owner) { + bool = false + } else { + bool = true + } + break + } + return bool } // global module workaround -window.havePermission = havePermission; +window.havePermission = havePermission -//socket.io actions -var io = require("socket.io-client"); +// socket.io actions +var io = require('socket.io-client') var socket = io.connect({ - path: urlpath ? '/' + urlpath + '/socket.io/' : '', - timeout: 5000, //5 secs to timeout, - reconnectionAttempts: 20 // retry 20 times on connect failed -}); -//overwrite original event for checking login state -var on = socket.on; + path: urlpath ? '/' + urlpath + '/socket.io/' : '', + timeout: 5000, // 5 secs to timeout, + reconnectionAttempts: 20 // retry 20 times on connect failed +}) +// overwrite original event for checking login state +var on = socket.on socket.on = function () { - if (!checkLoginStateChanged() && !needRefresh) - return on.apply(socket, arguments); -}; -var emit = socket.emit; + if (!checkLoginStateChanged() && !window.needRefresh) { return on.apply(socket, arguments) } +} +var emit = socket.emit socket.emit = function () { - if (!checkLoginStateChanged() && !needRefresh) - emit.apply(socket, arguments); -}; + if (!checkLoginStateChanged() && !window.needRefresh) { emit.apply(socket, arguments) } +} socket.on('info', function (data) { - console.error(data); - switch (data.code) { - case 403: - location.href = serverurl + "/403"; - break; - case 404: - location.href = serverurl + "/404"; - break; - case 500: - location.href = serverurl + "/500"; - break; - } -}); + console.error(data) + switch (data.code) { + case 403: + location.href = serverurl + '/403' + break + case 404: + location.href = serverurl + '/404' + break + case 500: + location.href = serverurl + '/500' + break + } +}) socket.on('error', function (data) { - console.error(data); - if (data.message && data.message.indexOf('AUTH failed') === 0) - location.href = serverurl + "/403"; -}); + console.error(data) + if (data.message && data.message.indexOf('AUTH failed') === 0) { location.href = serverurl + '/403' } +}) socket.on('delete', function () { - if (personalInfo.login) { - deleteServerHistory(noteid, function (err, data) { - if (!err) location.href = serverurl; - }); - } else { - getHistory(function (notehistory) { - var newnotehistory = removeHistory(noteid, notehistory); - saveHistory(newnotehistory); - location.href = serverurl; - }); - } -}); -var retryTimer = null; + if (window.personalInfo.login) { + deleteServerHistory(noteid, function (err, data) { + if (!err) location.href = serverurl + }) + } else { + getHistory(function (notehistory) { + var newnotehistory = removeHistory(noteid, notehistory) + saveHistory(newnotehistory) + location.href = serverurl + }) + } +}) +var retryTimer = null socket.on('maintenance', function () { - cmClient.revision = -1; -}); + cmClient.revision = -1 +}) socket.on('disconnect', function (data) { - showStatus(statusType.offline); - if (loaded) { - saveInfo(); - lastInfo.history = editor.getHistory(); - } - if (!editor.getOption('readOnly')) - editor.setOption('readOnly', true); - if (!retryTimer) { - retryTimer = setInterval(function () { - if (!needRefresh) socket.connect(); - }, 1000); - } -}); + showStatus(statusType.offline) + if (window.loaded) { + saveInfo() + window.lastInfo.history = editor.getHistory() + } + if (!editor.getOption('readOnly')) { editor.setOption('readOnly', true) } + if (!retryTimer) { + retryTimer = setInterval(function () { + if (!window.needRefresh) socket.connect() + }, 1000) + } +}) socket.on('reconnect', function (data) { - //sync back any change in offline - emitUserStatus(true); - cursorActivity(); - socket.emit('online users'); -}); + // sync back any change in offline + emitUserStatus(true) + cursorActivity() + socket.emit('online users') +}) socket.on('connect', function (data) { - clearInterval(retryTimer); - retryTimer = null; - personalInfo['id'] = socket.id; - showStatus(statusType.connected); - socket.emit('version'); -}); + clearInterval(retryTimer) + retryTimer = null + window.personalInfo['id'] = socket.id + showStatus(statusType.connected) + socket.emit('version') +}) socket.on('version', function (data) { - if (version != data.version) { - if (version < data.minimumCompatibleVersion) { - setRefreshModal('incompatible-version'); - setNeedRefresh(); - } else { - setRefreshModal('new-version'); - } - } -}); -var authors = []; -var authorship = []; -var authorshipMarks = {}; -var authorMarks = {}; // temp variable -var addTextMarkers = []; // temp variable -function updateInfo(data) { - //console.log(data); - if (data.hasOwnProperty('createtime') && createtime !== data.createtime) { - createtime = data.createtime; - updateLastChange(); - } - if (data.hasOwnProperty('updatetime') && lastchangetime !== data.updatetime) { - lastchangetime = data.updatetime; - updateLastChange(); - } - if (data.hasOwnProperty('owner') && owner !== data.owner) { - owner = data.owner; - ownerprofile = data.ownerprofile; - updateOwner(); - } - if (data.hasOwnProperty('lastchangeuser') && lastchangeuser !== data.lastchangeuser) { - lastchangeuser = data.lastchangeuser; - lastchangeuserprofile = data.lastchangeuserprofile; - updateLastChangeUser(); - updateOwner(); - } - if (data.hasOwnProperty('authors') && authors !== data.authors) { - authors = data.authors; - } - if (data.hasOwnProperty('authorship') && authorship !== data.authorship) { - authorship = data.authorship; - updateAuthorship(); + if (version !== data.version) { + if (version < data.minimumCompatibleVersion) { + setRefreshModal('incompatible-version') + setNeedRefresh() + } else { + setRefreshModal('new-version') } + } +}) +var authors = [] +var authorship = [] +var authorMarks = {} // temp variable +var addTextMarkers = [] // temp variable +function updateInfo (data) { + // console.log(data); + if (data.hasOwnProperty('createtime') && window.createtime !== data.createtime) { + window.createtime = data.createtime + updateLastChange() + } + if (data.hasOwnProperty('updatetime') && window.lastchangetime !== data.updatetime) { + window.lastchangetime = data.updatetime + updateLastChange() + } + if (data.hasOwnProperty('owner') && window.owner !== data.owner) { + window.owner = data.owner + window.ownerprofile = data.ownerprofile + updateOwner() + } + if (data.hasOwnProperty('lastchangeuser') && window.lastchangeuser !== data.lastchangeuser) { + window.lastchangeuser = data.lastchangeuser + window.lastchangeuserprofile = data.lastchangeuserprofile + updateLastChangeUser() + updateOwner() + } + if (data.hasOwnProperty('authors') && authors !== data.authors) { + authors = data.authors + } + if (data.hasOwnProperty('authorship') && authorship !== data.authorship) { + authorship = data.authorship + updateAuthorship() + } } var updateAuthorship = _.debounce(function () { - editor.operation(updateAuthorshipInner); -}, 50); -function initMark() { - return { - gutter: { - userid: null, - timestamp: null - }, - textmarkers: [] - }; -} -function initMarkAndCheckGutter(mark, author, timestamp) { - if (!mark) mark = initMark(); - if (!mark.gutter.userid || mark.gutter.timestamp > timestamp) { - mark.gutter.userid = author.userid; - mark.gutter.timestamp = timestamp; - } - return mark; -} -var gutterStylePrefix = "border-left: 3px solid "; -var gutterStylePostfix = "; height: " + defaultTextHeight + "px; margin-left: 3px;"; -var textMarkderStylePrefix = "background-image: linear-gradient(to top, "; -var textMarkderStylePostfix = " 1px, transparent 1px);"; + editor.operation(updateAuthorshipInner) +}, 50) +function initMark () { + return { + gutter: { + userid: null, + timestamp: null + }, + textmarkers: [] + } +} +function initMarkAndCheckGutter (mark, author, timestamp) { + if (!mark) mark = initMark() + if (!mark.gutter.userid || mark.gutter.timestamp > timestamp) { + mark.gutter.userid = author.userid + mark.gutter.timestamp = timestamp + } + return mark +} +var gutterStylePrefix = 'border-left: 3px solid ' +var gutterStylePostfix = '; height: ' + defaultTextHeight + 'px; margin-left: 3px;' +var textMarkderStylePrefix = 'background-image: linear-gradient(to top, ' +var textMarkderStylePostfix = ' 1px, transparent 1px);' var addStyleRule = (function () { - var added = {}; - var styleElement = document.createElement('style'); - document.documentElement.getElementsByTagName('head')[0].appendChild(styleElement); - var styleSheet = styleElement.sheet; - - return function (css) { - if (added[css]) { - return; - } - added[css] = true; - styleSheet.insertRule(css, (styleSheet.cssRules || styleSheet.rules).length); - }; -}()); -function updateAuthorshipInner() { + var added = {} + var styleElement = document.createElement('style') + document.documentElement.getElementsByTagName('head')[0].appendChild(styleElement) + var styleSheet = styleElement.sheet + + return function (css) { + if (added[css]) { + return + } + added[css] = true + styleSheet.insertRule(css, (styleSheet.cssRules || styleSheet.rules).length) + } +}()) +function updateAuthorshipInner () { // ignore when ot not synced yet - if (havePendingOperation()) return; - authorMarks = {}; - for (var i = 0; i < authorship.length; i++) { - var atom = authorship[i]; - var author = authors[atom[0]]; - if (author) { - var prePos = editor.posFromIndex(atom[1]); - var preLine = editor.getLine(prePos.line); - var postPos = editor.posFromIndex(atom[2]); - var postLine = editor.getLine(postPos.line); - if (prePos.ch == 0 && postPos.ch == postLine.length) { - for (var j = prePos.line; j <= postPos.line; j++) { - if (editor.getLine(j)) { - authorMarks[j] = initMarkAndCheckGutter(authorMarks[j], author, atom[3]); - } - } - } else if (postPos.line - prePos.line >= 1) { - var startLine = prePos.line; - var endLine = postPos.line; - if (prePos.ch == preLine.length) { - startLine++; - } else if (prePos.ch != 0) { - var mark = initMarkAndCheckGutter(authorMarks[prePos.line], author, atom[3]); - var _postPos = { - line: prePos.line, - ch: preLine.length - }; - if (JSON.stringify(prePos) != JSON.stringify(_postPos)) { - mark.textmarkers.push({ - userid: author.userid, - pos: [prePos, _postPos] - }); - startLine++; - } - authorMarks[prePos.line] = mark; - } - if (postPos.ch == 0) { - endLine--; - } else if (postPos.ch != postLine.length) { - var mark = initMarkAndCheckGutter(authorMarks[postPos.line], author, atom[3]); - var _prePos = { - line: postPos.line, - ch: 0 - }; - if (JSON.stringify(_prePos) != JSON.stringify(postPos)) { - mark.textmarkers.push({ - userid: author.userid, - pos: [_prePos, postPos] - }); - endLine--; - } - authorMarks[postPos.line] = mark; - } - for (var j = startLine; j <= endLine; j++) { - if (editor.getLine(j)) { - authorMarks[j] = initMarkAndCheckGutter(authorMarks[j], author, atom[3]); - } - } - } else { - var mark = initMarkAndCheckGutter(authorMarks[prePos.line], author, atom[3]); - if (JSON.stringify(prePos) != JSON.stringify(postPos)) { - mark.textmarkers.push({ - userid: author.userid, - pos: [prePos, postPos] - }); - } - authorMarks[prePos.line] = mark; - } - } - } - addTextMarkers = []; - editor.eachLine(iterateLine); - var allTextMarks = editor.getAllMarks(); - for (var i = 0; i < allTextMarks.length; i++) { - var _textMarker = allTextMarks[i]; - var pos = _textMarker.find(); - var found = false; - for (var j = 0; j < addTextMarkers.length; j++) { - var textMarker = addTextMarkers[j]; - var author = authors[textMarker.userid]; - var className = 'authorship-inline-' + author.color.substr(1); - var obj = { - from: textMarker.pos[0], - to: textMarker.pos[1] - }; - if (JSON.stringify(pos) == JSON.stringify(obj) && _textMarker.className && + if (havePendingOperation()) return + authorMarks = {} + for (let i = 0; i < authorship.length; i++) { + var atom = authorship[i] + let author = authors[atom[0]] + if (author) { + var prePos = editor.posFromIndex(atom[1]) + var preLine = editor.getLine(prePos.line) + var postPos = editor.posFromIndex(atom[2]) + var postLine = editor.getLine(postPos.line) + if (prePos.ch === 0 && postPos.ch === postLine.length) { + for (let j = prePos.line; j <= postPos.line; j++) { + if (editor.getLine(j)) { + authorMarks[j] = initMarkAndCheckGutter(authorMarks[j], author, atom[3]) + } + } + } else if (postPos.line - prePos.line >= 1) { + var startLine = prePos.line + var endLine = postPos.line + if (prePos.ch === preLine.length) { + startLine++ + } else if (prePos.ch !== 0) { + let mark = initMarkAndCheckGutter(authorMarks[prePos.line], author, atom[3]) + var _postPos = { + line: prePos.line, + ch: preLine.length + } + if (JSON.stringify(prePos) !== JSON.stringify(_postPos)) { + mark.textmarkers.push({ userid: author.userid, pos: [prePos, _postPos] }) + startLine++ + } + authorMarks[prePos.line] = mark + } + if (postPos.ch === 0) { + endLine-- + } else if (postPos.ch !== postLine.length) { + let mark = initMarkAndCheckGutter(authorMarks[postPos.line], author, atom[3]) + var _prePos = { + line: postPos.line, + ch: 0 + } + if (JSON.stringify(_prePos) !== JSON.stringify(postPos)) { + mark.textmarkers.push({ userid: author.userid, pos: [_prePos, postPos] }) + endLine-- + } + authorMarks[postPos.line] = mark + } + for (let j = startLine; j <= endLine; j++) { + if (editor.getLine(j)) { + authorMarks[j] = initMarkAndCheckGutter(authorMarks[j], author, atom[3]) + } + } + } else { + let mark = initMarkAndCheckGutter(authorMarks[prePos.line], author, atom[3]) + if (JSON.stringify(prePos) !== JSON.stringify(postPos)) { + mark.textmarkers.push({ + userid: author.userid, + pos: [prePos, postPos] + }) + } + authorMarks[prePos.line] = mark + } + } + } + addTextMarkers = [] + editor.eachLine(iterateLine) + var allTextMarks = editor.getAllMarks() + for (let i = 0; i < allTextMarks.length; i++) { + let _textMarker = allTextMarks[i] + var pos = _textMarker.find() + var found = false + for (let j = 0; j < addTextMarkers.length; j++) { + let textMarker = addTextMarkers[j] + let author = authors[textMarker.userid] + let className = 'authorship-inline-' + author.color.substr(1) + var obj = { + from: textMarker.pos[0], + to: textMarker.pos[1] + } + if (JSON.stringify(pos) === JSON.stringify(obj) && _textMarker.className && _textMarker.className.indexOf(className) > -1) { - addTextMarkers.splice(j, 1); - j--; - found = true; - break; - } - } - if (!found && _textMarker.className && _textMarker.className.indexOf('authorship-inline') > -1) { - _textMarker.clear(); - } - } - for (var i = 0; i < addTextMarkers.length; i++) { - var textMarker = addTextMarkers[i]; - var author = authors[textMarker.userid]; - var rgbcolor = hex2rgb(author.color); - var colorString = "rgba(" + rgbcolor.red + "," + rgbcolor.green + "," + rgbcolor.blue + ",0.7)"; - var styleString = textMarkderStylePrefix + colorString + textMarkderStylePostfix; - var className = 'authorship-inline-' + author.color.substr(1); - var rule = "." + className + "{" + styleString + "}"; - addStyleRule(rule); - var _textMarker = editor.markText(textMarker.pos[0], textMarker.pos[1], { - className: 'authorship-inline ' + className, - title: author.name - }); - } - authorshipMarks = authorMarks; -} -function iterateLine(line) { - var lineNumber = line.lineNo(); - var currMark = authorMarks[lineNumber]; - var author = currMark ? authors[currMark.gutter.userid] : null; - if (currMark && author) { - var className = 'authorship-gutter-' + author.color.substr(1); - var gutters = line.gutterMarkers; - if (!gutters || !gutters['authorship-gutters'] || + addTextMarkers.splice(j, 1) + j-- + found = true + break + } + } + if (!found && _textMarker.className && _textMarker.className.indexOf('authorship-inline') > -1) { + _textMarker.clear() + } + } + for (let i = 0; i < addTextMarkers.length; i++) { + let textMarker = addTextMarkers[i] + let author = authors[textMarker.userid] + var rgbcolor = hex2rgb(author.color) + var colorString = 'rgba(' + rgbcolor.red + ',' + rgbcolor.green + ',' + rgbcolor.blue + ',0.7)' + var styleString = textMarkderStylePrefix + colorString + textMarkderStylePostfix + let className = 'authorship-inline-' + author.color.substr(1) + var rule = '.' + className + '{' + styleString + '}' + addStyleRule(rule) + editor.markText(textMarker.pos[0], textMarker.pos[1], { + className: 'authorship-inline ' + className, + title: author.name + }) + } +} +function iterateLine (line) { + var lineNumber = line.lineNo() + var currMark = authorMarks[lineNumber] + var author = currMark ? authors[currMark.gutter.userid] : null + if (currMark && author) { + let className = 'authorship-gutter-' + author.color.substr(1) + var gutters = line.gutterMarkers + if (!gutters || !gutters['authorship-gutters'] || !gutters['authorship-gutters'].className || !gutters['authorship-gutters'].className.indexOf(className) < 0) { - var styleString = gutterStylePrefix + author.color + gutterStylePostfix; - var rule = "." + className + "{" + styleString + "}"; - addStyleRule(rule); - var gutter = $('<div>', { - class: 'authorship-gutter ' + className, - title: author.name - }); - editor.setGutterMarker(line, "authorship-gutters", gutter[0]); - } - } else { - editor.setGutterMarker(line, "authorship-gutters", null); - } - if (currMark && currMark.textmarkers.length > 0) { - for (var i = 0; i < currMark.textmarkers.length; i++) { - var textMarker = currMark.textmarkers[i]; - if (textMarker.userid != currMark.gutter.userid) { - addTextMarkers.push(textMarker); - } - } - } + var styleString = gutterStylePrefix + author.color + gutterStylePostfix + var rule = '.' + className + '{' + styleString + '}' + addStyleRule(rule) + var gutter = $('<div>', { + class: 'authorship-gutter ' + className, + title: author.name + }) + editor.setGutterMarker(line, 'authorship-gutters', gutter[0]) + } + } else { + editor.setGutterMarker(line, 'authorship-gutters', null) + } + if (currMark && currMark.textmarkers.length > 0) { + for (var i = 0; i < currMark.textmarkers.length; i++) { + let textMarker = currMark.textmarkers[i] + if (textMarker.userid !== currMark.gutter.userid) { + addTextMarkers.push(textMarker) + } + } + } } editor.on('update', function () { - $('.authorship-gutter:not([data-original-title])').tooltip({ - container: '.CodeMirror-lines', - placement: 'right', - delay: { "show": 500, "hide": 100 } - }); - $('.authorship-inline:not([data-original-title])').tooltip({ - container: '.CodeMirror-lines', - placement: 'bottom', - delay: { "show": 500, "hide": 100 } - }); + $('.authorship-gutter:not([data-original-title])').tooltip({ + container: '.CodeMirror-lines', + placement: 'right', + delay: { 'show': 500, 'hide': 100 } + }) + $('.authorship-inline:not([data-original-title])').tooltip({ + container: '.CodeMirror-lines', + placement: 'bottom', + delay: { 'show': 500, 'hide': 100 } + }) // clear tooltip which described element has been removed - $('[id^="tooltip"]').each(function (index, element) { - var $ele = $(element); - if ($('[aria-describedby="' + $ele.attr('id') + '"]').length <= 0) $ele.remove(); - }); -}); + $('[id^="tooltip"]').each(function (index, element) { + var $ele = $(element) + if ($('[aria-describedby="' + $ele.attr('id') + '"]').length <= 0) $ele.remove() + }) +}) socket.on('check', function (data) { - //console.log(data); - updateInfo(data); -}); + // console.log(data); + updateInfo(data) +}) socket.on('permission', function (data) { - updatePermission(data.permission); -}); -var docmaxlength = null; -var permission = null; + updatePermission(data.permission) +}) +var docmaxlength = null +var permission = null socket.on('refresh', function (data) { - //console.log(data); - docmaxlength = data.docmaxlength; - editor.setOption("maxLength", docmaxlength); - updateInfo(data); - updatePermission(data.permission); - if (!loaded) { + // console.log(data); + docmaxlength = data.docmaxlength + editor.setOption('maxLength', docmaxlength) + updateInfo(data) + updatePermission(data.permission) + if (!window.loaded) { // auto change mode if no content detected - var nocontent = editor.getValue().length <= 0; - if (nocontent) { - if (visibleXS) - currentMode = modeType.edit; - else - currentMode = modeType.both; - } + var nocontent = editor.getValue().length <= 0 + if (nocontent) { + if (window.visibleXS) { window.currentMode = modeType.edit } else { window.currentMode = modeType.both } + } // parse mode from url - if (window.location.search.length > 0) { - var urlMode = modeType[window.location.search.substr(1)]; - if (urlMode) currentMode = urlMode; - } - changeMode(currentMode); - if (nocontent && !visibleXS) { - editor.focus(); - editor.refresh(); - } - updateViewInner(); // bring up view rendering earlier - updateHistory(); //update history whether have content or not - loaded = true; - emitUserStatus(); //send first user status - updateOnlineStatus(); //update first online status - setTimeout(function () { - //work around editor not refresh or doc not fully loaded - windowResizeInner(); - //work around might not scroll to hash - scrollToHash(); - }, 1); - } - if (editor.getOption('readOnly')) - editor.setOption('readOnly', false); -}); - -var EditorClient = ot.EditorClient; -var SocketIOAdapter = ot.SocketIOAdapter; -var CodeMirrorAdapter = ot.CodeMirrorAdapter; -var cmClient = null; -var synchronized_ = null; - -function havePendingOperation() { - return (cmClient && cmClient.state && cmClient.state.hasOwnProperty('outstanding')) ? true : false; -} - -socket.on('doc', function (obj) { - var body = obj.str; - var bodyMismatch = editor.getValue() !== body; - var setDoc = !cmClient || (cmClient && (cmClient.revision === -1 || (cmClient.revision !== obj.revision && !havePendingOperation()))) || obj.force; + if (window.location.search.length > 0) { + var urlMode = modeType[window.location.search.substr(1)] + if (urlMode) window.currentMode = urlMode + } + changeMode(window.currentMode) + if (nocontent && !window.visibleXS) { + editor.focus() + editor.refresh() + } + updateViewInner() // bring up view rendering earlier + updateHistory() // update history whether have content or not + window.loaded = true + emitUserStatus() // send first user status + updateOnlineStatus() // update first online status + setTimeout(function () { + // work around editor not refresh or doc not fully loaded + windowResizeInner() + // work around might not scroll to hash + scrollToHash() + }, 1) + } + if (editor.getOption('readOnly')) { editor.setOption('readOnly', false) } +}) - saveInfo(); - if (setDoc && bodyMismatch) { - if (cmClient) cmClient.editorAdapter.ignoreNextChange = true; - if (body) editor.setValue(body); - else editor.setValue(""); - } +var EditorClient = ot.EditorClient +var SocketIOAdapter = ot.SocketIOAdapter +var CodeMirrorAdapter = ot.CodeMirrorAdapter +var cmClient = null +var synchronized_ = null - if (!loaded) { - editor.clearHistory(); - ui.spinner.hide(); - ui.content.fadeIn(); - } else { - //if current doc is equal to the doc before disconnect - if (setDoc && bodyMismatch) editor.clearHistory(); - else if (lastInfo.history) editor.setHistory(lastInfo.history); - lastInfo.history = null; - } +function havePendingOperation () { + return !!((cmClient && cmClient.state && cmClient.state.hasOwnProperty('outstanding'))) +} - if (!cmClient) { - cmClient = window.cmClient = new EditorClient( +socket.on('doc', function (obj) { + var body = obj.str + var bodyMismatch = editor.getValue() !== body + var setDoc = !cmClient || (cmClient && (cmClient.revision === -1 || (cmClient.revision !== obj.revision && !havePendingOperation()))) || obj.force + + saveInfo() + if (setDoc && bodyMismatch) { + if (cmClient) cmClient.editorAdapter.ignoreNextChange = true + if (body) editor.setValue(body) + else editor.setValue('') + } + + if (!window.loaded) { + editor.clearHistory() + ui.spinner.hide() + ui.content.fadeIn() + } else { + // if current doc is equal to the doc before disconnect + if (setDoc && bodyMismatch) editor.clearHistory() + else if (window.lastInfo.history) editor.setHistory(window.lastInfo.history) + window.lastInfo.history = null + } + + if (!cmClient) { + cmClient = window.cmClient = new EditorClient( obj.revision, obj.clients, new SocketIOAdapter(socket), new CodeMirrorAdapter(editor) - ); - synchronized_ = cmClient.state; - } else if (setDoc) { - if (bodyMismatch) { - cmClient.undoManager.undoStack.length = 0; - cmClient.undoManager.redoStack.length = 0; - } - cmClient.revision = obj.revision; - cmClient.setState(synchronized_); - cmClient.initializeClientList(); - cmClient.initializeClients(obj.clients); - } else if (havePendingOperation()) { - cmClient.serverReconnect(); - } - - if (setDoc && bodyMismatch) { - isDirty = true; - updateView(); - } - - restoreInfo(); -}); + ) + synchronized_ = cmClient.state + } else if (setDoc) { + if (bodyMismatch) { + cmClient.undoManager.undoStack.length = 0 + cmClient.undoManager.redoStack.length = 0 + } + cmClient.revision = obj.revision + cmClient.setState(synchronized_) + cmClient.initializeClientList() + cmClient.initializeClients(obj.clients) + } else if (havePendingOperation()) { + cmClient.serverReconnect() + } + + if (setDoc && bodyMismatch) { + window.isDirty = true + updateView() + } + + restoreInfo() +}) socket.on('ack', function () { - isDirty = true; - updateView(); -}); + window.isDirty = true + updateView() +}) socket.on('operation', function () { - isDirty = true; - updateView(); -}); + window.isDirty = true + updateView() +}) socket.on('online users', function (data) { - if (debug) - console.debug(data); - onlineUsers = data.users; - updateOnlineStatus(); - $('.CodeMirror-other-cursors').children().each(function (key, value) { - var found = false; - for (var i = 0; i < data.users.length; i++) { - var user = data.users[i]; - if ($(this).attr('id') == user.id) - found = true; - } - if (!found) - $(this).stop(true).fadeOut("normal", function () { - $(this).remove(); - }); - }); + if (debug) { console.debug(data) } + window.onlineUsers = data.users + updateOnlineStatus() + $('.CodeMirror-other-cursors').children().each(function (key, value) { + var found = false for (var i = 0; i < data.users.length; i++) { - var user = data.users[i]; - if (user.id != socket.id) - buildCursor(user); - else - personalInfo = user; - } -}); + var user = data.users[i] + if ($(this).attr('id') === user.id) { found = true } + } + if (!found) { + $(this).stop(true).fadeOut('normal', function () { + $(this).remove() + }) + } + }) + for (var i = 0; i < data.users.length; i++) { + var user = data.users[i] + if (user.id !== socket.id) { buildCursor(user) } else { window.personalInfo = user } + } +}) socket.on('user status', function (data) { - if (debug) - console.debug(data); - for (var i = 0; i < onlineUsers.length; i++) { - if (onlineUsers[i].id == data.id) { - onlineUsers[i] = data; - } - } - updateOnlineStatus(); - if (data.id != socket.id) - buildCursor(data); -}); + if (debug) { console.debug(data) } + for (var i = 0; i < window.onlineUsers.length; i++) { + if (window.onlineUsers[i].id === data.id) { + window.onlineUsers[i] = data + } + } + updateOnlineStatus() + if (data.id !== socket.id) { buildCursor(data) } +}) socket.on('cursor focus', function (data) { - if (debug) - console.debug(data); - for (var i = 0; i < onlineUsers.length; i++) { - if (onlineUsers[i].id == data.id) { - onlineUsers[i].cursor = data.cursor; - } - } - if (data.id != socket.id) - buildCursor(data); - //force show - var cursor = $('div[data-clientid="' + data.id + '"]'); - if (cursor.length > 0) { - cursor.stop(true).fadeIn(); - } -}); + if (debug) { console.debug(data) } + for (var i = 0; i < window.onlineUsers.length; i++) { + if (window.onlineUsers[i].id === data.id) { + window.onlineUsers[i].cursor = data.cursor + } + } + if (data.id !== socket.id) { buildCursor(data) } + // force show + var cursor = $('div[data-clientid="' + data.id + '"]') + if (cursor.length > 0) { + cursor.stop(true).fadeIn() + } +}) socket.on('cursor activity', function (data) { - if (debug) - console.debug(data); - for (var i = 0; i < onlineUsers.length; i++) { - if (onlineUsers[i].id == data.id) { - onlineUsers[i].cursor = data.cursor; - } + if (debug) { console.debug(data) } + for (var i = 0; i < window.onlineUsers.length; i++) { + if (window.onlineUsers[i].id === data.id) { + window.onlineUsers[i].cursor = data.cursor } - if (data.id != socket.id) - buildCursor(data); -}); + } + if (data.id !== socket.id) { buildCursor(data) } +}) socket.on('cursor blur', function (data) { - if (debug) - console.debug(data); - for (var i = 0; i < onlineUsers.length; i++) { - if (onlineUsers[i].id == data.id) { - onlineUsers[i].cursor = null; - } - } - if (data.id != socket.id) - buildCursor(data); - //force hide - var cursor = $('div[data-clientid="' + data.id + '"]'); - if (cursor.length > 0) { - cursor.stop(true).fadeOut(); - } -}); + if (debug) { console.debug(data) } + for (var i = 0; i < window.onlineUsers.length; i++) { + if (window.onlineUsers[i].id === data.id) { + window.onlineUsers[i].cursor = null + } + } + if (data.id !== socket.id) { buildCursor(data) } + // force hide + var cursor = $('div[data-clientid="' + data.id + '"]') + if (cursor.length > 0) { + cursor.stop(true).fadeOut() + } +}) var options = { - valueNames: ['id', 'name'], - item: '<li class="ui-user-item">\ - <span class="id" style="display:none;"></span>\ - <a href="#">\ - <span class="pull-left"><i class="ui-user-icon"></i></span><span class="ui-user-name name"></span><span class="pull-right"><i class="fa fa-circle ui-user-status"></i></span>\ - </a>\ - </li>' -}; -var onlineUserList = new List('online-user-list', options); -var shortOnlineUserList = new List('short-online-user-list', options); - -function updateOnlineStatus() { - if (!loaded || !socket.connected) return; - var _onlineUsers = deduplicateOnlineUsers(onlineUsers); - showStatus(statusType.online, _onlineUsers.length); - var items = onlineUserList.items; - //update or remove current list items - for (var i = 0; i < items.length; i++) { - var found = false; - var foundindex = null; - for (var j = 0; j < _onlineUsers.length; j++) { - if (items[i].values().id == _onlineUsers[j].id) { - foundindex = j; - found = true; - break; - } - } - var id = items[i].values().id; - if (found) { - onlineUserList.get('id', id)[0].values(_onlineUsers[foundindex]); - shortOnlineUserList.get('id', id)[0].values(_onlineUsers[foundindex]); - } else { - onlineUserList.remove('id', id); - shortOnlineUserList.remove('id', id); - } - } - //add not in list items - for (var i = 0; i < _onlineUsers.length; i++) { - var found = false; - for (var j = 0; j < items.length; j++) { - if (items[j].values().id == _onlineUsers[i].id) { - found = true; - break; - } - } - if (!found) { - onlineUserList.add(_onlineUsers[i]); - shortOnlineUserList.add(_onlineUsers[i]); - } + valueNames: ['id', 'name'], + item: '<li class="ui-user-item">' + + '<span class="id" style="display:none;"></span>' + + '<a href="#">' + + '<span class="pull-left"><i class="ui-user-icon"></i></span><span class="ui-user-name name"></span><span class="pull-right"><i class="fa fa-circle ui-user-status"></i></span>' + + '</a>' + + '</li>' +} +var onlineUserList = new List('online-user-list', options) +var shortOnlineUserList = new List('short-online-user-list', options) + +function updateOnlineStatus () { + if (!window.loaded || !socket.connected) return + var _onlineUsers = deduplicateOnlineUsers(window.onlineUsers) + showStatus(statusType.online, _onlineUsers.length) + var items = onlineUserList.items + // update or remove current list items + for (let i = 0; i < items.length; i++) { + let found = false + let foundindex = null + for (let j = 0; j < _onlineUsers.length; j++) { + if (items[i].values().id === _onlineUsers[j].id) { + foundindex = j + found = true + break + } + } + let id = items[i].values().id + if (found) { + onlineUserList.get('id', id)[0].values(_onlineUsers[foundindex]) + shortOnlineUserList.get('id', id)[0].values(_onlineUsers[foundindex]) + } else { + onlineUserList.remove('id', id) + shortOnlineUserList.remove('id', id) + } + } + // add not in list items + for (let i = 0; i < _onlineUsers.length; i++) { + let found = false + for (let j = 0; j < items.length; j++) { + if (items[j].values().id === _onlineUsers[i].id) { + found = true + break + } + } + if (!found) { + onlineUserList.add(_onlineUsers[i]) + shortOnlineUserList.add(_onlineUsers[i]) + } + } + // sorting + sortOnlineUserList(onlineUserList) + sortOnlineUserList(shortOnlineUserList) + // render list items + renderUserStatusList(onlineUserList) + renderUserStatusList(shortOnlineUserList) +} + +function sortOnlineUserList (list) { + // sort order by isSelf, login state, idle state, alphabet name, color brightness + list.sort('', { + sortFunction: function (a, b) { + var usera = a.values() + var userb = b.values() + var useraIsSelf = (usera.id === window.personalInfo.id || (usera.login && usera.userid === window.personalInfo.userid)) + var userbIsSelf = (userb.id === window.personalInfo.id || (userb.login && userb.userid === window.personalInfo.userid)) + if (useraIsSelf && !userbIsSelf) { + return -1 + } else if (!useraIsSelf && userbIsSelf) { + return 1 + } else { + if (usera.login && !userb.login) { return -1 } else if (!usera.login && userb.login) { return 1 } else { + if (!usera.idle && userb.idle) { return -1 } else if (usera.idle && !userb.idle) { return 1 } else { + if (usera.name && userb.name && usera.name.toLowerCase() < userb.name.toLowerCase()) { + return -1 + } else if (usera.name && userb.name && usera.name.toLowerCase() > userb.name.toLowerCase()) { return 1 } else { if (usera.color && userb.color && usera.color.toLowerCase() < userb.color.toLowerCase()) { return -1 } else if (usera.color && userb.color && usera.color.toLowerCase() > userb.color.toLowerCase()) { return 1 } else { return 0 } } + } + } + } + } + }) +} + +function renderUserStatusList (list) { + var items = list.items + for (var j = 0; j < items.length; j++) { + var item = items[j] + var userstatus = $(item.elm).find('.ui-user-status') + var usericon = $(item.elm).find('.ui-user-icon') + if (item.values().login && item.values().photo) { + usericon.css('background-image', 'url(' + item.values().photo + ')') + // add 1px more to right, make it feel aligned + usericon.css('margin-right', '6px') + $(item.elm).css('border-left', '4px solid ' + item.values().color) + usericon.css('margin-left', '-4px') + } else { + usericon.css('background-color', item.values().color) } - //sorting - sortOnlineUserList(onlineUserList); - sortOnlineUserList(shortOnlineUserList); - //render list items - renderUserStatusList(onlineUserList); - renderUserStatusList(shortOnlineUserList); -} - -function sortOnlineUserList(list) { - //sort order by isSelf, login state, idle state, alphabet name, color brightness - list.sort('', { - sortFunction: function (a, b) { - var usera = a.values(); - var userb = b.values(); - var useraIsSelf = (usera.id == personalInfo.id || (usera.login && usera.userid == personalInfo.userid)); - var userbIsSelf = (userb.id == personalInfo.id || (userb.login && userb.userid == personalInfo.userid)); - if (useraIsSelf && !userbIsSelf) { - return -1; - } else if (!useraIsSelf && userbIsSelf) { - return 1; - } else { - if (usera.login && !userb.login) - return -1; - else if (!usera.login && userb.login) - return 1; - else { - if (!usera.idle && userb.idle) - return -1; - else if (usera.idle && !userb.idle) - return 1; - else { - if (usera.name && userb.name && usera.name.toLowerCase() < userb.name.toLowerCase()) { - return -1; - } else if (usera.name && userb.name && usera.name.toLowerCase() > userb.name.toLowerCase()) { - return 1; - } else { - if (usera.color && userb.color && usera.color.toLowerCase() < userb.color.toLowerCase()) - return -1; - else if (usera.color && userb.color && usera.color.toLowerCase() > userb.color.toLowerCase()) - return 1; - else - return 0; - } - } - } - } - } - }); -} - -function renderUserStatusList(list) { - var items = list.items; - for (var j = 0; j < items.length; j++) { - var item = items[j]; - var userstatus = $(item.elm).find('.ui-user-status'); - var usericon = $(item.elm).find('.ui-user-icon'); - if (item.values().login && item.values().photo) { - usericon.css('background-image', 'url(' + item.values().photo + ')'); - //add 1px more to right, make it feel aligned - usericon.css('margin-right', '6px'); - $(item.elm).css('border-left', '4px solid ' + item.values().color); - usericon.css('margin-left', '-4px'); - } else { - usericon.css('background-color', item.values().color); - } - userstatus.removeClass('ui-user-status-offline ui-user-status-online ui-user-status-idle'); - if (item.values().idle) - userstatus.addClass('ui-user-status-idle'); - else - userstatus.addClass('ui-user-status-online'); - } -} - -function deduplicateOnlineUsers(list) { - var _onlineUsers = []; - for (var i = 0; i < list.length; i++) { - var user = $.extend({}, list[i]); - if (!user.userid) - _onlineUsers.push(user); - else { - var found = false; - for (var j = 0; j < _onlineUsers.length; j++) { - if (_onlineUsers[j].userid == user.userid) { - //keep self color when login - if (user.id == personalInfo.id) { - _onlineUsers[j].color = user.color; - } - //keep idle state if any of self client not idle - if (!user.idle) { - _onlineUsers[j].idle = user.idle; - _onlineUsers[j].color = user.color; - } - found = true; - break; - } - } - if (!found) - _onlineUsers.push(user); + userstatus.removeClass('ui-user-status-offline ui-user-status-online ui-user-status-idle') + if (item.values().idle) { userstatus.addClass('ui-user-status-idle') } else { userstatus.addClass('ui-user-status-online') } + } +} + +function deduplicateOnlineUsers (list) { + var _onlineUsers = [] + for (var i = 0; i < list.length; i++) { + var user = $.extend({}, list[i]) + if (!user.userid) { _onlineUsers.push(user) } else { + var found = false + for (var j = 0; j < _onlineUsers.length; j++) { + if (_onlineUsers[j].userid === user.userid) { + // keep self color when login + if (user.id === window.personalInfo.id) { + _onlineUsers[j].color = user.color + } + // keep idle state if any of self client not idle + if (!user.idle) { + _onlineUsers[j].idle = user.idle + _onlineUsers[j].color = user.color + } + found = true + break } + } + if (!found) { _onlineUsers.push(user) } } - return _onlineUsers; + } + return _onlineUsers } -var userStatusCache = null; +var userStatusCache = null -function emitUserStatus(force) { - if (!loaded) return; - var type = null; - if (visibleXS) - type = 'xs'; - else if (visibleSM) - type = 'sm'; - else if (visibleMD) - type = 'md'; - else if (visibleLG) - type = 'lg'; +function emitUserStatus (force) { + if (!window.loaded) return + var type = null + if (window.visibleXS) { type = 'xs' } else if (window.visibleSM) { type = 'sm' } else if (window.visibleMD) { type = 'md' } else if (window.visibleLG) { type = 'lg' } - personalInfo['idle'] = idle.isAway; - personalInfo['type'] = type; + window.personalInfo['idle'] = idle.isAway + window.personalInfo['type'] = type - for (var i = 0; i < onlineUsers.length; i++) { - if (onlineUsers[i].id == personalInfo.id) { - onlineUsers[i] = personalInfo; - } + for (var i = 0; i < window.onlineUsers.length; i++) { + if (window.onlineUsers[i].id === window.personalInfo.id) { + window.onlineUsers[i] = window.personalInfo } + } - var userStatus = { - idle: idle.isAway, - type: type - }; + var userStatus = { + idle: idle.isAway, + type: type + } - if (force || JSON.stringify(userStatus) != JSON.stringify(userStatusCache)) { - socket.emit('user status', userStatus); - userStatusCache = userStatus; - } + if (force || JSON.stringify(userStatus) !== JSON.stringify(userStatusCache)) { + socket.emit('user status', userStatus) + userStatusCache = userStatus + } } -function checkCursorTag(coord, ele) { - if (!ele) return; // return if element not exists +function checkCursorTag (coord, ele) { + if (!ele) return // return if element not exists // set margin - var tagRightMargin = 0; - var tagBottomMargin = 2; + var tagRightMargin = 0 + var tagBottomMargin = 2 // use sizer to get the real doc size (won't count status bar and gutters) - var docWidth = ui.area.codemirrorSizer.width(); - var docHeight = ui.area.codemirrorSizer.height(); + var docWidth = ui.area.codemirrorSizer.width() // get editor size (status bar not count in) - var editorWidth = ui.area.codemirror.width(); - var editorHeight = ui.area.codemirror.height(); + var editorHeight = ui.area.codemirror.height() // get element size - var width = ele.outerWidth(); - var height = ele.outerHeight(); - var padding = (ele.outerWidth() - ele.width()) / 2; + var width = ele.outerWidth() + var height = ele.outerHeight() + var padding = (ele.outerWidth() - ele.width()) / 2 // get coord position - var left = coord.left; - var top = coord.top; + var left = coord.left + var top = coord.top // get doc top offset (to workaround with viewport) - var docTopOffset = ui.area.codemirrorSizerInner.position().top; + var docTopOffset = ui.area.codemirrorSizerInner.position().top // set offset - var offsetLeft = -3; - var offsetTop = defaultTextHeight; + var offsetLeft = -3 + var offsetTop = defaultTextHeight // only do when have width and height - if (width > 0 && height > 0) { + if (width > 0 && height > 0) { // flip x when element right bound larger than doc width - if (left + width + offsetLeft + tagRightMargin > docWidth) { - offsetLeft = -(width + tagRightMargin) + padding + offsetLeft; - } + if (left + width + offsetLeft + tagRightMargin > docWidth) { + offsetLeft = -(width + tagRightMargin) + padding + offsetLeft + } // flip y when element bottom bound larger than doc height // and element top position is larger than element height - if (top + docTopOffset + height + offsetTop + tagBottomMargin > Math.max(editor.doc.height, editorHeight) && top + docTopOffset > height + tagBottomMargin) { - offsetTop = -(height); - } + if (top + docTopOffset + height + offsetTop + tagBottomMargin > Math.max(editor.doc.height, editorHeight) && top + docTopOffset > height + tagBottomMargin) { + offsetTop = -(height) } + } // set position - ele[0].style.left = offsetLeft + 'px'; - ele[0].style.top = offsetTop + 'px'; -} - -function buildCursor(user) { - if (currentMode == modeType.view) return; - if (!user.cursor) return; - var coord = editor.charCoords(user.cursor, 'windows'); - coord.left = coord.left < 4 ? 4 : coord.left; - coord.top = coord.top < 0 ? 0 : coord.top; - var iconClass = 'fa-user'; - switch (user.type) { - case 'xs': - iconClass = 'fa-mobile'; - break; - case 'sm': - iconClass = 'fa-tablet'; - break; - case 'md': - iconClass = 'fa-desktop'; - break; - case 'lg': - iconClass = 'fa-desktop'; - break; - } - if ($('.CodeMirror-other-cursors').length <= 0) { - $("<div class='CodeMirror-other-cursors'>").insertAfter('.CodeMirror-cursors'); - } - if ($('div[data-clientid="' + user.id + '"]').length <= 0) { - var cursor = $('<div data-clientid="' + user.id + '" class="CodeMirror-other-cursor" style="display:none;"></div>'); - cursor.attr('data-line', user.cursor.line); - cursor.attr('data-ch', user.cursor.ch); - cursor.attr('data-offset-left', 0); - cursor.attr('data-offset-top', 0); - - var cursorbar = $('<div class="cursorbar"> </div>'); - cursorbar[0].style.height = defaultTextHeight + 'px'; - cursorbar[0].style.borderLeft = '2px solid ' + user.color; - - var icon = '<i class="fa ' + iconClass + '"></i>'; - - var cursortag = $('<div class="cursortag">' + icon + ' <span class="name">' + user.name + '</span></div>'); - //cursortag[0].style.background = color; - cursortag[0].style.color = user.color; - - cursor.attr('data-mode', 'hover'); - cursortag.delay(2000).fadeOut("fast"); - cursor.hover( + ele[0].style.left = offsetLeft + 'px' + ele[0].style.top = offsetTop + 'px' +} + +function buildCursor (user) { + if (window.currentMode === modeType.view) return + if (!user.cursor) return + var coord = editor.charCoords(user.cursor, 'windows') + coord.left = coord.left < 4 ? 4 : coord.left + coord.top = coord.top < 0 ? 0 : coord.top + var iconClass = 'fa-user' + switch (user.type) { + case 'xs': + iconClass = 'fa-mobile' + break + case 'sm': + iconClass = 'fa-tablet' + break + case 'md': + iconClass = 'fa-desktop' + break + case 'lg': + iconClass = 'fa-desktop' + break + } + if ($('.CodeMirror-other-cursors').length <= 0) { + $("<div class='CodeMirror-other-cursors'>").insertAfter('.CodeMirror-cursors') + } + if ($('div[data-clientid="' + user.id + '"]').length <= 0) { + let cursor = $('<div data-clientid="' + user.id + '" class="CodeMirror-other-cursor" style="display:none;"></div>') + cursor.attr('data-line', user.cursor.line) + cursor.attr('data-ch', user.cursor.ch) + cursor.attr('data-offset-left', 0) + cursor.attr('data-offset-top', 0) + + let cursorbar = $('<div class="cursorbar"> </div>') + cursorbar[0].style.height = defaultTextHeight + 'px' + cursorbar[0].style.borderLeft = '2px solid ' + user.color + + var icon = '<i class="fa ' + iconClass + '"></i>' + + let cursortag = $('<div class="cursortag">' + icon + ' <span class="name">' + user.name + '</span></div>') + // cursortag[0].style.background = color; + cursortag[0].style.color = user.color + + cursor.attr('data-mode', 'hover') + cursortag.delay(2000).fadeOut('fast') + cursor.hover( function () { - if (cursor.attr('data-mode') == 'hover') - cursortag.stop(true).fadeIn("fast"); + if (cursor.attr('data-mode') === 'hover') { cursortag.stop(true).fadeIn('fast') } }, function () { - if (cursor.attr('data-mode') == 'hover') - cursortag.stop(true).fadeOut("fast"); - }); - - function switchMode(ele) { - if (ele.attr('data-mode') == 'state') - ele.attr('data-mode', 'hover'); - else if (ele.attr('data-mode') == 'hover') - ele.attr('data-mode', 'state'); - } - - function switchTag(ele) { - if (ele.css('display') === 'none') - ele.stop(true).fadeIn("fast"); - else - ele.stop(true).fadeOut("fast"); - } - var hideCursorTagDelay = 2000; - var hideCursorTagTimer = null; - - function hideCursorTag() { - if (cursor.attr('data-mode') == 'hover') - cursortag.fadeOut("fast"); - } - cursor.on('touchstart', function (e) { - var display = cursortag.css('display'); - cursortag.stop(true).fadeIn("fast"); - clearTimeout(hideCursorTagTimer); - hideCursorTagTimer = setTimeout(hideCursorTag, hideCursorTagDelay); - if (display === 'none') { - e.preventDefault(); - e.stopPropagation(); - } - }); - cursortag.on('mousedown touchstart', function (e) { - if (cursor.attr('data-mode') == 'state') - switchTag(cursortag); - switchMode(cursor); - e.preventDefault(); - e.stopPropagation(); - }); - - cursor.append(cursorbar); - cursor.append(cursortag); - - cursor[0].style.left = coord.left + 'px'; - cursor[0].style.top = coord.top + 'px'; - $('.CodeMirror-other-cursors').append(cursor); - - if (!user.idle) - cursor.stop(true).fadeIn(); - - checkCursorTag(coord, cursortag); - } else { - var cursor = $('div[data-clientid="' + user.id + '"]'); - var lineDiff = Math.abs(cursor.attr('data-line') - user.cursor.line); - cursor.attr('data-line', user.cursor.line); - cursor.attr('data-ch', user.cursor.ch); - - var cursorbar = cursor.find('.cursorbar'); - cursorbar[0].style.height = defaultTextHeight + 'px'; - cursorbar[0].style.borderLeft = '2px solid ' + user.color; - - var cursortag = cursor.find('.cursortag'); - cursortag.find('i').removeClass().addClass('fa').addClass(iconClass); - cursortag.find(".name").text(user.name); - - if (cursor.css('display') === 'none') { - cursor[0].style.left = coord.left + 'px'; - cursor[0].style.top = coord.top + 'px'; - } else { - cursor.animate({ - "left": coord.left, - "top": coord.top - }, { - duration: cursorAnimatePeriod, - queue: false - }); - } + if (cursor.attr('data-mode') === 'hover') { cursortag.stop(true).fadeOut('fast') } + }) - if (user.idle && cursor.css('display') !== 'none') - cursor.stop(true).fadeOut(); - else if (!user.idle && cursor.css('display') === 'none') - cursor.stop(true).fadeIn(); + var hideCursorTagDelay = 2000 + var hideCursorTagTimer = null - checkCursorTag(coord, cursortag); + var switchMode = function (ele) { + if (ele.attr('data-mode') === 'state') { ele.attr('data-mode', 'hover') } else if (ele.attr('data-mode') === 'hover') { ele.attr('data-mode', 'state') } } -} -//editor actions -function removeNullByte(cm, change) { - var str = change.text.join("\n"); - if (/\u0000/g.test(str) && change.update) { - change.update(change.from, change.to, str.replace(/\u0000/g, "").split("\n")); + var switchTag = function (ele) { + if (ele.css('display') === 'none') { ele.stop(true).fadeIn('fast') } else { ele.stop(true).fadeOut('fast') } } -} -function enforceMaxLength(cm, change) { - var maxLength = cm.getOption("maxLength"); - if (maxLength && change.update) { - var str = change.text.join("\n"); - var delta = str.length - (cm.indexFromPos(change.to) - cm.indexFromPos(change.from)); - if (delta <= 0) { - return false; - } - delta = cm.getValue().length + delta - maxLength; - if (delta > 0) { - str = str.substr(0, str.length - delta); - change.update(change.from, change.to, str.split("\n")); - return true; - } + + var hideCursorTag = function () { + if (cursor.attr('data-mode') === 'hover') { cursortag.fadeOut('fast') } } - return false; -} -var ignoreEmitEvents = ['setValue', 'ignoreHistory']; -editor.on('beforeChange', function (cm, change) { - if (debug) - console.debug(change); - removeNullByte(cm, change); - if (enforceMaxLength(cm, change)) { - $('.limit-modal').modal('show'); - } - var isIgnoreEmitEvent = (ignoreEmitEvents.indexOf(change.origin) != -1); - if (!isIgnoreEmitEvent) { - if (!havePermission()) { - change.canceled = true; - switch (permission) { - case "editable": - $('.signin-modal').modal('show'); - break; - case "locked": - case "private": - $('.locked-modal').modal('show'); - break; - } - } + cursor.on('touchstart', function (e) { + var display = cursortag.css('display') + cursortag.stop(true).fadeIn('fast') + clearTimeout(hideCursorTagTimer) + hideCursorTagTimer = setTimeout(hideCursorTag, hideCursorTagDelay) + if (display === 'none') { + e.preventDefault() + e.stopPropagation() + } + }) + cursortag.on('mousedown touchstart', function (e) { + if (cursor.attr('data-mode') === 'state') { switchTag(cursortag) } + switchMode(cursor) + e.preventDefault() + e.stopPropagation() + }) + + cursor.append(cursorbar) + cursor.append(cursortag) + + cursor[0].style.left = coord.left + 'px' + cursor[0].style.top = coord.top + 'px' + $('.CodeMirror-other-cursors').append(cursor) + + if (!user.idle) { cursor.stop(true).fadeIn() } + + checkCursorTag(coord, cursortag) + } else { + let cursor = $('div[data-clientid="' + user.id + '"]') + cursor.attr('data-line', user.cursor.line) + cursor.attr('data-ch', user.cursor.ch) + + let cursorbar = cursor.find('.cursorbar') + cursorbar[0].style.height = defaultTextHeight + 'px' + cursorbar[0].style.borderLeft = '2px solid ' + user.color + + let cursortag = cursor.find('.cursortag') + cursortag.find('i').removeClass().addClass('fa').addClass(iconClass) + cursortag.find('.name').text(user.name) + + if (cursor.css('display') === 'none') { + cursor[0].style.left = coord.left + 'px' + cursor[0].style.top = coord.top + 'px' } else { - if (change.origin == 'ignoreHistory') { - setHaveUnreadChanges(true); - updateTitleReminder(); - } - } - if (cmClient && !socket.connected) - cmClient.editorAdapter.ignoreNextChange = true; -}); + cursor.animate({ + 'left': coord.left, + 'top': coord.top + }, { + duration: cursorAnimatePeriod, + queue: false + }) + } + + if (user.idle && cursor.css('display') !== 'none') { cursor.stop(true).fadeOut() } else if (!user.idle && cursor.css('display') === 'none') { cursor.stop(true).fadeIn() } + + checkCursorTag(coord, cursortag) + } +} + +// editor actions +function removeNullByte (cm, change) { + var str = change.text.join('\n') + if (/\u0000/g.test(str) && change.update) { + change.update(change.from, change.to, str.replace(/\u0000/g, '').split('\n')) + } +} +function enforceMaxLength (cm, change) { + var maxLength = cm.getOption('maxLength') + if (maxLength && change.update) { + var str = change.text.join('\n') + var delta = str.length - (cm.indexFromPos(change.to) - cm.indexFromPos(change.from)) + if (delta <= 0) { + return false + } + delta = cm.getValue().length + delta - maxLength + if (delta > 0) { + str = str.substr(0, str.length - delta) + change.update(change.from, change.to, str.split('\n')) + return true + } + } + return false +} +var ignoreEmitEvents = ['setValue', 'ignoreHistory'] +editor.on('beforeChange', function (cm, change) { + if (debug) { console.debug(change) } + removeNullByte(cm, change) + if (enforceMaxLength(cm, change)) { + $('.limit-modal').modal('show') + } + var isIgnoreEmitEvent = (ignoreEmitEvents.indexOf(change.origin) !== -1) + if (!isIgnoreEmitEvent) { + if (!havePermission()) { + change.canceled = true + switch (permission) { + case 'editable': + $('.signin-modal').modal('show') + break + case 'locked': + case 'private': + $('.locked-modal').modal('show') + break + } + } + } else { + if (change.origin === 'ignoreHistory') { + setHaveUnreadChanges(true) + updateTitleReminder() + } + } + if (cmClient && !socket.connected) { cmClient.editorAdapter.ignoreNextChange = true } +}) editor.on('cut', function () { - //na -}); + // na +}) editor.on('paste', function () { - //na -}); + // na +}) editor.on('changes', function (cm, changes) { - updateHistory(); - var docLength = editor.getValue().length; - //workaround for big documents - var newViewportMargin = 20; - if (docLength > 20000) { - newViewportMargin = 1; - } else if (docLength > 10000) { - newViewportMargin = 10; - } else if (docLength > 5000) { - newViewportMargin = 15; - } - if (newViewportMargin != viewportMargin) { - viewportMargin = newViewportMargin; - windowResize(); - } - checkEditorScrollbar(); - if (ui.area.codemirrorScroll[0].scrollHeight > ui.area.view[0].scrollHeight && editorHasFocus()) { - postUpdateEvent = function () { - syncScrollToView(); - postUpdateEvent = null; - }; - } -}); + updateHistory() + var docLength = editor.getValue().length + // workaround for big documents + var newViewportMargin = 20 + if (docLength > 20000) { + newViewportMargin = 1 + } else if (docLength > 10000) { + newViewportMargin = 10 + } else if (docLength > 5000) { + newViewportMargin = 15 + } + if (newViewportMargin !== viewportMargin) { + viewportMargin = newViewportMargin + windowResize() + } + checkEditorScrollbar() + if (ui.area.codemirrorScroll[0].scrollHeight > ui.area.view[0].scrollHeight && editorHasFocus()) { + postUpdateEvent = function () { + syncScrollToView() + postUpdateEvent = null + } + } +}) editor.on('focus', function (cm) { - for (var i = 0; i < onlineUsers.length; i++) { - if (onlineUsers[i].id == personalInfo.id) { - onlineUsers[i].cursor = editor.getCursor(); - } + for (var i = 0; i < window.onlineUsers.length; i++) { + if (window.onlineUsers[i].id === window.personalInfo.id) { + window.onlineUsers[i].cursor = editor.getCursor() } - personalInfo['cursor'] = editor.getCursor(); - socket.emit('cursor focus', editor.getCursor()); -}); + } + window.personalInfo['cursor'] = editor.getCursor() + socket.emit('cursor focus', editor.getCursor()) +}) editor.on('cursorActivity', function (cm) { - updateStatusBar(); - cursorActivity(); -}); + updateStatusBar() + cursorActivity() +}) editor.on('beforeSelectionChange', function (doc, selections) { - if (selections) - selection = selections.ranges[0]; - else - selection = null; - updateStatusBar(); -}); - -var cursorActivity = _.debounce(cursorActivityInner, cursorActivityDebounce); - -function cursorActivityInner() { - if (editorHasFocus() && !Visibility.hidden()) { - for (var i = 0; i < onlineUsers.length; i++) { - if (onlineUsers[i].id == personalInfo.id) { - onlineUsers[i].cursor = editor.getCursor(); - } - } - personalInfo['cursor'] = editor.getCursor(); - socket.emit('cursor activity', editor.getCursor()); + if (selections) { selection = selections.ranges[0] } else { selection = null } + updateStatusBar() +}) + +var cursorActivity = _.debounce(cursorActivityInner, cursorActivityDebounce) + +function cursorActivityInner () { + if (editorHasFocus() && !Visibility.hidden()) { + for (var i = 0; i < window.onlineUsers.length; i++) { + if (window.onlineUsers[i].id === window.personalInfo.id) { + window.onlineUsers[i].cursor = editor.getCursor() + } } + window.personalInfo['cursor'] = editor.getCursor() + socket.emit('cursor activity', editor.getCursor()) + } } editor.on('blur', function (cm) { - for (var i = 0; i < onlineUsers.length; i++) { - if (onlineUsers[i].id == personalInfo.id) { - onlineUsers[i].cursor = null; - } + for (var i = 0; i < window.onlineUsers.length; i++) { + if (window.onlineUsers[i].id === window.personalInfo.id) { + window.onlineUsers[i].cursor = null } - personalInfo['cursor'] = null; - socket.emit('cursor blur'); -}); - -function saveInfo() { - var scrollbarStyle = editor.getOption('scrollbarStyle'); - var left = $(window).scrollLeft(); - var top = $(window).scrollTop(); - switch (currentMode) { - case modeType.edit: - if (scrollbarStyle == 'native') { - lastInfo.edit.scroll.left = left; - lastInfo.edit.scroll.top = top; - } else { - lastInfo.edit.scroll = editor.getScrollInfo(); - } - break; - case modeType.view: - lastInfo.view.scroll.left = left; - lastInfo.view.scroll.top = top; - break; - case modeType.both: - lastInfo.edit.scroll = editor.getScrollInfo(); - lastInfo.view.scroll.left = ui.area.view.scrollLeft(); - lastInfo.view.scroll.top = ui.area.view.scrollTop(); - break; - } - lastInfo.edit.cursor = editor.getCursor(); - lastInfo.edit.selections = editor.listSelections(); - lastInfo.needRestore = true; -} - -function restoreInfo() { - var scrollbarStyle = editor.getOption('scrollbarStyle'); - if (lastInfo.needRestore) { - var line = lastInfo.edit.cursor.line; - var ch = lastInfo.edit.cursor.ch; - editor.setCursor(line, ch); - editor.setSelections(lastInfo.edit.selections); - switch (currentMode) { - case modeType.edit: - if (scrollbarStyle == 'native') { - $(window).scrollLeft(lastInfo.edit.scroll.left); - $(window).scrollTop(lastInfo.edit.scroll.top); - } else { - var left = lastInfo.edit.scroll.left; - var top = lastInfo.edit.scroll.top; - editor.scrollIntoView(); - editor.scrollTo(left, top); - } - break; - case modeType.view: - $(window).scrollLeft(lastInfo.view.scroll.left); - $(window).scrollTop(lastInfo.view.scroll.top); - break; - case modeType.both: - var left = lastInfo.edit.scroll.left; - var top = lastInfo.edit.scroll.top; - editor.scrollIntoView(); - editor.scrollTo(left, top); - ui.area.view.scrollLeft(lastInfo.view.scroll.left); - ui.area.view.scrollTop(lastInfo.view.scroll.top); - break; - } + } + window.personalInfo['cursor'] = null + socket.emit('cursor blur') +}) - lastInfo.needRestore = false; +function saveInfo () { + var scrollbarStyle = editor.getOption('scrollbarStyle') + var left = $(window).scrollLeft() + var top = $(window).scrollTop() + switch (window.currentMode) { + case modeType.edit: + if (scrollbarStyle === 'native') { + window.lastInfo.edit.scroll.left = left + window.lastInfo.edit.scroll.top = top + } else { + window.lastInfo.edit.scroll = editor.getScrollInfo() + } + break + case modeType.view: + window.lastInfo.view.scroll.left = left + window.lastInfo.view.scroll.top = top + break + case modeType.both: + window.lastInfo.edit.scroll = editor.getScrollInfo() + window.lastInfo.view.scroll.left = ui.area.view.scrollLeft() + window.lastInfo.view.scroll.top = ui.area.view.scrollTop() + break + } + window.lastInfo.edit.cursor = editor.getCursor() + window.lastInfo.edit.selections = editor.listSelections() + window.lastInfo.needRestore = true +} + +function restoreInfo () { + var scrollbarStyle = editor.getOption('scrollbarStyle') + if (window.lastInfo.needRestore) { + var line = window.lastInfo.edit.cursor.line + var ch = window.lastInfo.edit.cursor.ch + editor.setCursor(line, ch) + editor.setSelections(window.lastInfo.edit.selections) + switch (window.currentMode) { + case modeType.edit: + if (scrollbarStyle === 'native') { + $(window).scrollLeft(window.lastInfo.edit.scroll.left) + $(window).scrollTop(window.lastInfo.edit.scroll.top) + } else { + let left = window.lastInfo.edit.scroll.left + let top = window.lastInfo.edit.scroll.top + editor.scrollIntoView() + editor.scrollTo(left, top) + } + break + case modeType.view: + $(window).scrollLeft(window.lastInfo.view.scroll.left) + $(window).scrollTop(window.lastInfo.view.scroll.top) + break + case modeType.both: + let left = window.lastInfo.edit.scroll.left + let top = window.lastInfo.edit.scroll.top + editor.scrollIntoView() + editor.scrollTo(left, top) + ui.area.view.scrollLeft(window.lastInfo.view.scroll.left) + ui.area.view.scrollTop(window.lastInfo.view.scroll.top) + break } + + window.lastInfo.needRestore = false + } } -//view actions -function refreshView() { - ui.area.markdown.html(''); - isDirty = true; - updateViewInner(); +// view actions +function refreshView () { + ui.area.markdown.html('') + window.isDirty = true + updateViewInner() } var updateView = _.debounce(function () { - editor.operation(updateViewInner); -}, updateViewDebounce); - -var lastResult = null; -var postUpdateEvent = null; - -function updateViewInner() { - if (currentMode == modeType.edit || !isDirty) return; - var value = editor.getValue(); - var lastMeta = md.meta; - md.meta = {}; - delete md.metaError; - var rendered = md.render(value); - if (md.meta.type && md.meta.type === 'slide') { - var slideOptions = { - separator: '^(\r\n?|\n)---(\r\n?|\n)$', - verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$' - }; - var slides = RevealMarkdown.slidify(editor.getValue(), slideOptions); - ui.area.markdown.html(slides); - RevealMarkdown.initialize(); + editor.operation(updateViewInner) +}, updateViewDebounce) + +var lastResult = null +var postUpdateEvent = null + +function updateViewInner () { + if (window.currentMode === modeType.edit || !window.isDirty) return + var value = editor.getValue() + var lastMeta = md.meta + md.meta = {} + delete md.metaError + var rendered = md.render(value) + if (md.meta.type && md.meta.type === 'slide') { + var slideOptions = { + separator: '^(\r\n?|\n)---(\r\n?|\n)$', + verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$' + } + var slides = window.RevealMarkdown.slidify(editor.getValue(), slideOptions) + ui.area.markdown.html(slides) + window.RevealMarkdown.initialize() // prevent XSS - ui.area.markdown.html(preventXSS(ui.area.markdown.html())); - ui.area.markdown.addClass('slides'); - syncscroll = false; - checkSyncToggle(); - } else { - if (lastMeta.type && lastMeta.type === 'slide') { - refreshView(); - ui.area.markdown.removeClass('slides'); - syncscroll = true; - checkSyncToggle(); - } + ui.area.markdown.html(preventXSS(ui.area.markdown.html())) + ui.area.markdown.addClass('slides') + window.syncscroll = false + checkSyncToggle() + } else { + if (lastMeta.type && lastMeta.type === 'slide') { + refreshView() + ui.area.markdown.removeClass('slides') + window.syncscroll = true + checkSyncToggle() + } // only render again when meta changed - if (JSON.stringify(md.meta) != JSON.stringify(lastMeta)) { - parseMeta(md, ui.area.codemirror, ui.area.markdown, $('#ui-toc'), $('#ui-toc-affix')); - rendered = md.render(value); - } + if (JSON.stringify(md.meta) !== JSON.stringify(lastMeta)) { + parseMeta(md, ui.area.codemirror, ui.area.markdown, $('#ui-toc'), $('#ui-toc-affix')) + rendered = md.render(value) + } // prevent XSS - rendered = preventXSS(rendered); - var result = postProcess(rendered).children().toArray(); - partialUpdate(result, lastResult, ui.area.markdown.children().toArray()); - if (result && lastResult && result.length != lastResult.length) - updateDataAttrs(result, ui.area.markdown.children().toArray()); - lastResult = $(result).clone(); - } - finishView(ui.area.markdown); - autoLinkify(ui.area.markdown); - deduplicatedHeaderId(ui.area.markdown); - renderTOC(ui.area.markdown); - generateToc('ui-toc'); - generateToc('ui-toc-affix'); - generateScrollspy(); - updateScrollspy(); - smoothHashScroll(); - isDirty = false; - clearMap(); - //buildMap(); - updateTitleReminder(); - if (postUpdateEvent && typeof postUpdateEvent === 'function') - postUpdateEvent(); -} - -var updateHistoryDebounce = 600; + rendered = preventXSS(rendered) + var result = postProcess(rendered).children().toArray() + partialUpdate(result, lastResult, ui.area.markdown.children().toArray()) + if (result && lastResult && result.length !== lastResult.length) { updateDataAttrs(result, ui.area.markdown.children().toArray()) } + lastResult = $(result).clone() + } + finishView(ui.area.markdown) + autoLinkify(ui.area.markdown) + deduplicatedHeaderId(ui.area.markdown) + renderTOC(ui.area.markdown) + generateToc('ui-toc') + generateToc('ui-toc-affix') + generateScrollspy() + updateScrollspy() + smoothHashScroll() + window.isDirty = false + clearMap() + // buildMap(); + updateTitleReminder() + if (postUpdateEvent && typeof postUpdateEvent === 'function') { postUpdateEvent() } +} + +var updateHistoryDebounce = 600 var updateHistory = _.debounce(updateHistoryInner, updateHistoryDebounce) -function updateHistoryInner() { - writeHistory(renderFilename(ui.area.markdown), renderTags(ui.area.markdown)); -} - -function updateDataAttrs(src, des) { - //sync data attr startline and endline - for (var i = 0; i < src.length; i++) { - copyAttribute(src[i], des[i], 'data-startline'); - copyAttribute(src[i], des[i], 'data-endline'); - } -} - -function partialUpdate(src, tar, des) { - if (!src || src.length == 0 || !tar || tar.length == 0 || !des || des.length == 0) { - ui.area.markdown.html(src); - return; - } - if (src.length == tar.length) { //same length - for (var i = 0; i < src.length; i++) { - copyAttribute(src[i], des[i], 'data-startline'); - copyAttribute(src[i], des[i], 'data-endline'); - var rawSrc = cloneAndRemoveDataAttr(src[i]); - var rawTar = cloneAndRemoveDataAttr(tar[i]); - if (rawSrc.outerHTML != rawTar.outerHTML) { - //console.log(rawSrc); - //console.log(rawTar); - $(des[i]).replaceWith(src[i]); - } - } - } else { //diff length - var start = 0; - var end = 0; - //find diff start position - for (var i = 0; i < tar.length; i++) { - //copyAttribute(src[i], des[i], 'data-startline'); - //copyAttribute(src[i], des[i], 'data-endline'); - var rawSrc = cloneAndRemoveDataAttr(src[i]); - var rawTar = cloneAndRemoveDataAttr(tar[i]); - if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) { - start = i; - break; - } - } - //find diff end position - var srcEnd = 0; - var tarEnd = 0; - for (var i = 0; i < src.length; i++) { - //copyAttribute(src[i], des[i], 'data-startline'); - //copyAttribute(src[i], des[i], 'data-endline'); - var rawSrc = cloneAndRemoveDataAttr(src[i]); - var rawTar = cloneAndRemoveDataAttr(tar[i]); - if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) { - start = i; - break; - } - } - //tar end - for (var i = 1; i <= tar.length + 1; i++) { - var srcLength = src.length; - var tarLength = tar.length; - //copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline'); - //copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline'); - var rawSrc = cloneAndRemoveDataAttr(src[srcLength - i]); - var rawTar = cloneAndRemoveDataAttr(tar[tarLength - i]); - if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) { - tarEnd = tar.length - i; - break; - } - } - //src end - for (var i = 1; i <= src.length + 1; i++) { - var srcLength = src.length; - var tarLength = tar.length; - //copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline'); - //copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline'); - var rawSrc = cloneAndRemoveDataAttr(src[srcLength - i]); - var rawTar = cloneAndRemoveDataAttr(tar[tarLength - i]); - if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) { - srcEnd = src.length - i; - break; - } - } - //check if tar end overlap tar start - var overlap = 0; - for (var i = start; i >= 0; i--) { - var rawTarStart = cloneAndRemoveDataAttr(tar[i - 1]); - var rawTarEnd = cloneAndRemoveDataAttr(tar[tarEnd + 1 + start - i]); - if (rawTarStart && rawTarEnd && rawTarStart.outerHTML == rawTarEnd.outerHTML) - overlap++; - else - break; - } - if (debug) - console.log('overlap:' + overlap); - //show diff content - if (debug) { - console.log('start:' + start); - console.log('tarEnd:' + tarEnd); - console.log('srcEnd:' + srcEnd); - } - tarEnd += overlap; - srcEnd += overlap; - var repeatAdd = (start - srcEnd) < (start - tarEnd); - var repeatDiff = Math.abs(srcEnd - tarEnd) - 1; - //push new elements - var newElements = []; - if (srcEnd >= start) { - for (var j = start; j <= srcEnd; j++) { - if (!src[j]) continue; - newElements.push(src[j].outerHTML); - } - } else if (repeatAdd) { - for (var j = srcEnd - repeatDiff; j <= srcEnd; j++) { - if (!des[j]) continue; - newElements.push(des[j].outerHTML); - } - } - //push remove elements - var removeElements = []; - if (tarEnd >= start) { - for (var j = start; j <= tarEnd; j++) { - if (!des[j]) continue; - removeElements.push(des[j]); - } - } else if (!repeatAdd) { - for (var j = start; j <= start + repeatDiff; j++) { - if (!des[j]) continue; - removeElements.push(des[j]); - } - } - //add elements - if (debug) { - console.log('ADD ELEMENTS'); - console.log(newElements.join('\n')); - } - if (des[start]) - $(newElements.join('')).insertBefore(des[start]); - else - $(newElements.join('')).insertAfter(des[start - 1]); - //remove elements - if (debug) - console.log('REMOVE ELEMENTS'); - for (var j = 0; j < removeElements.length; j++) { - if (debug) { - console.log(removeElements[j].outerHTML); - } - if (removeElements[j]) - $(removeElements[j]).remove(); - } - } -} - -function cloneAndRemoveDataAttr(el) { - if (!el) return; - var rawEl = $(el).clone(); - rawEl.removeAttr('data-startline data-endline'); - rawEl.find('[data-startline]').removeAttr('data-startline data-endline'); - return rawEl[0]; -} - -function copyAttribute(src, des, attr) { - if (src && src.getAttribute(attr) && des) - des.setAttribute(attr, src.getAttribute(attr)); +function updateHistoryInner () { + writeHistory(renderFilename(ui.area.markdown), renderTags(ui.area.markdown)) +} + +function updateDataAttrs (src, des) { + // sync data attr startline and endline + for (var i = 0; i < src.length; i++) { + copyAttribute(src[i], des[i], 'data-startline') + copyAttribute(src[i], des[i], 'data-endline') + } +} + +function partialUpdate (src, tar, des) { + if (!src || src.length === 0 || !tar || tar.length === 0 || !des || des.length === 0) { + ui.area.markdown.html(src) + return + } + if (src.length === tar.length) { // same length + for (let i = 0; i < src.length; i++) { + copyAttribute(src[i], des[i], 'data-startline') + copyAttribute(src[i], des[i], 'data-endline') + var rawSrc = cloneAndRemoveDataAttr(src[i]) + var rawTar = cloneAndRemoveDataAttr(tar[i]) + if (rawSrc.outerHTML !== rawTar.outerHTML) { + // console.log(rawSrc); + // console.log(rawTar); + $(des[i]).replaceWith(src[i]) + } + } + } else { // diff length + var start = 0 + // find diff start position + for (let i = 0; i < tar.length; i++) { + // copyAttribute(src[i], des[i], 'data-startline'); + // copyAttribute(src[i], des[i], 'data-endline'); + let rawSrc = cloneAndRemoveDataAttr(src[i]) + let rawTar = cloneAndRemoveDataAttr(tar[i]) + if (!rawSrc || !rawTar || rawSrc.outerHTML !== rawTar.outerHTML) { + start = i + break + } + } + // find diff end position + var srcEnd = 0 + var tarEnd = 0 + for (let i = 0; i < src.length; i++) { + // copyAttribute(src[i], des[i], 'data-startline'); + // copyAttribute(src[i], des[i], 'data-endline'); + let rawSrc = cloneAndRemoveDataAttr(src[i]) + let rawTar = cloneAndRemoveDataAttr(tar[i]) + if (!rawSrc || !rawTar || rawSrc.outerHTML !== rawTar.outerHTML) { + start = i + break + } + } + // tar end + for (let i = 1; i <= tar.length + 1; i++) { + let srcLength = src.length + let tarLength = tar.length + // copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline'); + // copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline'); + let rawSrc = cloneAndRemoveDataAttr(src[srcLength - i]) + let rawTar = cloneAndRemoveDataAttr(tar[tarLength - i]) + if (!rawSrc || !rawTar || rawSrc.outerHTML !== rawTar.outerHTML) { + tarEnd = tar.length - i + break + } + } + // src end + for (let i = 1; i <= src.length + 1; i++) { + let srcLength = src.length + let tarLength = tar.length + // copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline'); + // copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline'); + let rawSrc = cloneAndRemoveDataAttr(src[srcLength - i]) + let rawTar = cloneAndRemoveDataAttr(tar[tarLength - i]) + if (!rawSrc || !rawTar || rawSrc.outerHTML !== rawTar.outerHTML) { + srcEnd = src.length - i + break + } + } + // check if tar end overlap tar start + var overlap = 0 + for (var i = start; i >= 0; i--) { + var rawTarStart = cloneAndRemoveDataAttr(tar[i - 1]) + var rawTarEnd = cloneAndRemoveDataAttr(tar[tarEnd + 1 + start - i]) + if (rawTarStart && rawTarEnd && rawTarStart.outerHTML === rawTarEnd.outerHTML) { overlap++ } else { break } + } + if (debug) { console.log('overlap:' + overlap) } + // show diff content + if (debug) { + console.log('start:' + start) + console.log('tarEnd:' + tarEnd) + console.log('srcEnd:' + srcEnd) + } + tarEnd += overlap + srcEnd += overlap + var repeatAdd = (start - srcEnd) < (start - tarEnd) + var repeatDiff = Math.abs(srcEnd - tarEnd) - 1 + // push new elements + var newElements = [] + if (srcEnd >= start) { + for (let j = start; j <= srcEnd; j++) { + if (!src[j]) continue + newElements.push(src[j].outerHTML) + } + } else if (repeatAdd) { + for (let j = srcEnd - repeatDiff; j <= srcEnd; j++) { + if (!des[j]) continue + newElements.push(des[j].outerHTML) + } + } + // push remove elements + var removeElements = [] + if (tarEnd >= start) { + for (let j = start; j <= tarEnd; j++) { + if (!des[j]) continue + removeElements.push(des[j]) + } + } else if (!repeatAdd) { + for (let j = start; j <= start + repeatDiff; j++) { + if (!des[j]) continue + removeElements.push(des[j]) + } + } + // add elements + if (debug) { + console.log('ADD ELEMENTS') + console.log(newElements.join('\n')) + } + if (des[start]) { $(newElements.join('')).insertBefore(des[start]) } else { $(newElements.join('')).insertAfter(des[start - 1]) } + // remove elements + if (debug) { console.log('REMOVE ELEMENTS') } + for (let j = 0; j < removeElements.length; j++) { + if (debug) { + console.log(removeElements[j].outerHTML) + } + if (removeElements[j]) { $(removeElements[j]).remove() } + } + } +} + +function cloneAndRemoveDataAttr (el) { + if (!el) return + var rawEl = $(el).clone() + rawEl.removeAttr('data-startline data-endline') + rawEl.find('[data-startline]').removeAttr('data-startline data-endline') + return rawEl[0] +} + +function copyAttribute (src, des, attr) { + if (src && src.getAttribute(attr) && des) { des.setAttribute(attr, src.getAttribute(attr)) } } if ($('.cursor-menu').length <= 0) { - $("<div class='cursor-menu'>").insertAfter('.CodeMirror-cursors'); + $("<div class='cursor-menu'>").insertAfter('.CodeMirror-cursors') } -function reverseSortCursorMenu(dropdown) { - var items = dropdown.find('.textcomplete-item'); - items.sort(function (a, b) { - return $(b).attr('data-index') - $(a).attr('data-index'); - }); - return items; +function reverseSortCursorMenu (dropdown) { + var items = dropdown.find('.textcomplete-item') + items.sort(function (a, b) { + return $(b).attr('data-index') - $(a).attr('data-index') + }) + return items } -var checkCursorMenu = _.throttle(checkCursorMenuInner, cursorMenuThrottle); +var checkCursorMenu = _.throttle(checkCursorMenuInner, cursorMenuThrottle) -function checkCursorMenuInner() { +function checkCursorMenuInner () { // get element - var dropdown = $('.cursor-menu > .dropdown-menu'); + var dropdown = $('.cursor-menu > .dropdown-menu') // return if not exists - if (dropdown.length <= 0) return; + if (dropdown.length <= 0) return // set margin - var menuRightMargin = 10; - var menuBottomMargin = 4; + var menuRightMargin = 10 + var menuBottomMargin = 4 // use sizer to get the real doc size (won't count status bar and gutters) - var docWidth = ui.area.codemirrorSizer.width(); - var docHeight = ui.area.codemirrorSizer.height(); + var docWidth = ui.area.codemirrorSizer.width() // get editor size (status bar not count in) - var editorWidth = ui.area.codemirror.width(); - var editorHeight = ui.area.codemirror.height(); + var editorHeight = ui.area.codemirror.height() // get element size - var width = dropdown.outerWidth(); - var height = dropdown.outerHeight(); + var width = dropdown.outerWidth() + var height = dropdown.outerHeight() // get cursor - var cursor = editor.getCursor(); + var cursor = editor.getCursor() // set element cursor data - if (!dropdown.hasClass('CodeMirror-other-cursor')) - dropdown.addClass('CodeMirror-other-cursor'); - dropdown.attr('data-line', cursor.line); - dropdown.attr('data-ch', cursor.ch); + if (!dropdown.hasClass('CodeMirror-other-cursor')) { dropdown.addClass('CodeMirror-other-cursor') } + dropdown.attr('data-line', cursor.line) + dropdown.attr('data-ch', cursor.ch) // get coord position - var coord = editor.charCoords({ - line: cursor.line, - ch: cursor.ch - }, 'windows'); - var left = coord.left; - var top = coord.top; + var coord = editor.charCoords({ + line: cursor.line, + ch: cursor.ch + }, 'windows') + var left = coord.left + var top = coord.top // get doc top offset (to workaround with viewport) - var docTopOffset = ui.area.codemirrorSizerInner.position().top; + var docTopOffset = ui.area.codemirrorSizerInner.position().top // set offset - var offsetLeft = 0; - var offsetTop = defaultTextHeight; + var offsetLeft = 0 + var offsetTop = defaultTextHeight // set up side down - window.upSideDown = false; - var lastUpSideDown = upSideDown = false; + window.upSideDown = false + var lastUpSideDown = window.upSideDown = false // only do when have width and height - if (width > 0 && height > 0) { + if (width > 0 && height > 0) { // make element right bound not larger than doc width - if (left + width + offsetLeft + menuRightMargin > docWidth) - offsetLeft = -(left + width - docWidth + menuRightMargin); + if (left + width + offsetLeft + menuRightMargin > docWidth) { offsetLeft = -(left + width - docWidth + menuRightMargin) } // flip y when element bottom bound larger than doc height // and element top position is larger than element height - if (top + docTopOffset + height + offsetTop + menuBottomMargin > Math.max(editor.doc.height, editorHeight) && top + docTopOffset > height + menuBottomMargin) { - offsetTop = -(height + menuBottomMargin); + if (top + docTopOffset + height + offsetTop + menuBottomMargin > Math.max(editor.doc.height, editorHeight) && top + docTopOffset > height + menuBottomMargin) { + offsetTop = -(height + menuBottomMargin) // reverse sort menu because upSideDown - dropdown.html(reverseSortCursorMenu(dropdown)); - upSideDown = true; - } - var textCompleteDropdown = $(editor.getInputField()).data('textComplete').dropdown; - lastUpSideDown = textCompleteDropdown.upSideDown; - textCompleteDropdown.upSideDown = upSideDown; + dropdown.html(reverseSortCursorMenu(dropdown)) + window.upSideDown = true } + var textCompleteDropdown = $(editor.getInputField()).data('textComplete').dropdown + lastUpSideDown = textCompleteDropdown.upSideDown + textCompleteDropdown.upSideDown = window.upSideDown + } // make menu scroll top only if upSideDown changed - if (upSideDown !== lastUpSideDown) - dropdown.scrollTop(dropdown[0].scrollHeight); + if (window.upSideDown !== lastUpSideDown) { dropdown.scrollTop(dropdown[0].scrollHeight) } // set element offset data - dropdown.attr('data-offset-left', offsetLeft); - dropdown.attr('data-offset-top', offsetTop); + dropdown.attr('data-offset-left', offsetLeft) + dropdown.attr('data-offset-top', offsetTop) // set position - dropdown[0].style.left = left + offsetLeft + 'px'; - dropdown[0].style.top = top + offsetTop + 'px'; + dropdown[0].style.left = left + offsetLeft + 'px' + dropdown[0].style.top = top + offsetTop + 'px' } -function checkInIndentCode() { +function checkInIndentCode () { // if line starts with tab or four spaces is a code block - var line = editor.getLine(editor.getCursor().line); - var isIndentCode = ((line.substr(0, 4) === ' ') || (line.substr(0, 1) === '\t')); - return isIndentCode; -} - -var isInCode = false; - -function checkInCode() { - isInCode = checkAbove(matchInCode) || checkInIndentCode(); -} - -function checkAbove(method) { - var cursor = editor.getCursor(); - var text = []; - for (var i = 0; i < cursor.line; i++) //contain current line - text.push(editor.getLine(i)); - text = text.join('\n') + '\n' + editor.getLine(cursor.line).slice(0, cursor.ch); - //console.log(text); - return method(text); -} - -function checkBelow(method) { - var cursor = editor.getCursor(); - var count = editor.lineCount(); - var text = []; - for (var i = cursor.line + 1; i < count; i++) //contain current line - text.push(editor.getLine(i)); - text = editor.getLine(cursor.line).slice(cursor.ch) + '\n' + text.join('\n'); - //console.log(text); - return method(text); -} - -function matchInCode(text) { - var match; - match = text.match(/`{3,}/g); + var line = editor.getLine(editor.getCursor().line) + var isIndentCode = ((line.substr(0, 4) === ' ') || (line.substr(0, 1) === '\t')) + return isIndentCode +} + +var isInCode = false + +function checkInCode () { + isInCode = checkAbove(matchInCode) || checkInIndentCode() +} + +function checkAbove (method) { + var cursor = editor.getCursor() + var text = [] + for (var i = 0; i < cursor.line; i++) { // contain current line + text.push(editor.getLine(i)) + } + text = text.join('\n') + '\n' + editor.getLine(cursor.line).slice(0, cursor.ch) + // console.log(text); + return method(text) +} + +function checkBelow (method) { + var cursor = editor.getCursor() + var count = editor.lineCount() + var text = [] + for (var i = cursor.line + 1; i < count; i++) { // contain current line + text.push(editor.getLine(i)) + } + text = editor.getLine(cursor.line).slice(cursor.ch) + '\n' + text.join('\n') + // console.log(text); + return method(text) +} + +function matchInCode (text) { + var match + match = text.match(/`{3,}/g) + if (match && match.length % 2) { + return true + } else { + match = text.match(/`/g) if (match && match.length % 2) { - return true; + return true } else { - match = text.match(/`/g); - if (match && match.length % 2) { - return true; - } else { - return false; - } + return false } + } } -var isInContainer = false; -var isInContainerSyntax = false; +var isInContainer = false +var isInContainerSyntax = false -function checkInContainer() { - isInContainer = checkAbove(matchInContainer) && !checkInIndentCode(); +function checkInContainer () { + isInContainer = checkAbove(matchInContainer) && !checkInIndentCode() } -function checkInContainerSyntax() { +function checkInContainerSyntax () { // if line starts with :::, it's in container syntax - var line = editor.getLine(editor.getCursor().line); - isInContainerSyntax = (line.substr(0, 3) === ':::'); + var line = editor.getLine(editor.getCursor().line) + isInContainerSyntax = (line.substr(0, 3) === ':::') } -function matchInContainer(text) { - var match; - match = text.match(/:{3,}/g); - if (match && match.length % 2) { - return true; - } else { - return false; - } +function matchInContainer (text) { + var match + match = text.match(/:{3,}/g) + if (match && match.length % 2) { + return true + } else { + return false + } } $(editor.getInputField()) .textcomplete([ - { // emoji strategy - match: /(^|\n|\s)\B:([\-+\w]*)$/, - search: function (term, callback) { - var line = editor.getLine(editor.getCursor().line); - term = line.match(this.match)[2]; - var list = []; - $.map(emojify.emojiNames, function (emoji) { - if (emoji.indexOf(term) === 0) //match at first character - list.push(emoji); - }); - $.map(emojify.emojiNames, function (emoji) { - if (emoji.indexOf(term) !== -1) //match inside the word - list.push(emoji); - }); - callback(list); - }, - template: function (value) { - return '<img class="emoji" src="' + serverurl + '/build/emojify.js/dist/images/basic/' + value + '.png"></img> ' + value; - }, - replace: function (value) { - return '$1:' + value + ': '; - }, - index: 1, - context: function (text) { - checkInCode(); - checkInContainer(); - checkInContainerSyntax(); - return !isInCode && !isInContainerSyntax; + { // emoji strategy + match: /(^|\n|\s)\B:([-+\w]*)$/, + search: function (term, callback) { + var line = editor.getLine(editor.getCursor().line) + term = line.match(this.match)[2] + var list = [] + $.map(window.emojify.emojiNames, function (emoji) { + if (emoji.indexOf(term) === 0) { // match at first character + list.push(emoji) } - }, - { // Code block language strategy - langs: supportCodeModes, - charts: supportCharts, - match: /(^|\n)```(\w+)$/, - search: function (term, callback) { - var line = editor.getLine(editor.getCursor().line); - term = line.match(this.match)[2]; - var list = []; - $.map(this.langs, function (lang) { - if (lang.indexOf(term) === 0 && lang !== term) - list.push(lang); - }); - $.map(this.charts, function (chart) { - if (chart.indexOf(term) === 0 && chart !== term) - list.push(chart); - }); - callback(list); - }, - replace: function (lang) { - var ending = ''; - if (!checkBelow(matchInCode)) { - ending = '\n\n```'; - } - if (this.langs.indexOf(lang) !== -1) - return '$1```' + lang + '=' + ending; - else if (this.charts.indexOf(lang) !== -1) - return '$1```' + lang + ending; - }, - done: function () { - var cursor = editor.getCursor(); - var text = []; - text.push(editor.getLine(cursor.line - 1)); - text.push(editor.getLine(cursor.line)); - text = text.join('\n'); - //console.log(text); - if (text == '\n```') - editor.doc.cm.execCommand("goLineUp"); - }, - context: function (text) { - return isInCode; + }) + $.map(window.emojify.emojiNames, function (emoji) { + if (emoji.indexOf(term) !== -1) { // match inside the word + list.push(emoji) } + }) + callback(list) }, - { // Container strategy - containers: supportContainers, - match: /(^|\n):::(\s*)(\w*)$/, - search: function (term, callback) { - var line = editor.getLine(editor.getCursor().line); - term = line.match(this.match)[3].trim(); - var list = []; - $.map(this.containers, function (container) { - if (container.indexOf(term) === 0 && container !== term) - list.push(container); - }); - callback(list); - }, - replace: function (lang) { - var ending = ''; - if (!checkBelow(matchInContainer)) { - ending = '\n\n:::'; - } - if (this.containers.indexOf(lang) !== -1) - return '$1:::$2' + lang + ending; - }, - done: function () { - var cursor = editor.getCursor(); - var text = []; - text.push(editor.getLine(cursor.line - 1)); - text.push(editor.getLine(cursor.line)); - text = text.join('\n'); - //console.log(text); - if (text == '\n:::') - editor.doc.cm.execCommand("goLineUp"); - }, - context: function (text) { - return !isInCode && isInContainer; - } + template: function (value) { + return '<img class="emoji" src="' + serverurl + '/build/emojify.js/dist/images/basic/' + value + '.png"></img> ' + value }, - { //header - match: /(?:^|\n)(\s{0,3})(#{1,6}\w*)$/, - search: function (term, callback) { - callback($.map(supportHeaders, function (header) { - return header.search.indexOf(term) === 0 ? header.text : null; - })); - }, - replace: function (value) { - return '$1' + value; - }, - context: function (text) { - return !isInCode; - } + replace: function (value) { + return '$1:' + value + ': ' }, - { //extra tags for blockquote - match: /(?:^|\n|\s)(\>.*|\s|)((\^|)\[(\^|)\](\[\]|\(\)|\:|)\s*\w*)$/, - search: function (term, callback) { - var line = editor.getLine(editor.getCursor().line); - var quote = line.match(this.match)[1].trim(); - var list = []; - if (quote.indexOf('>') == 0) { - $.map(supportExtraTags, function (extratag) { - if (extratag.search.indexOf(term) === 0) - list.push(extratag.command()); - }); - } - $.map(supportReferrals, function (referral) { - if (referral.search.indexOf(term) === 0) - list.push(referral.text); - }) - callback(list); - }, - replace: function (value) { - return '$1' + value; - }, - context: function (text) { - return !isInCode; - } + index: 1, + context: function (text) { + checkInCode() + checkInContainer() + checkInContainerSyntax() + return !isInCode && !isInContainerSyntax + } + }, + { // Code block language strategy + langs: supportCodeModes, + charts: supportCharts, + match: /(^|\n)```(\w+)$/, + search: function (term, callback) { + var line = editor.getLine(editor.getCursor().line) + term = line.match(this.match)[2] + var list = [] + $.map(this.langs, function (lang) { + if (lang.indexOf(term) === 0 && lang !== term) { list.push(lang) } + }) + $.map(this.charts, function (chart) { + if (chart.indexOf(term) === 0 && chart !== term) { list.push(chart) } + }) + callback(list) }, - { //extra tags for list - match: /(^[>\s]*[\-\+\*]\s(?:\[[x ]\]|.*))(\[\])(\w*)$/, - search: function (term, callback) { - var list = []; - $.map(supportExtraTags, function (extratag) { - if (extratag.search.indexOf(term) === 0) - list.push(extratag.command()); - }); - $.map(supportReferrals, function (referral) { - if (referral.search.indexOf(term) === 0) - list.push(referral.text); - }) - callback(list); - }, - replace: function (value) { - return '$1' + value; - }, - context: function (text) { - return !isInCode; - } + replace: function (lang) { + var ending = '' + if (!checkBelow(matchInCode)) { + ending = '\n\n```' + } + if (this.langs.indexOf(lang) !== -1) { return '$1```' + lang + '=' + ending } else if (this.charts.indexOf(lang) !== -1) { return '$1```' + lang + ending } }, - { //referral - match: /(^\s*|\n|\s{2})((\[\]|\[\]\[\]|\[\]\(\)|\!|\!\[\]|\!\[\]\[\]|\!\[\]\(\))\s*\w*)$/, - search: function (term, callback) { - callback($.map(supportReferrals, function (referral) { - return referral.search.indexOf(term) === 0 ? referral.text : null; - })); - }, - replace: function (value) { - return '$1' + value; - }, - context: function (text) { - return !isInCode; - } + done: function () { + var cursor = editor.getCursor() + var text = [] + text.push(editor.getLine(cursor.line - 1)) + text.push(editor.getLine(cursor.line)) + text = text.join('\n') + // console.log(text); + if (text === '\n```') { editor.doc.cm.execCommand('goLineUp') } }, - { //externals - match: /(^|\n|\s)\{\}(\w*)$/, - search: function (term, callback) { - callback($.map(supportExternals, function (external) { - return external.search.indexOf(term) === 0 ? external.text : null; - })); - }, - replace: function (value) { - return '$1' + value; - }, - context: function (text) { - return !isInCode; - } - } - ], { - appendTo: $('.cursor-menu') - }) - .on({ - 'textComplete:beforeSearch': function (e) { - //NA + context: function (text) { + return isInCode + } + }, + { // Container strategy + containers: supportContainers, + match: /(^|\n):::(\s*)(\w*)$/, + search: function (term, callback) { + var line = editor.getLine(editor.getCursor().line) + term = line.match(this.match)[3].trim() + var list = [] + $.map(this.containers, function (container) { + if (container.indexOf(term) === 0 && container !== term) { list.push(container) } + }) + callback(list) }, - 'textComplete:afterSearch': function (e) { - checkCursorMenu(); + replace: function (lang) { + var ending = '' + if (!checkBelow(matchInContainer)) { + ending = '\n\n:::' + } + if (this.containers.indexOf(lang) !== -1) { return '$1:::$2' + lang + ending } }, - 'textComplete:select': function (e, value, strategy) { - //NA + done: function () { + var cursor = editor.getCursor() + var text = [] + text.push(editor.getLine(cursor.line - 1)) + text.push(editor.getLine(cursor.line)) + text = text.join('\n') + // console.log(text); + if (text === '\n:::') { editor.doc.cm.execCommand('goLineUp') } }, - 'textComplete:show': function (e) { - $(this).data('autocompleting', true); - editor.setOption("extraKeys", { - "Up": function () { - return false; - }, - "Right": function () { - editor.doc.cm.execCommand("goCharRight"); - }, - "Down": function () { - return false; - }, - "Left": function () { - editor.doc.cm.execCommand("goCharLeft"); - }, - "Enter": function () { - return false; - }, - "Backspace": function () { - editor.doc.cm.execCommand("delCharBefore"); - } - }); + context: function (text) { + return !isInCode && isInContainer + } + }, + { // header + match: /(?:^|\n)(\s{0,3})(#{1,6}\w*)$/, + search: function (term, callback) { + callback($.map(supportHeaders, function (header) { + return header.search.indexOf(term) === 0 ? header.text : null + })) + }, + replace: function (value) { + return '$1' + value + }, + context: function (text) { + return !isInCode + } + }, + { // extra tags for blockquote + match: /(?:^|\n|\s)(>.*|\s|)((\^|)\[(\^|)\](\[\]|\(\)|:|)\s*\w*)$/, + search: function (term, callback) { + var line = editor.getLine(editor.getCursor().line) + var quote = line.match(this.match)[1].trim() + var list = [] + if (quote.indexOf('>') === 0) { + $.map(supportExtraTags, function (extratag) { + if (extratag.search.indexOf(term) === 0) { list.push(extratag.command()) } + }) + } + $.map(supportReferrals, function (referral) { + if (referral.search.indexOf(term) === 0) { list.push(referral.text) } + }) + callback(list) + }, + replace: function (value) { + return '$1' + value + }, + context: function (text) { + return !isInCode + } + }, + { // extra tags for list + match: /(^[>\s]*[-+*]\s(?:\[[x ]\]|.*))(\[\])(\w*)$/, + search: function (term, callback) { + var list = [] + $.map(supportExtraTags, function (extratag) { + if (extratag.search.indexOf(term) === 0) { list.push(extratag.command()) } + }) + $.map(supportReferrals, function (referral) { + if (referral.search.indexOf(term) === 0) { list.push(referral.text) } + }) + callback(list) + }, + replace: function (value) { + return '$1' + value + }, + context: function (text) { + return !isInCode + } + }, + { // referral + match: /(^\s*|\n|\s{2})((\[\]|\[\]\[\]|\[\]\(\)|!|!\[\]|!\[\]\[\]|!\[\]\(\))\s*\w*)$/, + search: function (term, callback) { + callback($.map(supportReferrals, function (referral) { + return referral.search.indexOf(term) === 0 ? referral.text : null + })) }, - 'textComplete:hide': function (e) { - $(this).data('autocompleting', false); - editor.setOption("extraKeys", editorInstance.defaultExtraKeys); + replace: function (value) { + return '$1' + value + }, + context: function (text) { + return !isInCode + } + }, + { // externals + match: /(^|\n|\s)\{\}(\w*)$/, + search: function (term, callback) { + callback($.map(supportExternals, function (external) { + return external.search.indexOf(term) === 0 ? external.text : null + })) + }, + replace: function (value) { + return '$1' + value + }, + context: function (text) { + return !isInCode } - }); + } + ], { + appendTo: $('.cursor-menu') + }) + .on({ + 'textComplete:beforeSearch': function (e) { + // NA + }, + 'textComplete:afterSearch': function (e) { + checkCursorMenu() + }, + 'textComplete:select': function (e, value, strategy) { + // NA + }, + 'textComplete:show': function (e) { + $(this).data('autocompleting', true) + editor.setOption('extraKeys', { + 'Up': function () { + return false + }, + 'Right': function () { + editor.doc.cm.execCommand('goCharRight') + }, + 'Down': function () { + return false + }, + 'Left': function () { + editor.doc.cm.execCommand('goCharLeft') + }, + 'Enter': function () { + return false + }, + 'Backspace': function () { + editor.doc.cm.execCommand('delCharBefore') + } + }) + }, + 'textComplete:hide': function (e) { + $(this).data('autocompleting', false) + editor.setOption('extraKeys', editorInstance.defaultExtraKeys) + } + }) |