2015-09-25 06:09:37 +00:00
var defaultTextHeight = 20 ;
2015-06-01 10:04:25 +00:00
var viewportMargin = 20 ;
var defaultExtraKeys = {
2016-04-24 04:29:55 +00:00
"F10" : function ( cm ) {
2016-04-24 03:03:57 +00:00
cm . setOption ( "fullScreen" , ! cm . getOption ( "fullScreen" ) ) ;
2016-01-17 20:28:04 +00:00
} ,
2016-04-24 04:30:16 +00:00
"Esc" : function ( cm ) {
2016-04-24 03:03:57 +00:00
if ( cm . getOption ( 'keyMap' ) . substr ( 0 , 3 ) === 'vim' ) return CodeMirror . Pass ;
2016-04-24 04:30:16 +00:00
else if ( cm . getOption ( "fullScreen" ) ) cm . setOption ( "fullScreen" , false ) ;
2016-01-17 20:28:04 +00:00
} ,
2015-07-01 16:10:20 +00:00
"Cmd-S" : function ( ) {
2016-04-28 03:10:09 +00:00
return false ;
2015-07-01 16:10:20 +00:00
} ,
"Ctrl-S" : function ( ) {
2016-04-28 03:10:09 +00:00
return false ;
2015-07-01 16:10:20 +00:00
} ,
2016-02-09 04:51:07 +00:00
"Enter" : "newlineAndIndentContinueMarkdownList" ,
2016-04-24 04:30:16 +00:00
"Tab" : function ( cm ) {
2016-02-09 04:51:07 +00:00
var tab = '\t' ;
2016-04-24 04:30:16 +00:00
var spaces = Array ( parseInt ( cm . getOption ( "indentUnit" ) ) + 1 ) . join ( " " ) ;
2016-02-09 04:51:07 +00:00
//auto indent whole line when in list or blockquote
var cursor = cm . getCursor ( ) ;
var line = cm . getLine ( cursor . line ) ;
var regex = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))/ ;
var match ;
2016-02-16 15:54:33 +00:00
var multiple = cm . getSelection ( ) . split ( '\n' ) . length > 1 || cm . getSelections ( ) . length > 1 ;
if ( multiple ) {
cm . execCommand ( 'defaultTab' ) ;
} else if ( ( match = regex . exec ( line ) ) !== null ) {
2016-02-09 04:51:07 +00:00
var ch = match [ 1 ] . length ;
var pos = {
line : cursor . line ,
ch : ch
} ;
2016-04-24 04:30:16 +00:00
if ( cm . getOption ( 'indentWithTabs' ) )
cm . replaceRange ( tab , pos , pos , '+input' ) ;
else
cm . replaceRange ( spaces , pos , pos , '+input' ) ;
2016-02-09 04:51:07 +00:00
} else {
2016-02-16 15:54:33 +00:00
if ( cm . getOption ( 'indentWithTabs' ) )
2016-02-09 04:51:07 +00:00
cm . execCommand ( 'defaultTab' ) ;
else {
2016-04-24 04:30:16 +00:00
cm . replaceSelection ( spaces ) ;
}
2016-02-09 04:51:07 +00:00
}
2016-03-15 03:05:05 +00:00
} ,
2016-04-24 04:30:16 +00:00
"Cmd-Left" : "goLineLeftSmart" ,
2016-03-15 03:05:05 +00:00
"Cmd-Right" : "goLineRight"
2015-06-01 10:04:25 +00:00
} ;
var idleTime = 300000 ; //5 mins
2015-09-25 10:01:15 +00:00
var updateViewDebounce = 200 ;
2016-03-15 03:12:45 +00:00
var cursorMenuThrottle = 50 ;
2015-09-25 10:01:15 +00:00
var cursorActivityDebounce = 50 ;
2015-12-18 15:43:23 +00:00
var cursorAnimatePeriod = 100 ;
2016-03-15 03:01:01 +00:00
var supportContainers = [ 'success' , 'info' , 'warning' , 'danger' ] ;
2015-06-01 10:04:25 +00:00
var supportCodeModes = [ 'javascript' , 'htmlmixed' , 'htmlembedded' , 'css' , 'xml' , 'clike' , 'clojure' , 'ruby' , 'python' , 'shell' , 'php' , 'sql' , 'coffeescript' , 'yaml' , 'jade' , 'lua' , 'cmake' , 'nginx' , 'perl' , 'sass' , 'r' , 'dockerfile' ] ;
2016-03-04 15:26:27 +00:00
var supportCharts = [ 'sequence' , 'flow' , 'graphviz' , 'mermaid' ] ;
2015-06-01 10:04:25 +00:00
var supportHeaders = [
{
text : '# h1' ,
search : '#'
} ,
{
text : '## h2' ,
search : '##'
} ,
{
text : '### h3' ,
search : '###'
} ,
{
text : '#### h4' ,
search : '####'
} ,
{
text : '##### h5' ,
search : '#####'
} ,
{
text : '###### h6' ,
search : '######'
} ,
{
text : '###### tags: `example`' ,
search : '###### tags:'
}
] ;
var supportReferrals = [
{
text : '[reference link]' ,
search : '[]'
} ,
{
text : '[reference]: url "title"' ,
search : '[]:'
} ,
{
text : '[^footnote link]' ,
search : '[^]'
} ,
{
text : '[^footnote reference]: url "title"' ,
search : '[^]:'
} ,
{
text : '^[inline footnote]' ,
search : '^[]'
} ,
{
text : '[link text][reference]' ,
search : '[][]'
} ,
{
text : '[link text](url "title")' ,
search : '[]()'
} ,
{
2016-05-27 05:38:59 +00:00
text : '![image alt][reference]' ,
2015-06-01 10:04:25 +00:00
search : '![][]'
} ,
{
2016-05-27 05:38:59 +00:00
text : '![image alt](url "title")' ,
2015-06-01 10:04:25 +00:00
search : '![]()'
2015-12-20 17:28:54 +00:00
} ,
2016-05-27 05:39:54 +00:00
{
text : '![image alt](url "title" =WidthxHeight)' ,
search : '![]()'
} ,
2015-12-20 17:28:54 +00:00
{
text : '[TOC]' ,
search : '[]'
2015-06-01 10:04:25 +00:00
}
] ;
var supportExternals = [
{
text : '{%youtube youtubeid %}' ,
search : 'youtube'
} ,
{
text : '{%vimeo vimeoid %}' ,
search : 'vimeo'
} ,
{
text : '{%gist gistid %}' ,
search : 'gist'
2015-12-22 23:51:07 +00:00
} ,
{
text : '{%slideshare slideshareid %}' ,
search : 'slideshare'
} ,
{
text : '{%speakerdeck speakerdeckid %}' ,
search : 'speakerdeck'
2015-06-01 10:04:25 +00:00
}
] ;
2015-09-25 09:41:15 +00:00
var supportExtraTags = [
2015-07-01 16:10:20 +00:00
{
text : '[name tag]' ,
search : '[]' ,
command : function ( ) {
return '[name=' + personalInfo . name + ']' ;
} ,
} ,
2015-06-01 10:04:25 +00:00
{
2015-07-01 16:10:20 +00:00
text : '[time tag]' ,
search : '[]' ,
2015-06-01 10:04:25 +00:00
command : function ( ) {
2015-07-01 16:10:20 +00:00
return '[time=' + moment ( ) . format ( 'llll' ) + ']' ;
2015-06-01 10:04:25 +00:00
} ,
2015-07-01 16:10:20 +00:00
} ,
{
2015-07-03 01:14:49 +00:00
text : '[my color tag]' ,
2015-07-01 16:10:20 +00:00
search : '[]' ,
command : function ( ) {
return '[color=' + personalInfo . color + ']' ;
}
2015-07-03 01:14:49 +00:00
} ,
{
text : '[random color tag]' ,
search : '[]' ,
command : function ( ) {
var color = randomColor ( {
luminosity : 'light'
} ) ;
return '[color=' + color + ']' ;
}
2015-06-01 10:04:25 +00:00
}
] ;
2015-05-04 07:53:29 +00:00
var modeType = {
edit : { } ,
view : { } ,
both : { }
}
var statusType = {
connected : {
msg : "CONNECTED" ,
label : "label-warning" ,
fa : "fa-wifi"
} ,
online : {
2015-06-01 10:04:25 +00:00
msg : "ONLINE" ,
2015-05-04 07:53:29 +00:00
label : "label-primary" ,
fa : "fa-users"
} ,
offline : {
msg : "OFFLINE" ,
label : "label-danger" ,
fa : "fa-plug"
}
}
2015-09-25 06:10:15 +00:00
var defaultMode = modeType . view ;
2015-05-04 07:53:29 +00:00
//global vars
var loaded = false ;
2015-07-01 16:10:20 +00:00
var needRefresh = false ;
2015-05-04 07:53:29 +00:00
var isDirty = false ;
var editShown = false ;
var visibleXS = false ;
var visibleSM = false ;
var visibleMD = false ;
var visibleLG = false ;
var isTouchDevice = 'ontouchstart' in document . documentElement ;
var currentMode = defaultMode ;
var currentStatus = statusType . offline ;
var lastInfo = {
needRestore : false ,
cursor : null ,
scroll : null ,
edit : {
scroll : {
left : null ,
top : null
} ,
cursor : {
line : null ,
ch : null
}
} ,
view : {
scroll : {
left : null ,
top : null
}
} ,
history : null
} ;
2015-06-01 10:04:25 +00:00
var personalInfo = { } ;
var onlineUsers = [ ] ;
2016-05-11 21:05:25 +00:00
var fileTypes = {
"pl" : "perl" ,
"cgi" : "perl" ,
"js" : "javascript" ,
"php" : "php" ,
"sh" : "bash" ,
"rb" : "ruby" ,
2016-05-13 14:00:34 +00:00
"html" : "html" ,
"py" : "python"
} ;
2015-05-04 07:53:29 +00:00
//editor settings
2015-05-15 04:58:13 +00:00
var textit = document . getElementById ( "textit" ) ;
if ( ! textit ) throw new Error ( "There was no textit area!" ) ;
var editor = CodeMirror . fromTextArea ( textit , {
2015-05-04 07:53:29 +00:00
mode : 'gfm' ,
2016-04-20 10:09:36 +00:00
backdrop : 'gfm' ,
2015-05-15 04:58:13 +00:00
keyMap : "sublime" ,
2015-06-01 10:04:25 +00:00
viewportMargin : viewportMargin ,
2015-05-04 07:53:29 +00:00
styleActiveLine : true ,
lineNumbers : true ,
lineWrapping : true ,
2015-05-15 04:58:13 +00:00
showCursorWhenSelecting : true ,
2015-09-25 11:02:51 +00:00
highlightSelectionMatches : true ,
2015-06-01 10:04:25 +00:00
indentUnit : 4 ,
indentWithTabs : true ,
continueComments : "Enter" ,
2015-09-25 11:02:51 +00:00
theme : "one-dark" ,
2015-05-04 07:53:29 +00:00
inputStyle : "textarea" ,
matchBrackets : true ,
autoCloseBrackets : true ,
matchTags : {
bothTags : true
} ,
autoCloseTags : true ,
foldGutter : true ,
gutters : [ "CodeMirror-linenumbers" , "CodeMirror-foldgutter" ] ,
2015-06-01 10:04:25 +00:00
extraKeys : defaultExtraKeys ,
2015-09-25 11:02:51 +00:00
flattenSpans : true ,
addModeClass : true ,
2016-01-17 15:56:36 +00:00
readOnly : true ,
2016-01-17 20:28:04 +00:00
autoRefresh : true ,
2016-01-17 15:56:36 +00:00
placeholder : "← Start by enter title here\n===\nVisit /features if you don't know what to do.\nHappy hacking :)"
2015-05-04 07:53:29 +00:00
} ) ;
2015-07-01 16:10:20 +00:00
var inlineAttach = inlineAttachment . editors . codemirror4 . attach ( editor ) ;
2015-06-01 10:04:25 +00:00
defaultTextHeight = parseInt ( $ ( ".CodeMirror" ) . css ( 'line-height' ) ) ;
2015-05-04 07:53:29 +00:00
2016-02-09 04:51:07 +00:00
var statusBarTemplate = null ;
2015-09-24 06:59:45 +00:00
var statusBar = null ;
2016-04-20 08:16:46 +00:00
var statusPanel = null ;
2015-09-24 06:59:45 +00:00
var statusCursor = null ;
var statusFile = null ;
var statusIndicators = null ;
2016-02-09 04:51:07 +00:00
var statusLength = null ;
var statusKeymap = null ;
var statusIndent = null ;
2016-03-16 04:46:29 +00:00
function getStatusBarTemplate ( callback ) {
$ . get ( serverurl + '/views/statusbar.html' , function ( template ) {
statusBarTemplate = template ;
if ( callback ) callback ( ) ;
} ) ;
}
getStatusBarTemplate ( ) ;
2015-09-24 06:59:45 +00:00
function addStatusBar ( ) {
2016-03-16 04:46:29 +00:00
if ( ! statusBarTemplate ) {
getStatusBarTemplate ( addStatusBar ) ;
return ;
}
2016-02-09 04:51:07 +00:00
statusBar = $ ( statusBarTemplate ) ;
2015-09-24 06:59:45 +00:00
statusCursor = statusBar . find ( '.status-cursor' ) ;
statusFile = statusBar . find ( '.status-file' ) ;
statusIndicators = statusBar . find ( '.status-indicators' ) ;
2016-02-09 04:51:07 +00:00
statusIndent = statusBar . find ( '.status-indent' ) ;
statusKeymap = statusBar . find ( '.status-keymap' ) ;
statusLength = statusBar . find ( '.status-length' ) ;
2016-04-20 08:16:46 +00:00
statusPanel = editor . addPanel ( statusBar [ 0 ] , {
2015-09-24 06:59:45 +00:00
position : "bottom"
} ) ;
2016-04-24 04:30:16 +00:00
2016-02-09 04:51:07 +00:00
setIndent ( ) ;
setKeymap ( ) ;
}
function setIndent ( ) {
var cookieIndentType = Cookies . get ( 'indent_type' ) ;
var cookieTabSize = parseInt ( Cookies . get ( 'tab_size' ) ) ;
var cookieSpaceUnits = parseInt ( Cookies . get ( 'space_units' ) ) ;
if ( cookieIndentType ) {
if ( cookieIndentType == 'tab' ) {
editor . setOption ( 'indentWithTabs' , true ) ;
if ( cookieTabSize )
editor . setOption ( 'indentUnit' , cookieTabSize ) ;
} else if ( cookieIndentType == 'space' ) {
editor . setOption ( 'indentWithTabs' , false ) ;
if ( cookieSpaceUnits )
editor . setOption ( 'indentUnit' , cookieSpaceUnits ) ;
}
}
if ( cookieTabSize )
editor . setOption ( 'tabSize' , cookieTabSize ) ;
2016-04-24 04:30:16 +00:00
2016-02-09 04:51:07 +00:00
var type = statusIndicators . find ( '.indent-type' ) ;
var widthLabel = statusIndicators . find ( '.indent-width-label' ) ;
var widthInput = statusIndicators . find ( '.indent-width-input' ) ;
2016-04-24 04:30:16 +00:00
2016-02-09 04:51:07 +00:00
function setType ( ) {
if ( editor . getOption ( 'indentWithTabs' ) ) {
Cookies . set ( 'indent_type' , 'tab' , {
expires : 365
} ) ;
type . text ( 'Tab Size:' ) ;
} else {
Cookies . set ( 'indent_type' , 'space' , {
expires : 365
} ) ;
type . text ( 'Spaces:' ) ;
}
}
setType ( ) ;
2016-04-24 04:30:16 +00:00
2016-02-09 04:51:07 +00:00
function setUnit ( ) {
var unit = editor . getOption ( 'indentUnit' ) ;
if ( editor . getOption ( 'indentWithTabs' ) ) {
Cookies . set ( 'tab_size' , unit , {
expires : 365
} ) ;
} else {
Cookies . set ( 'space_units' , unit , {
expires : 365
} ) ;
}
widthLabel . text ( unit ) ;
}
setUnit ( ) ;
2016-04-24 04:30:16 +00:00
type . click ( function ( ) {
2016-02-09 04:51:07 +00:00
if ( editor . getOption ( 'indentWithTabs' ) ) {
editor . setOption ( 'indentWithTabs' , false ) ;
cookieSpaceUnits = parseInt ( Cookies . get ( 'space_units' ) ) ;
if ( cookieSpaceUnits )
editor . setOption ( 'indentUnit' , cookieSpaceUnits )
} else {
editor . setOption ( 'indentWithTabs' , true ) ;
cookieTabSize = parseInt ( Cookies . get ( 'tab_size' ) ) ;
if ( cookieTabSize ) {
editor . setOption ( 'indentUnit' , cookieTabSize ) ;
editor . setOption ( 'tabSize' , cookieTabSize ) ;
}
}
setType ( ) ;
2016-04-24 04:30:16 +00:00
setUnit ( ) ;
2016-02-09 04:51:07 +00:00
} ) ;
2016-04-24 04:30:16 +00:00
widthLabel . click ( function ( ) {
2016-02-09 04:51:07 +00:00
if ( widthLabel . is ( ':visible' ) ) {
widthLabel . addClass ( 'hidden' ) ;
widthInput . removeClass ( 'hidden' ) ;
widthInput . val ( editor . getOption ( 'indentUnit' ) ) ;
widthInput . select ( ) ;
} else {
widthLabel . removeClass ( 'hidden' ) ;
widthInput . addClass ( 'hidden' ) ;
}
} ) ;
2016-04-24 04:30:16 +00:00
widthInput . on ( 'change' , function ( ) {
2016-02-17 19:38:09 +00:00
var val = parseInt ( widthInput . val ( ) ) ;
2016-04-24 04:30:16 +00:00
if ( ! val ) val = editor . getOption ( 'indentUnit' ) ;
2016-02-09 04:51:07 +00:00
if ( val < 1 ) val = 1 ;
else if ( val > 10 ) val = 10 ;
2016-04-24 04:30:16 +00:00
2016-02-09 04:51:07 +00:00
if ( editor . getOption ( 'indentWithTabs' ) ) {
editor . setOption ( 'tabSize' , val ) ;
}
editor . setOption ( 'indentUnit' , val ) ;
setUnit ( ) ;
} ) ;
2016-04-24 04:30:16 +00:00
widthInput . on ( 'blur' , function ( ) {
2016-02-09 04:51:07 +00:00
widthLabel . removeClass ( 'hidden' ) ;
widthInput . addClass ( 'hidden' ) ;
} ) ;
}
function setKeymap ( ) {
var cookieKeymap = Cookies . get ( 'keymap' ) ;
if ( cookieKeymap )
editor . setOption ( 'keyMap' , cookieKeymap ) ;
2016-04-24 04:30:16 +00:00
2016-02-09 04:51:07 +00:00
var label = statusIndicators . find ( '.ui-keymap-label' ) ;
var sublime = statusIndicators . find ( '.ui-keymap-sublime' ) ;
var emacs = statusIndicators . find ( '.ui-keymap-emacs' ) ;
var vim = statusIndicators . find ( '.ui-keymap-vim' ) ;
2016-04-24 04:30:16 +00:00
2016-02-09 04:51:07 +00:00
function setKeymapLabel ( ) {
var keymap = editor . getOption ( 'keyMap' ) ;
Cookies . set ( 'keymap' , keymap , {
expires : 365
} ) ;
label . text ( keymap ) ;
}
setKeymapLabel ( ) ;
2016-04-24 04:30:16 +00:00
sublime . click ( function ( ) {
2016-02-09 04:51:07 +00:00
editor . setOption ( 'keyMap' , 'sublime' ) ;
setKeymapLabel ( ) ;
} ) ;
2016-04-24 04:30:16 +00:00
emacs . click ( function ( ) {
2016-02-09 04:51:07 +00:00
editor . setOption ( 'keyMap' , 'emacs' ) ;
setKeymapLabel ( ) ;
} ) ;
2016-04-24 04:30:16 +00:00
vim . click ( function ( ) {
2016-02-09 04:51:07 +00:00
editor . setOption ( 'keyMap' , 'vim' ) ;
setKeymapLabel ( ) ;
} ) ;
2015-09-24 06:59:45 +00:00
}
var selection = null ;
function updateStatusBar ( ) {
if ( ! statusBar ) return ;
var cursor = editor . getCursor ( ) ;
var cursorText = 'Line ' + ( cursor . line + 1 ) + ', Columns ' + ( cursor . ch + 1 ) ;
if ( selection ) {
var anchor = selection . anchor ;
var head = selection . head ;
var start = head . line <= anchor . line ? head : anchor ;
var end = head . line >= anchor . line ? head : anchor ;
var selectionText = ' — Selected ' ;
var selectionCharCount = Math . abs ( head . ch - anchor . ch ) ;
// borrow from brackets EditorStatusBar.js
if ( start . line !== end . line ) {
var lines = end . line - start . line + 1 ;
if ( end . ch === 0 ) {
lines -- ;
}
selectionText += lines + ' lines' ;
} else if ( selectionCharCount > 0 )
selectionText += selectionCharCount + ' columns' ;
if ( start . line !== end . line || selectionCharCount > 0 )
cursorText += selectionText ;
}
statusCursor . text ( cursorText ) ;
var fileText = ' — ' + editor . lineCount ( ) + ' Lines' ;
statusFile . text ( fileText ) ;
2016-02-09 04:51:07 +00:00
statusLength . text ( 'Length ' + editor . getValue ( ) . length ) ;
2015-09-24 06:59:45 +00:00
}
2015-05-04 07:53:29 +00:00
//ui vars
var ui = {
spinner : $ ( ".ui-spinner" ) ,
content : $ ( ".ui-content" ) ,
toolbar : {
shortStatus : $ ( ".ui-short-status" ) ,
status : $ ( ".ui-status" ) ,
new : $ ( ".ui-new" ) ,
2015-07-06 05:51:55 +00:00
publish : $ ( ".ui-publish" ) ,
2015-05-04 07:53:29 +00:00
download : {
2015-09-25 11:09:43 +00:00
markdown : $ ( ".ui-download-markdown" ) ,
2016-06-17 08:17:37 +00:00
html : $ ( ".ui-download-html" ) ,
rawhtml : $ ( ".ui-download-raw-html" )
2015-05-04 07:53:29 +00:00
} ,
2015-09-25 11:09:43 +00:00
export : {
2016-01-31 21:42:26 +00:00
dropbox : $ ( ".ui-save-dropbox" ) ,
2016-03-04 15:17:35 +00:00
googleDrive : $ ( ".ui-save-google-drive" ) ,
2016-05-09 21:07:02 +00:00
gist : $ ( ".ui-save-gist" ) ,
snippet : $ ( ".ui-save-snippet" )
2015-05-04 07:53:29 +00:00
} ,
import : {
dropbox : $ ( ".ui-import-dropbox" ) ,
2016-03-04 15:17:35 +00:00
googleDrive : $ ( ".ui-import-google-drive" ) ,
2016-04-20 10:06:36 +00:00
gist : $ ( ".ui-import-gist" ) ,
2016-05-09 21:07:02 +00:00
snippet : $ ( ".ui-import-snippet" ) ,
2015-05-04 07:53:29 +00:00
clipboard : $ ( ".ui-import-clipboard" )
} ,
2015-12-18 15:40:52 +00:00
beta : {
pdf : $ ( ".ui-beta-pdf" ) ,
slide : $ ( ".ui-beta-slide" )
} ,
2015-05-04 07:53:29 +00:00
mode : $ ( ".ui-mode" ) ,
edit : $ ( ".ui-edit" ) ,
view : $ ( ".ui-view" ) ,
2015-07-01 16:10:20 +00:00
both : $ ( ".ui-both" ) ,
uploadImage : $ ( ".ui-upload-image" )
} ,
infobar : {
lastchange : $ ( ".ui-lastchange" ) ,
2016-01-12 14:01:42 +00:00
lastchangeuser : $ ( ".ui-lastchangeuser" ) ,
nolastchangeuser : $ ( ".ui-no-lastchangeuser" ) ,
2015-07-01 16:10:20 +00:00
permission : {
permission : $ ( ".ui-permission" ) ,
label : $ ( ".ui-permission-label" ) ,
freely : $ ( ".ui-permission-freely" ) ,
editable : $ ( ".ui-permission-editable" ) ,
2016-01-17 15:51:27 +00:00
locked : $ ( ".ui-permission-locked" ) ,
private : $ ( ".ui-permission-private" )
2015-07-01 16:10:20 +00:00
}
} ,
toc : {
toc : $ ( '.ui-toc' ) ,
affix : $ ( '.ui-affix-toc' ) ,
label : $ ( '.ui-toc-label' ) ,
dropdown : $ ( '.ui-toc-dropdown' )
2015-05-04 07:53:29 +00:00
} ,
area : {
edit : $ ( ".ui-edit-area" ) ,
view : $ ( ".ui-view-area" ) ,
codemirror : $ ( ".ui-edit-area .CodeMirror" ) ,
2016-03-15 03:10:08 +00:00
codemirrorScroll : $ ( ".ui-edit-area .CodeMirror .CodeMirror-scroll" ) ,
2016-03-15 03:12:45 +00:00
codemirrorSizer : $ ( ".ui-edit-area .CodeMirror .CodeMirror-sizer" ) ,
codemirrorSizerInner : $ ( ".ui-edit-area .CodeMirror .CodeMirror-sizer > div" ) ,
2016-05-26 05:17:00 +00:00
markdown : $ ( ".ui-view-area .markdown-body" ) ,
resize : {
handle : $ ( '.ui-resizable-handle' ) ,
syncToggle : $ ( '.ui-sync-toggle' )
}
2016-05-12 15:19:14 +00:00
} ,
modal : {
2016-05-12 16:28:08 +00:00
snippetImportProjects : $ ( "#snippetImportModalProjects" ) ,
2016-06-17 08:15:53 +00:00
snippetImportSnippets : $ ( "#snippetImportModalSnippets" ) ,
revision : $ ( "#revisionModal" )
2015-05-04 07:53:29 +00:00
}
} ;
//page actions
var opts = {
lines : 11 , // The number of lines to draw
length : 20 , // The length of each line
width : 2 , // The line thickness
radius : 30 , // The radius of the inner circle
corners : 0 , // Corner roundness (0..1)
rotate : 0 , // The rotation offset
direction : 1 , // 1: clockwise, -1: counterclockwise
color : '#000' , // #rgb or #rrggbb or array of colors
speed : 1.1 , // Rounds per second
trail : 60 , // Afterglow percentage
shadow : false , // Whether to render a shadow
hwaccel : true , // Whether to use hardware acceleration
className : 'spinner' , // The CSS class to assign to the spinner
zIndex : 2e9 , // The z-index (defaults to 2000000000)
top : '50%' , // Top position relative to parent
left : '50%' // Left position relative to parent
} ;
var spinner = new Spinner ( opts ) . spin ( ui . spinner [ 0 ] ) ;
2015-06-01 10:04:25 +00:00
//idle
var idle = new Idle ( {
onAway : idleStateChange ,
onAwayBack : idleStateChange ,
awayTimeout : idleTime
} ) ;
ui . area . codemirror . on ( 'touchstart' , function ( ) {
idle . onActive ( ) ;
} ) ;
2015-09-25 10:48:45 +00:00
var haveUnreadChanges = false ;
function setHaveUnreadChanges ( bool ) {
if ( ! loaded ) return ;
if ( bool && ( idle . isAway || Visibility . hidden ( ) ) ) {
haveUnreadChanges = true ;
} else if ( ! bool && ! idle . isAway && ! Visibility . hidden ( ) ) {
haveUnreadChanges = false ;
}
}
function updateTitleReminder ( ) {
if ( ! loaded ) return ;
if ( haveUnreadChanges ) {
2016-01-12 14:01:42 +00:00
document . title = '• ' + renderTitle ( ui . area . markdown ) ;
2015-09-25 10:48:45 +00:00
} else {
2016-01-12 14:01:42 +00:00
document . title = renderTitle ( ui . area . markdown ) ;
2015-09-25 10:48:45 +00:00
}
}
2015-06-01 10:04:25 +00:00
function idleStateChange ( ) {
emitUserStatus ( ) ;
updateOnlineStatus ( ) ;
2015-09-25 10:48:45 +00:00
if ( ! idle . isAway )
setHaveUnreadChanges ( false ) ;
updateTitleReminder ( ) ;
2015-06-01 10:04:25 +00:00
}
2016-06-17 08:31:36 +00:00
function setRefreshModal ( status ) {
2015-07-01 16:10:20 +00:00
$ ( '#refreshModal' ) . modal ( 'show' ) ;
2016-06-17 08:31:36 +00:00
$ ( '#refreshModal' ) . find ( '.modal-body > div' ) . hide ( ) ;
$ ( '#refreshModal' ) . find ( '.' + status ) . show ( ) ;
}
function setNeedRefresh ( ) {
2015-07-01 16:10:20 +00:00
needRefresh = true ;
editor . setOption ( 'readOnly' , true ) ;
socket . disconnect ( ) ;
showStatus ( statusType . offline ) ;
2015-06-01 10:04:25 +00:00
}
2016-06-17 08:31:36 +00:00
loginStateChangeEvent = function ( ) {
setRefreshModal ( 'user-state-changed' ) ;
setNeedRefresh ( ) ;
} ;
2015-07-01 16:10:20 +00:00
2015-06-01 10:04:25 +00:00
//visibility
var wasFocus = false ;
Visibility . change ( function ( e , state ) {
var hidden = Visibility . hidden ( ) ;
if ( hidden ) {
if ( editorHasFocus ( ) ) {
wasFocus = true ;
editor . getInputField ( ) . blur ( ) ;
}
} else {
if ( wasFocus ) {
2016-03-15 03:10:58 +00:00
if ( ! visibleXS ) {
editor . focus ( ) ;
editor . refresh ( ) ;
}
2015-06-01 10:04:25 +00:00
wasFocus = false ;
}
2015-09-25 10:48:45 +00:00
setHaveUnreadChanges ( false ) ;
2015-06-01 10:04:25 +00:00
}
2015-09-25 10:48:45 +00:00
updateTitleReminder ( ) ;
2015-06-01 10:04:25 +00:00
} ) ;
2015-05-04 07:53:29 +00:00
//when page ready
$ ( document ) . ready ( function ( ) {
2015-06-01 10:04:25 +00:00
idle . checkAway ( ) ;
2015-05-04 07:53:29 +00:00
checkResponsive ( ) ;
2015-06-01 10:04:25 +00:00
//if in smaller screen, we don't need advanced scrollbar
var scrollbarStyle ;
if ( visibleXS ) {
scrollbarStyle = 'native' ;
} else {
scrollbarStyle = 'overlay' ;
}
if ( scrollbarStyle != editor . getOption ( 'scrollbarStyle' ) ) {
editor . setOption ( 'scrollbarStyle' , scrollbarStyle ) ;
clearMap ( ) ;
}
checkEditorStyle ( ) ;
2015-05-04 07:53:29 +00:00
/* we need this only on touch devices */
if ( isTouchDevice ) {
2015-05-15 04:58:13 +00:00
/* cache dom references */
var $body = jQuery ( 'body' ) ;
2015-05-04 07:53:29 +00:00
/* bind events */
$ ( document )
2015-05-15 04:58:13 +00:00
. on ( 'focus' , 'textarea, input' , function ( ) {
$body . addClass ( 'fixfixed' ) ;
} )
. on ( 'blur' , 'textarea, input' , function ( ) {
$body . removeClass ( 'fixfixed' ) ;
} ) ;
2015-05-04 07:53:29 +00:00
}
2015-07-01 16:10:20 +00:00
//showup
$ ( ) . showUp ( '.navbar' , {
upClass : 'navbar-hide' ,
downClass : 'navbar-show'
} ) ;
2016-01-12 14:01:42 +00:00
//tooltip
$ ( '[data-toggle="tooltip"]' ) . tooltip ( ) ;
2015-05-04 07:53:29 +00:00
} ) ;
//when page resize
$ ( window ) . resize ( function ( ) {
2015-09-25 10:37:40 +00:00
checkLayout ( ) ;
checkEditorStyle ( ) ;
checkTocStyle ( ) ;
checkCursorMenu ( ) ;
windowResize ( ) ;
2015-05-04 07:53:29 +00:00
} ) ;
2015-06-01 10:04:25 +00:00
//when page unload
$ ( window ) . unload ( function ( ) {
2015-09-25 10:37:40 +00:00
updateHistoryInner ( ) ;
2015-07-11 04:43:08 +00:00
} ) ;
$ ( window ) . error ( function ( ) {
2015-09-25 10:37:40 +00:00
//setNeedRefresh();
2015-06-01 10:04:25 +00:00
} ) ;
2016-05-29 05:58:32 +00:00
function autoSyncscroll ( ) {
if ( editorHasFocus ( ) ) {
syncScrollToView ( ) ;
} else {
syncScrollToEdit ( ) ;
}
}
2015-09-25 10:01:15 +00:00
var windowResizeDebounce = 200 ;
var windowResize = _ . debounce ( windowResizeInner , windowResizeDebounce ) ;
2016-03-15 03:10:08 +00:00
function windowResizeInner ( callback ) {
2015-09-25 10:37:40 +00:00
checkLayout ( ) ;
2015-05-15 04:58:13 +00:00
checkResponsive ( ) ;
2015-06-01 10:04:25 +00:00
checkEditorStyle ( ) ;
2015-07-01 16:10:20 +00:00
checkTocStyle ( ) ;
2015-09-25 10:37:40 +00:00
checkCursorMenu ( ) ;
2015-07-01 16:10:20 +00:00
//refresh editor
2015-06-01 10:04:25 +00:00
if ( loaded ) {
2016-03-15 03:10:08 +00:00
if ( editor . getOption ( 'scrollbarStyle' ) === 'native' ) {
2016-05-26 18:04:38 +00:00
setTimeout ( function ( ) {
clearMap ( ) ;
2016-05-29 05:58:32 +00:00
autoSyncscroll ( ) ;
2016-05-26 18:04:38 +00:00
updateScrollspy ( ) ;
if ( callback && typeof callback === 'function' )
callback ( ) ;
} , 1 ) ;
2016-03-15 03:10:08 +00:00
} else {
// force it load all docs at once to prevent scroll knob blink
editor . setOption ( 'viewportMargin' , Infinity ) ;
setTimeout ( function ( ) {
clearMap ( ) ;
2016-05-29 05:58:32 +00:00
autoSyncscroll ( ) ;
2016-03-15 03:10:08 +00:00
editor . setOption ( 'viewportMargin' , viewportMargin ) ;
//add or update user cursors
for ( var i = 0 ; i < onlineUsers . length ; i ++ ) {
if ( onlineUsers [ i ] . id != personalInfo . id )
buildCursor ( onlineUsers [ i ] ) ;
}
updateScrollspy ( ) ;
if ( callback && typeof callback === 'function' )
callback ( ) ;
} , 1 ) ;
}
2015-06-01 10:04:25 +00:00
}
}
2015-09-25 10:37:40 +00:00
function checkLayout ( ) {
var navbarHieght = $ ( '.navbar' ) . outerHeight ( ) ;
$ ( 'body' ) . css ( 'padding-top' , navbarHieght + 'px' ) ;
}
2015-06-01 10:04:25 +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
2015-05-04 07:53:29 +00:00
//768-792px have a gap
function checkResponsive ( ) {
visibleXS = $ ( ".visible-xs" ) . is ( ":visible" ) ;
visibleSM = $ ( ".visible-sm" ) . is ( ":visible" ) ;
visibleMD = $ ( ".visible-md" ) . is ( ":visible" ) ;
visibleLG = $ ( ".visible-lg" ) . is ( ":visible" ) ;
2015-06-01 10:04:25 +00:00
2015-05-04 07:53:29 +00:00
if ( visibleXS && currentMode == modeType . both )
2015-06-01 10:04:25 +00:00
if ( editorHasFocus ( ) )
2015-05-04 07:53:29 +00:00
changeMode ( modeType . edit ) ;
else
changeMode ( modeType . view ) ;
2015-06-01 10:04:25 +00:00
emitUserStatus ( ) ;
}
2015-09-25 10:46:08 +00:00
var lastEditorWidth = 0 ;
2016-05-26 16:12:07 +00:00
var previousFocusOnEditor = null ;
2015-09-25 10:46:08 +00:00
2015-06-01 10:04:25 +00:00
function checkEditorStyle ( ) {
2016-03-15 03:10:08 +00:00
var desireHeight = statusBar ? ( ui . area . edit . height ( ) - statusBar . outerHeight ( ) ) : ui . area . edit . height ( ) ;
// set editor height and min height based on scrollbar style and mode
2015-06-01 10:04:25 +00:00
var scrollbarStyle = editor . getOption ( 'scrollbarStyle' ) ;
if ( scrollbarStyle == 'overlay' || currentMode == modeType . both ) {
2016-03-15 03:10:08 +00:00
ui . area . codemirrorScroll . css ( 'height' , desireHeight + 'px' ) ;
ui . area . codemirrorScroll . css ( 'min-height' , '' ) ;
2016-06-17 07:57:34 +00:00
checkEditorScrollbar ( ) ;
2015-06-01 10:04:25 +00:00
} else if ( scrollbarStyle == 'native' ) {
2016-03-15 03:10:08 +00:00
ui . area . codemirrorScroll . css ( 'height' , '' ) ;
ui . area . codemirrorScroll . css ( 'min-height' , desireHeight + 'px' ) ;
}
2016-04-20 08:17:24 +00:00
// workaround editor will have wrong doc height when editor height changed
editor . setSize ( null , ui . area . edit . height ( ) ) ;
2015-09-25 10:46:08 +00:00
//make editor resizable
2016-05-26 05:17:00 +00:00
if ( ! ui . area . resize . handle . length ) {
2016-05-30 03:38:27 +00:00
ui . area . edit . resizable ( {
handles : 'e' ,
maxWidth : $ ( window ) . width ( ) * 0.7 ,
minWidth : $ ( window ) . width ( ) * 0.2 ,
create : function ( e , ui ) {
$ ( this ) . parent ( ) . on ( 'resize' , function ( e ) {
e . stopPropagation ( ) ;
} ) ;
} ,
start : function ( e ) {
editor . setOption ( 'viewportMargin' , Infinity ) ;
} ,
resize : function ( e ) {
ui . area . resize . syncToggle . stop ( true , true ) . show ( ) ;
checkTocStyle ( ) ;
} ,
stop : function ( e ) {
lastEditorWidth = ui . area . edit . width ( ) ;
// workaround that scroll event bindings
preventSyncScrollToView = 2 ;
preventSyncScrollToEdit = true ;
editor . setOption ( 'viewportMargin' , viewportMargin ) ;
if ( editorHasFocus ( ) ) {
windowResizeInner ( function ( ) {
ui . area . codemirrorScroll . scroll ( ) ;
} ) ;
} else {
windowResizeInner ( function ( ) {
ui . area . view . scroll ( ) ;
} ) ;
}
2016-06-17 07:57:34 +00:00
checkEditorScrollbar ( ) ;
2016-05-30 03:38:27 +00:00
}
} ) ;
2016-05-26 05:17:00 +00:00
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>' ) ;
2016-05-26 16:12:07 +00:00
ui . area . resize . syncToggle . hover ( function ( ) {
previousFocusOnEditor = editorHasFocus ( ) ;
} , function ( ) {
previousFocusOnEditor = null ;
} ) ;
2016-05-26 05:17:00 +00:00
ui . area . resize . syncToggle . click ( function ( ) {
syncscroll = ! syncscroll ;
checkSyncToggle ( ) ;
} ) ;
ui . area . resize . handle . append ( ui . area . resize . syncToggle ) ;
ui . area . resize . syncToggle . hide ( ) ;
ui . area . resize . handle . hover ( function ( ) {
ui . area . resize . syncToggle . stop ( true , true ) . delay ( 200 ) . fadeIn ( 100 ) ;
} , function ( ) {
ui . area . resize . syncToggle . stop ( true , true ) . delay ( 300 ) . fadeOut ( 300 ) ;
} ) ;
}
}
function checkSyncToggle ( ) {
if ( syncscroll ) {
2016-05-26 16:12:07 +00:00
if ( previousFocusOnEditor ) {
preventSyncScrollToView = false ;
syncScrollToView ( ) ;
} else {
preventSyncScrollToEdit = false ;
syncScrollToEdit ( ) ;
}
2016-05-26 05:17:00 +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
}
2016-06-17 07:57:34 +00:00
function checkEditorScrollbar ( ) {
// workaround simple scroll bar knob
// will get wrong position when editor height changed
var scrollInfo = editor . getScrollInfo ( ) ;
editor . scrollTo ( null , scrollInfo . top - 1 ) ;
editor . scrollTo ( null , scrollInfo . top ) ;
}
2015-07-01 16:10:20 +00:00
function checkTocStyle ( ) {
//toc right
var paddingRight = parseFloat ( ui . area . markdown . css ( 'padding-right' ) ) ;
var right = ( $ ( window ) . width ( ) - ( ui . area . markdown . offset ( ) . left + ui . area . markdown . outerWidth ( ) - paddingRight ) ) ;
ui . toc . toc . css ( 'right' , right + 'px' ) ;
//affix toc left
var newbool ;
var rightMargin = ( ui . area . markdown . parent ( ) . outerWidth ( ) - ui . area . markdown . outerWidth ( ) ) / 2 ;
//for ipad or wider device
if ( rightMargin >= 133 ) {
newbool = true ;
var affixLeftMargin = ( ui . toc . affix . outerWidth ( ) - ui . toc . affix . width ( ) ) / 2 ;
var left = ui . area . markdown . offset ( ) . left + ui . area . markdown . outerWidth ( ) - affixLeftMargin ;
ui . toc . affix . css ( 'left' , left + 'px' ) ;
2015-09-25 10:32:33 +00:00
ui . toc . affix . css ( 'width' , rightMargin + 'px' ) ;
2015-07-01 16:10:20 +00:00
} else {
newbool = false ;
}
//toc scrollspy
ui . toc . toc . removeClass ( 'scrollspy-body, scrollspy-view' ) ;
ui . toc . affix . removeClass ( 'scrollspy-body, scrollspy-view' ) ;
2015-09-25 10:32:33 +00:00
if ( currentMode == modeType . both ) {
ui . toc . toc . addClass ( 'scrollspy-view' ) ;
ui . toc . affix . addClass ( 'scrollspy-view' ) ;
} else if ( currentMode != modeType . both && ! newbool ) {
2015-07-01 16:10:20 +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 ( ) ;
}
}
2015-05-04 07:53:29 +00:00
function showStatus ( type , num ) {
currentStatus = type ;
var shortStatus = ui . toolbar . shortStatus ;
var status = ui . toolbar . status ;
var label = $ ( '<span class="label"></span>' ) ;
var fa = $ ( '<i class="fa"></i>' ) ;
var msg = "" ;
var shortMsg = "" ;
shortStatus . html ( "" ) ;
status . html ( "" ) ;
switch ( currentStatus ) {
2016-04-24 04:30:16 +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 ;
2015-05-04 07:53:29 +00:00
}
label . append ( fa ) ;
var shortLabel = label . clone ( ) ;
shortLabel . append ( " " + shortMsg ) ;
shortStatus . append ( shortLabel ) ;
label . append ( " " + msg ) ;
status . append ( label ) ;
}
function toggleMode ( ) {
2015-05-15 04:58:13 +00:00
switch ( currentMode ) {
2016-04-24 04:30:16 +00:00
case modeType . edit :
changeMode ( modeType . view ) ;
break ;
case modeType . view :
changeMode ( modeType . edit ) ;
break ;
case modeType . both :
changeMode ( modeType . view ) ;
break ;
2015-05-04 07:53:29 +00:00
}
}
2015-09-25 10:41:03 +00:00
var lastMode = null ;
2015-05-04 07:53:29 +00:00
function changeMode ( type ) {
2016-03-15 03:10:08 +00:00
// lock navbar to prevent it hide after changeMode
2015-09-24 05:55:02 +00:00
lockNavbar ( ) ;
2015-05-04 07:53:29 +00:00
saveInfo ( ) ;
2015-09-25 10:41:03 +00:00
if ( type ) {
lastMode = currentMode ;
2015-05-04 07:53:29 +00:00
currentMode = type ;
2015-09-25 10:41:03 +00:00
}
2015-05-04 07:53:29 +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 ) ;
switch ( currentMode ) {
2016-04-24 04:30:16 +00:00
case modeType . edit :
ui . area . edit . show ( ) ;
ui . area . view . hide ( ) ;
if ( ! editShown ) {
editor . refresh ( ) ;
editShown = true ;
}
break ;
case modeType . view :
ui . area . edit . hide ( ) ;
ui . area . view . show ( ) ;
break ;
case modeType . both :
ui . area . codemirror . addClass ( scrollClass ) ;
ui . area . edit . addClass ( responsiveClass ) . show ( ) ;
ui . area . view . addClass ( scrollClass ) ;
ui . area . view . show ( ) ;
break ;
2015-05-04 07:53:29 +00:00
}
2016-03-15 03:10:58 +00:00
if ( currentMode == modeType . view ) {
2015-05-04 07:53:29 +00:00
editor . getInputField ( ) . blur ( ) ;
}
2015-07-01 16:10:20 +00:00
if ( currentMode == modeType . edit || currentMode == modeType . both ) {
ui . toolbar . uploadImage . fadeIn ( ) ;
2015-09-24 06:59:45 +00:00
//add and update status bar
if ( ! statusBar ) {
addStatusBar ( ) ;
updateStatusBar ( ) ;
}
2015-09-25 10:35:18 +00:00
//work around foldGutter might not init properly
editor . setOption ( 'foldGutter' , false ) ;
editor . setOption ( 'foldGutter' , true ) ;
2015-07-01 16:10:20 +00:00
} else {
ui . toolbar . uploadImage . fadeOut ( ) ;
}
if ( currentMode != modeType . edit ) {
$ ( document . body ) . css ( 'background-color' , 'white' ) ;
2015-05-04 07:53:29 +00:00
updateView ( ) ;
2015-07-01 16:10:20 +00:00
} else {
$ ( document . body ) . css ( 'background-color' , ui . area . codemirror . css ( 'background-color' ) ) ;
}
2015-09-25 10:46:08 +00:00
//check resizable editor style
if ( currentMode == modeType . both ) {
if ( lastEditorWidth > 0 )
ui . area . edit . css ( 'width' , lastEditorWidth + 'px' ) ;
else
ui . area . edit . css ( 'width' , '' ) ;
2016-05-26 05:17:00 +00:00
ui . area . resize . handle . show ( ) ;
2015-09-25 10:46:08 +00:00
} else {
ui . area . edit . css ( 'width' , '' ) ;
2016-05-26 05:17:00 +00:00
ui . area . resize . handle . hide ( ) ;
2015-09-25 10:46:08 +00:00
}
2015-09-25 10:01:15 +00:00
windowResizeInner ( ) ;
2016-04-24 04:30:16 +00:00
2015-05-04 07:53:29 +00:00
restoreInfo ( ) ;
2016-04-24 04:30:16 +00:00
2015-09-25 10:41:03 +00:00
if ( lastMode == modeType . view && currentMode == modeType . both ) {
2016-05-26 16:12:07 +00:00
preventSyncScrollToView = 2 ;
2016-05-29 05:58:32 +00:00
syncScrollToEdit ( null , true ) ;
2016-03-15 03:10:08 +00:00
}
2016-05-26 16:12:07 +00:00
if ( lastMode == modeType . edit && currentMode == modeType . both ) {
preventSyncScrollToEdit = 2 ;
2016-05-29 05:58:32 +00:00
syncScrollToView ( null , true ) ;
2016-05-26 16:12:07 +00:00
}
2016-05-26 18:04:38 +00:00
if ( lastMode == modeType . both && currentMode != modeType . both ) {
preventSyncScrollToView = false ;
preventSyncScrollToEdit = false ;
}
2016-04-24 04:30:16 +00:00
2016-03-15 03:10:08 +00:00
if ( lastMode != modeType . edit && currentMode == modeType . edit ) {
editor . refresh ( ) ;
2015-09-25 10:41:03 +00:00
}
2015-06-01 10:04:25 +00:00
2015-05-04 07:53:29 +00:00
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-toggle-on' ) . removeClass ( 'fa-toggle-off' ) ;
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-toggle-off' ) ;
} else if ( ui . area . view . is ( ":visible" ) ) { //view
ui . toolbar . view . addClass ( "active" ) ;
modeIcon . addClass ( 'fa-toggle-on' ) ;
}
2015-09-24 05:55:02 +00:00
unlockNavbar ( ) ;
}
function lockNavbar ( ) {
$ ( '.navbar' ) . addClass ( 'locked' ) ;
}
var unlockNavbar = _ . debounce ( function ( ) {
$ ( '.navbar' ) . removeClass ( 'locked' ) ;
} , 200 ) ;
2015-09-25 10:41:03 +00:00
function closestIndex ( arr , closestTo ) {
var closest = Math . max . apply ( null , arr ) ; //Get the highest number in arr in case it match nothing.
var index = 0 ;
for ( var i = 0 ; i < arr . length ; i ++ ) { //Loop the array
if ( arr [ i ] >= closestTo && arr [ i ] < closest ) {
closest = arr [ i ] ; //Check if it's higher than your number, but lower than your closest value
index = i ;
}
}
return index ; // return the value
2015-05-04 07:53:29 +00:00
}
2016-03-04 15:17:35 +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-05-15 02:54:24 +00:00
// check if dropbox app key is set and load scripts
if ( DROPBOX _APP _KEY ) {
$ ( '<script>' )
. attr ( 'type' , 'text/javascript' )
. attr ( 'src' , 'https://www.dropbox.com/static/api/2/dropins.js' )
. attr ( 'id' , 'dropboxjs' )
. attr ( 'data-app-key' , DROPBOX _APP _KEY )
. appendTo ( 'body' ) ;
} else {
ui . toolbar . import . dropbox . hide ( ) ;
ui . toolbar . export . dropbox . hide ( ) ;
}
// check if google api key and client id are set and load scripts
if ( GOOGLE _API _KEY && GOOGLE _CLIENT _ID ) {
$ ( '<script>' )
. attr ( 'type' , 'text/javascript' )
. attr ( 'src' , 'https://www.google.com/jsapi' )
. appendTo ( 'body' ) ;
$ ( '<script>' )
. attr ( 'type' , 'text/javascript' )
. attr ( 'src' , 'https://apis.google.com/js/client:plusone.js?onload=onGoogleClientLoaded' )
. appendTo ( 'body' ) ;
} else {
ui . toolbar . import . googleDrive . hide ( ) ;
ui . toolbar . export . googleDrive . hide ( ) ;
}
2015-05-04 07:53:29 +00:00
//button actions
2015-07-01 16:10:20 +00:00
//share
2016-02-17 04:08:44 +00:00
ui . toolbar . publish . attr ( "href" , noteurl + "/publish" ) ;
2015-05-04 07:53:29 +00:00
//download
//markdown
2015-12-18 15:44:08 +00:00
ui . toolbar . download . markdown . click ( function ( e ) {
2016-04-24 04:30:16 +00:00
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
2015-05-04 07:53:29 +00:00
var filename = renderFilename ( ui . area . markdown ) + '.md' ;
var markdown = editor . getValue ( ) ;
2015-05-15 04:58:13 +00:00
var blob = new Blob ( [ markdown ] , {
type : "text/markdown;charset=utf-8"
} ) ;
2015-05-04 07:53:29 +00:00
saveAs ( blob , filename ) ;
} ) ;
2015-09-25 11:09:43 +00:00
//html
2015-12-18 15:44:08 +00:00
ui . toolbar . download . html . click ( function ( e ) {
2016-04-24 04:30:16 +00:00
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
2015-09-25 11:09:43 +00:00
exportToHTML ( ui . area . markdown ) ;
} ) ;
2016-06-17 08:17:37 +00:00
// raw html
ui . toolbar . download . rawhtml . click ( function ( e ) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
exportToRawHTML ( ui . area . markdown ) ;
} ) ;
2015-09-25 11:09:43 +00:00
//export to dropbox
ui . toolbar . export . dropbox . click ( function ( ) {
2015-05-04 07:53:29 +00:00
var filename = renderFilename ( ui . area . markdown ) + '.md' ;
var options = {
files : [
2015-05-15 04:58:13 +00:00
{
2016-02-17 04:08:44 +00:00
'url' : noteurl + "/download" ,
2015-05-15 04:58:13 +00:00
'filename' : filename
}
2015-12-16 05:16:31 +00:00
] ,
error : function ( errorMessage ) {
console . error ( errorMessage ) ;
}
2015-05-04 07:53:29 +00:00
} ;
Dropbox . save ( options ) ;
} ) ;
2016-03-04 15:17:35 +00:00
function uploadToGoogleDrive ( accessToken ) {
ui . spinner . show ( ) ;
var filename = renderFilename ( ui . area . markdown ) + '.md' ;
var markdown = editor . getValue ( ) ;
var blob = new Blob ( [ markdown ] , {
type : "text/markdown;charset=utf-8"
} ) ;
blob . name = filename ;
var uploader = new MediaUploader ( {
file : blob ,
token : accessToken ,
2016-04-24 04:30:16 +00:00
onComplete : function ( data ) {
2016-03-04 15:17:35 +00:00
data = JSON . parse ( data ) ;
showMessageModal ( '<i class="fa fa-cloud-upload"></i> Export to Google Drive' , 'Export Complete!' , data . alternateLink , 'Click here to view your file' , true ) ;
ui . spinner . hide ( ) ;
} ,
2016-04-24 04:30:16 +00:00
onError : function ( data ) {
2016-03-04 15:17:35 +00:00
var modal = $ ( '.export-modal' ) ;
showMessageModal ( '<i class="fa fa-cloud-upload"></i> Export to Google Drive' , 'Export Error :(' , '' , data , false ) ;
ui . spinner . hide ( ) ;
}
} ) ;
uploader . upload ( ) ;
}
function googleApiAuth ( immediate , callback ) {
gapi . auth . authorize (
2016-04-24 04:30:16 +00:00
{
'client_id' : GOOGLE _CLIENT _ID ,
'scope' : 'https://www.googleapis.com/auth/drive.file' ,
'immediate' : immediate
} , callback ? callback : function ( ) { } ) ;
2016-03-04 15:17:35 +00:00
}
function onGoogleClientLoaded ( ) {
googleApiAuth ( true ) ;
buildImportFromGoogleDrive ( ) ;
}
// export to google drive
ui . toolbar . export . googleDrive . click ( function ( e ) {
var token = gapi . auth . getToken ( ) ;
if ( token ) {
uploadToGoogleDrive ( token . access _token ) ;
} else {
2016-04-24 04:30:16 +00:00
googleApiAuth ( false , function ( result ) {
2016-03-04 15:17:35 +00:00
uploadToGoogleDrive ( result . access _token ) ;
} ) ;
}
} ) ;
2016-01-31 21:42:26 +00:00
//export to gist
2016-02-17 04:08:44 +00:00
ui . toolbar . export . gist . attr ( "href" , noteurl + "/gist" ) ;
2016-05-10 02:38:13 +00:00
//export to snippet
2016-05-12 16:28:08 +00:00
ui . toolbar . export . snippet . click ( function ( ) {
2016-05-15 04:22:51 +00:00
ui . spinner . show ( ) ;
2016-05-16 10:16:45 +00:00
$ . get ( serverurl + '/auth/gitlab/callback/' + noteid + '/projects' )
2016-05-12 16:28:08 +00:00
. success ( function ( data ) {
$ ( "#snippetExportModalAccessToken" ) . val ( data . accesstoken ) ;
$ ( "#snippetExportModalBaseURL" ) . val ( data . baseURL ) ;
$ ( "#snippetExportModalLoading" ) . hide ( ) ;
$ ( "#snippetExportModal" ) . modal ( 'toggle' ) ;
$ ( "#snippetExportModalProjects" ) . find ( 'option' ) . remove ( ) . end ( ) . append ( '<option value="init" selected="selected" disabled="disabled">Select From Available Projects</option>' ) ;
if ( data . projects ) {
data . projects . sort ( function ( a , b ) {
return ( a . path _with _namespace < b . path _with _namespace ) ? - 1 : ( ( a . path _with _namespace > b . path _with _namespace ) ? 1 : 0 ) ;
} ) ;
data . projects . forEach ( function ( project ) {
2016-05-13 14:32:30 +00:00
if ( ! project . snippets _enabled
|| ( project . permissions . project _access === null && project . permissions . group _access === null )
|| ( project . permissions . project _access !== null && project . permissions . project _access . access _level < 20 ) )
{
2016-05-12 18:26:55 +00:00
return ;
}
2016-05-12 16:28:08 +00:00
$ ( '<option>' ) . val ( project . id ) . text ( project . path _with _namespace ) . appendTo ( "#snippetExportModalProjects" ) ;
} ) ;
2016-05-15 05:01:41 +00:00
$ ( "#snippetExportModalProjects" ) . prop ( 'disabled' , false ) ;
2016-05-12 16:28:08 +00:00
}
$ ( "#snippetExportModalLoading" ) . hide ( ) ;
} )
. error ( function ( data ) {
showMessageModal ( '<i class="fa fa-gitlab"></i> Import from Snippet' , 'Unable to fetch gitlab parameters :(' , '' , '' , false ) ;
} )
. complete ( function ( ) {
2016-05-15 04:22:51 +00:00
ui . spinner . hide ( ) ;
2016-05-12 16:28:08 +00:00
} ) ;
} ) ;
2015-05-04 07:53:29 +00:00
//import from dropbox
2015-05-15 04:58:13 +00:00
ui . toolbar . import . dropbox . click ( function ( ) {
2015-05-04 07:53:29 +00:00
var options = {
2015-05-15 04:58:13 +00:00
success : function ( files ) {
2015-05-04 07:53:29 +00:00
ui . spinner . show ( ) ;
var url = files [ 0 ] . link ;
importFromUrl ( url ) ;
} ,
linkType : "direct" ,
multiselect : false ,
extensions : [ '.md' , '.html' ]
} ;
Dropbox . choose ( options ) ;
} ) ;
2016-03-04 15:17:35 +00:00
// import from google drive
var picker = null ;
function buildImportFromGoogleDrive ( ) {
picker = new FilePicker ( {
apiKey : GOOGLE _API _KEY ,
clientId : GOOGLE _CLIENT _ID ,
buttonEl : ui . toolbar . import . googleDrive ,
2016-04-24 04:30:16 +00:00
onSelect : function ( file ) {
2016-03-04 15:17:35 +00:00
if ( file . downloadUrl ) {
ui . spinner . show ( ) ;
var accessToken = gapi . auth . getToken ( ) . access _token ;
$ . ajax ( {
type : 'GET' ,
2016-04-24 04:30:16 +00:00
beforeSend : function ( request ) {
2016-03-04 15:17:35 +00:00
request . setRequestHeader ( 'Authorization' , 'Bearer ' + accessToken ) ;
} ,
url : file . downloadUrl ,
2016-04-24 04:30:16 +00:00
success : function ( data ) {
2016-03-04 15:17:35 +00:00
if ( file . fileExtension == 'html' )
parseToEditor ( data ) ;
else
replaceAll ( data ) ;
} ,
error : function ( data ) {
showMessageModal ( '<i class="fa fa-cloud-download"></i> Import from Google Drive' , 'Import failed :(' , '' , data , false ) ;
} ,
complete : function ( ) {
ui . spinner . hide ( ) ;
}
} ) ;
}
}
} ) ;
}
2016-04-20 10:06:36 +00:00
//import from gist
ui . toolbar . import . gist . click ( function ( ) {
//na
} ) ;
2016-05-10 02:38:13 +00:00
//import from snippet
ui . toolbar . import . snippet . click ( function ( ) {
2016-05-15 04:22:51 +00:00
ui . spinner . show ( ) ;
2016-05-16 10:16:45 +00:00
$ . get ( serverurl + '/auth/gitlab/callback/' + noteid + '/projects' )
2016-05-11 21:05:25 +00:00
. success ( function ( data ) {
$ ( "#snippetImportModalAccessToken" ) . val ( data . accesstoken ) ;
$ ( "#snippetImportModalBaseURL" ) . val ( data . baseURL ) ;
$ ( "#snippetImportModalContent" ) . prop ( 'disabled' , false ) ;
$ ( "#snippetImportModalConfirm" ) . prop ( 'disabled' , false ) ;
$ ( "#snippetImportModalLoading" ) . hide ( ) ;
$ ( "#snippetImportModal" ) . modal ( 'toggle' ) ;
2016-05-12 15:19:14 +00:00
$ ( "#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 ) {
2016-05-13 14:32:30 +00:00
if ( ! project . snippets _enabled
|| ( project . permissions . project _access === null && project . permissions . group _access === null )
|| ( project . permissions . project _access !== null && project . permissions . project _access . access _level < 20 ) )
{
2016-05-13 14:00:34 +00:00
return ;
}
2016-05-12 15:19:14 +00:00
$ ( '<option>' ) . val ( project . id ) . text ( project . path _with _namespace ) . appendTo ( "#snippetImportModalProjects" ) ;
} ) ;
2016-05-15 05:01:41 +00:00
$ ( "#snippetImportModalProjects" ) . prop ( 'disabled' , false ) ;
2016-05-12 15:19:14 +00:00
}
$ ( "#snippetImportModalLoading" ) . hide ( ) ;
2016-05-11 21:05:25 +00:00
} )
. error ( function ( data ) {
showMessageModal ( '<i class="fa fa-gitlab"></i> Import from Snippet' , 'Unable to fetch gitlab parameters :(' , '' , '' , false ) ;
} )
. complete ( function ( ) {
2016-05-15 04:30:28 +00:00
ui . spinner . hide ( ) ;
2016-05-11 21:05:25 +00:00
} ) ;
2016-05-10 02:38:13 +00:00
} ) ;
2015-05-04 07:53:29 +00:00
//import from clipboard
2015-05-15 04:58:13 +00:00
ui . toolbar . import . clipboard . click ( function ( ) {
2015-05-04 07:53:29 +00:00
//na
} ) ;
2015-07-01 16:10:20 +00:00
//upload image
ui . toolbar . uploadImage . bind ( 'change' , function ( e ) {
var files = e . target . files || e . dataTransfer . files ;
e . dataTransfer = { } ;
e . dataTransfer . files = files ;
inlineAttach . onDrop ( e ) ;
} ) ;
//toc
ui . toc . dropdown . click ( function ( e ) {
e . stopPropagation ( ) ;
} ) ;
2015-12-18 15:40:52 +00:00
//beta
//pdf
2016-02-17 04:08:44 +00:00
ui . toolbar . beta . pdf . attr ( "download" , "" ) . attr ( "href" , noteurl + "/pdf" ) ;
2015-12-18 15:40:52 +00:00
//slide
2016-02-17 04:08:44 +00:00
ui . toolbar . beta . slide . attr ( "href" , noteurl + "/slide" ) ;
2015-07-01 16:10:20 +00:00
2016-05-12 15:19:14 +00:00
//modal actions
2016-06-17 08:15:53 +00:00
var revisions = [ ] ;
var revisionViewer = null ;
var revisionList = ui . modal . revision . find ( '.ui-revision-list' ) ;
var revision = null ;
var revisionTime = null ;
ui . modal . revision . on ( 'show.bs.modal' , function ( e ) {
$ . get ( noteurl + '/revision' )
. success ( function ( data ) {
parseRevisions ( JSON . parse ( data ) . revision ) ;
initRevisionViewer ( ) ;
} )
. error ( function ( err ) {
} )
. complete ( function ( ) {
//na
} ) ;
} ) ;
function checkRevisionViewer ( ) {
if ( revisionViewer ) {
var container = $ ( revisionViewer . display . wrapper ) . parent ( ) ;
$ ( revisionViewer . display . scroller ) . css ( 'height' , container . height ( ) + 'px' ) ;
revisionViewer . refresh ( ) ;
}
}
ui . modal . revision . on ( 'shown.bs.modal' , checkRevisionViewer ) ;
$ ( window ) . resize ( checkRevisionViewer ) ;
function parseRevisions ( _revisions ) {
if ( _revisions . length != revisions ) {
revisions = _revisions ;
var lastRevision = null ;
if ( revisionList . children ( ) . length > 0 ) {
lastRevision = revisionList . find ( '.active' ) . attr ( 'data-revision-time' ) ;
}
revisionList . html ( '' ) ;
for ( var i = 0 ; i < revisions . length ; i ++ ) {
var revision = revisions [ i ] ;
var item = $ ( '<a href="#" class="list-group-item"></a>' ) ;
item . attr ( 'data-revision-time' , revision . time ) ;
if ( lastRevision == revision . time ) item . addClass ( 'active' ) ;
var itemHeading = $ ( '<h5 class="list-group-item-heading"></h5>' ) ;
itemHeading . html ( '<i class="fa fa-clock-o"></i> ' + moment ( revision . time ) . format ( 'llll' ) ) ;
var itemText = $ ( '<p class="list-group-item-text"></p>' ) ;
itemText . html ( '<i class="fa fa-file-text"></i> Length: ' + revision . length ) ;
item . append ( itemHeading ) . append ( itemText ) ;
item . click ( function ( e ) {
var time = $ ( this ) . attr ( 'data-revision-time' ) ;
selectRevision ( time ) ;
} ) ;
revisionList . append ( item ) ;
}
if ( ! lastRevision ) {
selectRevision ( revisions [ 0 ] . time ) ;
}
}
}
function selectRevision ( time ) {
if ( time == revisionTime ) return ;
$ . get ( noteurl + '/revision/' + time )
. success ( function ( data ) {
revision = JSON . parse ( 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 ) ;
// mark the text which have been insert or delete
if ( revision . patch . length > 0 ) {
var bias = 0 ;
for ( j = 0 ; j < revision . patch . length ; j ++ ) {
var patch = revision . patch [ j ] ;
var currIndex = patch . start1 + bias ;
for ( var i = 0 ; i < patch . diffs . length ; i ++ ) {
var diff = patch . diffs [ i ] ;
// ignore if diff only contains line breaks
if ( ( diff [ 1 ] . match ( new RegExp ( "\n" , "g" ) ) || [ ] ) . length == diff [ 1 ] . length ) continue ;
switch ( diff [ 0 ] ) {
case 0 : // retain
currIndex += diff [ 1 ] . length ;
break ;
case 1 : // insert
var prePos = revisionViewer . posFromIndex ( currIndex ) ;
var postPos = revisionViewer . posFromIndex ( currIndex + diff [ 1 ] . length ) ;
revisionViewer . markText ( prePos , postPos , {
css : 'background-color: rgba(230,255,230,0.7); text-decoration: underline;'
} ) ;
currIndex += diff [ 1 ] . length ;
break ;
case - 1 : // delete
var prePos = revisionViewer . posFromIndex ( currIndex ) ;
revisionViewer . replaceRange ( diff [ 1 ] , prePos ) ;
var postPos = revisionViewer . posFromIndex ( currIndex + diff [ 1 ] . length ) ;
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 ;
}
}
}
}
} )
. error ( function ( err ) {
} )
. complete ( function ( ) {
//na
} ) ;
}
function initRevisionViewer ( ) {
if ( revisionViewer ) return ;
var revisionViewerTextArea = document . getElementById ( "revisionViewer" ) ;
revisionViewer = CodeMirror . fromTextArea ( revisionViewerTextArea , {
mode : 'gfm' ,
viewportMargin : viewportMargin ,
lineNumbers : true ,
lineWrapping : true ,
showCursorWhenSelecting : true ,
inputStyle : "textarea" ,
gutters : [ "CodeMirror-linenumbers" ] ,
flattenSpans : true ,
addModeClass : true ,
readOnly : true ,
autoRefresh : true ,
scrollbarStyle : 'overlay'
} ) ;
}
$ ( '#revisionModalDownload' ) . click ( function ( ) {
if ( ! revision ) return ;
var filename = renderFilename ( ui . area . markdown ) + '_' + revisionTime + '.md' ;
var blob = new Blob ( [ revision . content ] , {
type : "text/markdown;charset=utf-8"
} ) ;
saveAs ( blob , filename ) ;
} ) ;
$ ( '#revisionModalRevert' ) . click ( function ( ) {
if ( ! revision ) return ;
editor . setValue ( revision . content ) ;
ui . modal . revision . modal ( 'hide' ) ;
} ) ;
2016-05-12 15:19:14 +00:00
//snippet projects
2016-05-12 16:28:08 +00:00
ui . modal . snippetImportProjects . change ( function ( ) {
2016-05-12 15:19:14 +00:00
var accesstoken = $ ( "#snippetImportModalAccessToken" ) . val ( ) ,
baseURL = $ ( "#snippetImportModalBaseURL" ) . val ( ) ,
project = $ ( "#snippetImportModalProjects" ) . val ( ) ;
$ ( "#snippetImportModalLoading" ) . show ( ) ;
$ ( "#snippetImportModalContent" ) . val ( '/projects/' + project ) ;
$ . get ( baseURL + '/api/v3/projects/' + project + '/snippets?access_token=' + accesstoken )
. success ( 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 ( ) ;
2016-05-15 05:01:41 +00:00
$ ( "#snippetImportModalSnippets" ) . prop ( 'disabled' , false ) ;
2016-05-12 15:19:14 +00:00
} )
. error ( function ( err ) {
} )
. complete ( function ( ) {
//na
} ) ;
} ) ;
//snippet snippets
2016-05-12 16:28:08 +00:00
ui . modal . snippetImportSnippets . change ( function ( ) {
2016-05-12 15:19:14 +00:00
var project = $ ( "#snippetImportModalProjects" ) . val ( ) ,
snippet = $ ( "#snippetImportModalSnippets" ) . val ( ) ;
$ ( "#snippetImportModalContent" ) . val ( $ ( "#snippetImportModalContent" ) . val ( ) + '/snippets/' + snippet ) ;
} )
2015-07-01 16:10:20 +00:00
function scrollToTop ( ) {
if ( currentMode == modeType . both ) {
if ( editor . getScrollInfo ( ) . top != 0 )
editor . scrollTo ( 0 , 0 ) ;
else
ui . area . view . animate ( {
scrollTop : 0
} , 100 , "linear" ) ;
} else {
2015-09-25 06:08:02 +00:00
$ ( 'body, html' ) . stop ( true , true ) . animate ( {
2015-07-01 16:10:20 +00:00
scrollTop : 0
} , 100 , "linear" ) ;
}
}
function scrollToBottom ( ) {
if ( currentMode == modeType . both ) {
var scrollInfo = editor . getScrollInfo ( ) ;
var scrollHeight = scrollInfo . height ;
if ( scrollInfo . top != scrollHeight )
editor . scrollTo ( 0 , scrollHeight * 2 ) ;
else
ui . area . view . animate ( {
scrollTop : ui . area . view [ 0 ] . scrollHeight
} , 100 , "linear" ) ;
} else {
2015-09-25 06:08:02 +00:00
$ ( 'body, html' ) . stop ( true , true ) . animate ( {
2015-07-01 16:10:20 +00:00
scrollTop : $ ( document . body ) [ 0 ] . scrollHeight
} , 100 , "linear" ) ;
}
}
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 ( ) ;
}
2015-09-25 10:48:45 +00:00
//$(document.body).scroll();
//ui.area.view.scroll();
2015-07-01 16:10:20 +00:00
}
2015-09-25 10:29:01 +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' ) ) ) ;
}
2016-04-24 04:30:16 +00:00
applyScrollspyActive ( $ ( window ) . scrollTop ( ) , headerMap , headers ,
$ ( '.scrollspy-body' ) , 0 ) ;
2015-09-25 10:29:01 +00:00
var offset = ui . area . view . scrollTop ( ) - ui . area . view . offset ( ) . top ;
2016-04-24 04:30:16 +00:00
applyScrollspyActive ( ui . area . view . scrollTop ( ) , headerMap , headers ,
$ ( '.scrollspy-view' ) , offset - 10 ) ;
2015-09-25 10:29:01 +00:00
}
function applyScrollspyActive ( top , headerMap , headers , target , offset ) {
var index = 0 ;
2016-04-24 04:30:16 +00:00
for ( var i = headerMap . length - 1 ; i >= 0 ; i -- ) {
if ( top >= ( headerMap [ i ] + offset ) && headerMap [ i + 1 ] && top < ( headerMap [ i + 1 ] + offset ) ) {
2015-09-25 10:29:01 +00:00
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' ) ;
}
2016-04-20 10:06:36 +00:00
// clipboard modal
2015-05-04 07:53:29 +00:00
//fix for wrong autofocus
2015-05-15 04:58:13 +00:00
$ ( '#clipboardModal' ) . on ( 'shown.bs.modal' , function ( ) {
2015-05-04 07:53:29 +00:00
$ ( '#clipboardModal' ) . blur ( ) ;
} ) ;
2015-05-15 04:58:13 +00:00
$ ( "#clipboardModalClear" ) . click ( function ( ) {
2015-05-04 07:53:29 +00:00
$ ( "#clipboardModalContent" ) . html ( '' ) ;
} ) ;
2015-05-15 04:58:13 +00:00
$ ( "#clipboardModalConfirm" ) . click ( function ( ) {
2015-05-04 07:53:29 +00:00
var data = $ ( "#clipboardModalContent" ) . html ( ) ;
2015-05-15 04:58:13 +00:00
if ( data ) {
2015-05-04 07:53:29 +00:00
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 ( ) {
location . reload ( true ) ;
} ) ;
2015-05-15 04:58:13 +00:00
2016-04-20 10:06:36 +00:00
// gist import modal
$ ( "#gistImportModalClear" ) . click ( function ( ) {
$ ( "#gistImportModalContent" ) . val ( '' ) ;
} ) ;
$ ( "#gistImportModalConfirm" ) . click ( function ( ) {
var gisturl = $ ( "#gistImportModalContent" ) . val ( ) ;
if ( ! gisturl ) return ;
$ ( '#gistImportModal' ) . modal ( 'hide' ) ;
$ ( "#gistImportModalContent" ) . val ( '' ) ;
if ( ! isValidURL ( gisturl ) ) {
showMessageModal ( '<i class="fa fa-github"></i> Import from Gist' , 'Not a valid URL :(' , '' , '' , false ) ;
return ;
} else {
var hostname = url ( 'hostname' , gisturl )
if ( hostname !== 'gist.github.com' ) {
showMessageModal ( '<i class="fa fa-github"></i> Import from Gist' , 'Not a valid Gist URL :(' , '' , '' , false ) ;
} else {
ui . spinner . show ( ) ;
$ . get ( 'https://api.github.com/gists/' + url ( '-1' , gisturl ) )
. success ( 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 ) ;
}
} )
. error ( function ( data ) {
showMessageModal ( '<i class="fa fa-github"></i> Import from Gist' , 'Not a valid Gist URL :(' , '' , JSON . stringify ( data ) , false ) ;
} )
. complete ( function ( ) {
ui . spinner . hide ( ) ;
} ) ;
}
}
} ) ;
2016-05-10 02:38:13 +00:00
// snippet import modal
$ ( "#snippetImportModalClear" ) . click ( function ( ) {
$ ( "#snippetImportModalContent" ) . val ( '' ) ;
2016-05-12 15:19:14 +00:00
$ ( "#snippetImportModalProjects" ) . val ( 'init' ) ;
$ ( "#snippetImportModalSnippets" ) . val ( 'init' ) ;
2016-05-15 05:01:41 +00:00
$ ( "#snippetImportModalSnippets" ) . prop ( 'disabled' , true ) ;
2016-05-10 02:38:13 +00:00
} ) ;
$ ( "#snippetImportModalConfirm" ) . click ( function ( ) {
var snippeturl = $ ( "#snippetImportModalContent" ) . val ( ) ;
if ( ! snippeturl ) return ;
$ ( '#snippetImportModal' ) . modal ( 'hide' ) ;
$ ( "#snippetImportModalContent" ) . val ( '' ) ;
2016-05-11 21:05:25 +00:00
if ( ! /^.+\/snippets\/.+$/ . test ( snippeturl ) ) {
showMessageModal ( '<i class="fa fa-github"></i> Import from Snippet' , 'Not a valid Snippet URL :(' , '' , '' , false ) ;
2016-05-10 02:38:13 +00:00
} else {
ui . spinner . show ( ) ;
2016-05-11 21:05:25 +00:00
var accessToken = '?access_token=' + $ ( "#snippetImportModalAccessToken" ) . val ( ) ;
var fullURL = $ ( "#snippetImportModalBaseURL" ) . val ( ) + '/api/v3' + snippeturl ;
$ . get ( fullURL + accessToken )
. success ( function ( data ) {
var content = '# ' + ( data . title || "Snippet Import" ) ;
var fileInfo = data . file _name . split ( '.' ) ;
2016-05-13 14:00:34 +00:00
fileInfo [ 1 ] = ( fileInfo [ 1 ] ) ? fileInfo [ 1 ] : "md" ;
2016-05-11 21:05:25 +00:00
$ . get ( fullURL + '/raw' + accessToken )
. success ( function ( raw ) {
if ( raw ) {
2016-05-13 14:00:34 +00:00
content += "\n\n" ;
if ( fileInfo [ 1 ] != "md" ) {
content += "```" + fileTypes [ fileInfo [ 1 ] ] + "\n" ;
}
2016-05-11 21:05:25 +00:00
content += raw ;
2016-05-13 14:00:34 +00:00
if ( fileInfo [ 1 ] != "md" ) {
content += "\n```" ;
}
2016-05-11 21:05:25 +00:00
replaceAll ( content ) ;
}
} )
. error ( function ( data ) {
showMessageModal ( '<i class="fa fa-gitlab"></i> Import from Snippet' , 'Not a valid Snippet URL :(' , '' , JSON . stringify ( data ) , false ) ;
} )
. complete ( function ( ) {
ui . spinner . hide ( ) ;
2016-05-10 02:38:13 +00:00
} ) ;
} )
. error ( function ( data ) {
showMessageModal ( '<i class="fa fa-gitlab"></i> Import from Snippet' , 'Not a valid Snippet URL :(' , '' , JSON . stringify ( data ) , false ) ;
} ) ;
}
} ) ;
2016-05-12 16:28:08 +00:00
//snippet export modal
$ ( "#snippetExportModalConfirm" ) . click ( function ( ) {
var accesstoken = $ ( "#snippetExportModalAccessToken" ) . val ( ) ,
baseURL = $ ( "#snippetExportModalBaseURL" ) . val ( ) ,
data = {
title : $ ( "#snippetExportModalTitle" ) . val ( ) ,
file _name : $ ( "#snippetExportModalFileName" ) . val ( ) ,
code : editor . getValue ( ) ,
visibility _level : $ ( "#snippetExportModalVisibility" ) . val ( )
} ;
2016-05-15 05:01:41 +00:00
if ( ! data . title || ! data . file _name || ! data . code || ! data . visibility _level || ! $ ( "#snippetExportModalProjects" ) . val ( ) ) return ;
2016-05-12 16:28:08 +00:00
$ ( "#snippetExportModalLoading" ) . show ( ) ;
var fullURL = baseURL + '/api/v3/projects/' + $ ( "#snippetExportModalProjects" ) . val ( ) + '/snippets?access_token=' + accesstoken ;
$ . post ( fullURL
, data
, function ( ret ) {
$ ( "#snippetExportModalLoading" ) . hide ( ) ;
$ ( '#snippetExportModal' ) . modal ( 'hide' ) ;
var redirect = baseURL + '/' + $ ( "#snippetExportModalProjects option[value='" + $ ( "#snippetExportModalProjects" ) . val ( ) + "']" ) . text ( ) + '/snippets/' + ret . id ;
showMessageModal ( '<i class="fa fa-gitlab"></i> Export to Snippet' , 'Export Successful!' , redirect , 'View Snippet Here' , true ) ;
}
, 'json'
) ;
} ) ;
2015-05-04 07:53:29 +00:00
function parseToEditor ( data ) {
var parsed = toMarkdown ( data ) ;
2015-05-15 04:58:13 +00:00
if ( parsed )
2016-01-12 14:03:56 +00:00
replaceAll ( parsed ) ;
}
function replaceAll ( data ) {
editor . replaceRange ( data , {
line : 0 ,
ch : 0
} , {
2016-04-24 04:30:16 +00:00
line : editor . lastLine ( ) ,
ch : editor . lastLine ( ) . length
} , '+input' ) ;
2015-05-04 07:53:29 +00:00
}
2015-05-15 04:58:13 +00:00
2015-05-04 07:53:29 +00:00
function importFromUrl ( url ) {
//console.log(url);
2016-04-20 10:06:36 +00:00
if ( ! url ) return ;
2015-05-15 04:58:13 +00:00
if ( ! isValidURL ( url ) ) {
2016-04-20 10:06:36 +00:00
showMessageModal ( '<i class="fa fa-cloud-download"></i> Import from URL' , 'Not a valid URL :(' , '' , '' , false ) ;
2015-05-04 07:53:29 +00:00
return ;
}
$ . ajax ( {
method : "GET" ,
url : url ,
2015-05-15 04:58:13 +00:00
success : function ( data ) {
2016-01-12 14:03:56 +00:00
var extension = url . split ( '.' ) . pop ( ) ;
if ( extension == 'html' )
parseToEditor ( data ) ;
else
replaceAll ( data ) ;
2015-05-04 07:53:29 +00:00
} ,
2016-03-04 15:17:35 +00:00
error : function ( data ) {
2016-04-20 10:06:36 +00:00
showMessageModal ( '<i class="fa fa-cloud-download"></i> Import from URL' , 'Import failed :(' , '' , JSON . stringify ( data ) , false ) ;
2015-05-04 07:53:29 +00:00
} ,
2015-05-15 04:58:13 +00:00
complete : function ( ) {
2015-05-04 07:53:29 +00:00
ui . spinner . hide ( ) ;
}
} ) ;
}
2015-05-15 04:58:13 +00:00
2015-05-04 07:53:29 +00:00
function isValidURL ( str ) {
2015-07-01 16:10:20 +00:00
var pattern = new RegExp ( '^(https?:\\/\\/)?' + // protocol
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
'((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
'(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
'(\\#[-a-z\\d_]*)?$' , 'i' ) ; // fragment locator
if ( ! pattern . test ( str ) ) {
return false ;
} else {
return true ;
2015-05-04 07:53:29 +00:00
}
2015-07-01 16:10:20 +00:00
}
//mode
2015-05-04 07:53:29 +00:00
ui . toolbar . mode . click ( function ( ) {
toggleMode ( ) ;
} ) ;
//edit
ui . toolbar . edit . click ( function ( ) {
changeMode ( modeType . edit ) ;
} ) ;
//view
ui . toolbar . view . click ( function ( ) {
changeMode ( modeType . view ) ;
} ) ;
//both
ui . toolbar . both . click ( function ( ) {
changeMode ( modeType . both ) ;
} ) ;
2015-07-01 16:10:20 +00:00
//permission
//freely
ui . infobar . permission . freely . click ( function ( ) {
2015-09-25 06:02:34 +00:00
emitPermission ( "freely" ) ;
2015-07-01 16:10:20 +00:00
} ) ;
//editable
ui . infobar . permission . editable . click ( function ( ) {
2015-09-25 06:02:34 +00:00
emitPermission ( "editable" ) ;
2015-07-01 16:10:20 +00:00
} ) ;
//locked
ui . infobar . permission . locked . click ( function ( ) {
2015-09-25 06:02:34 +00:00
emitPermission ( "locked" ) ;
2015-07-01 16:10:20 +00:00
} ) ;
2016-01-17 15:51:27 +00:00
//private
ui . infobar . permission . private . click ( function ( ) {
emitPermission ( "private" ) ;
} ) ;
2015-07-01 16:10:20 +00:00
2015-09-25 06:02:34 +00:00
function emitPermission ( _permission ) {
2015-07-01 16:10:20 +00:00
if ( _permission != permission ) {
socket . emit ( 'permission' , _permission ) ;
}
}
2015-09-25 06:02:34 +00:00
function updatePermission ( newPermission ) {
if ( permission != newPermission ) {
permission = newPermission ;
2016-06-17 07:58:51 +00:00
if ( loaded ) refreshView ( ) ;
2015-09-25 06:02:34 +00:00
}
2015-07-01 16:10:20 +00:00
var label = null ;
var title = null ;
switch ( permission ) {
2016-04-24 04:30:16 +00:00
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 "locked" :
label = '<i class="fa fa-lock"></i> Locked' ;
title = "Only owner can edit" ;
break ;
case "private" :
label = '<i class="fa fa-hand-stop-o"></i> Private' ;
title = "Only owner can view & edit" ;
break ;
2015-07-01 16:10:20 +00:00
}
2016-06-17 08:29:45 +00:00
if ( personalInfo . userid && owner && personalInfo . userid == owner ) {
2015-07-01 16:10:20 +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 ) ;
}
2015-05-04 07:53:29 +00:00
2015-09-25 06:02:34 +00:00
function havePermission ( ) {
var bool = false ;
switch ( permission ) {
2016-04-24 04:30:16 +00:00
case "freely" :
2015-09-25 06:02:34 +00:00
bool = true ;
2016-04-24 04:30:16 +00:00
break ;
case "editable" :
if ( ! personalInfo . login ) {
bool = false ;
} else {
bool = true ;
}
break ;
case "locked" :
case "private" :
2016-06-17 08:29:45 +00:00
if ( ! owner || personalInfo . userid != owner ) {
2016-04-24 04:30:16 +00:00
bool = false ;
} else {
bool = true ;
}
break ;
2015-09-25 06:02:34 +00:00
}
return bool ;
}
2015-05-04 07:53:29 +00:00
//socket.io actions
2015-09-25 10:34:34 +00:00
var socket = io . connect ( {
2016-02-17 04:08:44 +00:00
path : urlpath ? '/' + urlpath + '/socket.io/' : '' ,
2015-09-25 10:34:34 +00:00
timeout : 10000 //10 secs to timeout
} ) ;
2015-06-01 10:04:25 +00:00
//overwrite original event for checking login state
var on = socket . on ;
socket . on = function ( ) {
2015-07-01 16:10:20 +00:00
if ( ! checkLoginStateChanged ( ) && ! needRefresh )
2015-06-01 10:04:25 +00:00
on . apply ( socket , arguments ) ;
} ;
var emit = socket . emit ;
socket . emit = function ( ) {
2015-07-01 16:10:20 +00:00
if ( ! checkLoginStateChanged ( ) && ! needRefresh )
2015-06-01 10:04:25 +00:00
emit . apply ( socket , arguments ) ;
} ;
2015-05-04 07:53:29 +00:00
socket . on ( 'info' , function ( data ) {
console . error ( data ) ;
2016-01-17 15:51:27 +00:00
switch ( data . code ) {
2016-04-24 04:30:16 +00:00
case 403 :
location . href = "./403" ;
break ;
case 404 :
location . href = "./404" ;
break ;
case 500 :
location . href = "./500" ;
break ;
2016-01-17 15:51:27 +00:00
}
2015-12-30 05:33:36 +00:00
} ) ;
socket . on ( 'error' , function ( data ) {
console . error ( data ) ;
2016-03-16 04:46:29 +00:00
if ( data . message && data . message . indexOf ( 'AUTH failed' ) === 0 )
2016-01-31 21:33:25 +00:00
location . href = "./403" ;
2015-05-04 07:53:29 +00:00
} ) ;
2016-06-01 06:18:54 +00:00
var retryOnDisconnect = false ;
2016-06-04 02:49:10 +00:00
var retryTimer = null ;
2016-06-17 08:31:36 +00:00
socket . on ( 'maintenance' , function ( ) {
retryOnDisconnect = true ;
2016-06-01 06:18:54 +00:00
} ) ;
2015-05-04 07:53:29 +00:00
socket . on ( 'disconnect' , function ( data ) {
showStatus ( statusType . offline ) ;
if ( loaded ) {
saveInfo ( ) ;
lastInfo . history = editor . getHistory ( ) ;
}
if ( ! editor . getOption ( 'readOnly' ) )
editor . setOption ( 'readOnly' , true ) ;
2016-06-04 02:49:10 +00:00
if ( retryOnDisconnect && ! retryTimer ) {
retryTimer = setInterval ( function ( ) {
2016-06-17 08:31:36 +00:00
if ( ! needRefresh ) socket . connect ( ) ;
2016-06-04 02:49:10 +00:00
} , 1000 ) ;
}
2015-05-04 07:53:29 +00:00
} ) ;
2015-06-01 10:04:25 +00:00
socket . on ( 'reconnect' , function ( data ) {
//sync back any change in offline
emitUserStatus ( true ) ;
cursorActivity ( ) ;
socket . emit ( 'online users' ) ;
2016-06-04 02:49:10 +00:00
clearInterval ( retryTimer ) ;
retryTimer = null ;
2016-06-01 06:18:54 +00:00
retryOnDisconnect = false ;
2015-06-01 10:04:25 +00:00
} ) ;
2015-05-04 07:53:29 +00:00
socket . on ( 'connect' , function ( data ) {
2015-06-01 10:04:25 +00:00
personalInfo [ 'id' ] = socket . id ;
2015-05-04 07:53:29 +00:00
showStatus ( statusType . connected ) ;
socket . emit ( 'version' ) ;
2016-01-19 15:57:58 +00:00
if ( socket . id . indexOf ( '/' ) == - 1 )
socket . id = socket . nsp + '#' + socket . id ;
2015-05-04 07:53:29 +00:00
} ) ;
socket . on ( 'version' , function ( data ) {
2016-06-17 08:31:36 +00:00
if ( version != data . version ) {
if ( version < data . minimumCompatibleVersion ) {
setRefreshModal ( 'incompatible-version' ) ;
setNeedRefresh ( ) ;
} else {
setRefreshModal ( 'new-version' ) ;
}
}
2015-07-01 16:10:20 +00:00
} ) ;
2016-03-15 02:45:02 +00:00
function updateLastInfo ( data ) {
//console.log(data);
2016-04-20 10:03:55 +00:00
if ( data . hasOwnProperty ( 'createtime' ) && createtime !== data . createtime ) {
createtime = data . createtime ;
updateLastChange ( ) ;
}
if ( data . hasOwnProperty ( 'updatetime' ) && lastchangetime !== data . updatetime ) {
2016-03-15 02:45:02 +00:00
lastchangetime = data . updatetime ;
updateLastChange ( ) ;
}
2016-04-20 10:03:55 +00:00
if ( data . hasOwnProperty ( 'lastchangeuser' ) && lastchangeuser !== data . lastchangeuser ) {
2016-03-15 02:45:02 +00:00
lastchangeuser = data . lastchangeuser ;
lastchangeuserprofile = data . lastchangeuserprofile ;
updateLastChangeUser ( ) ;
}
}
2015-07-01 16:10:20 +00:00
socket . on ( 'check' , function ( data ) {
2016-03-15 02:45:02 +00:00
//console.log(data);
updateLastInfo ( data ) ;
2015-05-04 07:53:29 +00:00
} ) ;
2015-07-01 16:10:20 +00:00
socket . on ( 'permission' , function ( data ) {
2015-09-25 06:02:34 +00:00
updatePermission ( data . permission ) ;
2015-07-01 16:10:20 +00:00
} ) ;
2015-07-16 14:46:06 +00:00
var docmaxlength = null ;
2015-07-01 16:10:20 +00:00
var otk = null ;
var owner = null ;
var permission = null ;
2015-05-04 07:53:29 +00:00
socket . on ( 'refresh' , function ( data ) {
2016-03-15 02:45:02 +00:00
//console.log(data);
2015-07-16 14:46:06 +00:00
docmaxlength = data . docmaxlength ;
editor . setOption ( "maxLength" , docmaxlength ) ;
2015-07-01 16:10:20 +00:00
otk = data . otk ;
owner = data . owner ;
2015-09-25 06:02:34 +00:00
updatePermission ( data . permission ) ;
2016-03-15 02:45:02 +00:00
updateLastInfo ( data ) ;
2015-09-25 05:59:28 +00:00
if ( ! loaded ) {
2016-06-17 07:59:50 +00:00
// auto change mode if no content detected
2016-01-17 15:56:36 +00:00
var nocontent = editor . getValue ( ) . length <= 0 ;
if ( nocontent ) {
if ( visibleXS )
currentMode = modeType . edit ;
else
currentMode = modeType . both ;
}
2015-09-25 05:59:28 +00:00
changeMode ( currentMode ) ;
2016-03-15 03:10:58 +00:00
if ( nocontent && ! visibleXS ) {
2016-01-17 15:56:36 +00:00
editor . focus ( ) ;
editor . refresh ( ) ;
}
2016-06-17 07:59:25 +00:00
updateViewInner ( ) ; // bring up view rendering earlier
2016-01-17 15:59:03 +00:00
updateHistory ( ) ; //update history whether have content or not
2016-06-17 07:59:50 +00:00
loaded = true ;
2015-09-25 05:59:28 +00:00
emitUserStatus ( ) ; //send first user status
updateOnlineStatus ( ) ; //update first online status
setTimeout ( function ( ) {
//work around editor not refresh or doc not fully loaded
windowResizeInner ( ) ;
//work around might not scroll to hash
scrollToHash ( ) ;
} , 1 ) ;
}
2015-07-11 04:43:08 +00:00
} ) ;
var EditorClient = ot . EditorClient ;
var SocketIOAdapter = ot . SocketIOAdapter ;
var CodeMirrorAdapter = ot . CodeMirrorAdapter ;
var cmClient = null ;
2015-07-01 16:10:20 +00:00
2015-07-11 04:43:08 +00:00
socket . on ( 'doc' , function ( obj ) {
obj = LZString . decompressFromUTF16 ( obj ) ;
obj = JSON . parse ( obj ) ;
var body = obj . str ;
var bodyMismatch = ( editor . getValue ( ) != body ) ;
saveInfo ( ) ;
if ( bodyMismatch ) {
2015-09-25 05:59:28 +00:00
if ( cmClient )
cmClient . editorAdapter . ignoreNextChange = true ;
2015-07-01 16:10:20 +00:00
if ( body )
editor . setValue ( body ) ;
else
editor . setValue ( "" ) ;
}
2015-05-04 07:53:29 +00:00
if ( ! loaded ) {
editor . clearHistory ( ) ;
ui . spinner . hide ( ) ;
ui . content . fadeIn ( ) ;
} else {
2015-06-01 10:04:25 +00:00
//if current doc is equal to the doc before disconnect
2015-07-11 04:43:08 +00:00
if ( bodyMismatch )
2015-05-04 07:53:29 +00:00
editor . clearHistory ( ) ;
else {
if ( lastInfo . history )
editor . setHistory ( lastInfo . history ) ;
}
lastInfo . history = null ;
}
2015-09-25 05:59:28 +00:00
if ( ! cmClient ) {
cmClient = window . cmClient = new EditorClient (
obj . revision , obj . clients ,
new SocketIOAdapter ( socket ) , new CodeMirrorAdapter ( editor )
) ;
} else {
if ( bodyMismatch ) {
cmClient . undoManager . undoStack . length = 0 ;
cmClient . undoManager . redoStack . length = 0 ;
}
cmClient . revision = obj . revision ;
cmClient . setState ( new ot . Client . Synchronized ( ) ) ;
cmClient . initializeClientList ( ) ;
cmClient . initializeClients ( obj . clients ) ;
}
2015-07-11 04:43:08 +00:00
if ( bodyMismatch ) {
isDirty = true ;
2015-07-01 16:10:20 +00:00
updateView ( ) ;
2015-07-11 04:43:08 +00:00
}
2015-05-04 07:53:29 +00:00
if ( editor . getOption ( 'readOnly' ) )
editor . setOption ( 'readOnly' , false ) ;
2015-05-15 04:58:13 +00:00
2015-05-04 07:53:29 +00:00
restoreInfo ( ) ;
} ) ;
2015-07-01 16:10:20 +00:00
2015-09-25 10:01:15 +00:00
socket . on ( 'ack' , function ( ) {
2015-07-11 04:43:08 +00:00
isDirty = true ;
updateView ( ) ;
2015-09-25 10:01:15 +00:00
} ) ;
2015-07-01 16:10:20 +00:00
2015-09-25 10:01:15 +00:00
socket . on ( 'operation' , function ( ) {
2015-07-11 04:43:08 +00:00
isDirty = true ;
updateView ( ) ;
2015-09-25 10:01:15 +00:00
} ) ;
2015-07-01 16:10:20 +00:00
2015-05-04 07:53:29 +00:00
socket . on ( 'online users' , function ( data ) {
2015-06-01 10:04:25 +00:00
data = LZString . decompressFromUTF16 ( data ) ;
data = JSON . parse ( data ) ;
2015-05-04 07:53:29 +00:00
if ( debug )
console . debug ( data ) ;
2015-06-01 10:04:25 +00:00
onlineUsers = data . users ;
updateOnlineStatus ( ) ;
2015-05-15 04:58:13 +00:00
$ ( '.other-cursors' ) . children ( ) . each ( function ( key , value ) {
var found = false ;
for ( var i = 0 ; i < data . users . length ; i ++ ) {
var user = data . users [ i ] ;
if ( $ ( this ) . attr ( 'id' ) == user . id )
found = true ;
}
if ( ! found )
2015-06-01 10:04:25 +00:00
$ ( this ) . stop ( true ) . fadeOut ( "normal" , function ( ) {
$ ( this ) . remove ( ) ;
} ) ;
2015-05-15 04:58:13 +00:00
} ) ;
for ( var i = 0 ; i < data . users . length ; i ++ ) {
2015-05-04 07:53:29 +00:00
var user = data . users [ i ] ;
2015-05-15 04:58:13 +00:00
if ( user . id != socket . id )
2015-06-01 10:04:25 +00:00
buildCursor ( user ) ;
else
personalInfo = user ;
}
} ) ;
socket . on ( 'user status' , function ( data ) {
if ( debug )
console . debug ( data ) ;
for ( var i = 0 ; i < onlineUsers . length ; i ++ ) {
if ( onlineUsers [ i ] . id == data . id ) {
onlineUsers [ i ] = data ;
}
2015-05-04 07:53:29 +00:00
}
2015-06-01 10:04:25 +00:00
updateOnlineStatus ( ) ;
if ( data . id != socket . id )
buildCursor ( data ) ;
2015-05-04 07:53:29 +00:00
} ) ;
socket . on ( 'cursor focus' , function ( data ) {
2015-05-15 04:58:13 +00:00
if ( debug )
2015-05-04 07:53:29 +00:00
console . debug ( data ) ;
2015-06-01 10:04:25 +00:00
for ( var i = 0 ; i < onlineUsers . length ; i ++ ) {
if ( onlineUsers [ i ] . id == data . id ) {
2015-09-25 05:43:19 +00:00
onlineUsers [ i ] . cursor = data . cursor ;
2015-06-01 10:04:25 +00:00
}
}
if ( data . id != socket . id )
buildCursor ( data ) ;
//force show
2016-01-23 01:48:07 +00:00
var cursor = $ ( 'div[data-clientid="' + data . id + '"]' ) ;
2015-05-15 04:58:13 +00:00
if ( cursor . length > 0 ) {
2015-06-01 10:04:25 +00:00
cursor . stop ( true ) . fadeIn ( ) ;
2015-05-04 07:53:29 +00:00
}
} ) ;
socket . on ( 'cursor activity' , function ( data ) {
2015-05-15 04:58:13 +00:00
if ( debug )
2015-05-04 07:53:29 +00:00
console . debug ( data ) ;
2015-06-01 10:04:25 +00:00
for ( var i = 0 ; i < onlineUsers . length ; i ++ ) {
if ( onlineUsers [ i ] . id == data . id ) {
2015-09-25 05:43:19 +00:00
onlineUsers [ i ] . cursor = data . cursor ;
2015-06-01 10:04:25 +00:00
}
}
2015-05-15 04:58:13 +00:00
if ( data . id != socket . id )
2015-06-01 10:04:25 +00:00
buildCursor ( data ) ;
2015-05-04 07:53:29 +00:00
} ) ;
socket . on ( 'cursor blur' , function ( data ) {
2015-05-15 04:58:13 +00:00
if ( debug )
2015-05-04 07:53:29 +00:00
console . debug ( data ) ;
2015-06-01 10:04:25 +00:00
for ( var i = 0 ; i < onlineUsers . length ; i ++ ) {
if ( onlineUsers [ i ] . id == data . id ) {
onlineUsers [ i ] . cursor = null ;
}
}
if ( data . id != socket . id )
buildCursor ( data ) ;
//force hide
2016-01-23 01:48:07 +00:00
var cursor = $ ( 'div[data-clientid="' + data . id + '"]' ) ;
2015-05-15 04:58:13 +00:00
if ( cursor . length > 0 ) {
2015-06-01 10:04:25 +00:00
cursor . stop ( true ) . fadeOut ( ) ;
2015-05-04 07:53:29 +00:00
}
} ) ;
2015-05-15 04:58:13 +00:00
2015-06-01 10:04:25 +00:00
var options = {
valueNames : [ 'id' , 'name' ] ,
item : ' < li class = "ui-user-item" > \
< span class = "id" style = "display:none;" > < / s p a n > \
< a href = "#" > \
2015-07-04 14:19:09 +00:00
< span class = "pull-left" > < i class = "ui-user-icon" > < / i > < / s p a n > < s p a n c l a s s = " u i - u s e r - n a m e n a m e " > < / s p a n > < s p a n c l a s s = " p u l l - r i g h t " > < i c l a s s = " f a f a - c i r c l e u i - u s e r - s t a t u s " > < / i > < / s p a n > \
2015-06-01 10:04:25 +00:00
< / a > \
< / l i > '
} ;
var onlineUserList = new List ( 'online-user-list' , options ) ;
var shortOnlineUserList = new List ( 'short-online-user-list' , options ) ;
function updateOnlineStatus ( ) {
2015-07-01 16:10:20 +00:00
if ( ! loaded || ! socket . connected ) return ;
2015-06-01 10:04:25 +00:00
var _onlineUsers = deduplicateOnlineUsers ( onlineUsers ) ;
showStatus ( statusType . online , _onlineUsers . length ) ;
var items = onlineUserList . items ;
//update or remove current list items
for ( var i = 0 ; i < items . length ; i ++ ) {
var found = false ;
var foundindex = null ;
for ( var j = 0 ; j < _onlineUsers . length ; j ++ ) {
if ( items [ i ] . values ( ) . id == _onlineUsers [ j ] . id ) {
foundindex = j ;
found = true ;
break ;
}
}
var id = items [ i ] . values ( ) . id ;
if ( found ) {
onlineUserList . get ( 'id' , id ) [ 0 ] . values ( _onlineUsers [ foundindex ] ) ;
shortOnlineUserList . get ( 'id' , id ) [ 0 ] . values ( _onlineUsers [ foundindex ] ) ;
} else {
onlineUserList . remove ( 'id' , id ) ;
shortOnlineUserList . remove ( 'id' , id ) ;
}
}
//add not in list items
for ( var i = 0 ; i < _onlineUsers . length ; i ++ ) {
var found = false ;
for ( var j = 0 ; j < items . length ; j ++ ) {
if ( items [ j ] . values ( ) . id == _onlineUsers [ i ] . id ) {
found = true ;
break ;
}
2015-05-04 07:53:29 +00:00
}
2015-06-01 10:04:25 +00:00
if ( ! found ) {
onlineUserList . add ( _onlineUsers [ i ] ) ;
shortOnlineUserList . add ( _onlineUsers [ i ] ) ;
}
}
//sorting
sortOnlineUserList ( onlineUserList ) ;
sortOnlineUserList ( shortOnlineUserList ) ;
//render list items
renderUserStatusList ( onlineUserList ) ;
renderUserStatusList ( shortOnlineUserList ) ;
}
function sortOnlineUserList ( list ) {
//sort order by isSelf, login state, idle state, alphabet name, color brightness
list . sort ( '' , {
sortFunction : function ( a , b ) {
var usera = a . values ( ) ;
var userb = b . values ( ) ;
var useraIsSelf = ( usera . id == personalInfo . id || ( usera . login && usera . userid == personalInfo . userid ) ) ;
var userbIsSelf = ( userb . id == personalInfo . id || ( userb . login && userb . userid == personalInfo . userid ) ) ;
if ( useraIsSelf && ! userbIsSelf ) {
return - 1 ;
2015-07-01 16:10:20 +00:00
} else if ( ! useraIsSelf && userbIsSelf ) {
2015-06-01 10:04:25 +00:00
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 . toLowerCase ( ) < userb . name . toLowerCase ( ) ) {
return - 1 ;
} else if ( usera . name . toLowerCase ( ) > userb . name . toLowerCase ( ) ) {
return 1 ;
} else {
if ( usera . color . toLowerCase ( ) < userb . color . toLowerCase ( ) )
return - 1 ;
else if ( usera . color . toLowerCase ( ) > userb . color . toLowerCase ( ) )
return 1 ;
else
return 0 ;
}
}
}
}
}
} ) ;
2015-05-04 07:53:29 +00:00
}
2015-06-01 10:04:25 +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' ) ;
2015-07-11 04:43:08 +00:00
if ( item . values ( ) . login && item . values ( ) . photo ) {
2015-07-04 14:19:09 +00:00
usericon . css ( 'background-image' , 'url(' + item . values ( ) . photo + ')' ) ;
usericon . css ( 'box-shadow' , '0px 0px 2px ' + item . values ( ) . color ) ;
//add 1px more to right, make it feel aligned
usericon . css ( 'margin-right' , '6px' ) ;
} else {
usericon . css ( 'background-color' , item . values ( ) . color ) ;
}
2015-06-01 10:04:25 +00:00
userstatus . removeClass ( 'ui-user-status-offline ui-user-status-online ui-user-status-idle' ) ;
if ( item . values ( ) . idle )
userstatus . addClass ( 'ui-user-status-idle' ) ;
else
userstatus . addClass ( 'ui-user-status-online' ) ;
}
}
function deduplicateOnlineUsers ( list ) {
var _onlineUsers = [ ] ;
for ( var i = 0 ; i < list . length ; i ++ ) {
var user = $ . extend ( { } , list [ i ] ) ;
if ( ! user . userid )
_onlineUsers . push ( user ) ;
else {
var found = false ;
for ( var j = 0 ; j < _onlineUsers . length ; j ++ ) {
if ( _onlineUsers [ j ] . userid == user . userid ) {
//keep self color when login
if ( user . id == personalInfo . id ) {
_onlineUsers [ j ] . color = user . color ;
}
//keep idle state if any of self client not idle
if ( ! user . idle ) {
_onlineUsers [ j ] . idle = user . idle ;
2015-07-04 14:19:09 +00:00
_onlineUsers [ j ] . color = user . color ;
2015-06-01 10:04:25 +00:00
}
found = true ;
break ;
}
}
if ( ! found )
_onlineUsers . push ( user ) ;
}
}
return _onlineUsers ;
}
var userStatusCache = null ;
function emitUserStatus ( force ) {
if ( ! loaded ) return ;
var type = null ;
if ( visibleXS )
type = 'xs' ;
else if ( visibleSM )
type = 'sm' ;
else if ( visibleMD )
type = 'md' ;
else if ( visibleLG )
type = 'lg' ;
personalInfo [ 'idle' ] = idle . isAway ;
personalInfo [ 'type' ] = type ;
for ( var i = 0 ; i < onlineUsers . length ; i ++ ) {
if ( onlineUsers [ i ] . id == personalInfo . id ) {
onlineUsers [ i ] = personalInfo ;
}
}
var userStatus = {
idle : idle . isAway ,
type : type
} ;
if ( force || JSON . stringify ( userStatus ) != JSON . stringify ( userStatusCache ) ) {
socket . emit ( 'user status' , userStatus ) ;
userStatusCache = userStatus ;
}
}
function checkCursorTag ( coord , ele ) {
2016-03-15 03:12:45 +00:00
if ( ! ele ) return ; // return if element not exists
// set margin
var tagRightMargin = 0 ;
var tagBottomMargin = 2 ;
// use sizer to get the real doc size (won't count status bar and gutters)
var docWidth = ui . area . codemirrorSizer . width ( ) ;
var docHeight = ui . area . codemirrorSizer . height ( ) ;
// get editor size (status bar not count in)
2015-06-01 10:04:25 +00:00
var editorWidth = ui . area . codemirror . width ( ) ;
var editorHeight = ui . area . codemirror . height ( ) ;
2016-03-15 03:12:45 +00:00
// get element size
var width = ele . outerWidth ( ) ;
var height = ele . outerHeight ( ) ;
var padding = ( ele . outerWidth ( ) - ele . width ( ) ) / 2 ;
// get coord position
2015-06-01 10:04:25 +00:00
var left = coord . left ;
2016-03-15 03:12:45 +00:00
var top = coord . top ;
2016-06-17 07:55:27 +00:00
// get doc top offset (to workaround with viewport)
var docTopOffset = ui . area . codemirrorSizerInner . position ( ) . top ;
2016-03-15 03:12:45 +00:00
// set offset
2015-06-01 10:04:25 +00:00
var offsetLeft = - 3 ;
var offsetTop = defaultTextHeight ;
2016-03-15 03:12:45 +00:00
// only do when have width and height
2015-06-01 10:04:25 +00:00
if ( width > 0 && height > 0 ) {
2016-03-15 03:12:45 +00:00
// flip x when element right bound larger than doc width
if ( left + width + offsetLeft + tagRightMargin > docWidth ) {
offsetLeft = - ( width + tagRightMargin ) + padding + offsetLeft ;
2015-06-01 10:04:25 +00:00
}
2016-03-15 03:12:45 +00:00
// flip y when element bottom bound larger than doc height
// and element top position is larger than element height
2016-06-17 07:55:27 +00:00
if ( top + docTopOffset + height + offsetTop + tagBottomMargin > Math . max ( editor . doc . height , editorHeight ) && top + docTopOffset > height + tagBottomMargin ) {
2016-03-15 03:12:45 +00:00
offsetTop = - ( height ) ;
2015-06-01 10:04:25 +00:00
}
}
2016-03-15 03:12:45 +00:00
// set position
2015-06-01 10:04:25 +00:00
ele [ 0 ] . style . left = offsetLeft + 'px' ;
ele [ 0 ] . style . top = offsetTop + 'px' ;
}
function buildCursor ( user ) {
2015-09-25 05:43:19 +00:00
if ( currentMode == modeType . view ) return ;
2015-06-01 10:04:25 +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 ) {
2016-04-24 04:30:16 +00:00
case 'xs' :
iconClass = 'fa-mobile' ;
break ;
case 'sm' :
iconClass = 'fa-tablet' ;
break ;
case 'md' :
iconClass = 'fa-desktop' ;
break ;
case 'lg' :
iconClass = 'fa-desktop' ;
break ;
2015-06-01 10:04:25 +00:00
}
2015-05-04 07:53:29 +00:00
if ( $ ( '.other-cursors' ) . length <= 0 ) {
$ ( "<div class='other-cursors'>" ) . insertAfter ( '.CodeMirror-cursors' ) ;
}
2016-01-23 01:48:07 +00:00
if ( $ ( 'div[data-clientid="' + user . id + '"]' ) . length <= 0 ) {
var cursor = $ ( '<div data-clientid="' + user . id + '" class="other-cursor" style="display:none;"></div>' ) ;
2015-06-01 10:04:25 +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 ) ;
var cursorbar = $ ( '<div class="cursorbar"> </div>' ) ;
cursorbar [ 0 ] . style . height = defaultTextHeight + 'px' ;
cursorbar [ 0 ] . style . borderLeft = '2px solid ' + user . color ;
var icon = '<i class="fa ' + iconClass + '"></i>' ;
var cursortag = $ ( '<div class="cursortag">' + icon + ' <span class="name">' + user . name + '</span></div>' ) ;
//cursortag[0].style.background = color;
cursortag [ 0 ] . style . color = user . color ;
2016-06-17 07:52:11 +00:00
cursor . attr ( 'data-mode' , 'hover' ) ;
cursortag . delay ( 2000 ) . fadeOut ( "fast" ) ;
2015-06-01 10:04:25 +00:00
cursor . hover (
function ( ) {
if ( cursor . attr ( 'data-mode' ) == 'hover' )
cursortag . stop ( true ) . fadeIn ( "fast" ) ;
} ,
function ( ) {
if ( cursor . attr ( 'data-mode' ) == 'hover' )
cursortag . stop ( true ) . fadeOut ( "fast" ) ;
} ) ;
function switchMode ( ele ) {
if ( ele . attr ( 'data-mode' ) == 'state' )
ele . attr ( 'data-mode' , 'hover' ) ;
else if ( ele . attr ( 'data-mode' ) == 'hover' )
ele . attr ( 'data-mode' , 'state' ) ;
}
function switchTag ( ele ) {
if ( ele . css ( 'display' ) === 'none' )
ele . stop ( true ) . fadeIn ( "fast" ) ;
else
ele . stop ( true ) . fadeOut ( "fast" ) ;
}
var hideCursorTagDelay = 2000 ;
var hideCursorTagTimer = null ;
function hideCursorTag ( ) {
if ( cursor . attr ( 'data-mode' ) == 'hover' )
cursortag . fadeOut ( "fast" ) ;
}
cursor . on ( 'touchstart' , function ( e ) {
var display = cursortag . css ( 'display' ) ;
cursortag . stop ( true ) . fadeIn ( "fast" ) ;
clearTimeout ( hideCursorTagTimer ) ;
hideCursorTagTimer = setTimeout ( hideCursorTag , hideCursorTagDelay ) ;
if ( display === 'none' ) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
}
} ) ;
cursortag . on ( 'mousedown touchstart' , function ( e ) {
if ( cursor . attr ( 'data-mode' ) == 'state' )
switchTag ( cursortag ) ;
switchMode ( cursor ) ;
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
} ) ;
cursor . append ( cursorbar ) ;
cursor . append ( cursortag ) ;
2015-05-04 07:53:29 +00:00
cursor [ 0 ] . style . left = coord . left + 'px' ;
cursor [ 0 ] . style . top = coord . top + 'px' ;
$ ( '.other-cursors' ) . append ( cursor ) ;
2015-06-01 10:04:25 +00:00
if ( ! user . idle )
cursor . stop ( true ) . fadeIn ( ) ;
checkCursorTag ( coord , cursortag ) ;
2015-05-04 07:53:29 +00:00
} else {
2016-01-23 01:48:07 +00:00
var cursor = $ ( 'div[data-clientid="' + user . id + '"]' ) ;
2015-07-11 04:43:08 +00:00
var lineDiff = Math . abs ( cursor . attr ( 'data-line' ) - user . cursor . line ) ;
2015-06-01 10:04:25 +00:00
cursor . attr ( 'data-line' , user . cursor . line ) ;
cursor . attr ( 'data-ch' , user . cursor . ch ) ;
var cursorbar = cursor . find ( '.cursorbar' ) ;
cursorbar [ 0 ] . style . height = defaultTextHeight + 'px' ;
cursorbar [ 0 ] . style . borderLeft = '2px solid ' + user . color ;
var cursortag = cursor . find ( '.cursortag' ) ;
cursortag . find ( 'i' ) . removeClass ( ) . addClass ( 'fa' ) . addClass ( iconClass ) ;
cursortag . find ( ".name" ) . text ( user . name ) ;
2016-04-24 04:30:16 +00:00
2015-06-01 10:04:25 +00:00
if ( cursor . css ( 'display' ) === 'none' ) {
cursor [ 0 ] . style . left = coord . left + 'px' ;
cursor [ 0 ] . style . top = coord . top + 'px' ;
} else {
cursor . animate ( {
"left" : coord . left ,
"top" : coord . top
} , {
2016-04-24 04:30:16 +00:00
duration : cursorAnimatePeriod ,
queue : false
} ) ;
2015-06-01 10:04:25 +00:00
}
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 ) ;
2015-05-04 07:53:29 +00:00
}
}
//editor actions
2015-07-16 14:46:06 +00:00
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 ;
}
2015-07-11 04:43:08 +00:00
var ignoreEmitEvents = [ 'setValue' , 'ignoreHistory' ] ;
2015-05-04 07:53:29 +00:00
editor . on ( 'beforeChange' , function ( cm , change ) {
if ( debug )
console . debug ( change ) ;
2015-07-16 14:46:06 +00:00
if ( enforceMaxLength ( cm , change ) ) {
$ ( '.limit-modal' ) . modal ( 'show' ) ;
}
2015-07-11 04:43:08 +00:00
var isIgnoreEmitEvent = ( ignoreEmitEvents . indexOf ( change . origin ) != - 1 ) ;
if ( ! isIgnoreEmitEvent ) {
2015-09-25 10:48:45 +00:00
if ( ! havePermission ( ) ) {
change . canceled = true ;
2015-12-18 16:15:16 +00:00
switch ( permission ) {
2016-04-24 04:30:16 +00:00
case "editable" :
$ ( '.signin-modal' ) . modal ( 'show' ) ;
break ;
case "locked" :
case "private" :
$ ( '.locked-modal' ) . modal ( 'show' ) ;
break ;
2015-12-18 16:15:16 +00:00
}
2015-09-25 10:48:45 +00:00
}
} else {
if ( change . origin == 'ignoreHistory' ) {
setHaveUnreadChanges ( true ) ;
updateTitleReminder ( ) ;
2015-07-01 16:10:20 +00:00
}
}
2015-09-25 10:48:45 +00:00
if ( cmClient && ! socket . connected )
cmClient . editorAdapter . ignoreNextChange = true ;
} ) ;
2016-04-24 04:30:16 +00:00
editor . on ( 'cut' , function ( ) {
2016-05-29 05:58:32 +00:00
//na
2016-02-02 19:56:10 +00:00
} ) ;
2016-04-24 04:30:16 +00:00
editor . on ( 'paste' , function ( ) {
2016-05-29 05:58:32 +00:00
//na
2016-02-02 19:56:10 +00:00
} ) ;
2015-09-25 10:05:50 +00:00
editor . on ( 'changes' , function ( cm , changes ) {
updateHistory ( ) ;
2016-02-02 19:56:10 +00:00
var docLength = editor . getValue ( ) . length ;
//workaround for big documents
var newViewportMargin = 20 ;
if ( docLength > 20000 ) {
newViewportMargin = 1 ;
} else if ( docLength > 10000 ) {
newViewportMargin = 10 ;
} else if ( docLength > 5000 ) {
newViewportMargin = 15 ;
}
if ( newViewportMargin != viewportMargin ) {
viewportMargin = newViewportMargin ;
windowResize ( ) ;
}
2016-06-17 07:57:34 +00:00
checkEditorScrollbar ( ) ;
2016-06-17 07:58:23 +00:00
if ( editorHasFocus ( ) ) {
postUpdateEvent = function ( ) {
syncScrollToView ( ) ;
postUpdateEvent = null ;
} ;
}
2015-05-04 07:53:29 +00:00
} ) ;
editor . on ( 'focus' , function ( cm ) {
2015-06-01 10:04:25 +00:00
for ( var i = 0 ; i < onlineUsers . length ; i ++ ) {
if ( onlineUsers [ i ] . id == personalInfo . id ) {
onlineUsers [ i ] . cursor = editor . getCursor ( ) ;
}
}
personalInfo [ 'cursor' ] = editor . getCursor ( ) ;
2015-05-04 07:53:29 +00:00
socket . emit ( 'cursor focus' , editor . getCursor ( ) ) ;
} ) ;
editor . on ( 'cursorActivity' , function ( cm ) {
2015-09-24 06:59:45 +00:00
updateStatusBar ( ) ;
2015-09-25 10:01:15 +00:00
cursorActivity ( ) ;
} ) ;
2015-09-24 06:59:45 +00:00
editor . on ( 'beforeSelectionChange' , function ( doc , selections ) {
if ( selections )
selection = selections . ranges [ 0 ] ;
else
selection = null ;
updateStatusBar ( ) ;
2015-05-04 07:53:29 +00:00
} ) ;
2015-05-15 04:58:13 +00:00
2015-09-25 10:01:15 +00:00
var cursorActivity = _ . debounce ( cursorActivityInner , cursorActivityDebounce ) ;
function cursorActivityInner ( ) {
2015-06-01 10:04:25 +00:00
if ( editorHasFocus ( ) && ! Visibility . hidden ( ) ) {
for ( var i = 0 ; i < onlineUsers . length ; i ++ ) {
if ( onlineUsers [ i ] . id == personalInfo . id ) {
onlineUsers [ i ] . cursor = editor . getCursor ( ) ;
}
}
personalInfo [ 'cursor' ] = editor . getCursor ( ) ;
socket . emit ( 'cursor activity' , editor . getCursor ( ) ) ;
}
2015-05-04 07:53:29 +00:00
}
editor . on ( 'blur' , function ( cm ) {
2015-06-01 10:04:25 +00:00
for ( var i = 0 ; i < onlineUsers . length ; i ++ ) {
if ( onlineUsers [ i ] . id == personalInfo . id ) {
onlineUsers [ i ] . cursor = null ;
}
}
personalInfo [ 'cursor' ] = null ;
2015-05-04 07:53:29 +00:00
socket . emit ( 'cursor blur' ) ;
} ) ;
function saveInfo ( ) {
2015-06-01 10:04:25 +00:00
var scrollbarStyle = editor . getOption ( 'scrollbarStyle' ) ;
2015-09-25 10:34:03 +00:00
var left = $ ( window ) . scrollLeft ( ) ;
var top = $ ( window ) . scrollTop ( ) ;
2015-05-04 07:53:29 +00:00
switch ( currentMode ) {
2016-04-24 04:30:16 +00:00
case modeType . edit :
if ( scrollbarStyle == 'native' ) {
lastInfo . edit . scroll . left = left ;
lastInfo . edit . scroll . top = top ;
} else {
lastInfo . edit . scroll = editor . getScrollInfo ( ) ;
}
break ;
case modeType . view :
lastInfo . view . scroll . left = left ;
lastInfo . view . scroll . top = top ;
break ;
case modeType . both :
2015-06-01 10:04:25 +00:00
lastInfo . edit . scroll = editor . getScrollInfo ( ) ;
2016-04-24 04:30:16 +00:00
lastInfo . view . scroll . left = ui . area . view . scrollLeft ( ) ;
lastInfo . view . scroll . top = ui . area . view . scrollTop ( ) ;
break ;
2015-05-04 07:53:29 +00:00
}
lastInfo . edit . cursor = editor . getCursor ( ) ;
lastInfo . needRestore = true ;
}
function restoreInfo ( ) {
2015-06-01 10:04:25 +00:00
var scrollbarStyle = editor . getOption ( 'scrollbarStyle' ) ;
2015-05-04 07:53:29 +00:00
if ( lastInfo . needRestore ) {
var line = lastInfo . edit . cursor . line ;
var ch = lastInfo . edit . cursor . ch ;
editor . setCursor ( line , ch ) ;
switch ( currentMode ) {
2016-04-24 04:30:16 +00:00
case modeType . edit :
if ( scrollbarStyle == 'native' ) {
$ ( window ) . scrollLeft ( lastInfo . edit . scroll . left ) ;
$ ( window ) . scrollTop ( lastInfo . edit . scroll . top ) ;
} else {
var left = lastInfo . edit . scroll . left ;
var top = lastInfo . edit . scroll . top ;
editor . scrollIntoView ( ) ;
editor . scrollTo ( left , top ) ;
}
break ;
case modeType . view :
$ ( window ) . scrollLeft ( lastInfo . view . scroll . left ) ;
$ ( window ) . scrollTop ( lastInfo . view . scroll . top ) ;
break ;
case modeType . both :
2015-06-01 10:04:25 +00:00
var left = lastInfo . edit . scroll . left ;
var top = lastInfo . edit . scroll . top ;
editor . scrollIntoView ( ) ;
editor . scrollTo ( left , top ) ;
2016-04-24 04:30:16 +00:00
ui . area . view . scrollLeft ( lastInfo . view . scroll . left ) ;
ui . area . view . scrollTop ( lastInfo . view . scroll . top ) ;
break ;
2015-05-04 07:53:29 +00:00
}
lastInfo . needRestore = false ;
}
}
//view actions
2015-09-25 10:01:15 +00:00
function refreshView ( ) {
ui . area . markdown . html ( '' ) ;
isDirty = true ;
updateViewInner ( ) ;
2015-05-04 07:53:29 +00:00
}
2015-09-25 10:01:15 +00:00
var updateView = _ . debounce ( updateViewInner , updateViewDebounce ) ;
2015-05-04 07:53:29 +00:00
var lastResult = null ;
2016-06-17 07:58:23 +00:00
var postUpdateEvent = null ;
2015-05-04 07:53:29 +00:00
2015-09-25 10:01:15 +00:00
function updateViewInner ( ) {
2015-05-04 07:53:29 +00:00
if ( currentMode == modeType . edit || ! isDirty ) return ;
var value = editor . getValue ( ) ;
2016-02-11 20:35:25 +00:00
var lastMeta = md . meta ;
2016-01-12 14:01:42 +00:00
md . meta = { } ;
2016-02-11 09:45:13 +00:00
var rendered = md . render ( value ) ;
2016-02-11 20:35:25 +00:00
// only render again when meta changed
if ( JSON . stringify ( md . meta ) != JSON . stringify ( lastMeta ) ) {
2016-02-16 01:13:17 +00:00
parseMeta ( md , ui . area . codemirror , ui . area . markdown , $ ( '#toc' ) , $ ( '#toc-affix' ) ) ;
2016-02-11 20:35:25 +00:00
rendered = md . render ( value ) ;
}
// prevent XSS
2016-02-11 09:45:13 +00:00
rendered = preventXSS ( rendered ) ;
var result = postProcess ( rendered ) . children ( ) . toArray ( ) ;
2015-05-04 07:53:29 +00:00
partialUpdate ( result , lastResult , ui . area . markdown . children ( ) . toArray ( ) ) ;
2015-06-01 10:04:25 +00:00
if ( result && lastResult && result . length != lastResult . length )
updateDataAttrs ( result , ui . area . markdown . children ( ) . toArray ( ) ) ;
lastResult = $ ( result ) . clone ( ) ;
2016-01-12 14:01:42 +00:00
finishView ( ui . area . markdown ) ;
autoLinkify ( ui . area . markdown ) ;
deduplicatedHeaderId ( ui . area . markdown ) ;
renderTOC ( ui . area . markdown ) ;
2015-07-01 16:10:20 +00:00
generateToc ( 'toc' ) ;
generateToc ( 'toc-affix' ) ;
generateScrollspy ( ) ;
2015-09-25 10:29:01 +00:00
updateScrollspy ( ) ;
2015-07-01 16:10:20 +00:00
smoothHashScroll ( ) ;
2015-05-04 07:53:29 +00:00
isDirty = false ;
2015-05-15 04:58:13 +00:00
clearMap ( ) ;
2015-09-25 10:20:06 +00:00
//buildMap();
2015-09-25 10:48:45 +00:00
updateTitleReminder ( ) ;
2016-06-17 07:58:23 +00:00
if ( postUpdateEvent && typeof postUpdateEvent === 'function' )
postUpdateEvent ( ) ;
2015-09-25 10:48:45 +00:00
}
2015-09-25 10:01:15 +00:00
var updateHistoryDebounce = 600 ;
var updateHistory = _ . debounce ( updateHistoryInner , updateHistoryDebounce )
function updateHistoryInner ( ) {
writeHistory ( ui . area . markdown ) ;
2015-06-01 10:04:25 +00:00
}
function updateDataAttrs ( src , des ) {
//sync data attr startline and endline
for ( var i = 0 ; i < src . length ; i ++ ) {
copyAttribute ( src [ i ] , des [ i ] , 'data-startline' ) ;
copyAttribute ( src [ i ] , des [ i ] , 'data-endline' ) ;
}
2015-05-04 07:53:29 +00:00
}
function partialUpdate ( src , tar , des ) {
if ( ! src || src . length == 0 || ! tar || tar . length == 0 || ! des || des . length == 0 ) {
ui . area . markdown . html ( src ) ;
return ;
}
if ( src . length == tar . length ) { //same length
for ( var i = 0 ; i < src . length ; i ++ ) {
copyAttribute ( src [ i ] , des [ i ] , 'data-startline' ) ;
copyAttribute ( src [ i ] , des [ i ] , 'data-endline' ) ;
var rawSrc = cloneAndRemoveDataAttr ( src [ i ] ) ;
var rawTar = cloneAndRemoveDataAttr ( tar [ i ] ) ;
if ( rawSrc . outerHTML != rawTar . outerHTML ) {
//console.log(rawSrc);
//console.log(rawTar);
$ ( des [ i ] ) . replaceWith ( src [ i ] ) ;
}
}
} else { //diff length
var start = 0 ;
var end = 0 ;
//find diff start position
for ( var i = 0 ; i < tar . length ; i ++ ) {
2015-06-01 10:04:25 +00:00
//copyAttribute(src[i], des[i], 'data-startline');
//copyAttribute(src[i], des[i], 'data-endline');
2015-05-04 07:53:29 +00:00
var rawSrc = cloneAndRemoveDataAttr ( src [ i ] ) ;
var rawTar = cloneAndRemoveDataAttr ( tar [ i ] ) ;
if ( ! rawSrc || ! rawTar || rawSrc . outerHTML != rawTar . outerHTML ) {
start = i ;
break ;
}
}
//find diff end position
var srcEnd = 0 ;
var tarEnd = 0 ;
for ( var i = 0 ; i < src . length ; i ++ ) {
2015-06-01 10:04:25 +00:00
//copyAttribute(src[i], des[i], 'data-startline');
//copyAttribute(src[i], des[i], 'data-endline');
2015-05-04 07:53:29 +00:00
var rawSrc = cloneAndRemoveDataAttr ( src [ i ] ) ;
var rawTar = cloneAndRemoveDataAttr ( tar [ i ] ) ;
if ( ! rawSrc || ! rawTar || rawSrc . outerHTML != rawTar . outerHTML ) {
start = i ;
break ;
}
}
//tar end
2015-05-15 04:58:13 +00:00
for ( var i = 1 ; i <= tar . length + 1 ; i ++ ) {
2015-05-04 07:53:29 +00:00
var srcLength = src . length ;
var tarLength = tar . length ;
2015-06-01 10:04:25 +00:00
//copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline');
//copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline');
2015-05-04 07:53:29 +00:00
var rawSrc = cloneAndRemoveDataAttr ( src [ srcLength - i ] ) ;
var rawTar = cloneAndRemoveDataAttr ( tar [ tarLength - i ] ) ;
if ( ! rawSrc || ! rawTar || rawSrc . outerHTML != rawTar . outerHTML ) {
tarEnd = tar . length - i ;
break ;
}
}
//src end
2015-05-15 04:58:13 +00:00
for ( var i = 1 ; i <= src . length + 1 ; i ++ ) {
2015-05-04 07:53:29 +00:00
var srcLength = src . length ;
var tarLength = tar . length ;
2015-06-01 10:04:25 +00:00
//copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline');
//copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline');
2015-05-04 07:53:29 +00:00
var rawSrc = cloneAndRemoveDataAttr ( src [ srcLength - i ] ) ;
var rawTar = cloneAndRemoveDataAttr ( tar [ tarLength - i ] ) ;
if ( ! rawSrc || ! rawTar || rawSrc . outerHTML != rawTar . outerHTML ) {
srcEnd = src . length - i ;
break ;
}
}
//check if tar end overlap tar start
var overlap = 0 ;
for ( var i = start ; i >= 0 ; i -- ) {
2015-05-15 04:58:13 +00:00
var rawTarStart = cloneAndRemoveDataAttr ( tar [ i - 1 ] ) ;
var rawTarEnd = cloneAndRemoveDataAttr ( tar [ tarEnd + 1 + start - i ] ) ;
if ( rawTarStart && rawTarEnd && rawTarStart . outerHTML == rawTarEnd . outerHTML )
2015-05-04 07:53:29 +00:00
overlap ++ ;
else
break ;
}
2015-05-15 04:58:13 +00:00
if ( debug )
2015-05-04 07:53:29 +00:00
console . log ( 'overlap:' + overlap ) ;
//show diff content
2015-05-15 04:58:13 +00:00
if ( debug ) {
2015-05-04 07:53:29 +00:00
console . log ( 'start:' + start ) ;
console . log ( 'tarEnd:' + tarEnd ) ;
console . log ( 'srcEnd:' + srcEnd ) ;
}
tarEnd += overlap ;
srcEnd += overlap ;
2015-05-15 04:58:13 +00:00
var repeatAdd = ( start - srcEnd ) < ( start - tarEnd ) ;
var repeatDiff = Math . abs ( srcEnd - tarEnd ) - 1 ;
//push new elements
var newElements = [ ] ;
2015-06-01 10:04:25 +00:00
if ( srcEnd >= start ) {
2015-05-15 04:58:13 +00:00
for ( var j = start ; j <= srcEnd ; j ++ ) {
if ( ! src [ j ] ) continue ;
newElements . push ( src [ j ] . outerHTML ) ;
}
2015-06-01 10:04:25 +00:00
} else if ( repeatAdd ) {
2015-05-15 04:58:13 +00:00
for ( var j = srcEnd - repeatDiff ; j <= srcEnd ; j ++ ) {
if ( ! des [ j ] ) continue ;
newElements . push ( des [ j ] . outerHTML ) ;
}
2015-05-04 07:53:29 +00:00
}
2015-05-15 04:58:13 +00:00
//push remove elements
var removeElements = [ ] ;
2015-06-01 10:04:25 +00:00
if ( tarEnd >= start ) {
2015-05-15 04:58:13 +00:00
for ( var j = start ; j <= tarEnd ; j ++ ) {
if ( ! des [ j ] ) continue ;
removeElements . push ( des [ j ] ) ;
}
2015-06-01 10:04:25 +00:00
} else if ( ! repeatAdd ) {
2015-05-15 04:58:13 +00:00
for ( var j = start ; j <= start + repeatDiff ; j ++ ) {
if ( ! des [ j ] ) continue ;
removeElements . push ( des [ j ] ) ;
}
2015-05-04 07:53:29 +00:00
}
2015-05-15 04:58:13 +00:00
//add elements
if ( debug ) {
console . log ( 'ADD ELEMENTS' ) ;
console . log ( newElements . join ( '\n' ) ) ;
2015-05-04 07:53:29 +00:00
}
2015-05-15 04:58:13 +00:00
if ( des [ start ] )
$ ( newElements . join ( '' ) ) . insertBefore ( des [ start ] ) ;
else
$ ( newElements . join ( '' ) ) . insertAfter ( des [ start - 1 ] ) ;
//remove elements
if ( debug )
console . log ( 'REMOVE ELEMENTS' ) ;
for ( var j = 0 ; j < removeElements . length ; j ++ ) {
if ( debug ) {
console . log ( removeElements [ j ] . outerHTML ) ;
}
if ( removeElements [ j ] )
2015-12-18 16:15:28 +00:00
$ ( removeElements [ j ] ) . remove ( ) ;
2015-05-04 07:53:29 +00:00
}
}
}
function cloneAndRemoveDataAttr ( el ) {
2015-05-15 04:58:13 +00:00
if ( ! el ) return ;
2016-06-17 08:33:01 +00:00
var rawEl = $ ( el ) . clone ( ) ;
rawEl . removeAttr ( 'data-startline data-endline' ) ;
rawEl . find ( '[data-startline]' ) . removeAttr ( 'data-startline data-endline' ) ;
return rawEl [ 0 ] ;
2015-05-04 07:53:29 +00:00
}
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 ) {
$ ( "<div class='cursor-menu'>" ) . insertAfter ( '.CodeMirror-cursors' ) ;
}
2016-03-15 03:12:45 +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 ;
}
var lastUpSideDown = false ;
2015-06-01 10:04:25 +00:00
var upSideDown = false ;
2016-02-25 05:45:02 +00:00
var checkCursorMenu = _ . throttle ( checkCursorMenuInner , cursorMenuThrottle ) ;
function checkCursorMenuInner ( ) {
2016-03-15 03:12:45 +00:00
// get element
var dropdown = $ ( '.cursor-menu > .dropdown-menu' ) ;
// return if not exists
2015-09-25 05:39:08 +00:00
if ( dropdown . length <= 0 ) return ;
2016-03-15 03:12:45 +00:00
// set margin
var menuRightMargin = 10 ;
var menuBottomMargin = 4 ;
// use sizer to get the real doc size (won't count status bar and gutters)
var docWidth = ui . area . codemirrorSizer . width ( ) ;
var docHeight = ui . area . codemirrorSizer . height ( ) ;
// get editor size (status bar not count in)
var editorWidth = ui . area . codemirror . width ( ) ;
var editorHeight = ui . area . codemirror . height ( ) ;
// get element size
var width = dropdown . outerWidth ( ) ;
var height = dropdown . outerHeight ( ) ;
// get cursor
2015-06-01 10:04:25 +00:00
var cursor = editor . getCursor ( ) ;
2016-03-15 03:12:45 +00:00
// set element cursor data
2015-06-01 10:04:25 +00:00
if ( ! dropdown . hasClass ( 'other-cursor' ) )
dropdown . addClass ( 'other-cursor' ) ;
dropdown . attr ( 'data-line' , cursor . line ) ;
dropdown . attr ( 'data-ch' , cursor . ch ) ;
2016-03-15 03:12:45 +00:00
// get coord position
2015-06-01 10:04:25 +00:00
var coord = editor . charCoords ( {
line : cursor . line ,
ch : cursor . ch
} , 'windows' ) ;
var left = coord . left ;
2016-03-15 03:12:45 +00:00
var top = coord . top ;
2016-06-17 07:55:27 +00:00
// get doc top offset (to workaround with viewport)
var docTopOffset = ui . area . codemirrorSizerInner . position ( ) . top ;
2016-03-15 03:12:45 +00:00
// set offset
2015-06-01 10:04:25 +00:00
var offsetLeft = 0 ;
var offsetTop = defaultTextHeight ;
2016-03-15 03:12:45 +00:00
// only do when have width and height
if ( width > 0 && height > 0 ) {
// make element right bound not larger than doc width
if ( left + width + offsetLeft + menuRightMargin > docWidth )
offsetLeft = - ( left + width - docWidth + menuRightMargin ) ;
// flip y when element bottom bound larger than doc height
// and element top position is larger than element height
2016-06-17 07:55:27 +00:00
if ( top + docTopOffset + height + offsetTop + menuBottomMargin > Math . max ( editor . doc . height , editorHeight ) && top + docTopOffset > height + menuBottomMargin ) {
2016-03-15 03:12:45 +00:00
offsetTop = - ( height + menuBottomMargin ) ;
// reverse sort menu because upSideDown
dropdown . html ( reverseSortCursorMenu ( dropdown ) ) ;
lastUpSideDown = upSideDown ;
upSideDown = true ;
} else {
lastUpSideDown = upSideDown ;
upSideDown = false ;
}
2015-06-01 10:04:25 +00:00
}
2016-03-15 03:12:45 +00:00
// make menu scroll top only if upSideDown changed
if ( upSideDown !== lastUpSideDown )
dropdown . scrollTop ( dropdown [ 0 ] . scrollHeight ) ;
// set element offset data
2015-06-01 10:04:25 +00:00
dropdown . attr ( 'data-offset-left' , offsetLeft ) ;
dropdown . attr ( 'data-offset-top' , offsetTop ) ;
2016-03-15 03:12:45 +00:00
// set position
2015-06-01 10:04:25 +00:00
dropdown [ 0 ] . style . left = left + offsetLeft + 'px' ;
dropdown [ 0 ] . style . top = top + offsetTop + 'px' ;
}
2016-03-15 03:04:45 +00:00
function checkInIndentCode ( ) {
// if line starts with tab or four spaces is a code block
var line = editor . getLine ( editor . getCursor ( ) . line ) ;
var isIndentCode = ( ( line . substr ( 0 , 4 ) === ' ' ) || ( line . substr ( 0 , 1 ) === '\t' ) ) ;
return isIndentCode ;
}
2015-06-01 10:04:25 +00:00
var isInCode = false ;
2016-01-31 21:32:35 +00:00
function checkInCode ( ) {
2016-03-15 03:04:45 +00:00
isInCode = checkAbove ( matchInCode ) || checkInIndentCode ( ) ;
2016-01-31 21:32:35 +00:00
}
2016-03-15 03:04:45 +00:00
function checkAbove ( method ) {
2016-01-31 21:32:35 +00:00
var cursor = editor . getCursor ( ) ;
var text = [ ] ;
for ( var i = 0 ; i < cursor . line ; i ++ ) //contain current line
text . push ( editor . getLine ( i ) ) ;
text = text . join ( '\n' ) + '\n' + editor . getLine ( cursor . line ) . slice ( 0 , cursor . ch ) ;
//console.log(text);
2016-03-15 03:04:45 +00:00
return method ( text ) ;
2016-01-31 21:32:35 +00:00
}
2016-03-15 03:04:45 +00:00
function checkBelow ( method ) {
2015-06-01 10:04:25 +00:00
var cursor = editor . getCursor ( ) ;
2016-01-31 21:32:35 +00:00
var count = editor . lineCount ( ) ;
var text = [ ] ;
2016-02-01 06:42:30 +00:00
for ( var i = cursor . line + 1 ; i < count ; i ++ ) //contain current line
2015-06-01 10:04:25 +00:00
text . push ( editor . getLine ( i ) ) ;
2016-02-01 06:42:30 +00:00
text = editor . getLine ( cursor . line ) . slice ( cursor . ch ) + '\n' + text . join ( '\n' ) ;
2015-06-01 10:04:25 +00:00
//console.log(text);
2016-03-15 03:04:45 +00:00
return method ( text ) ;
2016-01-31 21:32:35 +00:00
}
function matchInCode ( text ) {
2015-06-01 10:04:25 +00:00
var match ;
match = text . match ( /`{3,}/g ) ;
if ( match && match . length % 2 ) {
2016-01-31 21:32:35 +00:00
return true ;
2015-06-01 10:04:25 +00:00
} else {
match = text . match ( /`/g ) ;
if ( match && match . length % 2 ) {
2016-01-31 21:32:35 +00:00
return true ;
2015-06-01 10:04:25 +00:00
} else {
2016-01-31 21:32:35 +00:00
return false ;
2015-06-01 10:04:25 +00:00
}
}
}
2016-03-15 03:04:45 +00:00
var isInContainer = false ;
var isInContainerSyntax = false ;
function checkInContainer ( ) {
isInContainer = checkAbove ( matchInContainer ) && ! checkInIndentCode ( ) ;
}
function checkInContainerSyntax ( ) {
// if line starts with :::, it's in container syntax
var line = editor . getLine ( editor . getCursor ( ) . line ) ;
isInContainerSyntax = ( line . substr ( 0 , 3 ) === ':::' ) ;
}
function matchInContainer ( text ) {
var match ;
match = text . match ( /:{3,}/g ) ;
if ( match && match . length % 2 ) {
return true ;
} else {
return false ;
}
}
2015-06-01 10:04:25 +00:00
$ ( editor . getInputField ( ) )
. textcomplete ( [
{ // emoji strategy
2016-05-27 05:38:59 +00:00
match : /(^|\n|\s)\B:([\-+\w]*)$/ ,
2015-06-01 10:04:25 +00:00
search : function ( term , callback ) {
2016-05-27 05:38:59 +00:00
var line = editor . getLine ( editor . getCursor ( ) . line ) ;
term = line . match ( this . match ) [ 2 ] ;
2016-01-31 21:32:35 +00:00
var list = [ ] ;
$ . map ( emojify . emojiNames , function ( emoji ) {
if ( emoji . indexOf ( term ) === 0 ) //match at first character
2016-04-24 04:30:16 +00:00
list . push ( emoji ) ;
2016-01-31 21:32:35 +00:00
} ) ;
$ . map ( emojify . emojiNames , function ( emoji ) {
if ( emoji . indexOf ( term ) !== - 1 ) //match inside the word
2016-04-24 04:30:16 +00:00
list . push ( emoji ) ;
2016-01-31 21:32:35 +00:00
} ) ;
callback ( list ) ;
2015-06-01 10:04:25 +00:00
} ,
template : function ( value ) {
2016-02-17 04:08:44 +00:00
return '<img class="emoji" src="' + serverurl + '/vendor/emojify/images/' + value + '.png"></img> ' + value ;
2015-06-01 10:04:25 +00:00
} ,
replace : function ( value ) {
2016-05-27 05:38:59 +00:00
return '$1:' + value + ': ' ;
2015-06-01 10:04:25 +00:00
} ,
index : 1 ,
context : function ( text ) {
2016-01-31 21:32:35 +00:00
checkInCode ( ) ;
2016-03-15 03:04:45 +00:00
checkInContainer ( ) ;
checkInContainerSyntax ( ) ;
return ! isInCode && ! isInContainerSyntax ;
2015-06-01 10:04:25 +00:00
}
2016-04-24 04:30:16 +00:00
} ,
2015-06-01 10:04:25 +00:00
{ // Code block language strategy
langs : supportCodeModes ,
2016-01-19 16:01:40 +00:00
charts : supportCharts ,
2015-07-16 16:06:05 +00:00
match : /(^|\n)```(\w+)$/ ,
2015-06-01 10:04:25 +00:00
search : function ( term , callback ) {
2016-03-15 03:04:45 +00:00
var line = editor . getLine ( editor . getCursor ( ) . line ) ;
term = line . match ( this . match ) [ 2 ] ;
2016-01-19 16:01:40 +00:00
var list = [ ] ;
$ . map ( this . langs , function ( lang ) {
2016-01-31 21:32:35 +00:00
if ( lang . indexOf ( term ) === 0 && lang !== term )
2016-01-19 16:01:40 +00:00
list . push ( lang ) ;
} ) ;
$ . map ( this . charts , function ( chart ) {
2016-01-31 21:32:35 +00:00
if ( chart . indexOf ( term ) === 0 && chart !== term )
2016-01-19 16:01:40 +00:00
list . push ( chart ) ;
} ) ;
callback ( list ) ;
2015-06-01 10:04:25 +00:00
} ,
replace : function ( lang ) {
2016-01-31 21:32:35 +00:00
var ending = '' ;
2016-03-15 03:04:45 +00:00
if ( ! checkBelow ( matchInCode ) ) {
2016-01-31 21:32:35 +00:00
ending = '\n\n```' ;
}
2016-01-19 16:01:40 +00:00
if ( this . langs . indexOf ( lang ) !== - 1 )
2016-01-31 21:32:35 +00:00
return '$1```' + lang + '=' + ending ;
2016-01-19 16:01:40 +00:00
else if ( this . charts . indexOf ( lang ) !== - 1 )
2016-01-31 21:32:35 +00:00
return '$1```' + lang + ending ;
2015-06-01 10:04:25 +00:00
} ,
done : function ( ) {
2016-01-31 21:32:35 +00:00
var cursor = editor . getCursor ( ) ;
var text = [ ] ;
text . push ( editor . getLine ( cursor . line - 1 ) ) ;
text . push ( editor . getLine ( cursor . line ) ) ;
text = text . join ( '\n' ) ;
//console.log(text);
if ( text == '\n```' )
editor . doc . cm . execCommand ( "goLineUp" ) ;
2015-06-01 10:04:25 +00:00
} ,
2016-01-31 21:32:35 +00:00
context : function ( text ) {
2016-02-01 06:42:30 +00:00
return isInCode ;
2015-06-01 10:04:25 +00:00
}
2016-04-24 04:30:16 +00:00
} ,
2016-03-15 03:01:01 +00:00
{ // Container strategy
containers : supportContainers ,
match : /(^|\n):::(\s*)(\w*)$/ ,
search : function ( term , callback ) {
var line = editor . getLine ( editor . getCursor ( ) . line ) ;
2016-05-27 05:38:59 +00:00
term = line . match ( this . match ) [ 3 ] . trim ( ) ;
2016-03-15 03:01:01 +00:00
var list = [ ] ;
$ . map ( this . containers , function ( container ) {
if ( container . indexOf ( term ) === 0 && container !== term )
list . push ( container ) ;
} ) ;
callback ( list ) ;
} ,
replace : function ( lang ) {
var ending = '' ;
if ( ! checkBelow ( matchInContainer ) ) {
ending = '\n\n:::' ;
}
if ( this . containers . indexOf ( lang ) !== - 1 )
return '$1:::$2' + lang + ending ;
} ,
done : function ( ) {
var cursor = editor . getCursor ( ) ;
var text = [ ] ;
text . push ( editor . getLine ( cursor . line - 1 ) ) ;
text . push ( editor . getLine ( cursor . line ) ) ;
text = text . join ( '\n' ) ;
//console.log(text);
if ( text == '\n:::' )
editor . doc . cm . execCommand ( "goLineUp" ) ;
} ,
context : function ( text ) {
return ! isInCode && isInContainer ;
}
2016-04-24 04:30:16 +00:00
} ,
2015-06-01 10:04:25 +00:00
{ //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 ;
}
2016-04-24 04:30:16 +00:00
} ,
2015-09-25 09:41:15 +00:00
{ //extra tags for blockquote
2016-05-27 05:38:59 +00:00
match : /(?:^|\n|\s)(\>.*|)((\^|)\[(\^|)\](\[\]|\(\)|\:|))(\w*)$/ ,
2015-06-01 10:04:25 +00:00
search : function ( term , callback ) {
2016-05-27 05:38:59 +00:00
var line = editor . getLine ( editor . getCursor ( ) . line ) ;
quote = line . match ( this . match ) [ 1 ] . trim ( ) ;
2015-07-01 16:10:20 +00:00
var list = [ ] ;
2016-05-27 05:38:59 +00:00
if ( quote . indexOf ( '>' ) == 0 ) {
$ . map ( supportExtraTags , function ( extratag ) {
if ( extratag . search . indexOf ( term ) === 0 )
list . push ( extratag . command ( ) ) ;
} ) ;
}
2015-09-25 09:41:15 +00:00
$ . map ( supportReferrals , function ( referral ) {
if ( referral . search . indexOf ( term ) === 0 )
list . push ( referral . text ) ;
} )
callback ( list ) ;
} ,
replace : function ( value ) {
return '$1' + value ;
} ,
context : function ( text ) {
return ! isInCode ;
}
2016-04-24 04:30:16 +00:00
} ,
2015-09-25 09:41:15 +00:00
{ //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 ( ) ) ;
2015-07-01 16:10:20 +00:00
} ) ;
$ . map ( supportReferrals , function ( referral ) {
if ( referral . search . indexOf ( term ) === 0 )
list . push ( referral . text ) ;
} )
callback ( list ) ;
2015-06-01 10:04:25 +00:00
} ,
replace : function ( value ) {
return '$1' + value ;
} ,
context : function ( text ) {
return ! isInCode ;
}
2016-04-24 04:30:16 +00:00
} ,
2015-07-01 16:10:20 +00:00
{ //referral
2016-05-27 05:38:59 +00:00
match : /(^|\n|\s)(\!(\[\]|)(\[\]|\(\)|))(\w*)$/ ,
2015-06-01 10:04:25 +00:00
search : function ( term , callback ) {
2015-07-01 16:10:20 +00:00
callback ( $ . map ( supportReferrals , function ( referral ) {
return referral . search . indexOf ( term ) === 0 ? referral . text : null ;
2015-06-01 10:04:25 +00:00
} ) ) ;
} ,
replace : function ( value ) {
return '$1' + value ;
} ,
context : function ( text ) {
return ! isInCode ;
}
2016-04-24 04:30:16 +00:00
} ,
2015-07-01 16:10:20 +00:00
{ //externals
match : /(^|\n|\s)\{\}(\w*)$/ ,
2015-06-01 10:04:25 +00:00
search : function ( term , callback ) {
2015-07-01 16:10:20 +00:00
callback ( $ . map ( supportExternals , function ( external ) {
return external . search . indexOf ( term ) === 0 ? external . text : null ;
} ) ) ;
2015-06-01 10:04:25 +00:00
} ,
replace : function ( value ) {
2015-07-01 16:10:20 +00:00
return '$1' + value ;
2015-06-01 10:04:25 +00:00
} ,
context : function ( text ) {
return ! isInCode ;
}
2016-04-24 04:30:16 +00:00
}
] , {
2015-06-01 10:04:25 +00:00
appendTo : $ ( '.cursor-menu' )
} )
. on ( {
2016-03-15 03:04:45 +00:00
'textComplete:beforeSearch' : function ( e ) {
//NA
} ,
'textComplete:afterSearch' : function ( e ) {
checkCursorMenu ( ) ;
} ,
2015-06-01 10:04:25 +00:00
'textComplete:select' : function ( e , value , strategy ) {
//NA
} ,
'textComplete:show' : function ( e ) {
$ ( this ) . data ( 'autocompleting' , true ) ;
editor . setOption ( "extraKeys" , {
"Up" : function ( ) {
2016-04-28 03:10:09 +00:00
return false ;
2015-06-01 10:04:25 +00:00
} ,
"Right" : function ( ) {
editor . doc . cm . execCommand ( "goCharRight" ) ;
} ,
"Down" : function ( ) {
2016-04-28 03:10:09 +00:00
return false ;
2015-06-01 10:04:25 +00:00
} ,
"Left" : function ( ) {
editor . doc . cm . execCommand ( "goCharLeft" ) ;
} ,
"Enter" : function ( ) {
2016-04-28 03:10:09 +00:00
return false ;
2015-06-01 10:04:25 +00:00
} ,
"Backspace" : function ( ) {
editor . doc . cm . execCommand ( "delCharBefore" ) ;
}
} ) ;
} ,
'textComplete:hide' : function ( e ) {
$ ( this ) . data ( 'autocompleting' , false ) ;
editor . setOption ( "extraKeys" , defaultExtraKeys ) ;
}
2016-04-23 15:26:40 +00:00
} ) ;