2017-03-08 18:41:05 +00:00
/* eslint-env browser, jquery */
/ * g l o b a l C o d e M i r r o r , C o o k i e s , m o m e n t , e d i t o r , u i , S p i n n e r ,
modeType , Idle , serverurl , key , gapi , Dropbox , FilePicker
ot , MediaUploader , hex2rgb , num _loaded , Visibility * /
2016-10-07 15:06:10 +00:00
2017-03-08 18:41:05 +00:00
require ( '../vendor/showup/showup' )
2016-11-02 03:26:06 +00:00
2017-03-08 18:41:05 +00:00
require ( '../css/index.css' )
require ( '../css/extra.css' )
require ( '../css/slide-preview.css' )
require ( '../css/site.css' )
2016-11-07 13:27:58 +00:00
2017-03-08 18:41:05 +00:00
require ( 'highlight.js/styles/github-gist.css' )
2016-10-07 15:06:10 +00:00
2017-03-08 18:41:05 +00:00
var toMarkdown = require ( 'to-markdown' )
2016-10-07 15:06:10 +00:00
2017-03-08 18:41:05 +00:00
var saveAs = require ( 'file-saver' ) . saveAs
var randomColor = require ( 'randomcolor' )
2016-10-13 03:42:17 +00:00
2017-03-08 18:41:05 +00:00
var _ = require ( 'lodash' )
var List = require ( 'list.js' )
2016-10-13 00:56:56 +00:00
2017-01-05 08:48:23 +00:00
import {
checkLoginStateChanged ,
2017-01-13 14:51:44 +00:00
setloginStateChangeEvent
2017-03-08 18:41:05 +00:00
} from './lib/common/login'
2017-01-13 14:51:44 +00:00
import {
2017-01-05 08:48:23 +00:00
debug ,
DROPBOX _APP _KEY ,
GOOGLE _API _KEY ,
GOOGLE _CLIENT _ID ,
noteid ,
noteurl ,
urlpath ,
version
2017-03-08 18:41:05 +00:00
} from './lib/config'
2016-10-08 12:02:30 +00:00
2017-01-05 09:52:32 +00:00
import {
autoLinkify ,
deduplicatedHeaderId ,
exportToHTML ,
exportToRawHTML ,
2017-03-14 08:27:55 +00:00
removeDOMEvents ,
2017-01-05 09:52:32 +00:00
finishView ,
generateToc ,
isValidURL ,
md ,
parseMeta ,
postProcess ,
renderFilename ,
renderTOC ,
renderTags ,
renderTitle ,
scrollToHash ,
smoothHashScroll ,
updateLastChange ,
updateLastChangeUser ,
updateOwner
2017-03-08 18:41:05 +00:00
} from './extra'
2016-10-08 12:02:30 +00:00
2017-01-04 15:01:44 +00:00
import {
clearMap ,
setupSyncAreas ,
syncScrollToEdit ,
syncScrollToView
2017-03-08 18:41:05 +00:00
} from './syncscroll'
2016-10-13 16:02:55 +00:00
2017-01-05 12:56:16 +00:00
import {
writeHistory ,
deleteServerHistory ,
getHistory ,
saveHistory ,
removeHistory
2017-03-08 18:41:05 +00:00
} from './history'
2016-10-08 12:02:30 +00:00
2017-03-08 18:41:05 +00:00
var renderer = require ( './render' )
var preventXSS = renderer . preventXSS
2016-10-07 15:06:10 +00:00
2017-03-08 18:41:05 +00:00
var defaultTextHeight = 20
var viewportMargin = 20
var mac = CodeMirror . keyMap [ 'default' ] === CodeMirror . keyMap . macDefault
var defaultEditorMode = 'gfm'
2015-06-01 10:04:25 +00:00
var defaultExtraKeys = {
2017-03-08 18:41:05 +00:00
'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' ) }
2016-09-18 08:35:24 +00:00
} else {
2017-03-08 18:41:05 +00:00
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' )
2016-09-18 08:35:24 +00:00
// workaround selection range not correct after add symbol
2017-03-08 18:41:05 +00:00
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 )
2016-10-07 15:06:10 +00:00
// check if surround symbol are list in array and matched
2017-03-08 18:41:05 +00:00
if ( preIndex > - 1 && postIndex > - 1 && preIndex === postIndex ) {
cm . replaceRange ( '' , to , preEndPos , '+delete' )
cm . replaceRange ( '' , postEndPos , from , '+delete' )
}
2016-09-18 08:35:24 +00:00
}
2017-03-08 18:41:05 +00:00
}
2016-09-18 08:35:24 +00:00
}
2017-03-08 18:41:05 +00:00
}
2016-09-18 08:35:24 +00:00
}
2017-03-08 18:41:05 +00:00
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' ]
2015-06-01 10:04:25 +00:00
var supportHeaders = [
2017-03-08 18:41:05 +00:00
{
text : '# h1' ,
search : '#'
} ,
{
text : '## h2' ,
search : '##'
} ,
{
text : '### h3' ,
search : '###'
} ,
{
text : '#### h4' ,
search : '####'
} ,
{
text : '##### h5' ,
search : '#####'
} ,
{
text : '###### h6' ,
search : '######'
} ,
{
text : '###### tags: `example`' ,
search : '###### tags:'
}
]
2015-06-01 10:04:25 +00:00
var supportReferrals = [
2017-03-08 18:41:05 +00:00
{
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 : '[]'
}
]
2015-06-01 10:04:25 +00:00
var supportExternals = [
2017-03-08 18:41:05 +00:00
{
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'
}
]
2015-09-25 09:41:15 +00:00
var supportExtraTags = [
2017-03-08 18:41:05 +00:00
{
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 + ']'
}
}
]
2016-10-08 12:02:30 +00:00
window . modeType = {
2017-03-08 18:41:05 +00:00
edit : {
name : 'edit'
} ,
view : {
name : 'view'
} ,
both : {
name : 'both'
}
}
2015-05-04 07:53:29 +00:00
var statusType = {
2017-03-08 18:41:05 +00:00
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
2016-10-09 02:51:39 +00:00
window . lastInfo = {
2017-03-08 18:41:05 +00:00
needRestore : false ,
cursor : null ,
scroll : null ,
edit : {
scroll : {
left : null ,
top : null
2015-05-04 07:53:29 +00:00
} ,
2017-03-08 18:41:05 +00:00
cursor : {
line : null ,
ch : null
2015-05-04 07:53:29 +00:00
} ,
2017-03-08 18:41:05 +00:00
selections : null
} ,
view : {
scroll : {
left : null ,
top : null
}
} ,
history : null
}
window . personalInfo = { }
window . onlineUsers = [ ]
2016-10-09 02:51:39 +00:00
window . fileTypes = {
2017-03-08 18:41:05 +00:00
'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!' )
2016-10-08 15:03:33 +00:00
window . editor = CodeMirror . fromTextArea ( textit , {
2017-03-08 18:41:05 +00:00
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'
2016-02-09 04:51:07 +00:00
}
2017-03-08 18:41:05 +00:00
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' )
2016-06-21 13:45:45 +00:00
}
2017-03-08 18:41:05 +00:00
}
checkTheme ( )
2016-06-21 13:45:45 +00:00
}
2017-03-08 18:41:05 +00:00
function setSpellcheck ( ) {
var cookieSpellcheck = Cookies . get ( 'spellcheck' )
if ( cookieSpellcheck ) {
var mode = null
if ( cookieSpellcheck === 'true' || cookieSpellcheck === true ) {
mode = 'spell-checker'
} else {
mode = defaultEditorMode
2016-06-21 13:45:45 +00:00
}
2017-03-08 18:41:05 +00:00
if ( mode && mode !== editor . getOption ( 'mode' ) ) {
editor . setOption ( 'mode' , mode )
2016-06-21 13:45:45 +00:00
}
2017-03-08 18:41:05 +00:00
}
2016-06-21 13:45:45 +00:00
2017-03-08 18:41:05 +00:00
var spellcheckToggle = statusSpellcheck . find ( '.ui-spellcheck-toggle' )
spellcheckToggle . click ( function ( ) {
var mode = editor . getOption ( 'mode' )
if ( mode === defaultEditorMode ) {
mode = 'spell-checker'
2016-12-03 16:56:14 +00:00
} else {
2017-03-08 18:41:05 +00:00
mode = defaultEditorMode
2016-12-03 16:56:14 +00:00
}
2017-03-08 18:41:05 +00:00
if ( mode && mode !== editor . getOption ( 'mode' ) ) {
editor . setOption ( 'mode' , mode )
2016-12-03 16:56:14 +00:00
}
2017-03-08 18:41:05 +00:00
Cookies . set ( 'spellcheck' , ( mode === 'spell-checker' ) , {
expires : 365
} )
checkSpellcheck ( )
} )
function checkSpellcheck ( ) {
var mode = editor . getOption ( 'mode' )
if ( mode === defaultEditorMode ) {
spellcheckToggle . removeClass ( 'active' )
2016-10-10 12:33:17 +00:00
} else {
2017-03-08 18:41:05 +00:00
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
2016-10-13 08:35:43 +00:00
window . ui = {
2017-03-08 18:41:05 +00:00
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' )
2015-07-01 16:10:20 +00:00
} ,
2017-03-08 18:41:05 +00:00
download : {
markdown : $ ( '.ui-download-markdown' ) ,
html : $ ( '.ui-download-html' ) ,
rawhtml : $ ( '.ui-download-raw-html' ) ,
pdf : $ ( '.ui-download-pdf-beta' )
2015-07-01 16:10:20 +00:00
} ,
2017-03-08 18:41:05 +00:00
export : {
dropbox : $ ( '.ui-save-dropbox' ) ,
googleDrive : $ ( '.ui-save-google-drive' ) ,
gist : $ ( '.ui-save-gist' ) ,
snippet : $ ( '.ui-save-snippet' )
2015-05-04 07:53:29 +00:00
} ,
2017-03-08 18:41:05 +00:00
import : {
dropbox : $ ( '.ui-import-dropbox' ) ,
googleDrive : $ ( '.ui-import-google-drive' ) ,
gist : $ ( '.ui-import-gist' ) ,
snippet : $ ( '.ui-import-snippet' ) ,
clipboard : $ ( '.ui-import-clipboard' )
2016-05-12 15:19:14 +00:00
} ,
2017-03-08 18:41:05 +00:00
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
2015-05-04 07:53:29 +00:00
var opts = {
2017-03-08 18:41:05 +00:00
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
2015-06-01 10:04:25 +00:00
var idle = new Idle ( {
2017-03-08 18:41:05 +00:00
onAway : function ( ) {
idle . isAway = true
emitUserStatus ( )
updateOnlineStatus ( )
} ,
onAwayBack : function ( ) {
idle . isAway = false
emitUserStatus ( )
updateOnlineStatus ( )
setHaveUnreadChanges ( false )
updateTitleReminder ( )
} ,
awayTimeout : idleTime
} )
2015-06-01 10:04:25 +00:00
ui . area . codemirror . on ( 'touchstart' , function ( ) {
2017-03-08 18:41:05 +00:00
idle . onActive ( )
} )
2015-06-01 10:04:25 +00:00
2017-03-08 18:41:05 +00:00
var haveUnreadChanges = false
2015-09-25 10:48:45 +00:00
2017-03-08 18:41:05 +00:00
function setHaveUnreadChanges ( bool ) {
if ( ! window . loaded ) return
if ( bool && ( idle . isAway || Visibility . hidden ( ) ) ) {
haveUnreadChanges = true
} else if ( ! bool && ! idle . isAway && ! Visibility . hidden ( ) ) {
haveUnreadChanges = false
}
2015-09-25 10:48:45 +00:00
}
2017-03-08 18:41:05 +00:00
function updateTitleReminder ( ) {
if ( ! window . loaded ) return
if ( haveUnreadChanges ) {
document . title = '• ' + renderTitle ( ui . area . markdown )
} else {
document . title = renderTitle ( ui . area . markdown )
}
2015-09-25 10:48:45 +00:00
}
2017-03-08 18:41:05 +00:00
function setRefreshModal ( status ) {
$ ( '#refreshModal' ) . modal ( 'show' )
$ ( '#refreshModal' ) . find ( '.modal-body > div' ) . hide ( )
$ ( '#refreshModal' ) . find ( '.' + status ) . show ( )
2016-06-17 08:31:36 +00:00
}
2017-03-08 18:41:05 +00:00
function setNeedRefresh ( ) {
window . needRefresh = true
editor . setOption ( 'readOnly' , true )
socket . disconnect ( )
showStatus ( statusType . offline )
2015-06-01 10:04:25 +00:00
}
2017-01-05 08:48:23 +00:00
setloginStateChangeEvent ( function ( ) {
2017-03-08 18:41:05 +00:00
setRefreshModal ( 'user-state-changed' )
setNeedRefresh ( )
} )
2015-07-01 16:10:20 +00:00
2017-03-08 18:41:05 +00:00
// visibility
var wasFocus = false
2015-06-01 10:04:25 +00:00
Visibility . change ( function ( e , state ) {
2017-03-08 18:41:05 +00:00
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 ( )
} )
2015-06-01 10:04:25 +00:00
2017-03-08 18:41:05 +00:00
// when page ready
2015-05-04 07:53:29 +00:00
$ ( document ) . ready ( function ( ) {
2017-03-08 18:41:05 +00:00
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 ( )
2015-05-04 07:53:29 +00:00
/* we need this only on touch devices */
2017-03-08 18:41:05 +00:00
if ( window . isTouchDevice ) {
2015-05-15 04:58:13 +00:00
/* cache dom references */
2017-03-08 18:41:05 +00:00
var $body = jQuery ( 'body' )
2015-05-04 07:53:29 +00:00
/* bind events */
2017-03-08 18:41:05 +00:00
$ ( document )
2015-05-15 04:58:13 +00:00
. on ( 'focus' , 'textarea, input' , function ( ) {
2017-03-08 18:41:05 +00:00
$body . addClass ( 'fixfixed' )
2015-05-15 04:58:13 +00:00
} )
. on ( 'blur' , 'textarea, input' , function ( ) {
2017-03-08 18:41:05 +00:00
$body . removeClass ( 'fixfixed' )
} )
}
// showup
$ ( ) . showUp ( '.navbar' , {
upClass : 'navbar-hide' ,
downClass : 'navbar-show'
} )
// tooltip
$ ( '[data-toggle="tooltip"]' ) . tooltip ( )
2016-09-18 08:30:08 +00:00
// shortcuts
// allow on all tags
2017-03-08 18:41:05 +00:00
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 )
} )
2016-12-03 16:56:14 +00:00
// toggle-dropdown
2017-03-08 18:41:05 +00:00
$ ( document ) . on ( 'click' , '.toggle-dropdown .dropdown-menu' , function ( e ) {
e . stopPropagation ( )
} )
} )
// when page resize
2015-05-04 07:53:29 +00:00
$ ( window ) . resize ( function ( ) {
2017-03-08 18:41:05 +00:00
checkLayout ( )
checkEditorStyle ( )
checkTocStyle ( )
checkCursorMenu ( )
windowResize ( )
} )
// when page unload
2016-07-30 04:19:42 +00:00
$ ( window ) . on ( 'unload' , function ( ) {
2017-03-08 18:41:05 +00:00
// updateHistoryInner();
} )
2016-07-30 04:19:42 +00:00
$ ( window ) . on ( 'error' , function ( ) {
2017-03-08 18:41:05 +00:00
// setNeedRefresh();
} )
2016-09-18 08:51:19 +00:00
2017-03-08 18:41:05 +00:00
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 )
2016-05-29 05:58:32 +00:00
} else {
2016-03-15 03:10:08 +00:00
// force it load all docs at once to prevent scroll knob blink
2017-03-08 18:41:05 +00:00
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 ] ) }
2016-03-15 03:10:08 +00:00
}
2017-03-08 18:41:05 +00:00
updateScrollspy ( )
if ( callback && typeof callback === 'function' ) { callback ( ) }
} , 1 )
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
}
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
function checkLayout ( ) {
var navbarHieght = $ ( '.navbar' ) . outerHeight ( )
$ ( 'body' ) . css ( 'padding-top' , navbarHieght + 'px' )
2015-09-25 10:37:40 +00:00
}
2017-03-08 18:41:05 +00:00
function editorHasFocus ( ) {
return $ ( editor . getInputField ( ) ) . is ( ':focus' )
2015-05-15 04:58:13 +00:00
}
2015-06-01 10:04:25 +00:00
2017-03-08 18:41:05 +00:00
// 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' )
2015-06-01 10:04:25 +00:00
2017-03-08 18:41:05 +00:00
if ( window . visibleXS && window . currentMode === modeType . both ) {
if ( editorHasFocus ( ) ) { changeMode ( modeType . edit ) } else { changeMode ( modeType . view ) }
}
2015-06-01 10:04:25 +00:00
2017-03-08 18:41:05 +00:00
emitUserStatus ( )
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
var lastEditorWidth = 0
var previousFocusOnEditor = null
2015-09-25 10:46:08 +00:00
2017-03-08 18:41:05 +00:00
function checkEditorStyle ( ) {
var desireHeight = statusBar ? ( ui . area . edit . height ( ) - statusBar . outerHeight ( ) ) : ui . area . edit . height ( )
2016-03-15 03:10:08 +00:00
// set editor height and min height based on scrollbar style and mode
2017-03-08 18:41:05 +00:00
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' )
}
2016-04-20 08:17:24 +00:00
// workaround editor will have wrong doc height when editor height changed
2017-03-08 18:41:05 +00:00
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 ( )
2016-10-07 15:06:10 +00:00
// workaround that scroll event bindings
2017-03-08 18:41:05 +00:00
window . preventSyncScrollToView = 2
window . preventSyncScrollToEdit = true
editor . setOption ( 'viewportMargin' , viewportMargin )
if ( editorHasFocus ( ) ) {
windowResizeInner ( function ( ) {
ui . area . codemirrorScroll . scroll ( )
} )
2016-05-26 16:12:07 +00:00
} else {
2017-03-08 18:41:05 +00:00
windowResizeInner ( function ( ) {
ui . area . view . scroll ( )
} )
2016-05-26 16:12:07 +00:00
}
2017-03-08 18:41:05 +00:00
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 ( )
2016-05-26 05:17:00 +00:00
} else {
2017-03-08 18:41:05 +00:00
window . preventSyncScrollToEdit = false
syncScrollToEdit ( )
2016-05-26 05:17:00 +00:00
}
2017-03-08 18:41:05 +00:00
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' )
}
2015-05-04 07:53:29 +00:00
}
2017-01-02 03:06:02 +00:00
var checkEditorScrollbar = _ . debounce ( function ( ) {
2017-03-08 18:41:05 +00:00
editor . operation ( checkEditorScrollbarInner )
} , 50 )
2017-01-02 03:06:02 +00:00
2017-03-08 18:41:05 +00:00
function checkEditorScrollbarInner ( ) {
2016-06-17 07:57:34 +00:00
// workaround simple scroll bar knob
// will get wrong position when editor height changed
2017-03-08 18:41:05 +00:00
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 ) {
2016-03-15 03:10:08 +00:00
// lock navbar to prevent it hide after changeMode
2017-03-08 18:41:05 +00:00
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
}
2016-09-18 08:33:33 +00:00
// save mode to url
2017-03-08 18:41:05 +00:00
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' )
2015-09-24 05:55:02 +00:00
}
var unlockNavbar = _ . debounce ( function ( ) {
2017-03-08 18:41:05 +00:00
$ ( '.navbar' ) . removeClass ( 'locked' )
} , 200 )
2015-05-04 07:53:29 +00:00
2017-03-08 18:41:05 +00:00
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' )
2016-03-04 15:17:35 +00:00
}
2016-05-15 02:54:24 +00:00
// check if dropbox app key is set and load scripts
if ( DROPBOX _APP _KEY ) {
2017-03-08 18:41:05 +00:00
$ ( '<script>' )
2016-05-15 02:54:24 +00:00
. 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 )
2016-09-18 08:27:21 +00:00
. prop ( 'async' , true )
. prop ( 'defer' , true )
2017-03-08 18:41:05 +00:00
. appendTo ( 'body' )
2016-05-15 02:54:24 +00:00
} else {
2017-03-08 18:41:05 +00:00
ui . toolbar . import . dropbox . hide ( )
ui . toolbar . export . dropbox . hide ( )
2016-05-15 02:54:24 +00:00
}
// check if google api key and client id are set and load scripts
if ( GOOGLE _API _KEY && GOOGLE _CLIENT _ID ) {
2017-03-08 18:41:05 +00:00
$ ( '<script>' )
2016-05-15 02:54:24 +00:00
. attr ( 'type' , 'text/javascript' )
2016-09-18 08:27:21 +00:00
. attr ( 'src' , 'https://www.google.com/jsapi?callback=onGoogleAPILoaded' )
. prop ( 'async' , true )
. prop ( 'defer' , true )
2017-03-08 18:41:05 +00:00
. appendTo ( 'body' )
2016-09-18 08:27:21 +00:00
} else {
2017-03-08 18:41:05 +00:00
ui . toolbar . import . googleDrive . hide ( )
ui . toolbar . export . googleDrive . hide ( )
2016-09-18 08:27:21 +00:00
}
2017-03-08 18:41:05 +00:00
function onGoogleAPILoaded ( ) {
$ ( '<script>' )
2016-05-15 02:54:24 +00:00
. attr ( 'type' , 'text/javascript' )
. attr ( 'src' , 'https://apis.google.com/js/client:plusone.js?onload=onGoogleClientLoaded' )
2016-09-18 08:27:21 +00:00
. prop ( 'async' , true )
. prop ( 'defer' , true )
2017-03-08 18:41:05 +00:00
. appendTo ( 'body' )
2016-05-15 02:54:24 +00:00
}
2017-03-08 18:41:05 +00:00
window . onGoogleAPILoaded = onGoogleAPILoaded
2016-05-15 02:54:24 +00:00
2017-03-08 18:41:05 +00:00
// button actions
// share
ui . toolbar . publish . attr ( 'href' , noteurl + '/publish' )
2016-07-02 08:18:10 +00:00
// extra
2017-03-08 18:41:05 +00:00
// slide
ui . toolbar . extra . slide . attr ( 'href' , noteurl + '/slide' )
// download
// markdown
2015-12-18 15:44:08 +00:00
ui . toolbar . download . markdown . click ( function ( e ) {
2017-03-08 18:41:05 +00:00
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
2015-12-18 15:44:08 +00:00
ui . toolbar . download . html . click ( function ( e ) {
2017-03-08 18:41:05 +00:00
e . preventDefault ( )
e . stopPropagation ( )
exportToHTML ( ui . area . markdown )
} )
2016-06-17 08:17:37 +00:00
// raw html
ui . toolbar . download . rawhtml . click ( function ( e ) {
2017-03-08 18:41:05 +00:00
e . preventDefault ( )
e . stopPropagation ( )
exportToRawHTML ( ui . area . markdown )
} )
// pdf
ui . toolbar . download . pdf . attr ( 'download' , '' ) . attr ( 'href' , noteurl + '/pdf' )
// export to dropbox
2015-09-25 11:09:43 +00:00
ui . toolbar . export . dropbox . click ( function ( ) {
2017-03-08 18:41:05 +00:00
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
2016-03-04 15:17:35 +00:00
// export to google drive
ui . toolbar . export . googleDrive . click ( function ( e ) {
2017-03-08 18:41:05 +00:00
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' )
2016-07-30 04:19:42 +00:00
. done ( function ( data ) {
2017-03-08 18:41:05 +00:00
$ ( '#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 ( )
2016-05-12 16:28:08 +00:00
} )
2016-07-30 04:19:42 +00:00
. fail ( function ( data ) {
2017-03-08 18:41:05 +00:00
showMessageModal ( '<i class="fa fa-gitlab"></i> Import from Snippet' , 'Unable to fetch gitlab parameters :(' , '' , '' , false )
2016-05-12 16:28:08 +00:00
} )
2016-07-30 04:19:42 +00:00
. always ( function ( ) {
2017-03-08 18:41:05 +00:00
ui . spinner . hide ( )
} )
} )
// import from dropbox
2015-05-15 04:58:13 +00:00
ui . toolbar . import . dropbox . click ( function ( ) {
2017-03-08 18:41:05 +00:00
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 )
} )
2016-03-04 15:17:35 +00:00
// import from google drive
2017-03-08 18:41:05 +00:00
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 ( )
}
} )
}
}
} )
2016-03-04 15:17:35 +00:00
}
2017-03-08 18:41:05 +00:00
// import from gist
2016-04-20 10:06:36 +00:00
ui . toolbar . import . gist . click ( function ( ) {
2017-03-08 18:41:05 +00:00
// na
} )
// import from snippet
2016-05-10 02:38:13 +00:00
ui . toolbar . import . snippet . click ( function ( ) {
2017-03-08 18:41:05 +00:00
ui . spinner . show ( )
$ . get ( serverurl + '/auth/gitlab/callback/' + noteid + '/projects' )
2016-07-30 04:19:42 +00:00
. done ( function ( data ) {
2017-03-08 18:41:05 +00:00
$ ( '#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 ( )
2016-05-11 21:05:25 +00:00
} )
2016-07-30 04:19:42 +00:00
. fail ( function ( data ) {
2017-03-08 18:41:05 +00:00
showMessageModal ( '<i class="fa fa-gitlab"></i> Import from Snippet' , 'Unable to fetch gitlab parameters :(' , '' , '' , false )
2016-05-11 21:05:25 +00:00
} )
2016-07-30 04:19:42 +00:00
. always ( function ( ) {
2017-03-08 18:41:05 +00:00
ui . spinner . hide ( )
} )
} )
// import from clipboard
2015-05-15 04:58:13 +00:00
ui . toolbar . import . clipboard . click ( function ( ) {
2017-03-08 18:41:05 +00:00
// na
} )
// upload image
2015-07-01 16:10:20 +00:00
ui . toolbar . uploadImage . bind ( 'change' , function ( e ) {
2017-03-08 18:41:05 +00:00
var files = e . target . files || e . dataTransfer . files
e . dataTransfer = { }
e . dataTransfer . files = files
inlineAttach . onDrop ( e )
} )
// toc
2015-07-01 16:10:20 +00:00
ui . toc . dropdown . click ( function ( e ) {
2017-03-08 18:41:05 +00:00
e . stopPropagation ( )
} )
2017-01-16 04:42:21 +00:00
// prevent empty link change hash
$ ( 'a[href="#"]' ) . click ( function ( e ) {
2017-03-08 18:41:05 +00:00
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
2016-06-17 08:15:53 +00:00
ui . modal . revision . on ( 'show.bs.modal' , function ( e ) {
2017-03-08 18:41:05 +00:00
$ . get ( noteurl + '/revision' )
. done ( function ( data ) {
parseRevisions ( data . revision )
initRevisionViewer ( )
2016-06-17 08:15:53 +00:00
} )
2017-03-08 18:41:05 +00:00
. fail ( function ( err ) {
if ( debug ) {
console . log ( err )
}
2016-06-17 08:15:53 +00:00
} )
2017-03-08 18:41:05 +00:00
. 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 = [ ]
2016-06-17 08:15:53 +00:00
// mark the text which have been insert or delete
2017-03-08 18:41:05 +00:00
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 ]
2016-06-17 08:15:53 +00:00
// ignore if diff only contains line breaks
2017-03-08 18:41:05 +00:00
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
2016-06-17 08:15:53 +00:00
}
2017-03-08 18:41:05 +00:00
}
2016-06-17 08:15:53 +00:00
}
2017-03-08 18:41:05 +00:00
}
revisionInsertAnnotation . update ( revisionInsert )
revisionDeleteAnnotation . update ( revisionDelete )
2016-06-17 08:15:53 +00:00
} )
2017-03-08 18:41:05 +00:00
. fail ( function ( err ) {
if ( debug ) {
console . log ( err )
}
} )
. always ( function ( ) {
// na
2016-06-17 08:15:53 +00:00
} )
2017-03-08 18:41:05 +00:00
}
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 ( )
2016-06-17 08:15:53 +00:00
}
$ ( '#revisionModalDownload' ) . click ( function ( ) {
2017-03-08 18:41:05 +00:00
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 )
} )
2016-06-17 08:15:53 +00:00
$ ( '#revisionModalRevert' ) . click ( function ( ) {
2017-03-08 18:41:05 +00:00
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 )
2016-05-12 15:19:14 +00:00
} )
2017-03-08 18:41:05 +00:00
. fail ( function ( err ) {
if ( debug ) {
console . log ( err )
}
2016-05-12 15:19:14 +00:00
} )
2017-03-08 18:41:05 +00:00
. always ( function ( ) {
// na
} )
} )
// snippet snippets
ui . modal . snippetImportSnippets . change ( function ( ) {
var snippet = $ ( '#snippetImportModalSnippets' ) . val ( )
$ ( '#snippetImportModalContent' ) . val ( $ ( '#snippetImportModalContent' ) . val ( ) + '/snippets/' + snippet )
} )
2015-09-25 10:29:01 +00:00
2017-03-08 18:41:05 +00:00
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' )
2015-09-25 10:29:01 +00:00
}
2016-04-20 10:06:36 +00:00
// clipboard modal
2017-03-08 18:41:05 +00:00
// fix for wrong autofocus
2015-05-15 04:58:13 +00:00
$ ( '#clipboardModal' ) . on ( 'shown.bs.modal' , function ( ) {
2017-03-08 18:41:05 +00:00
$ ( '#clipboardModal' ) . blur ( )
} )
$ ( '#clipboardModalClear' ) . click ( function ( ) {
$ ( '#clipboardModalContent' ) . html ( '' )
} )
$ ( '#clipboardModalConfirm' ) . click ( function ( ) {
var data = $ ( '#clipboardModalContent' ) . html ( )
if ( data ) {
parseToEditor ( data )
$ ( '#clipboardModal' ) . modal ( 'hide' )
$ ( '#clipboardModalContent' ) . html ( '' )
}
} )
2016-04-20 10:06:36 +00:00
// refresh modal
2015-07-01 16:10:20 +00:00
$ ( '#refreshModalRefresh' ) . click ( function ( ) {
2017-03-08 18:41:05 +00:00
location . reload ( true )
} )
2015-05-15 04:58:13 +00:00
2016-04-20 10:06:36 +00:00
// gist import modal
2017-03-08 18:41:05 +00:00
$ ( '#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 )
2016-04-20 10:06:36 +00:00
} else {
2017-03-08 18:41:05 +00:00
ui . spinner . show ( )
$ . get ( 'https://api.github.com/gists/' + window . url ( '-1' , gisturl ) )
2016-07-30 04:19:42 +00:00
. done ( function ( data ) {
2017-03-08 18:41:05 +00:00
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 )
}
2016-04-20 10:06:36 +00:00
} )
2016-07-30 04:19:42 +00:00
. fail ( function ( data ) {
2017-03-08 18:41:05 +00:00
showMessageModal ( '<i class="fa fa-github"></i> Import from Gist' , 'Not a valid Gist URL :(' , '' , JSON . stringify ( data ) , false )
2016-04-20 10:06:36 +00:00
} )
2016-07-30 04:19:42 +00:00
. always ( function ( ) {
2017-03-08 18:41:05 +00:00
ui . spinner . hide ( )
} )
2016-04-20 10:06:36 +00:00
}
2017-03-08 18:41:05 +00:00
}
} )
2016-04-20 10:06:36 +00:00
2016-05-10 02:38:13 +00:00
// snippet import modal
2017-03-08 18:41:05 +00:00
$ ( '#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 )
2016-07-30 04:19:42 +00:00
. done ( function ( raw ) {
2017-03-08 18:41:05 +00:00
if ( raw ) {
content += '\n\n'
if ( fileInfo [ 1 ] !== 'md' ) {
content += '```' + window . fileTypes [ fileInfo [ 1 ] ] + '\n'
2016-05-11 21:05:25 +00:00
}
2017-03-08 18:41:05 +00:00
content += raw
if ( fileInfo [ 1 ] !== 'md' ) {
content += '\n```'
}
replaceAll ( content )
}
2016-05-11 21:05:25 +00:00
} )
2016-07-30 04:19:42 +00:00
. fail ( function ( data ) {
2017-03-08 18:41:05 +00:00
showMessageModal ( '<i class="fa fa-gitlab"></i> Import from Snippet' , 'Not a valid Snippet URL :(' , '' , JSON . stringify ( data ) , false )
2016-05-11 21:05:25 +00:00
} )
2016-07-30 04:19:42 +00:00
. always ( function ( ) {
2017-03-08 18:41:05 +00:00
ui . spinner . hide ( )
} )
2016-05-10 02:38:13 +00:00
} )
2016-07-30 04:19:42 +00:00
. fail ( function ( data ) {
2017-03-08 18:41:05 +00:00
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
2016-05-12 16:28:08 +00:00
, data
2017-03-08 18:41:05 +00:00
, 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 )
2016-05-12 16:28:08 +00:00
}
, 'json'
2017-03-08 18:41:05 +00:00
)
} )
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 ( )
}
} )
2015-05-04 07:53:29 +00:00
}
2015-05-15 04:58:13 +00:00
2017-03-08 18:41:05 +00:00
// mode
2015-05-04 07:53:29 +00:00
ui . toolbar . mode . click ( function ( ) {
2017-03-08 18:41:05 +00:00
toggleMode ( )
} )
// edit
2015-05-04 07:53:29 +00:00
ui . toolbar . edit . click ( function ( ) {
2017-03-08 18:41:05 +00:00
changeMode ( modeType . edit )
} )
// view
2015-05-04 07:53:29 +00:00
ui . toolbar . view . click ( function ( ) {
2017-03-08 18:41:05 +00:00
changeMode ( modeType . view )
} )
// both
2015-05-04 07:53:29 +00:00
ui . toolbar . both . click ( function ( ) {
2017-03-08 18:41:05 +00:00
changeMode ( modeType . both )
} )
// permission
// freely
2015-07-01 16:10:20 +00:00
ui . infobar . permission . freely . click ( function ( ) {
2017-03-08 18:41:05 +00:00
emitPermission ( 'freely' )
} )
// editable
2015-07-01 16:10:20 +00:00
ui . infobar . permission . editable . click ( function ( ) {
2017-03-08 18:41:05 +00:00
emitPermission ( 'editable' )
} )
// locked
2015-07-01 16:10:20 +00:00
ui . infobar . permission . locked . click ( function ( ) {
2017-03-08 18:41:05 +00:00
emitPermission ( 'locked' )
} )
// private
2016-01-17 15:51:27 +00:00
ui . infobar . permission . private . click ( function ( ) {
2017-03-08 18:41:05 +00:00
emitPermission ( 'private' )
} )
// limited
ui . infobar . permission . limited . click ( function ( ) {
emitPermission ( 'limited' )
} )
// protected
ui . infobar . permission . protected . click ( function ( ) {
emitPermission ( 'protected' )
} )
2016-10-10 13:04:24 +00:00
// delete note
ui . infobar . delete . click ( function ( ) {
2017-03-08 18:41:05 +00:00
$ ( '.delete-modal' ) . modal ( 'show' )
} )
2016-10-10 13:04:24 +00:00
$ ( '.ui-delete-modal-confirm' ) . click ( function ( ) {
2017-03-08 18:41:05 +00:00
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
2015-09-25 06:02:34 +00:00
}
2016-10-09 02:51:39 +00:00
// global module workaround
2017-03-08 18:41:05 +00:00
window . havePermission = havePermission
2015-09-25 06:02:34 +00:00
2017-03-08 18:41:05 +00:00
// socket.io actions
var io = require ( 'socket.io-client' )
2015-09-25 10:34:34 +00:00
var socket = io . connect ( {
2017-03-08 18:41:05 +00:00
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
2015-06-01 10:04:25 +00:00
socket . on = function ( ) {
2017-03-08 18:41:05 +00:00
if ( ! checkLoginStateChanged ( ) && ! window . needRefresh ) { return on . apply ( socket , arguments ) }
}
var emit = socket . emit
2015-06-01 10:04:25 +00:00
socket . emit = function ( ) {
2017-03-08 18:41:05 +00:00
if ( ! checkLoginStateChanged ( ) && ! window . needRefresh ) { emit . apply ( socket , arguments ) }
}
2015-05-04 07:53:29 +00:00
socket . on ( 'info' , function ( data ) {
2017-03-08 18:41:05 +00:00
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
}
} )
2015-12-30 05:33:36 +00:00
socket . on ( 'error' , function ( data ) {
2017-03-08 18:41:05 +00:00
console . error ( data )
if ( data . message && data . message . indexOf ( 'AUTH failed' ) === 0 ) { location . href = serverurl + '/403' }
} )
2016-10-10 13:04:24 +00:00
socket . on ( 'delete' , function ( ) {
2017-03-08 18:41:05 +00:00
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
2016-06-17 08:31:36 +00:00
socket . on ( 'maintenance' , function ( ) {
2017-03-08 18:41:05 +00:00
cmClient . revision = - 1
} )
2015-05-04 07:53:29 +00:00
socket . on ( 'disconnect' , function ( data ) {
2017-03-08 18:41:05 +00:00
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 )
}
} )
2015-06-01 10:04:25 +00:00
socket . on ( 'reconnect' , function ( data ) {
2017-03-08 18:41:05 +00:00
// sync back any change in offline
emitUserStatus ( true )
cursorActivity ( )
socket . emit ( 'online users' )
} )
2016-07-02 08:13:34 +00:00
socket . on ( 'connect' , function ( data ) {
2017-03-08 18:41:05 +00:00
clearInterval ( retryTimer )
retryTimer = null
window . personalInfo [ 'id' ] = socket . id
showStatus ( statusType . connected )
socket . emit ( 'version' )
} )
2015-05-04 07:53:29 +00:00
socket . on ( 'version' , function ( data ) {
2017-03-08 18:41:05 +00:00
if ( version !== data . version ) {
if ( version < data . minimumCompatibleVersion ) {
setRefreshModal ( 'incompatible-version' )
setNeedRefresh ( )
} else {
setRefreshModal ( 'new-version' )
2016-07-30 03:28:24 +00:00
}
2017-03-08 18:41:05 +00:00
}
} )
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 ( )
}
2016-07-30 03:28:24 +00:00
}
2017-01-02 03:05:49 +00:00
var updateAuthorship = _ . debounce ( function ( ) {
2017-03-08 18:41:05 +00:00
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);'
2016-07-30 03:28:24 +00:00
var addStyleRule = ( function ( ) {
2017-03-08 18:41:05 +00:00
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 ( ) {
2016-07-30 03:28:24 +00:00
// ignore when ot not synced yet
2017-03-08 18:41:05 +00:00
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 &&
2016-07-30 03:28:24 +00:00
_textMarker . className . indexOf ( className ) > - 1 ) {
2017-03-08 18:41:05 +00:00
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' ] ||
2016-09-18 08:22:50 +00:00
! gutters [ 'authorship-gutters' ] . className ||
! gutters [ 'authorship-gutters' ] . className . indexOf ( className ) < 0 ) {
2017-03-08 18:41:05 +00:00
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 )
}
}
}
2016-09-18 08:22:50 +00:00
}
2016-07-30 03:28:24 +00:00
editor . on ( 'update' , function ( ) {
2017-03-08 18:41:05 +00:00
$ ( '.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 }
} )
2016-07-30 03:28:24 +00:00
// clear tooltip which described element has been removed
2017-03-08 18:41:05 +00:00
$ ( '[id^="tooltip"]' ) . each ( function ( index , element ) {
var $ele = $ ( element )
if ( $ ( '[aria-describedby="' + $ele . attr ( 'id' ) + '"]' ) . length <= 0 ) $ele . remove ( )
} )
} )
2015-07-01 16:10:20 +00:00
socket . on ( 'check' , function ( data ) {
2017-03-08 18:41:05 +00:00
// console.log(data);
updateInfo ( data )
} )
2015-07-01 16:10:20 +00:00
socket . on ( 'permission' , function ( data ) {
2017-03-08 18:41:05 +00:00
updatePermission ( data . permission )
} )
var docmaxlength = null
var permission = null
2015-05-04 07:53:29 +00:00
socket . on ( 'refresh' , function ( data ) {
2017-03-08 18:41:05 +00:00
// console.log(data);
docmaxlength = data . docmaxlength
editor . setOption ( 'maxLength' , docmaxlength )
updateInfo ( data )
updatePermission ( data . permission )
if ( ! window . loaded ) {
2016-06-17 07:59:50 +00:00
// auto change mode if no content detected
2017-03-08 18:41:05 +00:00
var nocontent = editor . getValue ( ) . length <= 0
if ( nocontent ) {
if ( window . visibleXS ) { window . currentMode = modeType . edit } else { window . currentMode = modeType . both }
}
2016-09-18 08:33:33 +00:00
// parse mode from url
2017-03-08 18:41:05 +00:00
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 ) }
} )
2015-07-11 04:43:08 +00:00
2017-03-08 18:41:05 +00:00
var EditorClient = ot . EditorClient
var SocketIOAdapter = ot . SocketIOAdapter
var CodeMirrorAdapter = ot . CodeMirrorAdapter
var cmClient = null
var synchronized _ = null
2015-05-04 07:53:29 +00:00
2017-03-08 18:41:05 +00:00
function havePendingOperation ( ) {
return ! ! ( ( cmClient && cmClient . state && cmClient . state . hasOwnProperty ( 'outstanding' ) ) )
}
2015-05-04 07:53:29 +00:00
2017-03-08 18:41:05 +00:00
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 (
2015-09-25 05:59:28 +00:00
obj . revision , obj . clients ,
new SocketIOAdapter ( socket ) , new CodeMirrorAdapter ( editor )
2017-03-08 18:41:05 +00:00
)
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 ( )
} )
2015-07-01 16:10:20 +00:00
2015-09-25 10:01:15 +00:00
socket . on ( 'ack' , function ( ) {
2017-03-08 18:41:05 +00:00
window . isDirty = true
updateView ( )
} )
2015-07-01 16:10:20 +00:00
2015-09-25 10:01:15 +00:00
socket . on ( 'operation' , function ( ) {
2017-03-08 18:41:05 +00:00
window . isDirty = true
updateView ( )
} )
2015-07-01 16:10:20 +00:00
2015-05-04 07:53:29 +00:00
socket . on ( 'online users' , function ( data ) {
2017-03-08 18:41:05 +00:00
if ( debug ) { console . debug ( data ) }
window . onlineUsers = data . users
updateOnlineStatus ( )
$ ( '.CodeMirror-other-cursors' ) . children ( ) . each ( function ( key , value ) {
var found = false
2015-05-15 04:58:13 +00:00
for ( var i = 0 ; i < data . users . length ; i ++ ) {
2017-03-08 18:41:05 +00:00
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 }
}
} )
2015-06-01 10:04:25 +00:00
socket . on ( 'user status' , function ( data ) {
2017-03-08 18:41:05 +00:00
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 ) }
} )
2015-05-04 07:53:29 +00:00
socket . on ( 'cursor focus' , function ( data ) {
2017-03-08 18:41:05 +00:00
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 ( )
}
} )
2015-05-04 07:53:29 +00:00
socket . on ( 'cursor activity' , function ( data ) {
2017-03-08 18:41:05 +00:00
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
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
}
if ( data . id !== socket . id ) { buildCursor ( data ) }
} )
2015-05-04 07:53:29 +00:00
socket . on ( 'cursor blur' , function ( data ) {
2017-03-08 18:41:05 +00:00
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 ( )
}
} )
2015-05-15 04:58:13 +00:00
2015-06-01 10:04:25 +00:00
var options = {
2017-03-08 18:41:05 +00:00
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 )
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
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
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
}
if ( ! found ) { _onlineUsers . push ( user ) }
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
}
return _onlineUsers
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
var userStatusCache = null
2015-06-01 10:04:25 +00:00
2017-03-08 18:41:05 +00:00
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' }
2015-06-01 10:04:25 +00:00
2017-03-08 18:41:05 +00:00
window . personalInfo [ 'idle' ] = idle . isAway
window . personalInfo [ 'type' ] = type
2015-06-01 10:04:25 +00:00
2017-03-08 18:41:05 +00:00
for ( var i = 0 ; i < window . onlineUsers . length ; i ++ ) {
if ( window . onlineUsers [ i ] . id === window . personalInfo . id ) {
window . onlineUsers [ i ] = window . personalInfo
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
}
2015-06-01 10:04:25 +00:00
2017-03-08 18:41:05 +00:00
var userStatus = {
idle : idle . isAway ,
type : type
}
2015-06-01 10:04:25 +00:00
2017-03-08 18:41:05 +00:00
if ( force || JSON . stringify ( userStatus ) !== JSON . stringify ( userStatusCache ) ) {
socket . emit ( 'user status' , userStatus )
userStatusCache = userStatus
}
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
function checkCursorTag ( coord , ele ) {
if ( ! ele ) return // return if element not exists
2016-03-15 03:12:45 +00:00
// set margin
2017-03-08 18:41:05 +00:00
var tagRightMargin = 0
var tagBottomMargin = 2
2016-03-15 03:12:45 +00:00
// use sizer to get the real doc size (won't count status bar and gutters)
2017-03-08 18:41:05 +00:00
var docWidth = ui . area . codemirrorSizer . width ( )
2016-03-15 03:12:45 +00:00
// get editor size (status bar not count in)
2017-03-08 18:41:05 +00:00
var editorHeight = ui . area . codemirror . height ( )
2016-03-15 03:12:45 +00:00
// get element size
2017-03-08 18:41:05 +00:00
var width = ele . outerWidth ( )
var height = ele . outerHeight ( )
var padding = ( ele . outerWidth ( ) - ele . width ( ) ) / 2
2016-03-15 03:12:45 +00:00
// get coord position
2017-03-08 18:41:05 +00:00
var left = coord . left
var top = coord . top
2016-06-17 07:55:27 +00:00
// get doc top offset (to workaround with viewport)
2017-03-08 18:41:05 +00:00
var docTopOffset = ui . area . codemirrorSizerInner . position ( ) . top
2016-03-15 03:12:45 +00:00
// set offset
2017-03-08 18:41:05 +00:00
var offsetLeft = - 3
var offsetTop = defaultTextHeight
2016-03-15 03:12:45 +00:00
// only do when have width and height
2017-03-08 18:41:05 +00:00
if ( width > 0 && height > 0 ) {
2016-03-15 03:12:45 +00:00
// flip x when element right bound larger than doc width
2017-03-08 18:41:05 +00:00
if ( left + width + offsetLeft + tagRightMargin > docWidth ) {
offsetLeft = - ( width + tagRightMargin ) + padding + offsetLeft
}
2016-03-15 03:12:45 +00:00
// flip y when element bottom bound larger than doc height
// and element top position is larger than element height
2017-03-08 18:41:05 +00:00
if ( top + docTopOffset + height + offsetTop + tagBottomMargin > Math . max ( editor . doc . height , editorHeight ) && top + docTopOffset > height + tagBottomMargin ) {
offsetTop = - ( height )
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
}
2016-03-15 03:12:45 +00:00
// set position
2017-03-08 18:41:05 +00:00
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 (
2015-06-01 10:04:25 +00:00
function ( ) {
2017-03-08 18:41:05 +00:00
if ( cursor . attr ( 'data-mode' ) === 'hover' ) { cursortag . stop ( true ) . fadeIn ( 'fast' ) }
2015-06-01 10:04:25 +00:00
} ,
function ( ) {
2017-03-08 18:41:05 +00:00
if ( cursor . attr ( 'data-mode' ) === 'hover' ) { cursortag . stop ( true ) . fadeOut ( 'fast' ) }
} )
2015-06-01 10:04:25 +00:00
2017-03-08 18:41:05 +00:00
var hideCursorTagDelay = 2000
var hideCursorTagTimer = null
2015-06-01 10:04:25 +00:00
2017-03-08 18:41:05 +00:00
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' ) }
2015-05-04 07:53:29 +00:00
}
2017-03-08 18:41:05 +00:00
var switchTag = function ( ele ) {
if ( ele . css ( 'display' ) === 'none' ) { ele . stop ( true ) . fadeIn ( 'fast' ) } else { ele . stop ( true ) . fadeOut ( 'fast' ) }
2017-01-02 03:05:05 +00:00
}
2017-03-08 18:41:05 +00:00
var hideCursorTag = function ( ) {
if ( cursor . attr ( 'data-mode' ) === 'hover' ) { cursortag . fadeOut ( 'fast' ) }
2015-07-16 14:46:06 +00:00
}
2017-03-08 18:41:05 +00:00
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'
2015-09-25 10:48:45 +00:00
} else {
2017-03-08 18:41:05 +00:00
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 }
} )
2016-04-24 04:30:16 +00:00
editor . on ( 'cut' , function ( ) {
2017-03-08 18:41:05 +00:00
// na
} )
2016-04-24 04:30:16 +00:00
editor . on ( 'paste' , function ( ) {
2017-03-08 18:41:05 +00:00
// na
} )
2015-09-25 10:05:50 +00:00
editor . on ( 'changes' , function ( cm , changes ) {
2017-03-08 18:41:05 +00:00
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
}
}
} )
2015-05-04 07:53:29 +00:00
editor . on ( 'focus' , function ( cm ) {
2017-03-08 18:41:05 +00:00
for ( var i = 0 ; i < window . onlineUsers . length ; i ++ ) {
if ( window . onlineUsers [ i ] . id === window . personalInfo . id ) {
window . onlineUsers [ i ] . cursor = editor . getCursor ( )
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
}
window . personalInfo [ 'cursor' ] = editor . getCursor ( )
socket . emit ( 'cursor focus' , editor . getCursor ( ) )
} )
2015-05-04 07:53:29 +00:00
editor . on ( 'cursorActivity' , function ( cm ) {
2017-03-08 18:41:05 +00:00
updateStatusBar ( )
cursorActivity ( )
} )
2015-09-24 06:59:45 +00:00
editor . on ( 'beforeSelectionChange' , function ( doc , selections ) {
2017-03-08 18:41:05 +00:00
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 ( )
}
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
window . personalInfo [ 'cursor' ] = editor . getCursor ( )
socket . emit ( 'cursor activity' , editor . getCursor ( ) )
}
2015-05-04 07:53:29 +00:00
}
editor . on ( 'blur' , function ( cm ) {
2017-03-08 18:41:05 +00:00
for ( var i = 0 ; i < window . onlineUsers . length ; i ++ ) {
if ( window . onlineUsers [ i ] . id === window . personalInfo . id ) {
window . onlineUsers [ i ] . cursor = null
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
}
window . personalInfo [ 'cursor' ] = null
socket . emit ( 'cursor blur' )
} )
2015-05-04 07:53:29 +00:00
2017-03-08 18:41:05 +00:00
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
2015-05-04 07:53:29 +00:00
}
2017-03-08 18:41:05 +00:00
window . lastInfo . needRestore = false
}
2015-05-04 07:53:29 +00:00
}
2017-03-08 18:41:05 +00:00
// view actions
function refreshView ( ) {
ui . area . markdown . html ( '' )
window . isDirty = true
updateViewInner ( )
2015-05-04 07:53:29 +00:00
}
2016-09-18 09:04:33 +00:00
var updateView = _ . debounce ( function ( ) {
2017-03-08 18:41:05 +00:00
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 ( )
2016-07-30 03:07:08 +00:00
// prevent XSS
2017-03-08 18:41:05 +00:00
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 ( )
}
2016-07-30 03:07:08 +00:00
// only render again when meta changed
2017-03-08 18:41:05 +00:00
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 )
}
2016-07-30 03:07:08 +00:00
// prevent XSS
2017-03-08 18:41:05 +00:00
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 ( )
}
2017-03-14 08:27:55 +00:00
removeDOMEvents ( ui . area . markdown )
2017-03-08 18:41:05 +00:00
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
2015-09-25 10:01:15 +00:00
var updateHistory = _ . debounce ( updateHistoryInner , updateHistoryDebounce )
2017-03-08 18:41:05 +00:00
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 ) ) }
2015-06-01 10:04:25 +00:00
}
if ( $ ( '.cursor-menu' ) . length <= 0 ) {
2017-03-08 18:41:05 +00:00
$ ( "<div class='cursor-menu'>" ) . insertAfter ( '.CodeMirror-cursors' )
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
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
2016-03-15 03:12:45 +00:00
}
2017-03-08 18:41:05 +00:00
var checkCursorMenu = _ . throttle ( checkCursorMenuInner , cursorMenuThrottle )
2016-02-25 05:45:02 +00:00
2017-03-08 18:41:05 +00:00
function checkCursorMenuInner ( ) {
2016-03-15 03:12:45 +00:00
// get element
2017-03-08 18:41:05 +00:00
var dropdown = $ ( '.cursor-menu > .dropdown-menu' )
2016-03-15 03:12:45 +00:00
// return if not exists
2017-03-08 18:41:05 +00:00
if ( dropdown . length <= 0 ) return
2016-03-15 03:12:45 +00:00
// set margin
2017-03-08 18:41:05 +00:00
var menuRightMargin = 10
var menuBottomMargin = 4
2016-03-15 03:12:45 +00:00
// use sizer to get the real doc size (won't count status bar and gutters)
2017-03-08 18:41:05 +00:00
var docWidth = ui . area . codemirrorSizer . width ( )
2016-03-15 03:12:45 +00:00
// get editor size (status bar not count in)
2017-03-08 18:41:05 +00:00
var editorHeight = ui . area . codemirror . height ( )
2016-03-15 03:12:45 +00:00
// get element size
2017-03-08 18:41:05 +00:00
var width = dropdown . outerWidth ( )
var height = dropdown . outerHeight ( )
2016-03-15 03:12:45 +00:00
// get cursor
2017-03-08 18:41:05 +00:00
var cursor = editor . getCursor ( )
2016-03-15 03:12:45 +00:00
// set element cursor data
2017-03-08 18:41:05 +00:00
if ( ! dropdown . hasClass ( 'CodeMirror-other-cursor' ) ) { dropdown . addClass ( 'CodeMirror-other-cursor' ) }
dropdown . attr ( 'data-line' , cursor . line )
dropdown . attr ( 'data-ch' , cursor . ch )
2016-03-15 03:12:45 +00:00
// get coord position
2017-03-08 18:41:05 +00:00
var coord = editor . charCoords ( {
line : cursor . line ,
ch : cursor . ch
} , 'windows' )
var left = coord . left
var top = coord . top
2016-06-17 07:55:27 +00:00
// get doc top offset (to workaround with viewport)
2017-03-08 18:41:05 +00:00
var docTopOffset = ui . area . codemirrorSizerInner . position ( ) . top
2016-03-15 03:12:45 +00:00
// set offset
2017-03-08 18:41:05 +00:00
var offsetLeft = 0
var offsetTop = defaultTextHeight
2016-12-06 17:34:42 +00:00
// set up side down
2017-03-08 18:41:05 +00:00
window . upSideDown = false
var lastUpSideDown = window . upSideDown = false
2016-03-15 03:12:45 +00:00
// only do when have width and height
2017-03-08 18:41:05 +00:00
if ( width > 0 && height > 0 ) {
2016-03-15 03:12:45 +00:00
// make element right bound not larger than doc width
2017-03-08 18:41:05 +00:00
if ( left + width + offsetLeft + menuRightMargin > docWidth ) { offsetLeft = - ( left + width - docWidth + menuRightMargin ) }
2016-03-15 03:12:45 +00:00
// flip y when element bottom bound larger than doc height
// and element top position is larger than element height
2017-03-08 18:41:05 +00:00
if ( top + docTopOffset + height + offsetTop + menuBottomMargin > Math . max ( editor . doc . height , editorHeight ) && top + docTopOffset > height + menuBottomMargin ) {
offsetTop = - ( height + menuBottomMargin )
2016-03-15 03:12:45 +00:00
// reverse sort menu because upSideDown
2017-03-08 18:41:05 +00:00
dropdown . html ( reverseSortCursorMenu ( dropdown ) )
window . upSideDown = true
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
var textCompleteDropdown = $ ( editor . getInputField ( ) ) . data ( 'textComplete' ) . dropdown
lastUpSideDown = textCompleteDropdown . upSideDown
textCompleteDropdown . upSideDown = window . upSideDown
}
2016-03-15 03:12:45 +00:00
// make menu scroll top only if upSideDown changed
2017-03-08 18:41:05 +00:00
if ( window . upSideDown !== lastUpSideDown ) { dropdown . scrollTop ( dropdown [ 0 ] . scrollHeight ) }
2016-03-15 03:12:45 +00:00
// set element offset data
2017-03-08 18:41:05 +00:00
dropdown . attr ( 'data-offset-left' , offsetLeft )
dropdown . attr ( 'data-offset-top' , offsetTop )
2016-03-15 03:12:45 +00:00
// set position
2017-03-08 18:41:05 +00:00
dropdown [ 0 ] . style . left = left + offsetLeft + 'px'
dropdown [ 0 ] . style . top = top + offsetTop + 'px'
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
function checkInIndentCode ( ) {
2016-03-15 03:04:45 +00:00
// if line starts with tab or four spaces is a code block
2017-03-08 18:41:05 +00:00
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 )
2015-06-01 10:04:25 +00:00
if ( match && match . length % 2 ) {
2017-03-08 18:41:05 +00:00
return true
2015-06-01 10:04:25 +00:00
} else {
2017-03-08 18:41:05 +00:00
return false
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
}
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
var isInContainer = false
var isInContainerSyntax = false
2016-03-15 03:04:45 +00:00
2017-03-08 18:41:05 +00:00
function checkInContainer ( ) {
isInContainer = checkAbove ( matchInContainer ) && ! checkInIndentCode ( )
2016-03-15 03:04:45 +00:00
}
2017-03-08 18:41:05 +00:00
function checkInContainerSyntax ( ) {
2016-03-15 03:04:45 +00:00
// if line starts with :::, it's in container syntax
2017-03-08 18:41:05 +00:00
var line = editor . getLine ( editor . getCursor ( ) . line )
isInContainerSyntax = ( line . substr ( 0 , 3 ) === ':::' )
2016-03-15 03:04:45 +00:00
}
2017-03-08 18:41:05 +00:00
function matchInContainer ( text ) {
var match
match = text . match ( /:{3,}/g )
if ( match && match . length % 2 ) {
return true
} else {
return false
}
2016-03-15 03:04:45 +00:00
}
2015-06-01 10:04:25 +00:00
$ ( editor . getInputField ( ) )
. textcomplete ( [
2017-03-08 18:41:05 +00:00
{ // 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 )
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
} )
$ . map ( window . emojify . emojiNames , function ( emoji ) {
if ( emoji . indexOf ( term ) !== - 1 ) { // match inside the word
list . push ( emoji )
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
} )
callback ( list )
2016-04-24 04:30:16 +00:00
} ,
2017-03-08 18:41:05 +00:00
template : function ( value ) {
return '<img class="emoji" src="' + serverurl + '/build/emojify.js/dist/images/basic/' + value + '.png"></img> ' + value
2016-04-24 04:30:16 +00:00
} ,
2017-03-08 18:41:05 +00:00
replace : function ( value ) {
return '$1:' + value + ': '
2016-04-24 04:30:16 +00:00
} ,
2017-03-08 18:41:05 +00:00
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 )
2016-04-24 04:30:16 +00:00
} ,
2017-03-08 18:41:05 +00:00
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 }
2016-04-24 04:30:16 +00:00
} ,
2017-03-08 18:41:05 +00:00
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' ) }
2016-04-24 04:30:16 +00:00
} ,
2017-03-08 18:41:05 +00:00
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 )
2016-03-15 03:04:45 +00:00
} ,
2017-03-08 18:41:05 +00:00
replace : function ( lang ) {
var ending = ''
if ( ! checkBelow ( matchInContainer ) ) {
ending = '\n\n:::'
}
if ( this . containers . indexOf ( lang ) !== - 1 ) { return '$1:::$2' + lang + ending }
2016-03-15 03:04:45 +00:00
} ,
2017-03-08 18:41:05 +00:00
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' ) }
2015-06-01 10:04:25 +00:00
} ,
2017-03-08 18:41:05 +00:00
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
} ) )
2015-06-01 10:04:25 +00:00
} ,
2017-03-08 18:41:05 +00:00
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
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
}
] , {
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' , defaultExtraKeys )
}
} )