2017-03-08 18:41:05 +00:00
/* eslint-env browser, jquery */
2019-08-01 15:56:26 +00:00
/ * 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 , s e r v e r u r l ,
2019-05-24 03:40:16 +00:00
key , Dropbox , ot , hex2rgb , Visibility , inlineAttachment * /
2016-10-07 15:06:10 +00:00
2018-11-21 10:11:47 +00:00
import TurndownService from 'turndown'
2016-10-07 15:06:10 +00:00
2017-03-28 10:31:36 +00:00
import { saveAs } from 'file-saver'
import randomColor from 'randomcolor'
2018-03-23 14:39:16 +00:00
import store from 'store'
2018-10-10 09:13:44 +00:00
import hljs from 'highlight.js'
2016-10-13 03:42:17 +00:00
2020-02-29 11:51:11 +00:00
import isURL from 'validator/lib/isURL'
2017-03-28 10:31:36 +00:00
import _ from 'lodash'
2017-03-08 18:41:05 +00:00
2019-04-11 11:34:55 +00:00
import wurl from 'wurl'
2017-03-28 10:31:36 +00:00
import List from 'list.js'
2016-10-13 00:56:56 +00:00
2019-04-11 10:22:24 +00:00
import Idle from '@hackmd/idle-js'
2019-08-01 17:03:16 +00:00
import { Spinner } from 'spin.js'
2019-08-01 15:56:26 +00:00
2017-01-05 08:48:23 +00:00
import {
2019-04-12 09:55:01 +00:00
checkLoginStateChanged ,
setloginStateChangeEvent
2017-03-08 18:41:05 +00:00
} from './lib/common/login'
2017-01-13 14:51:44 +00:00
import {
2019-04-12 09:55:01 +00:00
debug ,
DROPBOX _APP _KEY ,
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 {
2019-04-12 09:55:01 +00:00
autoLinkify ,
deduplicatedHeaderId ,
exportToHTML ,
exportToRawHTML ,
removeDOMEvents ,
finishView ,
generateToc ,
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 {
2019-04-12 09:55:01 +00:00
clearMap ,
setupSyncAreas ,
syncScrollToEdit ,
syncScrollToView
2017-04-11 13:33:54 +00:00
} from './lib/syncscroll'
2016-10-13 16:02:55 +00:00
2017-01-05 12:56:16 +00:00
import {
2019-04-12 09:55:01 +00:00
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-28 10:31:36 +00:00
import { preventXSS } from './render'
2016-10-07 15:06:10 +00:00
2017-03-13 13:32:50 +00:00
import Editor from './lib/editor'
2015-06-01 10:04:25 +00:00
2017-03-13 13:32:50 +00:00
import getUIElements from './lib/editor/ui-elements'
2020-02-06 06:31:25 +00:00
import { emojifyImageDir } from './lib/editor/constants'
2017-04-12 01:21:13 +00:00
import modeType from './lib/modeType'
import appState from './lib/appState'
2016-09-18 08:35:24 +00:00
2019-04-12 09:55:01 +00:00
require ( '../vendor/showup/showup' )
require ( '../css/index.css' )
require ( '../css/extra.css' )
require ( '../css/slide-preview.css' )
require ( '../css/site.css' )
2019-08-01 15:56:26 +00:00
require ( 'spin.js/spin.css' )
2019-04-12 09:55:01 +00:00
require ( 'highlight.js/styles/github-gist.css' )
2017-03-08 18:41:05 +00:00
var defaultTextHeight = 20
var viewportMargin = 20
var defaultEditorMode = 'gfm'
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
2019-08-03 03:53:42 +00:00
var supportContainers = [ 'success' , 'info' , 'warning' , 'danger' , 'spoiler' ]
2018-10-10 09:13:44 +00:00
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' , 'gherkin' ] . concat ( hljs . listLanguages ( ) )
2019-09-28 10:56:14 +00:00
var supportCharts = [ 'sequence' , 'flow' , 'graphviz' , 'mermaid' , 'abc' , 'plantuml' , 'vega' , 'geo' ]
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:'
}
]
2017-04-11 04:07:04 +00:00
const 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 : '[]'
}
]
2017-04-11 04:07:04 +00:00
const 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'
}
]
2017-04-11 04:07:04 +00:00
const supportExtraTags = [
2017-03-08 18:41:05 +00:00
{
text : '[name tag]' ,
search : '[]' ,
command : function ( ) {
2017-04-11 04:07:04 +00:00
return '[name=' + personalInfo . name + ']'
2017-03-08 18:41:05 +00:00
}
} ,
{
text : '[time tag]' ,
search : '[]' ,
command : function ( ) {
return '[time=' + moment ( ) . format ( 'llll' ) + ']'
}
} ,
{
text : '[my color tag]' ,
search : '[]' ,
command : function ( ) {
2017-04-11 04:07:04 +00:00
return '[color=' + personalInfo . color + ']'
2017-03-08 18:41:05 +00:00
}
} ,
{
text : '[random color tag]' ,
search : '[]' ,
command : function ( ) {
var color = randomColor ( )
return '[color=' + color + ']'
}
}
]
2017-04-11 04:07:04 +00:00
const 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'
}
}
// global vars
window . loaded = false
2017-04-11 03:48:39 +00:00
let needRefresh = false
let isDirty = false
let editShown = false
let visibleXS = false
let visibleSM = false
let visibleMD = false
let visibleLG = false
const isTouchDevice = 'ontouchstart' in document . documentElement
2017-04-11 04:07:04 +00:00
let currentStatus = statusType . offline
2019-08-01 17:03:16 +00:00
const 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
}
2017-04-11 04:07:04 +00:00
let personalInfo = { }
let onlineUsers = [ ]
const fileTypes = {
2019-08-01 17:03:16 +00:00
pl : 'perl' ,
cgi : 'perl' ,
js : 'javascript' ,
php : 'php' ,
sh : 'bash' ,
rb : 'ruby' ,
html : 'html' ,
py : 'python'
2017-03-08 18:41:05 +00:00
}
// editor settings
2017-04-11 04:07:04 +00:00
const textit = document . getElementById ( 'textit' )
2017-03-07 14:35:54 +00:00
if ( ! textit ) {
2017-03-13 13:32:50 +00:00
throw new Error ( 'There was no textit area!' )
2017-03-08 18:41:05 +00:00
}
2017-03-13 13:32:50 +00:00
const editorInstance = new Editor ( )
var editor = editorInstance . init ( textit )
2017-03-08 18:41:05 +00:00
2017-03-28 03:17:30 +00:00
// FIXME: global referncing in jquery-textcomplete patch
2017-03-13 13:32:50 +00:00
window . editor = editor
2017-03-08 18:41:05 +00:00
2017-03-13 13:32:50 +00:00
var inlineAttach = inlineAttachment . editors . codemirror4 . attach ( editor )
defaultTextHeight = parseInt ( $ ( '.CodeMirror' ) . css ( 'line-height' ) )
2017-03-08 18:41:05 +00:00
2017-03-07 14:35:54 +00:00
// initalize ui reference
2017-03-13 13:32:50 +00:00
const ui = getUIElements ( )
2015-05-04 07:53:29 +00:00
2017-03-08 18:41:05 +00:00
// 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
}
2019-08-01 15:56:26 +00:00
new Spinner ( opts ) . spin ( ui . spinner [ 0 ] )
2017-03-08 18:41:05 +00:00
// 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 ( ) {
2017-04-11 03:48:39 +00:00
needRefresh = true
2017-03-08 18:41:05 +00:00
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 ) {
2017-04-11 03:48:39 +00:00
if ( ! visibleXS ) {
2017-03-08 18:41:05 +00:00
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 ( )
2019-04-12 09:55:01 +00:00
// if in smaller screen, we don't need advanced scrollbar
2017-03-08 18:41:05 +00:00
var scrollbarStyle
2017-04-11 03:48:39 +00:00
if ( visibleXS ) {
2017-03-08 18:41:05 +00:00
scrollbarStyle = 'native'
} else {
scrollbarStyle = 'overlay'
}
if ( scrollbarStyle !== editor . getOption ( 'scrollbarStyle' ) ) {
editor . setOption ( 'scrollbarStyle' , scrollbarStyle )
clearMap ( )
}
checkEditorStyle ( )
2018-03-23 14:39:16 +00:00
/* cache dom references */
var $body = $ ( 'body' )
2017-03-28 12:38:31 +00:00
/* we need this only on touch devices */
2017-04-11 03:48:39 +00:00
if ( isTouchDevice ) {
2017-03-28 12:38:31 +00:00
/* bind events */
2017-03-08 18:41:05 +00:00
$ ( document )
2019-04-12 09:55:01 +00:00
. on ( 'focus' , 'textarea, input' , function ( ) {
$body . addClass ( 'fixfixed' )
} )
. on ( 'blur' , 'textarea, input' , function ( ) {
$body . removeClass ( 'fixfixed' )
} )
2017-03-08 18:41:05 +00:00
}
2018-03-23 14:39:16 +00:00
// Re-enable nightmode
if ( store . get ( 'nightMode' ) || Cookies . get ( 'nightMode' ) ) {
$body . addClass ( 'night' )
2018-03-25 18:11:53 +00:00
ui . toolbar . night . addClass ( 'active' )
2018-03-23 14:39:16 +00:00
}
2017-03-28 12:38:31 +00:00
// showup
2017-03-08 18:41:05 +00:00
$ ( ) . showUp ( '.navbar' , {
upClass : 'navbar-hide' ,
downClass : 'navbar-show'
} )
2017-03-28 12:38:31 +00:00
// tooltip
2017-03-08 18:41:05 +00:00
$ ( '[data-toggle="tooltip"]' ) . tooltip ( )
2017-03-28 12:38:31 +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 )
} )
2017-03-28 12:38:31 +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-28 12:38:31 +00:00
// updateHistoryInner();
2017-03-08 18:41:05 +00:00
} )
2016-07-30 04:19:42 +00:00
$ ( window ) . on ( 'error' , function ( ) {
2017-03-28 12:38:31 +00:00
// setNeedRefresh();
2017-03-08 18:41:05 +00:00
} )
2016-09-18 08:51:19 +00:00
2017-04-12 01:21:13 +00:00
setupSyncAreas ( ui . area . codemirrorScroll , ui . area . view , ui . area . markdown , editor )
2017-03-08 18:41:05 +00:00
function autoSyncscroll ( ) {
if ( editorHasFocus ( ) ) {
syncScrollToView ( )
} else {
syncScrollToEdit ( )
}
}
var windowResizeDebounce = 200
var windowResize = _ . debounce ( windowResizeInner , windowResizeDebounce )
function windowResizeInner ( callback ) {
checkLayout ( )
checkResponsive ( )
checkEditorStyle ( )
checkTocStyle ( )
checkCursorMenu ( )
2017-03-28 12:38:31 +00:00
// refresh editor
2017-03-08 18:41:05 +00:00
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 {
2017-03-28 12:38:31 +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 )
2017-03-28 12:38:31 +00:00
// add or update user cursors
2017-04-11 04:07:04 +00:00
for ( var i = 0 ; i < onlineUsers . length ; i ++ ) {
if ( onlineUsers [ i ] . id !== personalInfo . id ) { buildCursor ( 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 ( ) {
2017-04-11 03:48:39 +00:00
visibleXS = $ ( '.visible-xs' ) . is ( ':visible' )
visibleSM = $ ( '.visible-sm' ) . is ( ':visible' )
visibleMD = $ ( '.visible-md' ) . is ( ':visible' )
visibleLG = $ ( '.visible-lg' ) . is ( ':visible' )
2015-06-01 10:04:25 +00:00
2017-04-12 01:21:13 +00:00
if ( visibleXS && appState . currentMode === modeType . both ) {
2017-03-08 18:41:05 +00:00
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 ( ) {
2017-03-13 13:32:50 +00:00
var desireHeight = editorInstance . statusBar ? ( ui . area . edit . height ( ) - editorInstance . statusBar . outerHeight ( ) ) : ui . area . edit . height ( )
2018-06-19 14:03:32 +00:00
if ( editorInstance . toolBar ) {
2018-06-23 18:55:32 +00:00
desireHeight = desireHeight - editorInstance . toolBar . outerHeight ( )
2018-06-19 14:03:32 +00:00
}
2018-06-23 18:55:32 +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' )
2017-04-12 01:21:13 +00:00
if ( scrollbarStyle === 'overlay' || appState . currentMode === modeType . both ) {
2017-03-08 18:41:05 +00:00
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' )
}
2017-03-28 12:38:31 +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 ( ) )
2020-06-01 15:01:17 +00:00
checkEditorScrollOverLines ( )
2017-03-28 12:38:31 +00:00
// make editor resizable
2017-03-08 18:41:05 +00:00
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 ( )
2017-03-22 09:48:26 +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 ( ) {
2017-04-12 01:21:13 +00:00
appState . syncscroll = ! appState . syncscroll
2017-03-08 18:41:05 +00:00
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 ( ) {
2017-04-12 01:21:13 +00:00
if ( appState . syncscroll ) {
2017-03-08 18:41:05 +00:00
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 ( ) {
2017-03-28 12:38:31 +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 )
}
2020-06-01 15:01:17 +00:00
function checkEditorScrollOverLines ( ) {
const desireHeight = parseInt ( ui . area . codemirrorScroll [ 0 ] . style . height ) || parseInt ( ui . area . codemirrorScroll [ 0 ] . style . minHeight )
// make editor have extra padding in the bottom (except small screen)
const paddingBottom = editor . doc && editor . doc . height > defaultTextHeight ? ( desireHeight - defaultTextHeight ) : 0
if ( parseInt ( ui . area . codemirrorLines . css ( 'padding-bottom' ) ) !== paddingBottom ) {
ui . area . codemirrorLines . css ( 'padding-bottom' , paddingBottom + 'px' )
}
}
2017-03-08 18:41:05 +00:00
function checkTocStyle ( ) {
2019-04-12 09:55:01 +00:00
// toc right
2017-03-08 18:41:05 +00:00
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' )
2019-04-12 09:55:01 +00:00
// affix toc left
2017-03-08 18:41:05 +00:00
var newbool
var rightMargin = ( ui . area . markdown . parent ( ) . outerWidth ( ) - ui . area . markdown . outerWidth ( ) ) / 2
2019-04-12 09:55:01 +00:00
// for ipad or wider device
2017-03-08 18:41:05 +00:00
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
}
2017-03-28 12:38:31 +00:00
// toc scrollspy
2017-03-08 18:41:05 +00:00
ui . toc . toc . removeClass ( 'scrollspy-body, scrollspy-view' )
ui . toc . affix . removeClass ( 'scrollspy-body, scrollspy-view' )
2017-04-12 01:21:13 +00:00
if ( appState . currentMode === modeType . both ) {
2017-03-08 18:41:05 +00:00
ui . toc . toc . addClass ( 'scrollspy-view' )
ui . toc . affix . addClass ( 'scrollspy-view' )
2017-04-12 01:21:13 +00:00
} else if ( appState . currentMode !== modeType . both && ! newbool ) {
2017-03-08 18:41:05 +00:00
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 ) {
2017-04-11 04:07:04 +00:00
currentStatus = type
2017-03-08 18:41:05 +00:00
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 ( '' )
2017-04-11 04:07:04 +00:00
switch ( currentStatus ) {
2017-03-08 18:41:05 +00:00
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 ( ) {
2017-04-12 01:21:13 +00:00
switch ( appState . currentMode ) {
2017-03-08 18:41:05 +00:00
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 ) {
2019-04-12 09:55:01 +00:00
// lock navbar to prevent it hide after changeMode
2017-03-08 18:41:05 +00:00
lockNavbar ( )
saveInfo ( )
if ( type ) {
2017-04-12 01:21:13 +00:00
lastMode = appState . currentMode
appState . currentMode = type
2017-03-08 18:41:05 +00:00
}
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 )
2017-04-12 01:21:13 +00:00
switch ( appState . currentMode ) {
2017-03-08 18:41:05 +00:00
case modeType . edit :
ui . area . edit . show ( )
ui . area . view . hide ( )
2017-04-11 03:48:39 +00:00
if ( ! editShown ) {
2017-03-08 18:41:05 +00:00
editor . refresh ( )
2017-04-11 03:48:39 +00:00
editShown = true
2017-03-08 18:41:05 +00:00
}
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
}
2017-03-22 09:48:26 +00:00
// save mode to url
2017-04-12 01:21:13 +00:00
if ( history . replaceState && window . loaded ) history . replaceState ( null , '' , serverurl + '/' + noteid + '?' + appState . currentMode . name )
if ( appState . currentMode === modeType . view ) {
2017-03-08 18:41:05 +00:00
editor . getInputField ( ) . blur ( )
}
2017-04-12 01:21:13 +00:00
if ( appState . currentMode === modeType . edit || appState . currentMode === modeType . both ) {
2017-03-08 18:41:05 +00:00
ui . toolbar . uploadImage . fadeIn ( )
2017-03-22 09:48:26 +00:00
// add and update status bar
2017-03-13 13:32:50 +00:00
if ( ! editorInstance . statusBar ) {
editorInstance . addStatusBar ( )
2017-03-28 09:16:32 +00:00
editorInstance . updateStatusBar ( )
2017-03-08 18:41:05 +00:00
}
2018-06-19 14:03:32 +00:00
// add and update tool bar
if ( ! editorInstance . toolBar ) {
editorInstance . addToolBar ( )
}
2017-03-22 09:48:26 +00:00
// work around foldGutter might not init properly
2017-03-08 18:41:05 +00:00
editor . setOption ( 'foldGutter' , false )
editor . setOption ( 'foldGutter' , true )
} else {
ui . toolbar . uploadImage . fadeOut ( )
}
2017-04-12 01:21:13 +00:00
if ( appState . currentMode !== modeType . edit ) {
2017-03-08 18:41:05 +00:00
$ ( document . body ) . css ( 'background-color' , 'white' )
updateView ( )
} else {
$ ( document . body ) . css ( 'background-color' , ui . area . codemirror . css ( 'background-color' ) )
}
2019-04-12 09:55:01 +00:00
// check resizable editor style
2017-04-12 01:21:13 +00:00
if ( appState . currentMode === modeType . both ) {
2017-03-13 13:32:50 +00:00
if ( lastEditorWidth > 0 ) {
ui . area . edit . css ( 'width' , lastEditorWidth + 'px' )
2015-09-25 10:46:08 +00:00
} else {
2017-03-13 13:32:50 +00:00
ui . area . edit . css ( 'width' , '' )
}
2017-03-08 18:41:05 +00:00
ui . area . resize . handle . show ( )
} else {
ui . area . edit . css ( 'width' , '' )
ui . area . resize . handle . hide ( )
}
windowResizeInner ( )
restoreInfo ( )
2017-04-12 01:21:13 +00:00
if ( lastMode === modeType . view && appState . currentMode === modeType . both ) {
2017-03-08 18:41:05 +00:00
window . preventSyncScrollToView = 2
syncScrollToEdit ( null , true )
}
2017-04-12 01:21:13 +00:00
if ( lastMode === modeType . edit && appState . currentMode === modeType . both ) {
2017-03-08 18:41:05 +00:00
window . preventSyncScrollToEdit = 2
syncScrollToView ( null , true )
}
2017-04-12 01:21:13 +00:00
if ( lastMode === modeType . both && appState . currentMode !== modeType . both ) {
2017-03-08 18:41:05 +00:00
window . preventSyncScrollToView = false
window . preventSyncScrollToEdit = false
}
2017-04-12 01:21:13 +00:00
if ( lastMode !== modeType . edit && appState . currentMode === modeType . edit ) {
2017-03-08 18:41:05 +00:00
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>' )
2019-04-12 09:55:01 +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 )
. prop ( 'async' , true )
. prop ( 'defer' , true )
. 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
}
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' )
2019-08-25 03:02:15 +00:00
ui . modal . pandocExport . find ( '#pandoc-export-download' ) . click ( function ( e ) {
e . preventDefault ( )
const exportType = ui . modal . pandocExport . find ( 'select[name="output"]' ) . val ( )
window . open ( ` ${ noteurl } /pandoc?exportType= ${ exportType } ` , '_blank' )
} )
2017-03-08 18:41:05 +00:00
// 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 : [
{
2019-08-01 17:03:16 +00:00
url : noteurl + '/download' ,
filename : filename
2017-03-08 18:41:05 +00:00
}
] ,
error : function ( errorMessage ) {
console . error ( errorMessage )
}
}
Dropbox . save ( options )
} )
// 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' )
2019-04-12 09:55:01 +00:00
. done ( function ( data ) {
$ ( '#snippetExportModalAccessToken' ) . val ( data . accesstoken )
$ ( '#snippetExportModalBaseURL' ) . val ( data . baseURL )
$ ( '#snippetExportModalVersion' ) . val ( data . version )
$ ( '#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 ||
2017-03-08 18:41:05 +00:00
( project . permissions . project _access === null && project . permissions . group _access === null ) ||
( project . permissions . project _access !== null && project . permissions . project _access . access _level < 20 ) ) {
2019-04-12 09:55:01 +00:00
return
2017-03-08 18:41:05 +00:00
}
2019-04-12 09:55:01 +00:00
$ ( '<option>' ) . val ( project . id ) . text ( project . path _with _namespace ) . appendTo ( '#snippetExportModalProjects' )
2017-03-08 18:41:05 +00:00
} )
2019-04-12 09:55:01 +00:00
$ ( '#snippetExportModalProjects' ) . prop ( 'disabled' , false )
}
$ ( '#snippetExportModalLoading' ) . hide ( )
} )
. fail ( function ( data ) {
showMessageModal ( '<i class="fa fa-gitlab"></i> Import from Snippet' , 'Unable to fetch gitlab parameters :(' , '' , '' , false )
} )
. always ( function ( ) {
ui . spinner . hide ( )
} )
2017-03-08 18:41:05 +00:00
} )
// 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 )
} )
// import from gist
2016-04-20 10:06:36 +00:00
ui . toolbar . import . gist . click ( function ( ) {
2019-04-12 09:55:01 +00:00
// na
2017-03-08 18:41:05 +00:00
} )
// 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' )
2019-04-12 09:55:01 +00:00
. done ( function ( data ) {
$ ( '#snippetImportModalAccessToken' ) . val ( data . accesstoken )
$ ( '#snippetImportModalBaseURL' ) . val ( data . baseURL )
$ ( '#snippetImportModalVersion' ) . val ( data . version )
$ ( '#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 ||
2017-03-08 18:41:05 +00:00
( project . permissions . project _access === null && project . permissions . group _access === null ) ||
( project . permissions . project _access !== null && project . permissions . project _access . access _level < 20 ) ) {
2019-04-12 09:55:01 +00:00
return
2017-03-08 18:41:05 +00:00
}
2019-04-12 09:55:01 +00:00
$ ( '<option>' ) . val ( project . id ) . text ( project . path _with _namespace ) . appendTo ( '#snippetImportModalProjects' )
2017-03-08 18:41:05 +00:00
} )
2019-04-12 09:55:01 +00:00
$ ( '#snippetImportModalProjects' ) . prop ( 'disabled' , false )
}
$ ( '#snippetImportModalLoading' ) . hide ( )
} )
. fail ( function ( data ) {
showMessageModal ( '<i class="fa fa-gitlab"></i> Import from Snippet' , 'Unable to fetch gitlab parameters :(' , '' , '' , false )
} )
. always ( function ( ) {
ui . spinner . hide ( )
} )
2017-03-08 18:41:05 +00:00
} )
// import from clipboard
2015-05-15 04:58:13 +00:00
ui . toolbar . import . clipboard . click ( function ( ) {
2019-04-12 09:55:01 +00:00
// na
2017-03-08 18:41:05 +00:00
} )
// 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' )
2019-04-12 09:55:01 +00:00
. done ( function ( data ) {
parseRevisions ( data . revision )
initRevisionViewer ( )
} )
. fail ( function ( err ) {
if ( debug ) {
console . log ( err )
}
} )
. always ( function ( ) {
// na
} )
2017-03-08 18:41:05 +00:00
} )
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 )
2019-04-12 09:55:01 +00:00
. done ( function ( data ) {
revision = data
revisionTime = time
var lastScrollInfo = revisionViewer . getScrollInfo ( )
revisionList . children ( ) . removeClass ( 'active' )
revisionList . find ( '[data-revision-time="' + time + '"]' ) . addClass ( 'active' )
var content = revision . content
revisionViewer . setValue ( content )
revisionViewer . scrollTo ( null , lastScrollInfo . top )
revisionInsert = [ ]
revisionDelete = [ ]
// mark the text which have been insert or delete
if ( revision . patch . length > 0 ) {
var bias = 0
for ( var j = 0 ; j < revision . patch . length ; j ++ ) {
var patch = revision . patch [ j ]
var currIndex = patch . start1 + bias
for ( var i = 0 ; i < patch . diffs . length ; i ++ ) {
var diff = patch . diffs [ i ]
// ignore if diff only contains line breaks
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
}
2019-04-12 09:55:01 +00:00
}
}
revisionInsertAnnotation . update ( revisionInsert )
revisionDeleteAnnotation . update ( revisionDelete )
} )
. fail ( function ( err ) {
if ( debug ) {
console . log ( err )
}
} )
. always ( function ( ) {
// na
} )
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 ( )
2018-07-30 13:47:09 +00:00
var version = $ ( '#snippetImportModalVersion' ) . val ( )
2017-03-08 18:41:05 +00:00
$ ( '#snippetImportModalLoading' ) . show ( )
$ ( '#snippetImportModalContent' ) . val ( '/projects/' + project )
2018-07-30 13:47:09 +00:00
$ . get ( baseURL + '/api/' + version + '/projects/' + project + '/snippets?access_token=' + accesstoken )
2019-04-12 09:55:01 +00:00
. done ( function ( data ) {
$ ( '#snippetImportModalSnippets' ) . find ( 'option' ) . remove ( ) . end ( ) . append ( '<option value="init" selected="selected" disabled="disabled">Select From Available Snippets</option>' )
data . forEach ( function ( snippet ) {
$ ( '<option>' ) . val ( snippet . id ) . text ( snippet . title ) . appendTo ( $ ( '#snippetImportModalSnippets' ) )
} )
$ ( '#snippetImportModalLoading' ) . hide ( )
$ ( '#snippetImportModalSnippets' ) . prop ( 'disabled' , false )
} )
. fail ( function ( err ) {
if ( debug ) {
console . log ( err )
}
} )
. always ( function ( ) {
// na
} )
2017-03-08 18:41:05 +00:00
} )
// 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 ( ) {
2017-04-12 01:21:13 +00:00
if ( appState . currentMode === modeType . both ) {
2017-03-08 18:41:05 +00:00
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 ( ) {
2017-04-12 01:21:13 +00:00
if ( appState . currentMode === modeType . both ) {
2017-03-08 18:41:05 +00:00
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 ( )
}
2019-04-12 09:55:01 +00:00
// $(document.body).scroll();
// ui.area.view.scroll();
2017-03-08 18:41:05 +00:00
}
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 ,
2019-04-12 09:55:01 +00:00
$ ( '.scrollspy-body' ) , 0 )
2017-03-08 18:41:05 +00:00
var offset = ui . area . view . scrollTop ( ) - ui . area . view . offset ( ) . top
applyScrollspyActive ( ui . area . view . scrollTop ( ) , headerMap , headers ,
2019-04-12 09:55:01 +00:00
$ ( '.scrollspy-view' ) , offset - 10 )
2017-03-08 18:41:05 +00:00
}
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 ( '' )
2020-02-29 11:51:11 +00:00
if ( ! isURL ( gisturl ) ) {
2017-03-08 18:41:05 +00:00
showMessageModal ( '<i class="fa fa-github"></i> Import from Gist' , 'Not a valid URL :(' , '' , '' , false )
} else {
2019-04-11 11:34:55 +00:00
var hostname = wurl ( 'hostname' , gisturl )
2017-03-08 18:41:05 +00:00
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 ( )
2019-04-11 11:34:55 +00:00
$ . get ( 'https://api.github.com/gists/' + wurl ( '-1' , gisturl ) )
2019-04-12 09:55:01 +00:00
. done ( function ( data ) {
if ( data . files ) {
var contents = ''
Object . keys ( data . files ) . forEach ( function ( key ) {
contents += key
contents += '\n---\n'
contents += data . files [ key ] . content
contents += '\n\n'
} )
replaceAll ( contents )
} else {
showMessageModal ( '<i class="fa fa-github"></i> Import from Gist' , 'Unable to fetch gist files :(' , '' , '' , false )
}
} )
. fail ( function ( data ) {
showMessageModal ( '<i class="fa fa-github"></i> Import from Gist' , 'Not a valid Gist URL :(' , '' , JSON . stringify ( data ) , false )
} )
. always ( function ( ) {
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 ( )
2018-07-30 13:47:09 +00:00
var fullURL = $ ( '#snippetImportModalBaseURL' ) . val ( ) + '/api/' + $ ( '#snippetImportModalVersion' ) . val ( ) + snippeturl
2017-03-08 18:41:05 +00:00
$ . get ( fullURL + accessToken )
2019-04-12 09:55:01 +00:00
. done ( function ( data ) {
var content = '# ' + ( data . title || 'Snippet Import' )
var fileInfo = data . file _name . split ( '.' )
fileInfo [ 1 ] = ( fileInfo [ 1 ] ) ? fileInfo [ 1 ] : 'md'
$ . get ( fullURL + '/raw' + accessToken )
. done ( function ( raw ) {
if ( raw ) {
content += '\n\n'
if ( fileInfo [ 1 ] !== 'md' ) {
content += '```' + fileTypes [ fileInfo [ 1 ] ] + '\n'
}
content += raw
if ( fileInfo [ 1 ] !== 'md' ) {
content += '\n```'
}
replaceAll ( content )
}
} )
. fail ( function ( data ) {
showMessageModal ( '<i class="fa fa-gitlab"></i> Import from Snippet' , 'Not a valid Snippet URL :(' , '' , JSON . stringify ( data ) , false )
} )
. always ( function ( ) {
ui . spinner . hide ( )
} )
} )
. fail ( function ( data ) {
showMessageModal ( '<i class="fa fa-gitlab"></i> Import from Snippet' , 'Not a valid Snippet URL :(' , '' , JSON . stringify ( data ) , false )
} )
2017-03-08 18:41:05 +00:00
}
} )
// snippet export modal
$ ( '#snippetExportModalConfirm' ) . click ( function ( ) {
var accesstoken = $ ( '#snippetExportModalAccessToken' ) . val ( )
var baseURL = $ ( '#snippetExportModalBaseURL' ) . val ( )
2018-07-30 13:47:09 +00:00
var version = $ ( '#snippetExportModalVersion' ) . val ( )
2017-03-08 18:41:05 +00:00
var data = {
title : $ ( '#snippetExportModalTitle' ) . val ( ) ,
file _name : $ ( '#snippetExportModalFileName' ) . val ( ) ,
code : editor . getValue ( ) ,
2018-07-30 13:47:09 +00:00
visibility _level : $ ( '#snippetExportModalVisibility' ) . val ( ) ,
2018-10-09 06:46:25 +00:00
visibility : $ ( '#snippetExportModalVisibility' ) . val ( ) === '0' ? 'private' : ( $ ( '#snippetExportModalVisibility' ) . val ( ) === '10' ? 'internal' : 'private' )
2017-03-08 18:41:05 +00:00
}
2018-07-30 13:47:09 +00:00
2017-03-08 18:41:05 +00:00
if ( ! data . title || ! data . file _name || ! data . code || ! data . visibility _level || ! $ ( '#snippetExportModalProjects' ) . val ( ) ) return
$ ( '#snippetExportModalLoading' ) . show ( )
2018-07-30 13:47:09 +00:00
var fullURL = baseURL + '/api/' + version + '/projects/' + $ ( '#snippetExportModalProjects' ) . val ( ) + '/snippets?access_token=' + accesstoken
2017-03-08 18:41:05 +00:00
$ . post ( fullURL
2019-04-12 09:55:01 +00:00
, data
, function ( ret ) {
$ ( '#snippetExportModalLoading' ) . hide ( )
$ ( '#snippetExportModal' ) . modal ( 'hide' )
var redirect = baseURL + '/' + $ ( "#snippetExportModalProjects option[value='" + $ ( '#snippetExportModalProjects' ) . val ( ) + "']" ) . text ( ) + '/snippets/' + ret . id
showMessageModal ( '<i class="fa fa-gitlab"></i> Export to Snippet' , 'Export Successful!' , redirect , 'View Snippet Here' , true )
}
)
2017-03-08 18:41:05 +00:00
} )
function parseToEditor ( data ) {
2018-11-21 10:11:47 +00:00
var turndownService = new TurndownService ( {
defaultReplacement : function ( innerHTML , node ) {
return node . isBlock ? '\n\n' + node . outerHTML + '\n\n' : node . outerHTML
}
} )
var parsed = turndownService . turndown ( data )
2017-03-08 18:41:05 +00:00
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 ) {
2019-04-12 09:55:01 +00:00
// console.log(url);
2017-03-08 18:41:05 +00:00
if ( ! url ) return
2020-02-29 11:51:11 +00:00
if ( ! isURL ( url ) ) {
2017-03-08 18:41:05 +00:00
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 )
} )
2017-12-28 18:21:52 +00:00
ui . toolbar . night . click ( function ( ) {
toggleNightMode ( )
} )
2017-03-08 18:41:05 +00:00
// 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' )
} )
2017-12-28 18:21:52 +00:00
function toggleNightMode ( ) {
var $body = $ ( 'body' )
var isActive = ui . toolbar . night . hasClass ( 'active' )
if ( isActive ) {
$body . removeClass ( 'night' )
appState . nightMode = false
} else {
$body . addClass ( 'night' )
appState . nightMode = true
}
2018-03-23 14:39:16 +00:00
if ( store . enabled ) {
store . set ( 'nightMode' , ! isActive )
} else {
Cookies . set ( 'nightMode' , ! isActive , {
expires : 365
} )
}
2017-12-28 18:21:52 +00:00
}
2017-03-08 18:41:05 +00:00
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
}
2017-04-11 04:07:04 +00:00
if ( personalInfo . userid && window . owner && personalInfo . userid === window . owner ) {
2017-03-08 18:41:05 +00:00
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' :
2017-04-11 04:07:04 +00:00
if ( ! personalInfo . login ) {
2017-03-08 18:41:05 +00:00
bool = false
} else {
bool = true
}
break
case 'locked' :
case 'private' :
case 'protected' :
2017-04-11 04:07:04 +00:00
if ( ! window . owner || personalInfo . userid !== window . owner ) {
2017-03-08 18:41:05 +00:00
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/' : '' ,
2017-09-13 12:48:39 +00:00
query : {
noteId : noteid
} ,
2017-03-08 18:41:05 +00:00
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-04-11 03:48:39 +00:00
if ( ! checkLoginStateChanged ( ) && ! needRefresh ) { return on . apply ( socket , arguments ) }
2017-03-08 18:41:05 +00:00
}
var emit = socket . emit
2015-06-01 10:04:25 +00:00
socket . emit = function ( ) {
2017-04-11 03:48:39 +00:00
if ( ! checkLoginStateChanged ( ) && ! needRefresh ) { emit . apply ( socket , arguments ) }
2017-03-08 18:41:05 +00:00
}
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-04-11 04:07:04 +00:00
if ( personalInfo . login ) {
2017-03-08 18:41:05 +00:00
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 ( )
2017-04-11 04:07:04 +00:00
lastInfo . history = editor . getHistory ( )
2017-03-08 18:41:05 +00:00
}
if ( ! editor . getOption ( 'readOnly' ) ) { editor . setOption ( 'readOnly' , true ) }
if ( ! retryTimer ) {
retryTimer = setInterval ( function ( ) {
2017-04-11 03:48:39 +00:00
if ( ! needRefresh ) socket . connect ( )
2017-03-08 18:41:05 +00:00
} , 1000 )
}
} )
2015-06-01 10:04:25 +00:00
socket . on ( 'reconnect' , function ( data ) {
2019-04-12 09:55:01 +00:00
// sync back any change in offline
2017-03-08 18:41:05 +00:00
emitUserStatus ( true )
2017-03-28 04:15:56 +00:00
cursorActivity ( editor )
2017-03-08 18:41:05 +00:00
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
2020-07-01 03:33:41 +00:00
personalInfo . id = socket . id
2017-03-08 18:41:05 +00:00
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 ) {
2019-04-12 09:55:01 +00:00
// console.log(data);
2019-08-01 17:03:16 +00:00
if ( Object . hasOwnProperty . call ( data , 'createtime' ) && window . createtime !== data . createtime ) {
2017-03-08 18:41:05 +00:00
window . createtime = data . createtime
updateLastChange ( )
}
2019-08-01 17:03:16 +00:00
if ( Object . hasOwnProperty . call ( data , 'updatetime' ) && window . lastchangetime !== data . updatetime ) {
2017-03-08 18:41:05 +00:00
window . lastchangetime = data . updatetime
updateLastChange ( )
}
2019-08-01 17:03:16 +00:00
if ( Object . hasOwnProperty . call ( data , 'owner' ) && window . owner !== data . owner ) {
2017-03-08 18:41:05 +00:00
window . owner = data . owner
window . ownerprofile = data . ownerprofile
updateOwner ( )
}
2019-08-01 17:03:16 +00:00
if ( Object . hasOwnProperty . call ( data , 'lastchangeuser' ) && window . lastchangeuser !== data . lastchangeuser ) {
2017-03-08 18:41:05 +00:00
window . lastchangeuser = data . lastchangeuser
window . lastchangeuserprofile = data . lastchangeuserprofile
updateLastChangeUser ( )
updateOwner ( )
}
2019-08-01 17:03:16 +00:00
if ( Object . hasOwnProperty . call ( data , 'authors' ) && authors !== data . authors ) {
2017-03-08 18:41:05 +00:00
authors = data . authors
}
2019-08-01 17:03:16 +00:00
if ( Object . hasOwnProperty . call ( data , 'authorship' ) && authorship !== data . authorship ) {
2017-03-08 18:41:05 +00:00
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
}
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 ( ) {
2019-04-12 09:55:01 +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 ]
2019-08-01 17:03:16 +00:00
const author = authors [ atom [ 0 ] ]
2017-03-08 18:41:05 +00:00
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 ) {
2019-08-01 17:03:16 +00:00
const mark = initMarkAndCheckGutter ( authorMarks [ prePos . line ] , author , atom [ 3 ] )
2017-03-08 18:41:05 +00:00
var _postPos = {
line : prePos . line ,
ch : preLine . length
}
if ( JSON . stringify ( prePos ) !== JSON . stringify ( _postPos ) ) {
2017-03-22 09:48:26 +00:00
mark . textmarkers . push ( {
userid : author . userid ,
pos : [ prePos , _postPos ]
} )
2017-03-08 18:41:05 +00:00
startLine ++
}
authorMarks [ prePos . line ] = mark
}
if ( postPos . ch === 0 ) {
endLine --
} else if ( postPos . ch !== postLine . length ) {
2019-08-01 17:03:16 +00:00
const mark = initMarkAndCheckGutter ( authorMarks [ postPos . line ] , author , atom [ 3 ] )
2017-03-08 18:41:05 +00:00
var _prePos = {
line : postPos . line ,
ch : 0
}
if ( JSON . stringify ( _prePos ) !== JSON . stringify ( postPos ) ) {
2017-03-22 09:48:26 +00:00
mark . textmarkers . push ( {
userid : author . userid ,
pos : [ _prePos , postPos ]
} )
2017-03-08 18:41:05 +00:00
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 {
2019-08-01 17:03:16 +00:00
const mark = initMarkAndCheckGutter ( authorMarks [ prePos . line ] , author , atom [ 3 ] )
2017-03-08 18:41:05 +00:00
if ( JSON . stringify ( prePos ) !== JSON . stringify ( postPos ) ) {
mark . textmarkers . push ( {
userid : author . userid ,
pos : [ prePos , postPos ]
} )
}
authorMarks [ prePos . line ] = mark
}
}
}
addTextMarkers = [ ]
editor . eachLine ( iterateLine )
2019-08-01 17:03:16 +00:00
const allTextMarks = editor . getAllMarks ( )
2017-03-08 18:41:05 +00:00
for ( let i = 0 ; i < allTextMarks . length ; i ++ ) {
2019-08-01 17:03:16 +00:00
const _textMarker = allTextMarks [ i ]
const pos = _textMarker . find ( )
let found = false
2017-03-08 18:41:05 +00:00
for ( let j = 0 ; j < addTextMarkers . length ; j ++ ) {
2019-08-01 17:03:16 +00:00
const textMarker = addTextMarkers [ j ]
const author = authors [ textMarker . userid ]
const className = 'authorship-inline-' + author . color . substr ( 1 )
2017-03-08 18:41:05 +00:00
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 ++ ) {
2019-08-01 17:03:16 +00:00
const textMarker = addTextMarkers [ i ]
const author = authors [ textMarker . userid ]
2017-03-28 12:38:31 +00:00
const rgbcolor = hex2rgb ( author . color )
const colorString = ` rgba( ${ rgbcolor . red } , ${ rgbcolor . green } , ${ rgbcolor . blue } ,0.7) `
const styleString = ` background-image: linear-gradient(to top, ${ colorString } 1px, transparent 1px); `
2019-08-01 17:03:16 +00:00
const className = ` authorship-inline- ${ author . color . substr ( 1 ) } `
2017-03-28 12:38:31 +00:00
const rule = ` . ${ className } { ${ styleString } } `
2017-03-08 18:41:05 +00:00
addStyleRule ( rule )
editor . markText ( textMarker . pos [ 0 ] , textMarker . pos [ 1 ] , {
className : 'authorship-inline ' + className ,
title : author . name
} )
}
}
function iterateLine ( line ) {
2019-08-01 17:03:16 +00:00
const lineNumber = line . lineNo ( )
const currMark = authorMarks [ lineNumber ]
const author = currMark ? authors [ currMark . gutter . userid ] : null
2017-03-08 18:41:05 +00:00
if ( currMark && author ) {
2019-08-01 17:03:16 +00:00
const className = 'authorship-gutter-' + author . color . substr ( 1 )
2017-03-28 12:38:31 +00:00
const gutters = line . gutterMarkers
2017-03-08 18:41:05 +00:00
if ( ! gutters || ! gutters [ 'authorship-gutters' ] ||
2017-03-28 12:38:31 +00:00
! gutters [ 'authorship-gutters' ] . className ||
! gutters [ 'authorship-gutters' ] . className . indexOf ( className ) < 0 ) {
const styleString = ` border-left: 3px solid ${ author . color } ; height: ${ defaultTextHeight } px; margin-left: 3px; `
const rule = ` . ${ className } { ${ styleString } } `
2017-03-08 18:41:05 +00:00
addStyleRule ( rule )
2019-08-01 17:03:16 +00:00
const gutter = $ ( '<div>' , {
2017-03-08 18:41:05 +00:00
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 ) {
2019-08-01 17:03:16 +00:00
for ( let i = 0 ; i < currMark . textmarkers . length ; i ++ ) {
const textMarker = currMark . textmarkers [ i ]
2017-03-08 18:41:05 +00:00
if ( textMarker . userid !== currMark . gutter . userid ) {
addTextMarkers . push ( textMarker )
}
}
}
2016-09-18 08:22:50 +00:00
}
2017-03-28 03:18:36 +00:00
editorInstance . on ( 'update' , function ( ) {
2017-03-08 18:41:05 +00:00
$ ( '.authorship-gutter:not([data-original-title])' ) . tooltip ( {
container : '.CodeMirror-lines' ,
placement : 'right' ,
2019-08-01 17:03:16 +00:00
delay : { show : 500 , hide : 100 }
2017-03-08 18:41:05 +00:00
} )
$ ( '.authorship-inline:not([data-original-title])' ) . tooltip ( {
container : '.CodeMirror-lines' ,
placement : 'bottom' ,
2019-08-01 17:03:16 +00:00
delay : { show : 500 , hide : 100 }
2017-03-08 18:41:05 +00:00
} )
2017-03-28 12:38:31 +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-28 12:38:31 +00:00
// console.log(data);
2017-03-08 18:41:05 +00:00
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 )
} )
2017-03-28 09:16:32 +00:00
2017-03-08 18:41:05 +00:00
var permission = null
2015-05-04 07:53:29 +00:00
socket . on ( 'refresh' , function ( data ) {
2019-04-12 09:55:01 +00:00
// console.log(data);
2017-04-09 13:14:23 +00:00
editorInstance . config . docmaxlength = data . docmaxlength
editor . setOption ( 'maxLength' , editorInstance . config . docmaxlength )
2017-03-08 18:41:05 +00:00
updateInfo ( data )
updatePermission ( data . permission )
if ( ! window . loaded ) {
2019-04-12 09:55:01 +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 ) {
2017-04-12 01:21:13 +00:00
if ( visibleXS ) { appState . currentMode = modeType . edit } else { appState . currentMode = modeType . both }
2017-03-08 18:41:05 +00:00
}
2017-03-28 12:38:31 +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 ) ]
2017-04-12 01:21:13 +00:00
if ( urlMode ) appState . currentMode = urlMode
2017-03-08 18:41:05 +00:00
}
2017-04-12 01:21:13 +00:00
changeMode ( appState . currentMode )
2017-04-11 03:48:39 +00:00
if ( nocontent && ! visibleXS ) {
2017-03-08 18:41:05 +00:00
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 ( ) {
2017-03-28 12:38:31 +00:00
// work around editor not refresh or doc not fully loaded
2017-03-08 18:41:05 +00:00
windowResizeInner ( )
2017-03-28 12:38:31 +00:00
// work around might not scroll to hash
2017-03-08 18:41:05 +00:00
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 ( ) {
2019-08-01 17:03:16 +00:00
return ! ! ( ( cmClient && cmClient . state && Object . hasOwnProperty . call ( cmClient . state , 'outstanding' ) ) )
2017-03-08 18:41:05 +00:00
}
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 {
2017-03-28 12:38:31 +00:00
// if current doc is equal to the doc before disconnect
2017-03-08 18:41:05 +00:00
if ( setDoc && bodyMismatch ) editor . clearHistory ( )
2017-04-11 04:07:04 +00:00
else if ( lastInfo . history ) editor . setHistory ( lastInfo . history )
lastInfo . history = null
2017-03-08 18:41:05 +00:00
}
if ( ! cmClient ) {
cmClient = window . cmClient = new EditorClient (
2017-03-28 12:38:31 +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 ) {
2017-04-11 03:48:39 +00:00
isDirty = true
2017-03-08 18:41:05 +00:00
updateView ( )
}
restoreInfo ( )
} )
2015-07-01 16:10:20 +00:00
2015-09-25 10:01:15 +00:00
socket . on ( 'ack' , function ( ) {
2017-04-11 03:48:39 +00:00
isDirty = true
2017-03-08 18:41:05 +00:00
updateView ( )
} )
2015-07-01 16:10:20 +00:00
2015-09-25 10:01:15 +00:00
socket . on ( 'operation' , function ( ) {
2017-04-11 03:48:39 +00:00
isDirty = true
2017-03-08 18:41:05 +00:00
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 ) }
2017-04-11 04:07:04 +00:00
onlineUsers = data . users
2017-03-08 18:41:05 +00:00
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 ]
2017-04-11 04:07:04 +00:00
if ( user . id !== socket . id ) { buildCursor ( user ) } else { personalInfo = user }
2017-03-08 18:41:05 +00:00
}
} )
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 ) }
2017-04-11 04:07:04 +00:00
for ( var i = 0 ; i < onlineUsers . length ; i ++ ) {
if ( onlineUsers [ i ] . id === data . id ) {
onlineUsers [ i ] = data
2017-03-08 18:41:05 +00:00
}
}
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 ) }
2017-04-11 04:07:04 +00:00
for ( var i = 0 ; i < onlineUsers . length ; i ++ ) {
if ( onlineUsers [ i ] . id === data . id ) {
onlineUsers [ i ] . cursor = data . cursor
2017-03-08 18:41:05 +00:00
}
}
if ( data . id !== socket . id ) { buildCursor ( data ) }
2019-04-12 09:55:01 +00:00
// force show
2017-03-08 18:41:05 +00:00
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 ) }
2017-04-11 04:07:04 +00:00
for ( var i = 0 ; i < onlineUsers . length ; i ++ ) {
if ( onlineUsers [ i ] . id === data . id ) {
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 ) }
2017-04-11 04:07:04 +00:00
for ( var i = 0 ; i < onlineUsers . length ; i ++ ) {
if ( onlineUsers [ i ] . id === data . id ) {
onlineUsers [ i ] . cursor = null
2017-03-08 18:41:05 +00:00
}
}
if ( data . id !== socket . id ) { buildCursor ( data ) }
2019-04-12 09:55:01 +00:00
// force hide
2017-03-08 18:41:05 +00:00
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">' +
2017-03-28 12:38:31 +00:00
'<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>'
2017-03-08 18:41:05 +00:00
}
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
2017-04-11 04:07:04 +00:00
var _onlineUsers = deduplicateOnlineUsers ( onlineUsers )
2017-03-08 18:41:05 +00:00
showStatus ( statusType . online , _onlineUsers . length )
var items = onlineUserList . items
2019-04-12 09:55:01 +00:00
// update or remove current list items
2017-03-08 18:41:05 +00:00
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
}
}
2019-08-01 17:03:16 +00:00
const id = items [ i ] . values ( ) . id
2017-03-08 18:41:05 +00:00
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 )
}
}
2019-04-12 09:55:01 +00:00
// add not in list items
2017-03-08 18:41:05 +00:00
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 ] )
}
}
2019-04-12 09:55:01 +00:00
// sorting
2017-03-08 18:41:05 +00:00
sortOnlineUserList ( onlineUserList )
sortOnlineUserList ( shortOnlineUserList )
2019-04-12 09:55:01 +00:00
// render list items
2017-03-08 18:41:05 +00:00
renderUserStatusList ( onlineUserList )
renderUserStatusList ( shortOnlineUserList )
}
function sortOnlineUserList ( list ) {
2019-04-12 09:55:01 +00:00
// sort order by isSelf, login state, idle state, alphabet name, color brightness
2017-03-08 18:41:05 +00:00
list . sort ( '' , {
sortFunction : function ( a , b ) {
var usera = a . values ( )
var userb = b . values ( )
2017-04-11 04:07:04 +00:00
var useraIsSelf = ( usera . id === personalInfo . id || ( usera . login && usera . userid === personalInfo . userid ) )
var userbIsSelf = ( userb . id === personalInfo . id || ( userb . login && userb . userid === personalInfo . userid ) )
2017-03-08 18:41:05 +00:00
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
2017-03-22 09:48:26 +00:00
} 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 }
}
2017-03-08 18:41:05 +00:00
}
}
}
}
} )
}
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 + ')' )
2019-04-12 09:55:01 +00:00
// add 1px more to right, make it feel aligned
2017-03-08 18:41:05 +00:00
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 ) {
2017-03-22 09:48:26 +00:00
// keep self color when login
2017-04-11 04:07:04 +00:00
if ( user . id === personalInfo . id ) {
2017-03-08 18:41:05 +00:00
_onlineUsers [ j ] . color = user . color
}
2017-03-22 09:48:26 +00:00
// keep idle state if any of self client not idle
2017-03-08 18:41:05 +00:00
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
2017-04-11 03:48:39 +00:00
if ( visibleXS ) { type = 'xs' } else if ( visibleSM ) { type = 'sm' } else if ( visibleMD ) { type = 'md' } else if ( visibleLG ) { type = 'lg' }
2015-06-01 10:04:25 +00:00
2020-07-01 03:33:41 +00:00
personalInfo . idle = idle . isAway
personalInfo . type = type
2015-06-01 10:04:25 +00:00
2017-04-11 04:07:04 +00:00
for ( var i = 0 ; i < onlineUsers . length ; i ++ ) {
if ( onlineUsers [ i ] . id === personalInfo . id ) {
onlineUsers [ i ] = 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
2017-03-22 09:48:26 +00:00
// set margin
2017-03-08 18:41:05 +00:00
var tagRightMargin = 0
var tagBottomMargin = 2
2017-03-22 09:48:26 +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 ( )
2017-03-22 09:48:26 +00:00
// get editor size (status bar not count in)
2017-03-08 18:41:05 +00:00
var editorHeight = ui . area . codemirror . height ( )
2017-03-22 09:48:26 +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
2017-03-22 09:48:26 +00:00
// get coord position
2017-03-08 18:41:05 +00:00
var left = coord . left
var top = coord . top
2017-03-22 09:48:26 +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
2017-03-22 09:48:26 +00:00
// set offset
2017-03-08 18:41:05 +00:00
var offsetLeft = - 3
var offsetTop = defaultTextHeight
2017-03-22 09:48:26 +00:00
// only do when have width and height
2017-03-08 18:41:05 +00:00
if ( width > 0 && height > 0 ) {
2017-03-22 09:48:26 +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
}
2017-03-22 09:48:26 +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
}
2017-03-22 09:48:26 +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 ) {
2017-04-12 01:21:13 +00:00
if ( appState . currentMode === modeType . view ) return
2017-03-08 18:41:05 +00:00
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 ( $ ( 'div[data-clientid="' + user . id + '"]' ) . length <= 0 ) {
2019-08-01 17:03:16 +00:00
const cursor = $ ( '<div data-clientid="' + user . id + '" class="CodeMirror-other-cursor" style="display:none;"></div>' )
2017-03-08 18:41:05 +00:00
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 )
2019-08-01 17:03:16 +00:00
const cursorbar = $ ( '<div class="cursorbar"> </div>' )
2017-03-08 18:41:05 +00:00
cursorbar [ 0 ] . style . height = defaultTextHeight + 'px'
cursorbar [ 0 ] . style . borderLeft = '2px solid ' + user . color
var icon = '<i class="fa ' + iconClass + '"></i>'
2019-08-01 17:03:16 +00:00
const cursortag = $ ( '<div class="cursortag">' + icon + ' <span class="name">' + user . name + '</span></div>' )
2017-03-22 09:48:26 +00:00
// cursortag[0].style.background = color;
2017-03-08 18:41:05 +00:00
cursortag [ 0 ] . style . color = user . color
cursor . attr ( 'data-mode' , 'hover' )
cursortag . delay ( 2000 ) . fadeOut ( 'fast' )
cursor . hover (
2017-04-11 03:37:41 +00:00
function ( ) {
if ( cursor . attr ( 'data-mode' ) === 'hover' ) { cursortag . stop ( true ) . fadeIn ( 'fast' ) }
} ,
function ( ) {
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 {
2019-08-01 17:03:16 +00:00
const cursor = $ ( 'div[data-clientid="' + user . id + '"]' )
2017-03-08 18:41:05 +00:00
cursor . attr ( 'data-line' , user . cursor . line )
cursor . attr ( 'data-ch' , user . cursor . ch )
2019-08-01 17:03:16 +00:00
const cursorbar = cursor . find ( '.cursorbar' )
2017-03-08 18:41:05 +00:00
cursorbar [ 0 ] . style . height = defaultTextHeight + 'px'
cursorbar [ 0 ] . style . borderLeft = '2px solid ' + user . color
2019-08-01 17:03:16 +00:00
const cursortag = cursor . find ( '.cursortag' )
2017-03-08 18:41:05 +00:00
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 ( {
2019-08-01 17:03:16 +00:00
left : coord . left ,
top : coord . top
2017-03-08 18:41:05 +00:00
} , {
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' )
2018-11-14 13:10:14 +00:00
// eslint-disable-next-line no-control-regex
2017-03-08 18:41:05 +00:00
if ( /\u0000/g . test ( str ) && change . update ) {
2018-11-14 13:10:14 +00:00
// eslint-disable-next-line no-control-regex
2017-03-08 18:41:05 +00:00
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
}
2020-06-01 15:01:17 +00:00
let lastDocHeight
2017-03-08 18:41:05 +00:00
var ignoreEmitEvents = [ 'setValue' , 'ignoreHistory' ]
2017-03-28 03:18:36 +00:00
editorInstance . on ( 'beforeChange' , function ( cm , change ) {
2017-03-08 18:41:05 +00:00
if ( debug ) { console . debug ( change ) }
2020-06-01 15:01:17 +00:00
lastDocHeight = editor . doc . height
2017-03-08 18:41:05 +00:00
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 }
} )
2017-03-28 03:18:36 +00:00
editorInstance . on ( 'cut' , function ( ) {
2019-04-12 09:55:01 +00:00
// na
2017-03-08 18:41:05 +00:00
} )
2017-03-28 03:18:36 +00:00
editorInstance . on ( 'paste' , function ( ) {
2019-04-12 09:55:01 +00:00
// na
2017-03-08 18:41:05 +00:00
} )
2017-03-28 09:32:42 +00:00
editorInstance . on ( 'changes' , function ( editor , changes ) {
2020-06-01 15:01:17 +00:00
const docHeightChanged = editor . doc . height !== lastDocHeight
2017-03-08 18:41:05 +00:00
updateHistory ( )
var docLength = editor . getValue ( ) . length
2019-04-12 09:55:01 +00:00
// workaround for big documents
2017-03-08 18:41:05 +00:00
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 ( )
}
2020-06-01 15:01:17 +00:00
if ( docHeightChanged ) {
checkEditorScrollbar ( )
checkEditorScrollOverLines ( )
// always sync edit scrolling to view if user is editing
if ( ui . area . codemirrorScroll [ 0 ] . scrollHeight > ui . area . view [ 0 ] . scrollHeight && editorHasFocus ( ) ) {
postUpdateEvent = function ( ) {
syncScrollToView ( )
postUpdateEvent = null
}
2017-03-08 18:41:05 +00:00
}
}
2020-06-01 15:01:17 +00:00
lastDocHeight = editor . doc . height
2017-03-08 18:41:05 +00:00
} )
2017-03-28 04:11:05 +00:00
editorInstance . on ( 'focus' , function ( editor ) {
2017-04-11 04:07:04 +00:00
for ( var i = 0 ; i < onlineUsers . length ; i ++ ) {
if ( onlineUsers [ i ] . id === personalInfo . id ) {
onlineUsers [ i ] . cursor = editor . getCursor ( )
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
}
2020-07-01 03:33:41 +00:00
personalInfo . cursor = editor . getCursor ( )
2017-03-08 18:41:05 +00:00
socket . emit ( 'cursor focus' , editor . getCursor ( ) )
} )
2017-03-28 04:10:35 +00:00
const cursorActivity = _ . debounce ( cursorActivityInner , cursorActivityDebounce )
2017-03-28 04:15:56 +00:00
function cursorActivityInner ( editor ) {
2017-03-28 04:10:35 +00:00
if ( editorHasFocus ( ) && ! Visibility . hidden ( ) ) {
2017-04-11 04:07:04 +00:00
for ( var i = 0 ; i < onlineUsers . length ; i ++ ) {
if ( onlineUsers [ i ] . id === personalInfo . id ) {
onlineUsers [ i ] . cursor = editor . getCursor ( )
2017-03-28 04:10:35 +00:00
}
}
2020-07-01 03:33:41 +00:00
personalInfo . cursor = editor . getCursor ( )
2017-03-28 04:10:35 +00:00
socket . emit ( 'cursor activity' , editor . getCursor ( ) )
}
}
2017-03-28 09:16:32 +00:00
editorInstance . on ( 'cursorActivity' , editorInstance . updateStatusBar )
2017-03-28 04:10:35 +00:00
editorInstance . on ( 'cursorActivity' , cursorActivity )
2017-03-28 03:57:44 +00:00
2017-03-28 09:16:32 +00:00
editorInstance . on ( 'beforeSelectionChange' , editorInstance . updateStatusBar )
2017-03-28 03:18:36 +00:00
editorInstance . on ( 'beforeSelectionChange' , function ( doc , selections ) {
2017-03-28 03:57:44 +00:00
// check selection and whether the statusbar has added
if ( selections && editorInstance . statusSelection ) {
const selection = selections . ranges [ 0 ]
const anchor = selection . anchor
const head = selection . head
const start = head . line <= anchor . line ? head : anchor
const end = head . line >= anchor . line ? head : anchor
const selectionCharCount = Math . abs ( head . ch - anchor . ch )
let selectionText = ' — Selected '
// 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 ) {
editorInstance . statusSelection . text ( selectionText )
} else {
editorInstance . statusSelection . text ( '' )
}
}
2017-03-08 18:41:05 +00:00
} )
2017-03-28 03:18:36 +00:00
editorInstance . on ( 'blur' , function ( cm ) {
2017-04-11 04:07:04 +00:00
for ( var i = 0 ; i < onlineUsers . length ; i ++ ) {
if ( onlineUsers [ i ] . id === personalInfo . id ) {
onlineUsers [ i ] . cursor = null
2015-06-01 10:04:25 +00:00
}
2017-03-08 18:41:05 +00:00
}
2020-07-01 03:33:41 +00:00
personalInfo . cursor = null
2017-03-08 18:41:05 +00:00
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 ( )
2017-04-12 01:21:13 +00:00
switch ( appState . currentMode ) {
2017-03-08 18:41:05 +00:00
case modeType . edit :
if ( scrollbarStyle === 'native' ) {
2017-04-11 04:07:04 +00:00
lastInfo . edit . scroll . left = left
lastInfo . edit . scroll . top = top
2017-03-08 18:41:05 +00:00
} else {
2017-04-11 04:07:04 +00:00
lastInfo . edit . scroll = editor . getScrollInfo ( )
2017-03-08 18:41:05 +00:00
}
break
case modeType . view :
2017-04-11 04:07:04 +00:00
lastInfo . view . scroll . left = left
lastInfo . view . scroll . top = top
2017-03-08 18:41:05 +00:00
break
case modeType . both :
2017-04-11 04:07:04 +00:00
lastInfo . edit . scroll = editor . getScrollInfo ( )
lastInfo . view . scroll . left = ui . area . view . scrollLeft ( )
lastInfo . view . scroll . top = ui . area . view . scrollTop ( )
2017-03-08 18:41:05 +00:00
break
}
2017-04-11 04:07:04 +00:00
lastInfo . edit . cursor = editor . getCursor ( )
lastInfo . edit . selections = editor . listSelections ( )
lastInfo . needRestore = true
2017-03-08 18:41:05 +00:00
}
function restoreInfo ( ) {
var scrollbarStyle = editor . getOption ( 'scrollbarStyle' )
2017-04-11 04:07:04 +00:00
if ( lastInfo . needRestore ) {
var line = lastInfo . edit . cursor . line
var ch = lastInfo . edit . cursor . ch
2017-03-08 18:41:05 +00:00
editor . setCursor ( line , ch )
2017-04-11 04:07:04 +00:00
editor . setSelections ( lastInfo . edit . selections )
2017-04-12 01:21:13 +00:00
switch ( appState . currentMode ) {
2017-03-08 18:41:05 +00:00
case modeType . edit :
if ( scrollbarStyle === 'native' ) {
2017-04-11 04:07:04 +00:00
$ ( window ) . scrollLeft ( lastInfo . edit . scroll . left )
$ ( window ) . scrollTop ( lastInfo . edit . scroll . top )
2017-03-08 18:41:05 +00:00
} else {
2019-08-01 17:03:16 +00:00
const left = lastInfo . edit . scroll . left
const top = lastInfo . edit . scroll . top
2017-03-08 18:41:05 +00:00
editor . scrollIntoView ( )
editor . scrollTo ( left , top )
}
break
case modeType . view :
2017-04-11 04:07:04 +00:00
$ ( window ) . scrollLeft ( lastInfo . view . scroll . left )
$ ( window ) . scrollTop ( lastInfo . view . scroll . top )
2017-03-08 18:41:05 +00:00
break
case modeType . both :
2019-08-01 17:03:16 +00:00
const left = lastInfo . edit . scroll . left
const top = lastInfo . edit . scroll . top
2017-03-08 18:41:05 +00:00
editor . scrollIntoView ( )
editor . scrollTo ( left , top )
2017-04-11 04:07:04 +00:00
ui . area . view . scrollLeft ( lastInfo . view . scroll . left )
ui . area . view . scrollTop ( lastInfo . view . scroll . top )
2017-03-08 18:41:05 +00:00
break
2015-05-04 07:53:29 +00:00
}
2017-03-08 18:41:05 +00:00
2017-04-11 04:07:04 +00:00
lastInfo . needRestore = false
2017-03-08 18:41:05 +00:00
}
2015-05-04 07:53:29 +00:00
}
2017-03-08 18:41:05 +00:00
// view actions
function refreshView ( ) {
ui . area . markdown . html ( '' )
2017-04-11 03:48:39 +00:00
isDirty = true
2017-03-08 18:41:05 +00:00
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 ( ) {
2017-04-12 01:21:13 +00:00
if ( appState . currentMode === modeType . edit || ! isDirty ) return
2017-03-08 18:41:05 +00:00
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 ( )
2019-04-12 09:55:01 +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' )
2017-04-12 01:21:13 +00:00
appState . syncscroll = false
2017-03-08 18:41:05 +00:00
checkSyncToggle ( )
} else {
if ( lastMeta . type && lastMeta . type === 'slide' ) {
refreshView ( )
ui . area . markdown . removeClass ( 'slides' )
2017-04-12 01:21:13 +00:00
appState . syncscroll = true
2017-03-08 18:41:05 +00:00
checkSyncToggle ( )
}
2019-04-12 09:55:01 +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 )
}
2019-04-12 09:55:01 +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' )
2018-11-19 17:29:50 +00:00
autoLinkify ( ui . area . markdown )
2017-03-08 18:41:05 +00:00
generateScrollspy ( )
updateScrollspy ( )
smoothHashScroll ( )
2017-04-11 03:48:39 +00:00
isDirty = false
2017-03-08 18:41:05 +00:00
clearMap ( )
2019-04-12 09:55:01 +00:00
// buildMap();
2017-03-08 18:41:05 +00:00
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 ) {
2019-04-12 09:55:01 +00:00
// sync data attr startline and endline
2017-03-08 18:41:05 +00:00
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 ) {
2019-04-12 09:55:01 +00:00
// console.log(rawSrc);
// console.log(rawTar);
2017-03-08 18:41:05 +00:00
$ ( des [ i ] ) . replaceWith ( src [ i ] )
}
}
} else { // diff length
var start = 0
// find diff start position
for ( let i = 0 ; i < tar . length ; i ++ ) {
2019-04-12 09:55:01 +00:00
// copyAttribute(src[i], des[i], 'data-startline');
// copyAttribute(src[i], des[i], 'data-endline');
2019-08-01 17:03:16 +00:00
const rawSrc = cloneAndRemoveDataAttr ( src [ i ] )
const rawTar = cloneAndRemoveDataAttr ( tar [ i ] )
2017-03-08 18:41:05 +00:00
if ( ! rawSrc || ! rawTar || rawSrc . outerHTML !== rawTar . outerHTML ) {
start = i
break
}
}
2019-04-12 09:55:01 +00:00
// find diff end position
2017-03-08 18:41:05 +00:00
var srcEnd = 0
var tarEnd = 0
for ( let i = 0 ; i < src . length ; i ++ ) {
2019-04-12 09:55:01 +00:00
// copyAttribute(src[i], des[i], 'data-startline');
// copyAttribute(src[i], des[i], 'data-endline');
2019-08-01 17:03:16 +00:00
const rawSrc = cloneAndRemoveDataAttr ( src [ i ] )
const rawTar = cloneAndRemoveDataAttr ( tar [ i ] )
2017-03-08 18:41:05 +00:00
if ( ! rawSrc || ! rawTar || rawSrc . outerHTML !== rawTar . outerHTML ) {
start = i
break
}
}
2019-04-12 09:55:01 +00:00
// tar end
2017-03-08 18:41:05 +00:00
for ( let i = 1 ; i <= tar . length + 1 ; i ++ ) {
2019-08-01 17:03:16 +00:00
const srcLength = src . length
const tarLength = tar . length
2019-04-12 09:55:01 +00:00
// copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline');
// copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline');
2019-08-01 17:03:16 +00:00
const rawSrc = cloneAndRemoveDataAttr ( src [ srcLength - i ] )
const rawTar = cloneAndRemoveDataAttr ( tar [ tarLength - i ] )
2017-03-08 18:41:05 +00:00
if ( ! rawSrc || ! rawTar || rawSrc . outerHTML !== rawTar . outerHTML ) {
tarEnd = tar . length - i
break
}
}
2019-04-12 09:55:01 +00:00
// src end
2017-03-08 18:41:05 +00:00
for ( let i = 1 ; i <= src . length + 1 ; i ++ ) {
2019-08-01 17:03:16 +00:00
const srcLength = src . length
const tarLength = tar . length
2019-04-12 09:55:01 +00:00
// copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline');
// copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline');
2019-08-01 17:03:16 +00:00
const rawSrc = cloneAndRemoveDataAttr ( src [ srcLength - i ] )
const rawTar = cloneAndRemoveDataAttr ( tar [ tarLength - i ] )
2017-03-08 18:41:05 +00:00
if ( ! rawSrc || ! rawTar || rawSrc . outerHTML !== rawTar . outerHTML ) {
srcEnd = src . length - i
break
}
}
2019-04-12 09:55:01 +00:00
// check if tar end overlap tar start
2017-03-08 18:41:05 +00:00
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 ) }
2019-04-12 09:55:01 +00:00
// show diff content
2017-03-08 18:41:05 +00:00
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
2019-04-12 09:55:01 +00:00
// push new elements
2017-03-08 18:41:05 +00:00
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 )
}
}
2019-04-12 09:55:01 +00:00
// push remove elements
2017-03-08 18:41:05 +00:00
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 ] )
}
}
2019-04-12 09:55:01 +00:00
// add elements
2017-03-08 18:41:05 +00:00
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 ] ) }
2019-04-12 09:55:01 +00:00
// remove elements
2017-03-08 18:41:05 +00:00
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 ( ) {
2019-04-12 09:55:01 +00:00
// get element
2017-03-08 18:41:05 +00:00
var dropdown = $ ( '.cursor-menu > .dropdown-menu' )
2019-04-12 09:55:01 +00:00
// return if not exists
2017-03-08 18:41:05 +00:00
if ( dropdown . length <= 0 ) return
2019-04-12 09:55:01 +00:00
// set margin
2017-03-08 18:41:05 +00:00
var menuRightMargin = 10
var menuBottomMargin = 4
2019-04-12 09:55:01 +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 ( )
2019-04-12 09:55:01 +00:00
// get editor size (status bar not count in)
2017-03-08 18:41:05 +00:00
var editorHeight = ui . area . codemirror . height ( )
2019-04-12 09:55:01 +00:00
// get element size
2017-03-08 18:41:05 +00:00
var width = dropdown . outerWidth ( )
var height = dropdown . outerHeight ( )
2019-04-12 09:55:01 +00:00
// get cursor
2017-03-08 18:41:05 +00:00
var cursor = editor . getCursor ( )
2019-04-12 09:55:01 +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 )
2019-04-12 09:55:01 +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
2019-04-12 09:55:01 +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
2019-04-12 09:55:01 +00:00
// set offset
2017-03-08 18:41:05 +00:00
var offsetLeft = 0
var offsetTop = defaultTextHeight
2019-04-12 09:55:01 +00:00
// set up side down
2017-03-08 18:41:05 +00:00
window . upSideDown = false
var lastUpSideDown = window . upSideDown = false
2019-04-12 09:55:01 +00:00
// only do when have width and height
2017-03-08 18:41:05 +00:00
if ( width > 0 && height > 0 ) {
2019-04-12 09:55:01 +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 ) }
2019-04-12 09:55:01 +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 )
2019-04-12 09:55:01 +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
}
2019-04-12 09:55:01 +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 ) }
2019-04-12 09:55:01 +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 )
2019-04-12 09:55:01 +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 ( ) {
2019-04-12 09:55:01 +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 = [ ]
2018-11-14 13:10:14 +00:00
for ( var i = 0 ; i < cursor . line ; i ++ ) { // contain current line
2017-03-08 18:41:05 +00:00
text . push ( editor . getLine ( i ) )
}
text = text . join ( '\n' ) + '\n' + editor . getLine ( cursor . line ) . slice ( 0 , cursor . ch )
2019-04-12 09:55:01 +00:00
// console.log(text);
2017-03-08 18:41:05 +00:00
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' )
2019-04-12 09:55:01 +00:00
// console.log(text);
2017-03-08 18:41:05 +00:00
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 ( ) {
2019-04-12 09:55:01 +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
}
2019-10-26 07:02:35 +00:00
const textCompleteKeyMap = {
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' )
}
}
2015-06-01 10:04:25 +00:00
$ ( editor . getInputField ( ) )
2019-04-12 09:55:01 +00:00
. textcomplete ( [
{ // emoji strategy
match : /(^|\n|\s)\B:([-+\w]*)$/ ,
search : function ( term , callback ) {
var line = editor . getLine ( editor . getCursor ( ) . line )
term = line . match ( this . match ) [ 2 ]
var list = [ ]
$ . map ( window . emojify . emojiNames , function ( emoji ) {
if ( emoji . indexOf ( term ) === 0 ) { // match at first character
list . push ( emoji )
2017-03-08 18:41:05 +00:00
}
2019-04-12 09:55:01 +00:00
} )
$ . map ( window . emojify . emojiNames , function ( emoji ) {
if ( emoji . indexOf ( term ) !== - 1 ) { // match inside the word
list . push ( emoji )
2017-03-08 18:41:05 +00:00
}
2019-04-12 09:55:01 +00:00
} )
callback ( list )
} ,
template : function ( value ) {
2020-02-06 06:31:25 +00:00
return ` <img class="emoji" src=" ${ emojifyImageDir } / ${ value } .png"></img> ${ value } `
2019-04-12 09:55:01 +00:00
} ,
replace : function ( value ) {
return '$1:' + value + ': '
} ,
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 )
} ,
replace : function ( lang ) {
var ending = ''
if ( ! checkBelow ( matchInCode ) ) {
ending = '\n\n```'
2017-03-08 18:41:05 +00:00
}
2019-04-12 09:55:01 +00:00
if ( this . langs . indexOf ( lang ) !== - 1 ) { return '$1```' + lang + '=' + ending } else if ( this . charts . indexOf ( lang ) !== - 1 ) { return '$1```' + lang + ending }
2017-03-08 18:41:05 +00:00
} ,
2019-04-12 09:55:01 +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' ) }
} ,
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 )
} ,
replace : function ( lang ) {
var ending = ''
if ( ! checkBelow ( matchInContainer ) ) {
ending = '\n\n:::'
2017-03-08 18:41:05 +00:00
}
2019-04-12 09:55:01 +00:00
if ( this . containers . indexOf ( lang ) !== - 1 ) { return '$1:::$2' + lang + ending }
} ,
done : function ( ) {
var cursor = editor . getCursor ( )
var text = [ ]
text . push ( editor . getLine ( cursor . line - 1 ) )
text . push ( editor . getLine ( cursor . line ) )
text = text . join ( '\n' )
// console.log(text);
if ( text === '\n:::' ) { editor . doc . cm . execCommand ( 'goLineUp' ) }
} ,
context : function ( text ) {
return ! isInCode && isInContainer
}
} ,
{ // 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 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
2017-03-08 18:41:05 +00:00
} ,
2019-04-12 09:55:01 +00:00
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 ) {
2017-03-23 12:49:31 +00:00
$ . map ( supportExtraTags , function ( extratag ) {
if ( extratag . search . indexOf ( term ) === 0 ) { list . push ( extratag . command ( ) ) }
} )
2017-03-08 18:41:05 +00:00
}
2019-04-12 09:55:01 +00:00
$ . map ( supportReferrals , function ( referral ) {
if ( referral . search . indexOf ( term ) === 0 ) { list . push ( referral . text ) }
} )
callback ( list )
2017-03-08 18:41:05 +00:00
} ,
2019-04-12 09:55:01 +00:00
replace : function ( value ) {
return '$1' + value
2017-03-08 18:41:05 +00:00
} ,
2019-04-12 09:55:01 +00:00
context : function ( text ) {
return ! isInCode
2017-03-08 18:41:05 +00:00
}
2019-04-12 09:55:01 +00:00
} ,
{ // 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
} ) )
2017-03-08 18:41:05 +00:00
} ,
2019-04-12 09:55:01 +00:00
replace : function ( value ) {
return '$1' + value
2017-03-08 18:41:05 +00:00
} ,
2019-04-12 09:55:01 +00:00
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
} ) )
2017-03-08 18:41:05 +00:00
} ,
2019-04-12 09:55:01 +00:00
replace : function ( value ) {
return '$1' + value
2017-03-08 18:41:05 +00:00
} ,
2019-04-12 09:55:01 +00:00
context : function ( text ) {
return ! isInCode
2017-03-08 18:41:05 +00:00
}
2019-04-12 09:55:01 +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 )
2019-10-26 07:02:35 +00:00
editor . addKeyMap ( textCompleteKeyMap )
2019-04-12 09:55:01 +00:00
} ,
'textComplete:hide' : function ( e ) {
$ ( this ) . data ( 'autocompleting' , false )
2019-10-26 07:02:35 +00:00
editor . removeKeyMap ( textCompleteKeyMap )
2019-04-12 09:55:01 +00:00
}
} )