/* eslint-env browser, jquery */
/* global CodeMirror, Cookies, moment, editor, ui, Spinner,
modeType, Idle, serverurl, key, gapi, Dropbox, FilePicker
ot, MediaUploader, hex2rgb, num_loaded, Visibility */
require('../vendor/showup/showup')
require('../css/index.css')
require('../css/extra.css')
require('../css/slide-preview.css')
require('../css/site.css')
require('highlight.js/styles/github-gist.css')
var toMarkdown = require('to-markdown')
var saveAs = require('file-saver').saveAs
var randomColor = require('randomcolor')
var _ = require('lodash')
var List = require('list.js')
import {
checkLoginStateChanged,
setloginStateChangeEvent
} from './lib/common/login'
import {
debug,
DROPBOX_APP_KEY,
GOOGLE_API_KEY,
GOOGLE_CLIENT_ID,
noteid,
noteurl,
urlpath,
version
} from './lib/config'
import {
autoLinkify,
deduplicatedHeaderId,
exportToHTML,
exportToRawHTML,
finishView,
generateToc,
isValidURL,
md,
parseMeta,
postProcess,
renderFilename,
renderTOC,
renderTags,
renderTitle,
scrollToHash,
smoothHashScroll,
updateLastChange,
updateLastChangeUser,
updateOwner
} from './extra'
import {
clearMap,
setupSyncAreas,
syncScrollToEdit,
syncScrollToView
} from './syncscroll'
import {
writeHistory,
deleteServerHistory,
getHistory,
saveHistory,
removeHistory
} from './history'
var renderer = require('./render')
var preventXSS = renderer.preventXSS
var defaultTextHeight = 20
var viewportMargin = 20
var mac = CodeMirror.keyMap['default'] === CodeMirror.keyMap.macDefault
var defaultEditorMode = 'gfm'
var defaultExtraKeys = {
'F10': function (cm) {
cm.setOption('fullScreen', !cm.getOption('fullScreen'))
},
'Esc': function (cm) {
if (cm.getOption('keyMap').substr(0, 3) === 'vim') return CodeMirror.Pass
else if (cm.getOption('fullScreen')) cm.setOption('fullScreen', false)
},
'Cmd-S': function () {
return false
},
'Ctrl-S': function () {
return false
},
'Enter': 'newlineAndIndentContinueMarkdownList',
'Tab': function (cm) {
var tab = '\t'
var spaces = Array(parseInt(cm.getOption('indentUnit')) + 1).join(' ')
// auto indent whole line when in list or blockquote
var cursor = cm.getCursor()
var line = cm.getLine(cursor.line)
var regex = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))/
var match
var multiple = cm.getSelection().split('\n').length > 1 || cm.getSelections().length > 1
if (multiple) {
cm.execCommand('defaultTab')
} else if ((match = regex.exec(line)) !== null) {
var ch = match[1].length
var pos = {
line: cursor.line,
ch: ch
}
if (cm.getOption('indentWithTabs')) { cm.replaceRange(tab, pos, pos, '+input') } else { cm.replaceRange(spaces, pos, pos, '+input') }
} else {
if (cm.getOption('indentWithTabs')) { cm.execCommand('defaultTab') } else {
cm.replaceSelection(spaces)
}
}
},
'Cmd-Left': 'goLineLeftSmart',
'Cmd-Right': 'goLineRight',
'Ctrl-C': function (cm) {
if (!mac && cm.getOption('keyMap').substr(0, 3) === 'vim') document.execCommand('copy')
else return CodeMirror.Pass
},
'Ctrl-*': function (cm) {
wrapTextWith(cm, '*')
},
'Shift-Ctrl-8': function (cm) {
wrapTextWith(cm, '*')
},
'Ctrl-_': function (cm) {
wrapTextWith(cm, '_')
},
'Shift-Ctrl--': function (cm) {
wrapTextWith(cm, '_')
},
'Ctrl-~': function (cm) {
wrapTextWith(cm, '~')
},
'Shift-Ctrl-`': function (cm) {
wrapTextWith(cm, '~')
},
'Ctrl-^': function (cm) {
wrapTextWith(cm, '^')
},
'Shift-Ctrl-6': function (cm) {
wrapTextWith(cm, '^')
},
'Ctrl-+': function (cm) {
wrapTextWith(cm, '+')
},
'Shift-Ctrl-=': function (cm) {
wrapTextWith(cm, '+')
},
'Ctrl-=': function (cm) {
wrapTextWith(cm, '=')
},
'Shift-Ctrl-Backspace': function (cm) {
wrapTextWith(cm, 'Backspace')
}
}
var wrapSymbols = ['*', '_', '~', '^', '+', '=']
function wrapTextWith (cm, symbol) {
if (!cm.getSelection()) {
return CodeMirror.Pass
} else {
var ranges = cm.listSelections()
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i]
if (!range.empty()) {
var from = range.from()
var to = range.to()
if (symbol !== 'Backspace') {
cm.replaceRange(symbol, to, to, '+input')
cm.replaceRange(symbol, from, from, '+input')
// workaround selection range not correct after add symbol
var _ranges = cm.listSelections()
var anchorIndex = window.editor.indexFromPos(_ranges[i].anchor)
var headIndex = window.editor.indexFromPos(_ranges[i].head)
if (anchorIndex > headIndex) {
_ranges[i].anchor.ch--
} else {
_ranges[i].head.ch--
}
cm.setSelections(_ranges)
} else {
var preEndPos = {
line: to.line,
ch: to.ch + 1
}
var preText = cm.getRange(to, preEndPos)
var preIndex = wrapSymbols.indexOf(preText)
var postEndPos = {
line: from.line,
ch: from.ch - 1
}
var postText = cm.getRange(postEndPos, from)
var postIndex = wrapSymbols.indexOf(postText)
// check if surround symbol are list in array and matched
if (preIndex > -1 && postIndex > -1 && preIndex === postIndex) {
cm.replaceRange('', to, preEndPos, '+delete')
cm.replaceRange('', postEndPos, from, '+delete')
}
}
}
}
}
}
var idleTime = 300000 // 5 mins
var updateViewDebounce = 100
var cursorMenuThrottle = 50
var cursorActivityDebounce = 50
var cursorAnimatePeriod = 100
var supportContainers = ['success', 'info', 'warning', 'danger']
var supportCodeModes = ['javascript', 'typescript', 'jsx', 'htmlmixed', 'htmlembedded', 'css', 'xml', 'clike', 'clojure', 'ruby', 'python', 'shell', 'php', 'sql', 'haskell', 'coffeescript', 'yaml', 'pug', 'lua', 'cmake', 'nginx', 'perl', 'sass', 'r', 'dockerfile', 'tiddlywiki', 'mediawiki', 'go']
var supportCharts = ['sequence', 'flow', 'graphviz', 'mermaid']
var supportHeaders = [
{
text: '# h1',
search: '#'
},
{
text: '## h2',
search: '##'
},
{
text: '### h3',
search: '###'
},
{
text: '#### h4',
search: '####'
},
{
text: '##### h5',
search: '#####'
},
{
text: '###### h6',
search: '######'
},
{
text: '###### tags: `example`',
search: '###### tags:'
}
]
var supportReferrals = [
{
text: '[reference link]',
search: '[]'
},
{
text: '[reference]: https:// "title"',
search: '[]:'
},
{
text: '[^footnote link]',
search: '[^]'
},
{
text: '[^footnote reference]: https:// "title"',
search: '[^]:'
},
{
text: '^[inline footnote]',
search: '^[]'
},
{
text: '[link text][reference]',
search: '[][]'
},
{
text: '[link text](https:// "title")',
search: '[]()'
},
{
text: '![image alt][reference]',
search: '![][]'
},
{
text: '![image alt](https:// "title")',
search: '![]()'
},
{
text: '![image alt](https:// "title" =WidthxHeight)',
search: '![]()'
},
{
text: '[TOC]',
search: '[]'
}
]
var supportExternals = [
{
text: '{%youtube youtubeid %}',
search: 'youtube'
},
{
text: '{%vimeo vimeoid %}',
search: 'vimeo'
},
{
text: '{%gist gistid %}',
search: 'gist'
},
{
text: '{%slideshare slideshareid %}',
search: 'slideshare'
},
{
text: '{%speakerdeck speakerdeckid %}',
search: 'speakerdeck'
},
{
text: '{%pdf pdfurl %}',
search: 'pdf'
}
]
var supportExtraTags = [
{
text: '[name tag]',
search: '[]',
command: function () {
return '[name=' + window.personalInfo.name + ']'
}
},
{
text: '[time tag]',
search: '[]',
command: function () {
return '[time=' + moment().format('llll') + ']'
}
},
{
text: '[my color tag]',
search: '[]',
command: function () {
return '[color=' + window.personalInfo.color + ']'
}
},
{
text: '[random color tag]',
search: '[]',
command: function () {
var color = randomColor()
return '[color=' + color + ']'
}
}
]
window.modeType = {
edit: {
name: 'edit'
},
view: {
name: 'view'
},
both: {
name: 'both'
}
}
var statusType = {
connected: {
msg: 'CONNECTED',
label: 'label-warning',
fa: 'fa-wifi'
},
online: {
msg: 'ONLINE',
label: 'label-primary',
fa: 'fa-users'
},
offline: {
msg: 'OFFLINE',
label: 'label-danger',
fa: 'fa-plug'
}
}
var defaultMode = modeType.view
// global vars
window.loaded = false
window.needRefresh = false
window.isDirty = false
window.editShown = false
window.visibleXS = false
window.visibleSM = false
window.visibleMD = false
window.visibleLG = false
window.isTouchDevice = 'ontouchstart' in document.documentElement
window.currentMode = defaultMode
window.currentStatus = statusType.offline
window.lastInfo = {
needRestore: false,
cursor: null,
scroll: null,
edit: {
scroll: {
left: null,
top: null
},
cursor: {
line: null,
ch: null
},
selections: null
},
view: {
scroll: {
left: null,
top: null
}
},
history: null
}
window.personalInfo = {}
window.onlineUsers = []
window.fileTypes = {
'pl': 'perl',
'cgi': 'perl',
'js': 'javascript',
'php': 'php',
'sh': 'bash',
'rb': 'ruby',
'html': 'html',
'py': 'python'
}
// editor settings
var textit = document.getElementById('textit')
if (!textit) throw new Error('There was no textit area!')
window.editor = CodeMirror.fromTextArea(textit, {
mode: defaultEditorMode,
backdrop: defaultEditorMode,
keyMap: 'sublime',
viewportMargin: viewportMargin,
styleActiveLine: true,
lineNumbers: true,
lineWrapping: true,
showCursorWhenSelecting: true,
highlightSelectionMatches: true,
indentUnit: 4,
continueComments: 'Enter',
theme: 'one-dark',
inputStyle: 'textarea',
matchBrackets: true,
autoCloseBrackets: true,
matchTags: {
bothTags: true
},
autoCloseTags: true,
foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'authorship-gutters', 'CodeMirror-foldgutter'],
extraKeys: defaultExtraKeys,
flattenSpans: true,
addModeClass: true,
readOnly: true,
autoRefresh: true,
otherCursors: true,
placeholder: "← Start by entering a title here\n===\nVisit /features if you don't know what to do.\nHappy hacking :)"
})
var inlineAttach = window.inlineAttachment.editors.codemirror4.attach(editor)
defaultTextHeight = parseInt($('.CodeMirror').css('line-height'))
var statusBarTemplate = null
var statusBar = null
var statusCursor = null
var statusFile = null
var statusIndicators = null
var statusLength = null
var statusTheme = null
var statusSpellcheck = null
function getStatusBarTemplate (callback) {
$.get(serverurl + '/views/statusbar.html', function (template) {
statusBarTemplate = template
if (callback) callback()
})
}
getStatusBarTemplate()
function addStatusBar () {
if (!statusBarTemplate) {
getStatusBarTemplate(addStatusBar)
return
}
statusBar = $(statusBarTemplate)
statusCursor = statusBar.find('.status-cursor')
statusFile = statusBar.find('.status-file')
statusIndicators = statusBar.find('.status-indicators')
statusBar.find('.status-indent')
statusBar.find('.status-keymap')
statusLength = statusBar.find('.status-length')
statusTheme = statusBar.find('.status-theme')
statusSpellcheck = statusBar.find('.status-spellcheck')
statusBar.find('.status-preferences')
editor.addPanel(statusBar[0], {
position: 'bottom'
})
setIndent()
setKeymap()
setTheme()
setSpellcheck()
setPreferences()
}
function setIndent () {
var cookieIndentType = Cookies.get('indent_type')
var cookieTabSize = parseInt(Cookies.get('tab_size'))
var cookieSpaceUnits = parseInt(Cookies.get('space_units'))
if (cookieIndentType) {
if (cookieIndentType === 'tab') {
editor.setOption('indentWithTabs', true)
if (cookieTabSize) { editor.setOption('indentUnit', cookieTabSize) }
} else if (cookieIndentType === 'space') {
editor.setOption('indentWithTabs', false)
if (cookieSpaceUnits) { editor.setOption('indentUnit', cookieSpaceUnits) }
}
}
if (cookieTabSize) { editor.setOption('tabSize', cookieTabSize) }
var type = statusIndicators.find('.indent-type')
var widthLabel = statusIndicators.find('.indent-width-label')
var widthInput = statusIndicators.find('.indent-width-input')
function setType () {
if (editor.getOption('indentWithTabs')) {
Cookies.set('indent_type', 'tab', {
expires: 365
})
type.text('Tab Size:')
} else {
Cookies.set('indent_type', 'space', {
expires: 365
})
type.text('Spaces:')
}
}
setType()
function setUnit () {
var unit = editor.getOption('indentUnit')
if (editor.getOption('indentWithTabs')) {
Cookies.set('tab_size', unit, {
expires: 365
})
} else {
Cookies.set('space_units', unit, {
expires: 365
})
}
widthLabel.text(unit)
}
setUnit()
type.click(function () {
if (editor.getOption('indentWithTabs')) {
editor.setOption('indentWithTabs', false)
cookieSpaceUnits = parseInt(Cookies.get('space_units'))
if (cookieSpaceUnits) { editor.setOption('indentUnit', cookieSpaceUnits) }
} else {
editor.setOption('indentWithTabs', true)
cookieTabSize = parseInt(Cookies.get('tab_size'))
if (cookieTabSize) {
editor.setOption('indentUnit', cookieTabSize)
editor.setOption('tabSize', cookieTabSize)
}
}
setType()
setUnit()
})
widthLabel.click(function () {
if (widthLabel.is(':visible')) {
widthLabel.addClass('hidden')
widthInput.removeClass('hidden')
widthInput.val(editor.getOption('indentUnit'))
widthInput.select()
} else {
widthLabel.removeClass('hidden')
widthInput.addClass('hidden')
}
})
widthInput.on('change', function () {
var val = parseInt(widthInput.val())
if (!val) val = editor.getOption('indentUnit')
if (val < 1) val = 1
else if (val > 10) val = 10
if (editor.getOption('indentWithTabs')) {
editor.setOption('tabSize', val)
}
editor.setOption('indentUnit', val)
setUnit()
})
widthInput.on('blur', function () {
widthLabel.removeClass('hidden')
widthInput.addClass('hidden')
})
}
function setKeymap () {
var cookieKeymap = Cookies.get('keymap')
if (cookieKeymap) { editor.setOption('keyMap', cookieKeymap) }
var label = statusIndicators.find('.ui-keymap-label')
var sublime = statusIndicators.find('.ui-keymap-sublime')
var emacs = statusIndicators.find('.ui-keymap-emacs')
var vim = statusIndicators.find('.ui-keymap-vim')
function setKeymapLabel () {
var keymap = editor.getOption('keyMap')
Cookies.set('keymap', keymap, {
expires: 365
})
label.text(keymap)
restoreOverrideEditorKeymap()
setOverrideBrowserKeymap()
}
setKeymapLabel()
sublime.click(function () {
editor.setOption('keyMap', 'sublime')
setKeymapLabel()
})
emacs.click(function () {
editor.setOption('keyMap', 'emacs')
setKeymapLabel()
})
vim.click(function () {
editor.setOption('keyMap', 'vim')
setKeymapLabel()
})
}
function setTheme () {
var cookieTheme = Cookies.get('theme')
if (cookieTheme) {
editor.setOption('theme', cookieTheme)
}
var themeToggle = statusTheme.find('.ui-theme-toggle')
themeToggle.click(function () {
var theme = editor.getOption('theme')
if (theme === 'one-dark') {
theme = 'default'
} else {
theme = 'one-dark'
}
editor.setOption('theme', theme)
Cookies.set('theme', theme, {
expires: 365
})
checkTheme()
})
function checkTheme () {
var theme = editor.getOption('theme')
if (theme === 'one-dark') {
themeToggle.removeClass('active')
} else {
themeToggle.addClass('active')
}
}
checkTheme()
}
function setSpellcheck () {
var cookieSpellcheck = Cookies.get('spellcheck')
if (cookieSpellcheck) {
var mode = null
if (cookieSpellcheck === 'true' || cookieSpellcheck === true) {
mode = 'spell-checker'
} else {
mode = defaultEditorMode
}
if (mode && mode !== editor.getOption('mode')) {
editor.setOption('mode', mode)
}
}
var spellcheckToggle = statusSpellcheck.find('.ui-spellcheck-toggle')
spellcheckToggle.click(function () {
var mode = editor.getOption('mode')
if (mode === defaultEditorMode) {
mode = 'spell-checker'
} else {
mode = defaultEditorMode
}
if (mode && mode !== editor.getOption('mode')) {
editor.setOption('mode', mode)
}
Cookies.set('spellcheck', (mode === 'spell-checker'), {
expires: 365
})
checkSpellcheck()
})
function checkSpellcheck () {
var mode = editor.getOption('mode')
if (mode === defaultEditorMode) {
spellcheckToggle.removeClass('active')
} else {
spellcheckToggle.addClass('active')
}
}
checkSpellcheck()
// workaround spellcheck might not activate beacuse the ajax loading
/* eslint-disable camelcase */
if (num_loaded < 2) {
var spellcheckTimer = setInterval(function () {
if (num_loaded >= 2) {
if (editor.getOption('mode') === 'spell-checker') { editor.setOption('mode', 'spell-checker') }
clearInterval(spellcheckTimer)
}
}, 100)
}
/* eslint-endable camelcase */
}
var jumpToAddressBarKeymapName = mac ? 'Cmd-L' : 'Ctrl-L'
var jumpToAddressBarKeymapValue = null
function resetEditorKeymapToBrowserKeymap () {
var keymap = editor.getOption('keyMap')
if (!jumpToAddressBarKeymapValue) {
jumpToAddressBarKeymapValue = CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName]
delete CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName]
}
}
function restoreOverrideEditorKeymap () {
var keymap = editor.getOption('keyMap')
if (jumpToAddressBarKeymapValue) {
CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName] = jumpToAddressBarKeymapValue
jumpToAddressBarKeymapValue = null
}
}
function setOverrideBrowserKeymap () {
var overrideBrowserKeymap = $('.ui-preferences-override-browser-keymap label > input[type="checkbox"]')
if (overrideBrowserKeymap.is(':checked')) {
Cookies.set('preferences-override-browser-keymap', true, {
expires: 365
})
restoreOverrideEditorKeymap()
} else {
Cookies.remove('preferences-override-browser-keymap')
resetEditorKeymapToBrowserKeymap()
}
}
function setPreferences () {
var overrideBrowserKeymap = $('.ui-preferences-override-browser-keymap label > input[type="checkbox"]')
var cookieOverrideBrowserKeymap = Cookies.get('preferences-override-browser-keymap')
if (cookieOverrideBrowserKeymap && cookieOverrideBrowserKeymap === 'true') {
overrideBrowserKeymap.prop('checked', true)
} else {
overrideBrowserKeymap.prop('checked', false)
}
setOverrideBrowserKeymap()
overrideBrowserKeymap.change(function () {
setOverrideBrowserKeymap()
})
}
var selection = null
function updateStatusBar () {
if (!statusBar) return
var cursor = editor.getCursor()
var cursorText = 'Line ' + (cursor.line + 1) + ', Columns ' + (cursor.ch + 1)
if (selection) {
var anchor = selection.anchor
var head = selection.head
var start = head.line <= anchor.line ? head : anchor
var end = head.line >= anchor.line ? head : anchor
var selectionText = ' — Selected '
var selectionCharCount = Math.abs(head.ch - anchor.ch)
// borrow from brackets EditorStatusBar.js
if (start.line !== end.line) {
var lines = end.line - start.line + 1
if (end.ch === 0) {
lines--
}
selectionText += lines + ' lines'
} else if (selectionCharCount > 0) { selectionText += selectionCharCount + ' columns' }
if (start.line !== end.line || selectionCharCount > 0) { cursorText += selectionText }
}
statusCursor.text(cursorText)
var fileText = ' — ' + editor.lineCount() + ' Lines'
statusFile.text(fileText)
var docLength = editor.getValue().length
statusLength.text('Length ' + docLength)
if (docLength > (docmaxlength * 0.95)) {
statusLength.css('color', 'red')
statusLength.attr('title', 'Your almost reach note max length limit.')
} else if (docLength > (docmaxlength * 0.8)) {
statusLength.css('color', 'orange')
statusLength.attr('title', 'You nearly fill the note, consider to make more pieces.')
} else {
statusLength.css('color', 'white')
statusLength.attr('title', 'You could write up to ' + docmaxlength + ' characters in this note.')
}
}
// ui vars
window.ui = {
spinner: $('.ui-spinner'),
content: $('.ui-content'),
toolbar: {
shortStatus: $('.ui-short-status'),
status: $('.ui-status'),
new: $('.ui-new'),
publish: $('.ui-publish'),
extra: {
revision: $('.ui-extra-revision'),
slide: $('.ui-extra-slide')
},
download: {
markdown: $('.ui-download-markdown'),
html: $('.ui-download-html'),
rawhtml: $('.ui-download-raw-html'),
pdf: $('.ui-download-pdf-beta')
},
export: {
dropbox: $('.ui-save-dropbox'),
googleDrive: $('.ui-save-google-drive'),
gist: $('.ui-save-gist'),
snippet: $('.ui-save-snippet')
},
import: {
dropbox: $('.ui-import-dropbox'),
googleDrive: $('.ui-import-google-drive'),
gist: $('.ui-import-gist'),
snippet: $('.ui-import-snippet'),
clipboard: $('.ui-import-clipboard')
},
mode: $('.ui-mode'),
edit: $('.ui-edit'),
view: $('.ui-view'),
both: $('.ui-both'),
uploadImage: $('.ui-upload-image')
},
infobar: {
lastchange: $('.ui-lastchange'),
lastchangeuser: $('.ui-lastchangeuser'),
nolastchangeuser: $('.ui-no-lastchangeuser'),
permission: {
permission: $('.ui-permission'),
label: $('.ui-permission-label'),
freely: $('.ui-permission-freely'),
editable: $('.ui-permission-editable'),
locked: $('.ui-permission-locked'),
private: $('.ui-permission-private'),
limited: $('.ui-permission-limited'),
protected: $('.ui-permission-protected')
},
delete: $('.ui-delete-note')
},
toc: {
toc: $('.ui-toc'),
affix: $('.ui-affix-toc'),
label: $('.ui-toc-label'),
dropdown: $('.ui-toc-dropdown')
},
area: {
edit: $('.ui-edit-area'),
view: $('.ui-view-area'),
codemirror: $('.ui-edit-area .CodeMirror'),
codemirrorScroll: $('.ui-edit-area .CodeMirror .CodeMirror-scroll'),
codemirrorSizer: $('.ui-edit-area .CodeMirror .CodeMirror-sizer'),
codemirrorSizerInner: $('.ui-edit-area .CodeMirror .CodeMirror-sizer > div'),
markdown: $('.ui-view-area .markdown-body'),
resize: {
handle: $('.ui-resizable-handle'),
syncToggle: $('.ui-sync-toggle')
}
},
modal: {
snippetImportProjects: $('#snippetImportModalProjects'),
snippetImportSnippets: $('#snippetImportModalSnippets'),
revision: $('#revisionModal')
}
}
// page actions
var opts = {
lines: 11, // The number of lines to draw
length: 20, // The length of each line
width: 2, // The line thickness
radius: 30, // The radius of the inner circle
corners: 0, // Corner roundness (0..1)
rotate: 0, // The rotation offset
direction: 1, // 1: clockwise, -1: counterclockwise
color: '#000', // #rgb or #rrggbb or array of colors
speed: 1.1, // Rounds per second
trail: 60, // Afterglow percentage
shadow: false, // Whether to render a shadow
hwaccel: true, // Whether to use hardware acceleration
className: 'spinner', // The CSS class to assign to the spinner
zIndex: 2e9, // The z-index (defaults to 2000000000)
top: '50%', // Top position relative to parent
left: '50%' // Left position relative to parent
}
/* eslint-disable no-unused-vars */
var spinner = new Spinner(opts).spin(ui.spinner[0])
/* eslint-enable no-unused-vars */
// idle
var idle = new Idle({
onAway: function () {
idle.isAway = true
emitUserStatus()
updateOnlineStatus()
},
onAwayBack: function () {
idle.isAway = false
emitUserStatus()
updateOnlineStatus()
setHaveUnreadChanges(false)
updateTitleReminder()
},
awayTimeout: idleTime
})
ui.area.codemirror.on('touchstart', function () {
idle.onActive()
})
var haveUnreadChanges = false
function setHaveUnreadChanges (bool) {
if (!window.loaded) return
if (bool && (idle.isAway || Visibility.hidden())) {
haveUnreadChanges = true
} else if (!bool && !idle.isAway && !Visibility.hidden()) {
haveUnreadChanges = false
}
}
function updateTitleReminder () {
if (!window.loaded) return
if (haveUnreadChanges) {
document.title = '• ' + renderTitle(ui.area.markdown)
} else {
document.title = renderTitle(ui.area.markdown)
}
}
function setRefreshModal (status) {
$('#refreshModal').modal('show')
$('#refreshModal').find('.modal-body > div').hide()
$('#refreshModal').find('.' + status).show()
}
function setNeedRefresh () {
window.needRefresh = true
editor.setOption('readOnly', true)
socket.disconnect()
showStatus(statusType.offline)
}
setloginStateChangeEvent(function () {
setRefreshModal('user-state-changed')
setNeedRefresh()
})
// visibility
var wasFocus = false
Visibility.change(function (e, state) {
var hidden = Visibility.hidden()
if (hidden) {
if (editorHasFocus()) {
wasFocus = true
editor.getInputField().blur()
}
} else {
if (wasFocus) {
if (!window.visibleXS) {
editor.focus()
editor.refresh()
}
wasFocus = false
}
setHaveUnreadChanges(false)
}
updateTitleReminder()
})
// when page ready
$(document).ready(function () {
idle.checkAway()
checkResponsive()
// if in smaller screen, we don't need advanced scrollbar
var scrollbarStyle
if (window.visibleXS) {
scrollbarStyle = 'native'
} else {
scrollbarStyle = 'overlay'
}
if (scrollbarStyle !== editor.getOption('scrollbarStyle')) {
editor.setOption('scrollbarStyle', scrollbarStyle)
clearMap()
}
checkEditorStyle()
/* we need this only on touch devices */
if (window.isTouchDevice) {
/* cache dom references */
var $body = jQuery('body')
/* bind events */
$(document)
.on('focus', 'textarea, input', function () {
$body.addClass('fixfixed')
})
.on('blur', 'textarea, input', function () {
$body.removeClass('fixfixed')
})
}
// showup
$().showUp('.navbar', {
upClass: 'navbar-hide',
downClass: 'navbar-show'
})
// tooltip
$('[data-toggle="tooltip"]').tooltip()
// shortcuts
// allow on all tags
key.filter = function (e) { return true }
key('ctrl+alt+e', function (e) {
changeMode(modeType.edit)
})
key('ctrl+alt+v', function (e) {
changeMode(modeType.view)
})
key('ctrl+alt+b', function (e) {
changeMode(modeType.both)
})
// toggle-dropdown
$(document).on('click', '.toggle-dropdown .dropdown-menu', function (e) {
e.stopPropagation()
})
})
// when page resize
$(window).resize(function () {
checkLayout()
checkEditorStyle()
checkTocStyle()
checkCursorMenu()
windowResize()
})
// when page unload
$(window).on('unload', function () {
// updateHistoryInner();
})
$(window).on('error', function () {
// setNeedRefresh();
})
setupSyncAreas(ui.area.codemirrorScroll, ui.area.view, ui.area.markdown)
function autoSyncscroll () {
if (editorHasFocus()) {
syncScrollToView()
} else {
syncScrollToEdit()
}
}
var windowResizeDebounce = 200
var windowResize = _.debounce(windowResizeInner, windowResizeDebounce)
function windowResizeInner (callback) {
checkLayout()
checkResponsive()
checkEditorStyle()
checkTocStyle()
checkCursorMenu()
// refresh editor
if (window.loaded) {
if (editor.getOption('scrollbarStyle') === 'native') {
setTimeout(function () {
clearMap()
autoSyncscroll()
updateScrollspy()
if (callback && typeof callback === 'function') { callback() }
}, 1)
} else {
// force it load all docs at once to prevent scroll knob blink
editor.setOption('viewportMargin', Infinity)
setTimeout(function () {
clearMap()
autoSyncscroll()
editor.setOption('viewportMargin', viewportMargin)
// add or update user cursors
for (var i = 0; i < window.onlineUsers.length; i++) {
if (window.onlineUsers[i].id !== window.personalInfo.id) { buildCursor(window.onlineUsers[i]) }
}
updateScrollspy()
if (callback && typeof callback === 'function') { callback() }
}, 1)
}
}
}
function checkLayout () {
var navbarHieght = $('.navbar').outerHeight()
$('body').css('padding-top', navbarHieght + 'px')
}
function editorHasFocus () {
return $(editor.getInputField()).is(':focus')
}
// 768-792px have a gap
function checkResponsive () {
window.visibleXS = $('.visible-xs').is(':visible')
window.visibleSM = $('.visible-sm').is(':visible')
window.visibleMD = $('.visible-md').is(':visible')
window.visibleLG = $('.visible-lg').is(':visible')
if (window.visibleXS && window.currentMode === modeType.both) {
if (editorHasFocus()) { changeMode(modeType.edit) } else { changeMode(modeType.view) }
}
emitUserStatus()
}
var lastEditorWidth = 0
var previousFocusOnEditor = null
function checkEditorStyle () {
var desireHeight = statusBar ? (ui.area.edit.height() - statusBar.outerHeight()) : ui.area.edit.height()
// set editor height and min height based on scrollbar style and mode
var scrollbarStyle = editor.getOption('scrollbarStyle')
if (scrollbarStyle === 'overlay' || window.currentMode === modeType.both) {
ui.area.codemirrorScroll.css('height', desireHeight + 'px')
ui.area.codemirrorScroll.css('min-height', '')
checkEditorScrollbar()
} else if (scrollbarStyle === 'native') {
ui.area.codemirrorScroll.css('height', '')
ui.area.codemirrorScroll.css('min-height', desireHeight + 'px')
}
// workaround editor will have wrong doc height when editor height changed
editor.setSize(null, ui.area.edit.height())
// make editor resizable
if (!ui.area.resize.handle.length) {
ui.area.edit.resizable({
handles: 'e',
maxWidth: $(window).width() * 0.7,
minWidth: $(window).width() * 0.2,
create: function (e, ui) {
$(this).parent().on('resize', function (e) {
e.stopPropagation()
})
},
start: function (e) {
editor.setOption('viewportMargin', Infinity)
},
resize: function (e) {
ui.area.resize.syncToggle.stop(true, true).show()
checkTocStyle()
},
stop: function (e) {
lastEditorWidth = ui.area.edit.width()
// workaround that scroll event bindings
window.preventSyncScrollToView = 2
window.preventSyncScrollToEdit = true
editor.setOption('viewportMargin', viewportMargin)
if (editorHasFocus()) {
windowResizeInner(function () {
ui.area.codemirrorScroll.scroll()
})
} else {
windowResizeInner(function () {
ui.area.view.scroll()
})
}
checkEditorScrollbar()
}
})
ui.area.resize.handle = $('.ui-resizable-handle')
}
if (!ui.area.resize.syncToggle.length) {
ui.area.resize.syncToggle = $('')
ui.area.resize.syncToggle.hover(function () {
previousFocusOnEditor = editorHasFocus()
}, function () {
previousFocusOnEditor = null
})
ui.area.resize.syncToggle.click(function () {
window.syncscroll = !window.syncscroll
checkSyncToggle()
})
ui.area.resize.handle.append(ui.area.resize.syncToggle)
ui.area.resize.syncToggle.hide()
ui.area.resize.handle.hover(function () {
ui.area.resize.syncToggle.stop(true, true).delay(200).fadeIn(100)
}, function () {
ui.area.resize.syncToggle.stop(true, true).delay(300).fadeOut(300)
})
}
}
function checkSyncToggle () {
if (window.syncscroll) {
if (previousFocusOnEditor) {
window.preventSyncScrollToView = false
syncScrollToView()
} else {
window.preventSyncScrollToEdit = false
syncScrollToEdit()
}
ui.area.resize.syncToggle.find('i').removeClass('fa-unlink').addClass('fa-link')
} else {
ui.area.resize.syncToggle.find('i').removeClass('fa-link').addClass('fa-unlink')
}
}
var checkEditorScrollbar = _.debounce(function () {
editor.operation(checkEditorScrollbarInner)
}, 50)
function checkEditorScrollbarInner () {
// workaround simple scroll bar knob
// will get wrong position when editor height changed
var scrollInfo = editor.getScrollInfo()
editor.scrollTo(null, scrollInfo.top - 1)
editor.scrollTo(null, scrollInfo.top)
}
function checkTocStyle () {
// toc right
var paddingRight = parseFloat(ui.area.markdown.css('padding-right'))
var right = ($(window).width() - (ui.area.markdown.offset().left + ui.area.markdown.outerWidth() - paddingRight))
ui.toc.toc.css('right', right + 'px')
// affix toc left
var newbool
var rightMargin = (ui.area.markdown.parent().outerWidth() - ui.area.markdown.outerWidth()) / 2
// for ipad or wider device
if (rightMargin >= 133) {
newbool = true
var affixLeftMargin = (ui.toc.affix.outerWidth() - ui.toc.affix.width()) / 2
var left = ui.area.markdown.offset().left + ui.area.markdown.outerWidth() - affixLeftMargin
ui.toc.affix.css('left', left + 'px')
ui.toc.affix.css('width', rightMargin + 'px')
} else {
newbool = false
}
// toc scrollspy
ui.toc.toc.removeClass('scrollspy-body, scrollspy-view')
ui.toc.affix.removeClass('scrollspy-body, scrollspy-view')
if (window.currentMode === modeType.both) {
ui.toc.toc.addClass('scrollspy-view')
ui.toc.affix.addClass('scrollspy-view')
} else if (window.currentMode !== modeType.both && !newbool) {
ui.toc.toc.addClass('scrollspy-body')
ui.toc.affix.addClass('scrollspy-body')
} else {
ui.toc.toc.addClass('scrollspy-view')
ui.toc.affix.addClass('scrollspy-body')
}
if (newbool !== enoughForAffixToc) {
enoughForAffixToc = newbool
generateScrollspy()
}
}
function showStatus (type, num) {
window.currentStatus = type
var shortStatus = ui.toolbar.shortStatus
var status = ui.toolbar.status
var label = $('')
var fa = $('')
var msg = ''
var shortMsg = ''
shortStatus.html('')
status.html('')
switch (window.currentStatus) {
case statusType.connected:
label.addClass(statusType.connected.label)
fa.addClass(statusType.connected.fa)
msg = statusType.connected.msg
break
case statusType.online:
label.addClass(statusType.online.label)
fa.addClass(statusType.online.fa)
shortMsg = num
msg = num + ' ' + statusType.online.msg
break
case statusType.offline:
label.addClass(statusType.offline.label)
fa.addClass(statusType.offline.fa)
msg = statusType.offline.msg
break
}
label.append(fa)
var shortLabel = label.clone()
shortLabel.append(' ' + shortMsg)
shortStatus.append(shortLabel)
label.append(' ' + msg)
status.append(label)
}
function toggleMode () {
switch (window.currentMode) {
case modeType.edit:
changeMode(modeType.view)
break
case modeType.view:
changeMode(modeType.edit)
break
case modeType.both:
changeMode(modeType.view)
break
}
}
var lastMode = null
function changeMode (type) {
// lock navbar to prevent it hide after changeMode
lockNavbar()
saveInfo()
if (type) {
lastMode = window.currentMode
window.currentMode = type
}
var responsiveClass = 'col-lg-6 col-md-6 col-sm-6'
var scrollClass = 'ui-scrollable'
ui.area.codemirror.removeClass(scrollClass)
ui.area.edit.removeClass(responsiveClass)
ui.area.view.removeClass(scrollClass)
ui.area.view.removeClass(responsiveClass)
switch (window.currentMode) {
case modeType.edit:
ui.area.edit.show()
ui.area.view.hide()
if (!window.editShown) {
editor.refresh()
window.editShown = true
}
break
case modeType.view:
ui.area.edit.hide()
ui.area.view.show()
break
case modeType.both:
ui.area.codemirror.addClass(scrollClass)
ui.area.edit.addClass(responsiveClass).show()
ui.area.view.addClass(scrollClass)
ui.area.view.show()
break
}
// save mode to url
if (history.replaceState && window.loaded) history.replaceState(null, '', serverurl + '/' + noteid + '?' + window.currentMode.name)
if (window.currentMode === modeType.view) {
editor.getInputField().blur()
}
if (window.currentMode === modeType.edit || window.currentMode === modeType.both) {
ui.toolbar.uploadImage.fadeIn()
// add and update status bar
if (!statusBar) {
addStatusBar()
updateStatusBar()
}
// work around foldGutter might not init properly
editor.setOption('foldGutter', false)
editor.setOption('foldGutter', true)
} else {
ui.toolbar.uploadImage.fadeOut()
}
if (window.currentMode !== modeType.edit) {
$(document.body).css('background-color', 'white')
updateView()
} else {
$(document.body).css('background-color', ui.area.codemirror.css('background-color'))
}
// check resizable editor style
if (window.currentMode === modeType.both) {
if (lastEditorWidth > 0) { ui.area.edit.css('width', lastEditorWidth + 'px') } else { ui.area.edit.css('width', '') }
ui.area.resize.handle.show()
} else {
ui.area.edit.css('width', '')
ui.area.resize.handle.hide()
}
windowResizeInner()
restoreInfo()
if (lastMode === modeType.view && window.currentMode === modeType.both) {
window.preventSyncScrollToView = 2
syncScrollToEdit(null, true)
}
if (lastMode === modeType.edit && window.currentMode === modeType.both) {
window.preventSyncScrollToEdit = 2
syncScrollToView(null, true)
}
if (lastMode === modeType.both && window.currentMode !== modeType.both) {
window.preventSyncScrollToView = false
window.preventSyncScrollToEdit = false
}
if (lastMode !== modeType.edit && window.currentMode === modeType.edit) {
editor.refresh()
}
$(document.body).scrollspy('refresh')
ui.area.view.scrollspy('refresh')
ui.toolbar.both.removeClass('active')
ui.toolbar.edit.removeClass('active')
ui.toolbar.view.removeClass('active')
var modeIcon = ui.toolbar.mode.find('i')
modeIcon.removeClass('fa-pencil').removeClass('fa-eye')
if (ui.area.edit.is(':visible') && ui.area.view.is(':visible')) { // both
ui.toolbar.both.addClass('active')
modeIcon.addClass('fa-eye')
} else if (ui.area.edit.is(':visible')) { // edit
ui.toolbar.edit.addClass('active')
modeIcon.addClass('fa-eye')
} else if (ui.area.view.is(':visible')) { // view
ui.toolbar.view.addClass('active')
modeIcon.addClass('fa-pencil')
}
unlockNavbar()
}
function lockNavbar () {
$('.navbar').addClass('locked')
}
var unlockNavbar = _.debounce(function () {
$('.navbar').removeClass('locked')
}, 200)
function showMessageModal (title, header, href, text, success) {
var modal = $('.message-modal')
modal.find('.modal-title').html(title)
modal.find('.modal-body h5').html(header)
if (href) { modal.find('.modal-body a').attr('href', href).text(text) } else { modal.find('.modal-body a').removeAttr('href').text(text) }
modal.find('.modal-footer button').removeClass('btn-default btn-success btn-danger')
if (success) { modal.find('.modal-footer button').addClass('btn-success') } else { modal.find('.modal-footer button').addClass('btn-danger') }
modal.modal('show')
}
// check if dropbox app key is set and load scripts
if (DROPBOX_APP_KEY) {
$('