2020-05-05 09:19:32 +00:00
require ( [ '/api/config' ] , function ( ApiConfig ) {
2017-08-28 10:25:05 +00:00
// see ckeditor_base.js getUrl()
2020-05-05 09:19:32 +00:00
window . CKEDITOR _GETURL = function ( resource ) {
if ( resource . indexOf ( '/' ) === 0 ) {
2017-08-28 10:25:05 +00:00
resource = window . CKEDITOR . basePath . replace ( /\/bower_components\/.*/ , '' ) + resource ;
} else if ( resource . indexOf ( ':/' ) === - 1 ) {
resource = window . CKEDITOR . basePath + resource ;
}
if ( resource [ resource . length - 1 ] !== '/' && resource . indexOf ( 'ver=' ) === - 1 ) {
var args = ApiConfig . requireConf . urlArgs ;
resource += ( resource . indexOf ( '?' ) >= 0 ? '&' : '?' ) + args ;
}
return resource ;
} ;
2020-06-26 12:02:24 +00:00
window . MathJax = {
"HTML-CSS" : {
} ,
TeX : {
}
} ;
2017-08-28 10:25:05 +00:00
require ( [ '/bower_components/ckeditor/ckeditor.js' ] ) ;
} ) ;
2017-06-27 12:25:02 +00:00
define ( [
2017-08-28 10:25:05 +00:00
'jquery' ,
'/bower_components/hyperjson/hyperjson.js' ,
2017-09-25 13:45:08 +00:00
'/common/sframe-app-framework.js' ,
2017-08-28 10:25:05 +00:00
'/common/cursor.js' ,
'/common/TypingTests.js' ,
2017-09-25 13:45:08 +00:00
'/customize/messages.js' ,
2017-08-28 10:25:05 +00:00
'/pad/links.js' ,
2020-04-20 13:22:45 +00:00
'/pad/comments.js' ,
2018-10-18 16:50:38 +00:00
'/pad/export.js' ,
2018-12-10 10:57:39 +00:00
'/pad/cursor.js' ,
2017-08-28 10:25:05 +00:00
'/bower_components/nthen/index.js' ,
2017-09-15 11:21:37 +00:00
'/common/media-tag.js' ,
2017-08-28 10:25:05 +00:00
'/api/config' ,
2017-10-02 12:06:01 +00:00
'/common/common-hash.js' ,
'/common/common-util.js' ,
2018-08-29 16:20:34 +00:00
'/common/common-interface.js' ,
2020-08-06 11:56:50 +00:00
'/common/common-ui-elements.js' ,
2018-08-30 09:21:33 +00:00
'/common/hyperscript.js' ,
2017-11-09 16:07:04 +00:00
'/bower_components/chainpad/chainpad.dist.js' ,
2018-01-31 13:42:34 +00:00
'/customize/application_config.js' ,
2018-05-11 07:06:22 +00:00
'/common/test.js' ,
2017-08-28 10:25:05 +00:00
'/bower_components/diff-dom/diffDOM.js' ,
2020-11-25 15:19:10 +00:00
'/bower_components/file-saver/FileSaver.min.js' ,
2017-08-28 10:25:05 +00:00
2020-10-26 13:57:54 +00:00
'css!/customize/src/print.css' ,
2017-08-28 10:25:05 +00:00
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css' ,
2018-03-21 17:31:53 +00:00
'css!/bower_components/components-font-awesome/css/font-awesome.min.css' ,
2018-07-14 13:15:23 +00:00
'less!/pad/app-pad.less'
2020-05-05 09:19:32 +00:00
] , function (
2017-08-28 10:25:05 +00:00
$ ,
Hyperjson ,
2017-09-25 13:45:08 +00:00
Framework ,
2017-08-28 10:25:05 +00:00
Cursor ,
TypingTest ,
2017-09-25 13:45:08 +00:00
Messages ,
2017-08-28 10:25:05 +00:00
Links ,
2020-04-20 13:22:45 +00:00
Comments ,
2018-10-18 16:50:38 +00:00
Exporter ,
2018-12-10 10:57:39 +00:00
Cursors ,
2017-08-28 10:25:05 +00:00
nThen ,
2017-09-15 11:21:37 +00:00
MediaTag ,
2017-09-25 15:35:25 +00:00
ApiConfig ,
2017-10-02 12:06:01 +00:00
Hash ,
2017-11-06 16:18:10 +00:00
Util ,
2018-08-29 16:20:34 +00:00
UI ,
2020-08-06 11:56:50 +00:00
UIElements ,
2018-08-30 09:21:33 +00:00
h ,
2018-01-31 13:42:34 +00:00
ChainPad ,
2018-05-11 07:06:22 +00:00
AppConfig ,
Test
2020-05-05 09:19:32 +00:00
) {
2017-08-28 10:25:05 +00:00
var DiffDom = window . diffDOM ;
2020-05-05 09:19:32 +00:00
var slice = function ( coll ) {
2017-08-28 10:25:05 +00:00
return Array . prototype . slice . call ( coll ) ;
} ;
2020-05-05 09:19:32 +00:00
var removeListeners = function ( root ) {
slice ( root . attributes ) . map ( function ( attr ) {
2017-08-28 10:25:05 +00:00
if ( /^on/ . test ( attr . name ) ) {
root . attributes . removeNamedItem ( attr . name ) ;
}
} ) ;
slice ( root . children ) . forEach ( removeListeners ) ;
} ;
2020-05-05 09:19:32 +00:00
var hjsonToDom = function ( H ) {
2017-08-28 10:25:05 +00:00
var dom = Hyperjson . toDOM ( H ) ;
removeListeners ( dom ) ;
return dom ;
} ;
var module = window . REALTIME _MODULE = window . APP = {
Hyperjson : Hyperjson ,
logFights : true ,
fights : [ ] ,
Cursor : Cursor ,
} ;
2018-04-20 15:49:40 +00:00
// MEDIATAG: Filter elements to serialize
// * Remove the drag&drop and resizers from the hyperjson
2020-05-05 09:19:32 +00:00
var isWidget = function ( el ) {
return typeof ( el . getAttribute ) === "function" &&
( el . getAttribute ( 'data-cke-hidden-sel' ) ||
( el . getAttribute ( 'class' ) &&
( /cke_widget_drag/ . test ( el . getAttribute ( 'class' ) ) ||
/cke_image_resizer/ . test ( el . getAttribute ( 'class' ) ) )
)
) ;
2018-04-20 13:33:14 +00:00
} ;
2020-05-05 09:19:32 +00:00
var isNotMagicLine = function ( el ) {
2017-08-28 10:25:05 +00:00
return ! ( el && typeof ( el . getAttribute ) === 'function' &&
el . getAttribute ( 'class' ) &&
el . getAttribute ( 'class' ) . split ( ' ' ) . indexOf ( 'non-realtime' ) !== - 1 ) ;
} ;
2018-12-10 10:57:39 +00:00
var isCursor = Cursors . isCursor ;
2020-05-05 09:19:32 +00:00
var shouldSerialize = function ( el ) {
2018-12-10 10:57:39 +00:00
return isNotMagicLine ( el ) && ! isWidget ( el ) && ! isCursor ( el ) ;
2018-04-20 13:33:14 +00:00
} ;
2018-04-20 15:49:40 +00:00
// MEDIATAG: Filter attributes in the serialized elements
2020-05-05 09:19:32 +00:00
var widgetFilter = function ( hj ) {
2018-04-20 15:49:40 +00:00
// Send a widget ID == 0 to avoid a fight between browsers and
2018-04-20 13:33:14 +00:00
// prevent the container from having the "selected" class (blue border)
if ( hj [ 1 ] . class ) {
var split = hj [ 1 ] . class . split ( ' ' ) ;
if ( split . indexOf ( 'cke_widget_wrapper' ) !== - 1 &&
split . indexOf ( 'cke_widget_block' ) !== - 1 ) {
hj [ 1 ] . class = "cke_widget_wrapper cke_widget_block" ;
hj [ 1 ] [ 'data-cke-widget-id' ] = "0" ;
}
if ( split . indexOf ( 'cke_widget_wrapper' ) !== - 1 &&
split . indexOf ( 'cke_widget_inline' ) !== - 1 ) {
hj [ 1 ] . class = "cke_widget_wrapper cke_widget_inline" ;
2018-04-23 12:33:39 +00:00
delete hj [ 1 ] [ 'data-cke-widget-id' ] ;
//hj[1]['data-cke-widget-id'] = "0";
2018-04-20 13:33:14 +00:00
}
2018-04-20 15:49:40 +00:00
// Remove the title attribute of the drag&drop icons (translation conflicts)
2020-05-05 09:19:32 +00:00
if ( split . indexOf ( 'cke_widget_drag_handler' ) !== - 1 ||
2018-04-20 13:33:14 +00:00
split . indexOf ( 'cke_image_resizer' ) !== - 1 ) {
hj [ 1 ] . title = undefined ;
}
}
return hj ;
} ;
2020-05-05 09:19:32 +00:00
var hjsonFilters = function ( hj ) {
2017-09-25 15:35:25 +00:00
/* catch `type="_moz"` before it goes over the wire */
2020-05-05 09:19:32 +00:00
var brFilter = function ( hj ) {
2017-09-25 15:35:25 +00:00
if ( hj [ 1 ] . type === '_moz' ) { hj [ 1 ] . type = undefined ; }
return hj ;
} ;
2020-05-05 09:19:32 +00:00
var mediatagContentFilter = function ( hj ) {
2017-09-25 15:35:25 +00:00
if ( hj [ 0 ] === 'MEDIA-TAG' ) { hj [ 2 ] = [ ] ; }
return hj ;
} ;
2020-05-05 09:19:32 +00:00
var commentActiveFilter = function ( hj ) {
if ( hj [ 0 ] === 'COMMENT' ) { delete ( hj [ 1 ] || { } ) . class ; }
2020-04-23 14:19:58 +00:00
return hj ;
} ;
2017-09-15 11:21:37 +00:00
brFilter ( hj ) ;
mediatagContentFilter ( hj ) ;
2020-04-23 14:19:58 +00:00
commentActiveFilter ( hj ) ;
2018-04-20 13:33:14 +00:00
widgetFilter ( hj ) ;
2017-08-28 10:25:05 +00:00
return hj ;
} ;
2020-05-05 09:19:32 +00:00
var domFromHTML = function ( html ) {
2017-08-28 10:25:05 +00:00
return new DOMParser ( ) . parseFromString ( html , 'text/html' ) ;
} ;
var forbiddenTags = [
'SCRIPT' ,
2017-09-15 11:21:37 +00:00
//'IFRAME',
2017-08-28 10:25:05 +00:00
'OBJECT' ,
'APPLET' ,
2017-09-15 11:21:37 +00:00
//'VIDEO',
//'AUDIO'
2017-08-28 10:25:05 +00:00
] ;
var CKEDITOR _CHECK _INTERVAL = 100 ;
2020-05-05 09:19:32 +00:00
var ckEditorAvailable = function ( cb ) {
2017-08-28 10:25:05 +00:00
var intr ;
2020-05-05 09:19:32 +00:00
var check = function ( ) {
2017-08-28 10:25:05 +00:00
if ( window . CKEDITOR ) {
clearTimeout ( intr ) ;
cb ( window . CKEDITOR ) ;
}
} ;
2020-05-05 09:19:32 +00:00
intr = setInterval ( function ( ) {
2017-08-28 10:25:05 +00:00
console . log ( "Ckeditor was not defined. Trying again in %sms" , CKEDITOR _CHECK _INTERVAL ) ;
check ( ) ;
} , CKEDITOR _CHECK _INTERVAL ) ;
check ( ) ;
} ;
2021-02-05 18:10:03 +00:00
var mkSettingsMenu = function ( framework ) {
Messages . pad _settings _info = "Here you can change the defaults settings for this document. New users will use these settings until they change the values on their account." ;
Messages . pad _settings _width _small = "Page mode" ;
Messages . pad _settings _width _large = "Large mode" ;
Messages . pad _settings _outline = "Choose whether the Table of Contents should be visible or hidden by default for new users." ;
Messages . pad _settings _comments = "Choose whether the Comments should be visible or hidden by default for new users." ;
Messages . pad _settings _hide = "Hide" ;
Messages . pad _settings _show = "Show" ;
var getSettings = function ( ) {
var $d = $ ( h ( 'div.cp-pad-settings-dialog' ) ) ;
var common = framework . _ . sfCommon ;
var metadataMgr = common . getMetadataMgr ( ) ;
var md = Util . clone ( metadataMgr . getMetadata ( ) ) ;
var set = function ( key , val , spinner ) {
var md = Util . clone ( metadataMgr . getMetadata ( ) ) ;
if ( typeof ( val ) === "undefined" ) { delete md [ key ] ; }
else { md [ key ] = val ; }
metadataMgr . updateMetadata ( md ) ;
framework . localChange ( ) ;
framework . _ . cpNfInner . whenRealtimeSyncs ( spinner . done ) ;
} ;
// Pad width
var opt1 = UI . createRadio ( 'cp-pad-settings-width' , 'cp-pad-settings-width-small' ,
Messages . pad _settings _width _small , md . defaultWidth === 0 , {
input : { value : 0 } ,
label : { class : 'noTitle' }
} ) ;
var opt2 = UI . createRadio ( 'cp-pad-settings-width' , 'cp-pad-settings-width-large' ,
Messages . pad _settings _width _large , md . defaultWidth === 1 , {
input : { value : 1 } ,
label : { class : 'noTitle' }
} ) ;
var delWidth = h ( 'button.btn.btn-default.fa.fa-times' ) ;
var width = h ( 'div.cp-pad-settings-radio-container' , [
opt1 ,
opt2 ,
delWidth
] ) ;
var $width = $ ( width ) ;
var spinner = UI . makeSpinner ( $width ) ;
$ ( delWidth ) . click ( function ( ) {
spinner . spin ( ) ;
$width . find ( 'input[type="radio"]' ) . prop ( 'checked' , false ) ;
set ( 'defaultWidth' , undefined , spinner ) ;
} ) ;
$width . find ( 'input[type="radio"]' ) . on ( 'change' , function ( ) {
spinner . spin ( ) ;
var val = $ ( 'input:radio[name="cp-pad-settings-width"]:checked' ) . val ( ) ;
val = Number ( val ) || 0 ;
set ( 'defaultWidth' , val , spinner ) ;
} ) ;
// Outline
var opt3 = UI . createRadio ( 'cp-pad-settings-outline' , 'cp-pad-settings-outline-false' ,
Messages . pad _settings _hide , md . defaultOutline === 0 , {
input : { value : 0 } ,
label : { class : 'noTitle' }
} ) ;
var opt4 = UI . createRadio ( 'cp-pad-settings-outline' , 'cp-pad-settings-outline-true' ,
Messages . pad _settings _show , md . defaultOutline === 1 , {
input : { value : 1 } ,
label : { class : 'noTitle' }
} ) ;
var delOutline = h ( 'button.btn.btn-default.fa.fa-times' ) ;
var outline = h ( 'div.cp-pad-settings-radio-container' , [
opt3 ,
opt4 ,
delOutline
] ) ;
var $outline = $ ( outline ) ;
var spinner2 = UI . makeSpinner ( $outline ) ;
$ ( delOutline ) . click ( function ( ) {
spinner2 . spin ( ) ;
$outline . find ( 'input[type="radio"]' ) . prop ( 'checked' , false ) ;
set ( 'defaultOutline' , undefined , spinner2 ) ;
} ) ;
$outline . find ( 'input[type="radio"]' ) . on ( 'change' , function ( ) {
spinner2 . spin ( ) ;
var val = $ ( 'input:radio[name="cp-pad-settings-outline"]:checked' ) . val ( ) ;
val = Number ( val ) || 0 ;
set ( 'defaultOutline' , val , spinner2 ) ;
} ) ;
// Comments
var opt5 = UI . createRadio ( 'cp-pad-settings-comments' , 'cp-pad-settings-comments-false' ,
Messages . pad _settings _hide , md . defaultComments === 0 , {
input : { value : 0 } ,
label : { class : 'noTitle' }
} ) ;
var opt6 = UI . createRadio ( 'cp-pad-settings-comments' , 'cp-pad-settings-comments-true' ,
Messages . pad _settings _show , md . defaultComments === 1 , {
input : { value : 1 } ,
label : { class : 'noTitle' }
} ) ;
var delComments = h ( 'button.btn.btn-default.fa.fa-times' ) ;
var comments = h ( 'div.cp-pad-settings-radio-container' , [
opt5 ,
opt6 ,
delComments
] ) ;
var $comments = $ ( comments ) ;
var spinner3 = UI . makeSpinner ( $comments ) ;
$ ( delComments ) . click ( function ( ) {
spinner3 . spin ( ) ;
$comments . find ( 'input[type="radio"]' ) . prop ( 'checked' , false ) ;
set ( 'defaultComments' , undefined , spinner3 ) ;
} ) ;
$comments . find ( 'input[type="radio"]' ) . on ( 'change' , function ( ) {
spinner3 . spin ( ) ;
var val = $ ( 'input:radio[name="cp-pad-settings-comments"]:checked' ) . val ( ) ;
val = Number ( val ) || 0 ;
set ( 'defaultComments' , val , spinner3 ) ;
} ) ;
$d . append ( [
h ( 'p.cp-app-prop-content' , h ( 'em' , Messages . pad _settings _info ) ) ,
h ( 'label' , Messages . settings _padWidth ) ,
h ( 'p.cp-app-prop-content' , Messages . settings _padWidthHint ) ,
$width [ 0 ] ,
h ( 'label' , Messages . markdown _toc ) ,
h ( 'p.cp-app-prop-content' , Messages . pad _settings _outline ) ,
$outline [ 0 ] ,
h ( 'label' , Messages . poll _comment _list ) ,
h ( 'p.cp-app-prop-content' , Messages . pad _settings _comments ) ,
$comments [ 0 ] ,
] ) ;
return $d [ 0 ] ;
} ;
var $settingsButton = framework . _ . sfCommon . createButton ( '' , true , {
drawer : true ,
text : 'DOCUMENT SETTINGS' ,
name : 'pad_settings' ,
icon : 'fa-cog' ,
} , function ( ) {
UI . alert ( getSettings ( ) ) ;
} ) ;
framework . _ . toolbar . $drawer . append ( $settingsButton ) ;
} ;
2020-05-05 09:19:32 +00:00
var mkHelpMenu = function ( framework ) {
2018-02-27 16:38:29 +00:00
var $toolbarContainer = $ ( '.cke_toolbox_main' ) ;
2018-03-08 14:39:46 +00:00
var helpMenu = framework . _ . sfCommon . createHelpMenu ( [ 'text' , 'pad' ] ) ;
2018-02-27 16:38:29 +00:00
$toolbarContainer . before ( helpMenu . menu ) ;
2018-02-28 16:59:27 +00:00
framework . _ . toolbar . $drawer . append ( helpMenu . button ) ;
2018-02-27 16:38:29 +00:00
} ;
2020-05-05 09:19:32 +00:00
var mkDiffOptions = function ( cursor , readOnly ) {
2017-08-28 10:25:05 +00:00
return {
2020-05-05 09:19:32 +00:00
preDiffApply : function ( info ) {
2017-08-28 10:25:05 +00:00
/ *
Don 't accept attributes that begin with ' on '
these are probably listeners , and we don ' t want to
send scripts over the wire .
* /
if ( [ 'addAttribute' , 'modifyAttribute' ] . indexOf ( info . diff . action ) !== - 1 ) {
if ( info . diff . name === 'href' ) {
// console.log(info.diff);
//var href = info.diff.newValue;
// TODO normalize HTML entities
if ( /javascript *: */ . test ( info . diff . newValue ) ) {
// TODO remove javascript: links
}
}
if ( /^on/ . test ( info . diff . name ) ) {
console . log ( "Rejecting forbidden element attribute with name (%s)" , info . diff . name ) ;
return true ;
}
}
2018-04-19 15:45:08 +00:00
2018-12-10 10:57:39 +00:00
// Other users cursor
if ( Cursors . preDiffApply ( info ) ) {
return true ;
}
2018-04-19 15:45:08 +00:00
2018-12-13 12:22:19 +00:00
if ( info . node && info . node . tagName === 'DIV' &&
info . node . getAttribute ( 'class' ) &&
/cp-link-clicked/ . test ( info . node . getAttribute ( 'class' ) ) ) {
if ( info . diff . action === 'removeElement' ) {
return true ;
}
}
2018-04-20 15:49:40 +00:00
// MEDIATAG
// Never modify widget ids
2018-04-23 12:33:39 +00:00
if ( info . node && info . node . tagName === 'SPAN' && info . diff . name === 'data-cke-widget-id' ) {
2018-04-20 15:49:40 +00:00
return true ;
}
if ( info . node && info . node . tagName === 'SPAN' &&
info . node . getAttribute ( 'class' ) &&
/cke_widget_wrapper/ . test ( info . node . getAttribute ( 'class' ) ) ) {
if ( info . diff . action === 'modifyAttribute' && info . diff . name === 'class' ) {
2018-04-20 13:33:14 +00:00
return true ;
}
2018-04-20 15:49:40 +00:00
//console.log(info);
}
// CkEditor drag&drop icon container
if ( info . node && info . node . tagName === 'SPAN' &&
2020-05-05 09:19:32 +00:00
info . node . getAttribute ( 'class' ) &&
info . node . getAttribute ( 'class' ) . split ( ' ' ) . indexOf ( 'cke_widget_drag_handler_container' ) !== - 1 ) {
2018-04-20 15:49:40 +00:00
return true ;
}
// CkEditor drag&drop title (language fight)
if ( info . node && info . node . getAttribute &&
2020-05-05 09:19:32 +00:00
info . node . getAttribute ( 'class' ) &&
( info . node . getAttribute ( 'class' ) . split ( ' ' ) . indexOf ( 'cke_widget_drag_handler' ) !== - 1 ||
info . node . getAttribute ( 'class' ) . split ( ' ' ) . indexOf ( 'cke_image_resizer' ) !== - 1 ) ) {
2018-04-20 15:49:40 +00:00
return true ;
}
2018-04-19 15:45:08 +00:00
2017-08-28 10:25:05 +00:00
/ *
Also reject any elements which would insert any one of
our forbidden tag types : script , iframe , object ,
applet , video , or audio
* /
if ( [ 'addElement' , 'replaceElement' ] . indexOf ( info . diff . action ) !== - 1 ) {
if ( info . diff . element && forbiddenTags . indexOf ( info . diff . element . nodeName ) !== - 1 ) {
console . log ( "Rejecting forbidden tag of type (%s)" , info . diff . element . nodeName ) ;
return true ;
} else if ( info . diff . newValue && forbiddenTags . indexOf ( info . diff . newValue . nodeType ) !== - 1 ) {
console . log ( "Rejecting forbidden tag of type (%s)" , info . diff . newValue . nodeName ) ;
return true ;
}
}
2020-04-23 14:19:58 +00:00
// Don't remote the "active" class of our comments
if ( info . node && info . node . tagName === 'COMMENT' ) {
2020-05-05 09:19:32 +00:00
if ( info . diff . action === 'removeAttribute' && [ 'class' ] . indexOf ( info . diff . name ) !== - 1 ) {
2020-04-23 14:19:58 +00:00
return true ;
}
}
2017-08-28 10:25:05 +00:00
if ( info . node && info . node . tagName === 'BODY' ) {
2020-05-05 09:19:32 +00:00
if ( info . diff . action === 'removeAttribute' && [ 'class' , 'spellcheck' ] . indexOf ( info . diff . name ) !== - 1 ) {
2017-08-28 10:25:05 +00:00
return true ;
}
}
/ * D i f f D O M w i l l f i l t e r o u t m a g i c l i n e p l u g i n e l e m e n t s
in practice this will make it impossible to use it
while someone else is typing , which could be annoying .
we should check when such an element is going to be
removed , and prevent that from happening . * /
if ( info . node && info . node . tagName === 'SPAN' &&
info . node . getAttribute ( 'contentEditable' ) === "false" ) {
// it seems to be a magicline plugin element...
2018-04-20 15:49:40 +00:00
// but it can also be a widget (MEDIATAG), in which case the removal was
2018-04-20 13:33:14 +00:00
// probably intentional
2017-08-28 10:25:05 +00:00
if ( info . diff . action === 'removeElement' ) {
// and you're about to remove it...
2018-04-20 13:33:14 +00:00
if ( ! info . node . getAttribute ( 'class' ) ||
! /cke_widget_wrapper/ . test ( info . node . getAttribute ( 'class' ) ) ) {
// This element is not a widget!
// this probably isn't what you want
/ *
I have never seen this in the console , but the
magic line is still getting removed on remote
edits . This suggests that it ' s getting removed
by something other than diffDom .
* /
console . log ( "preventing removal of the magic line!" ) ;
// return true to prevent diff application
return true ;
}
2017-08-28 10:25:05 +00:00
}
}
2018-12-21 17:16:29 +00:00
// Do not change the spellcheck value in view mode
if ( readOnly && info . node && info . node . tagName === 'BODY' &&
info . diff . action === 'modifyAttribute' && info . diff . name === 'spellcheck' ) {
return true ;
}
2017-08-28 10:25:05 +00:00
// Do not change the contenteditable value in view mode
if ( readOnly && info . node && info . node . tagName === 'BODY' &&
info . diff . action === 'modifyAttribute' && info . diff . name === 'contenteditable' ) {
return true ;
}
2020-05-05 09:19:32 +00:00
/ *
cursor . update ( ) ;
2017-10-06 11:49:52 +00:00
2020-05-05 09:19:32 +00:00
// no use trying to recover the cursor if it doesn't exist
if ( ! cursor . exists ( ) ) { return ; }
2017-08-28 10:25:05 +00:00
2020-05-05 09:19:32 +00:00
/ * f r a m e i s e i t h e r 0 , 1 , 2 , o r 3 , d e p e n d i n g o n w h i c h
cursor frames were affected : none , first , last , or both
* /
/ *
var frame = info . frame = cursor . inNode ( info . node ) ;
2017-08-28 10:25:05 +00:00
2020-05-05 09:19:32 +00:00
if ( ! frame ) { return ; }
2017-08-28 10:25:05 +00:00
2020-05-05 09:19:32 +00:00
if ( frame && typeof info . diff . oldValue === 'string' && typeof info . diff . newValue === 'string' ) {
//var pushes = cursor.pushDelta(info.diff.oldValue, info.diff.newValue);
var ops = ChainPad . Diff . diff ( info . diff . oldValue , info . diff . newValue ) ;
2017-08-28 10:25:05 +00:00
2020-05-05 09:19:32 +00:00
if ( frame & 1 ) {
// push cursor start if necessary
cursor . transformRange ( cursor . Range . start , ops ) ;
}
if ( frame & 2 ) {
// push cursor end if necessary
cursor . transformRange ( cursor . Range . end , ops ) ;
}
}
* /
2017-08-28 10:25:05 +00:00
} ,
2020-05-05 09:19:32 +00:00
/ *
postDiffApply : function ( info ) {
if ( info . frame ) {
if ( info . node ) {
if ( info . frame & 1 ) { cursor . fixStart ( info . node ) ; }
if ( info . frame & 2 ) { cursor . fixEnd ( info . node ) ; }
} else { console . error ( "info.node did not exist" ) ; }
var sel = cursor . makeSelection ( ) ;
var range = cursor . makeRange ( ) ;
cursor . fixSelection ( sel , range ) ;
}
}
* /
2017-08-28 10:25:05 +00:00
} ;
} ;
////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
2020-05-05 09:19:32 +00:00
var addToolbarHideBtn = function ( framework , $bar ) {
2017-09-25 13:45:08 +00:00
// Expand / collapse the toolbar
2017-11-27 11:17:35 +00:00
var cfg = {
2020-04-27 13:13:51 +00:00
element : $bar
2017-09-25 13:45:08 +00:00
} ;
2020-05-05 09:19:32 +00:00
var onClick = function ( visible ) {
2017-11-27 11:17:35 +00:00
framework . _ . sfCommon . setAttribute ( [ 'pad' , 'showToolbar' ] , visible ) ;
} ;
2020-05-05 09:19:32 +00:00
framework . _ . sfCommon . getAttribute ( [ 'pad' , 'showToolbar' ] , function ( err , data ) {
2020-05-13 12:42:22 +00:00
var state = false ;
2020-05-05 09:19:32 +00:00
if ( ( $ ( window ) . height ( ) >= 800 || $ ( window ) . width ( ) >= 800 ) &&
2020-05-13 12:42:22 +00:00
( typeof ( data ) === "undefined" || data ) ) {
state = true ;
$ ( '.cke_toolbox_main' ) . show ( ) ;
} else {
$ ( '.cke_toolbox_main' ) . hide ( ) ;
}
2017-11-27 11:17:35 +00:00
var $collapse = framework . _ . sfCommon . createButton ( 'toggle' , true , cfg , onClick ) ;
2020-05-13 12:42:22 +00:00
framework . _ . toolbar . $bottomL . append ( $collapse ) ;
if ( state ) {
$collapse . addClass ( 'cp-toolbar-button-active' ) ;
}
2017-09-25 13:45:08 +00:00
} ) ;
} ;
2017-08-28 10:25:05 +00:00
2020-05-05 09:19:32 +00:00
var displayMediaTags = function ( framework , dom , mediaTagMap ) {
setTimeout ( function ( ) { // Just in case
2017-09-27 09:58:14 +00:00
var tags = dom . querySelectorAll ( 'media-tag:empty' ) ;
2020-05-05 09:19:32 +00:00
Array . prototype . slice . call ( tags ) . forEach ( function ( el ) {
2020-12-01 17:18:16 +00:00
var mediaObject = MediaTag ( el , {
body : dom
} ) ;
2020-05-05 09:19:32 +00:00
$ ( el ) . on ( 'keydown' , function ( e ) {
if ( [ 8 , 46 ] . indexOf ( e . which ) !== - 1 ) {
2017-09-27 09:58:14 +00:00
$ ( el ) . remove ( ) ;
framework . localChange ( ) ;
}
} ) ;
var observer = new MutationObserver ( function ( mutations ) {
mutations . forEach ( function ( mutation ) {
if ( mutation . type === 'childList' ) {
2020-12-01 17:18:16 +00:00
var list _values = slice ( el . children )
. map ( function ( el ) { return el . outerHTML ; } )
. join ( '' ) ;
2020-12-03 14:22:39 +00:00
mediaTagMap [ el . getAttribute ( 'src' ) ] = list _values ;
2020-11-24 15:38:31 +00:00
if ( mediaObject . complete ) { observer . disconnect ( ) ; }
2017-09-27 09:58:14 +00:00
}
} ) ;
} ) ;
observer . observe ( el , {
attributes : false ,
2020-12-01 17:18:16 +00:00
subtree : true ,
2017-09-27 09:58:14 +00:00
childList : true ,
characterData : false
} ) ;
} ) ;
} ) ;
} ;
2020-05-05 09:19:32 +00:00
var restoreMediaTags = function ( tempDom , mediaTagMap ) {
2017-09-27 09:58:14 +00:00
var tags = tempDom . querySelectorAll ( 'media-tag:empty' ) ;
2020-05-05 09:19:32 +00:00
Array . prototype . slice . call ( tags ) . forEach ( function ( tag ) {
2017-09-27 09:58:14 +00:00
var src = tag . getAttribute ( 'src' ) ;
if ( mediaTagMap [ src ] ) {
2020-12-07 15:39:58 +00:00
tag . innerHTML = mediaTagMap [ src ] ;
/ * m e d i a T a g M a p [ s r c ] . f o r E a c h ( f u n c t i o n ( n ) {
2020-11-24 15:38:31 +00:00
tag . appendChild ( n . cloneNode ( true ) ) ;
2020-12-07 15:39:58 +00:00
} ) ; * /
2017-09-27 09:58:14 +00:00
}
} ) ;
} ;
2020-06-23 11:34:49 +00:00
var mkPrintButton = function ( framework , editor ) {
2020-06-22 14:53:31 +00:00
var $printButton = framework . _ . sfCommon . createButton ( 'print' , true ) ;
$printButton . click ( function ( ) {
2020-10-26 13:57:54 +00:00
/ *
// NOTE: alternative print system in case we keep having more issues on Firefox
var $iframe = $ ( 'html' ) . find ( 'iframe' ) ;
var iframe = $iframe [ 0 ] . contentWindow ;
iframe . print ( ) ;
* /
2020-06-22 14:53:31 +00:00
editor . execCommand ( 'print' ) ;
framework . feedback ( 'PRINT_PAD' ) ;
} ) ;
framework . _ . toolbar . $drawer . append ( $printButton ) ;
} ;
2020-05-05 09:19:32 +00:00
var andThen2 = function ( editor , Ckeditor , framework ) {
2017-09-27 14:53:16 +00:00
var mediaTagMap = { } ;
2018-02-27 16:38:29 +00:00
var $contentContainer = $ ( '#cke_1_contents' ) ;
2020-04-21 14:07:04 +00:00
var $html = $ ( 'html' ) ;
2017-08-28 10:25:05 +00:00
var $faLink = $html . find ( 'head link[href*="/bower_components/components-font-awesome/css/font-awesome.min.css"]' ) ;
if ( $faLink . length ) {
$html . find ( 'iframe' ) . contents ( ) . find ( 'head' ) . append ( $faLink . clone ( ) ) ;
}
2020-04-20 13:22:16 +00:00
var ml = editor . _ . magiclineBackdoor . that . line . $ ;
2020-05-05 09:19:32 +00:00
[ ml , ml . parentElement ] . forEach ( function ( el ) {
2017-08-28 10:25:05 +00:00
el . setAttribute ( 'class' , 'non-realtime' ) ;
} ) ;
2020-04-20 13:22:16 +00:00
window . editor = editor ;
2018-12-13 12:22:19 +00:00
var $iframe = $ ( 'html' ) . find ( 'iframe' ) . contents ( ) ;
2017-08-28 10:25:05 +00:00
var ifrWindow = $html . find ( 'iframe' ) [ 0 ] . contentWindow ;
2019-01-09 15:16:44 +00:00
var customCss = '/customize/ckeditor-contents.css?' + window . CKEDITOR . CRYPTPAD _URLARGS ;
$iframe . find ( 'head' ) . append ( '<link href="' + customCss + '" type="text/css" rel="stylesheet" _fcktemp="true"/>' ) ;
2019-01-08 16:47:22 +00:00
2018-11-05 15:53:25 +00:00
framework . _ . sfCommon . addShortcuts ( ifrWindow ) ;
2020-06-22 14:53:31 +00:00
mkPrintButton ( framework , editor , Ckeditor ) ;
2017-08-28 10:25:05 +00:00
var documentBody = ifrWindow . document . body ;
2020-04-20 13:22:45 +00:00
var inner = window . inner = documentBody ;
var $inner = $ ( inner ) ;
2017-08-28 10:25:05 +00:00
2020-05-05 09:19:32 +00:00
var observer = new MutationObserver ( function ( muts ) {
muts . forEach ( function ( mut ) {
2018-10-29 10:47:57 +00:00
if ( mut . type === 'childList' ) {
var $a ;
for ( var i = 0 ; i < mut . addedNodes . length ; i ++ ) {
$a = $ ( mut . addedNodes [ i ] ) ;
2020-05-05 09:19:32 +00:00
if ( $a . is ( 'p' ) && $a . find ( '> span:empty' ) . length &&
$a . find ( '> br' ) . length && $a . children ( ) . length === 2 ) {
2018-10-29 10:47:57 +00:00
$a . find ( '> span' ) . append ( $a . find ( '> br' ) ) ;
}
}
}
} ) ;
} ) ;
observer . observe ( documentBody , {
childList : true
} ) ;
2020-04-20 13:22:45 +00:00
var metadataMgr = framework . _ . sfCommon . getMetadataMgr ( ) ;
var privateData = metadataMgr . getPrivateData ( ) ;
var common = framework . _ . sfCommon ;
var comments = Comments . create ( {
framework : framework ,
metadataMgr : metadataMgr ,
common : common ,
2020-04-21 14:07:04 +00:00
editor : editor ,
2020-04-22 14:23:45 +00:00
ifrWindow : ifrWindow ,
2020-04-24 15:53:33 +00:00
$iframe : $iframe ,
2020-04-22 14:23:45 +00:00
$inner : $inner ,
2020-04-24 15:53:33 +00:00
$contentContainer : $contentContainer ,
2020-04-22 14:23:45 +00:00
$container : $ ( '#cp-app-pad-comments' )
2020-04-20 13:22:45 +00:00
} ) ;
2021-02-05 18:10:03 +00:00
var $resize = $ ( '#cp-app-pad-resize' ) ;
2020-08-06 10:27:25 +00:00
var $toc = $ ( '#cp-app-pad-toc' ) ;
2021-02-05 18:10:03 +00:00
$toc . show ( ) ;
2020-08-06 10:27:25 +00:00
2018-12-10 10:57:39 +00:00
// My cursor
2017-08-28 10:25:05 +00:00
var cursor = module . cursor = Cursor ( inner ) ;
2018-12-10 10:57:39 +00:00
// Display other users cursor
var cursors = Cursors . create ( inner , hjsonToDom , cursor ) ;
2020-05-05 09:19:32 +00:00
var openLink = function ( e ) {
2017-08-28 10:25:05 +00:00
var el = e . currentTarget ;
if ( ! el || el . nodeName !== 'A' ) { return ; }
var href = el . getAttribute ( 'href' ) ;
2017-11-27 13:44:44 +00:00
if ( href ) {
framework . _ . sfCommon . openUnsafeURL ( href ) ;
}
2017-08-28 10:25:05 +00:00
} ;
2020-05-25 21:12:12 +00:00
if ( ! privateData . isEmbed ) {
mkHelpMenu ( framework ) ;
}
2021-02-05 18:10:03 +00:00
mkSettingsMenu ( framework ) ;
2018-02-27 16:38:29 +00:00
2020-05-05 09:19:32 +00:00
framework . _ . sfCommon . getAttribute ( [ 'pad' , 'width' ] , function ( err , data ) {
2020-04-27 11:01:42 +00:00
var active = data || typeof ( data ) === "undefined" ;
if ( active ) {
$contentContainer . addClass ( 'cke_body_width' ) ;
} else {
editor . execCommand ( 'pagemode' ) ;
}
} ) ;
2020-05-05 09:19:32 +00:00
framework . onEditableChange ( function ( unlocked ) {
2017-10-13 10:40:51 +00:00
if ( ! framework . isReadOnly ( ) ) {
2018-12-13 12:22:19 +00:00
$inner . attr ( 'contenteditable' , '' + Boolean ( unlocked ) ) ;
2017-10-13 10:40:51 +00:00
}
2018-12-13 12:22:19 +00:00
$inner . css ( { background : unlocked ? '#fff' : '#eee' } ) ;
2017-10-13 10:40:51 +00:00
} ) ;
2020-05-05 09:19:32 +00:00
framework . setMediaTagEmbedder ( function ( $mt ) {
2017-10-03 14:57:57 +00:00
$mt . attr ( 'contenteditable' , 'false' ) ;
2018-04-19 15:45:08 +00:00
//$mt.attr('tabindex', '1');
2018-04-23 13:38:21 +00:00
//MEDIATAG
2018-04-23 12:33:39 +00:00
var element = new window . CKEDITOR . dom . element ( $mt [ 0 ] ) ;
editor . insertElement ( element ) ;
2020-05-05 09:19:32 +00:00
editor . widgets . initOn ( element , 'mediatag' ) ;
2017-10-03 14:57:57 +00:00
} ) ;
2017-08-28 10:25:05 +00:00
2020-05-05 09:19:32 +00:00
framework . setTitleRecommender ( function ( ) {
2017-08-28 10:25:05 +00:00
var text ;
2020-05-05 09:19:32 +00:00
if ( [ 'h1' , 'h2' , 'h3' ] . some ( function ( t ) {
var $header = $inner . find ( t + ':first-of-type' ) ;
if ( $header . length && $header . text ( ) ) {
text = $header . text ( ) ;
return true ;
}
} ) ) { return text ; }
2017-09-25 13:45:08 +00:00
} ) ;
2017-08-28 10:25:05 +00:00
2017-09-25 13:45:08 +00:00
var DD = new DiffDom ( mkDiffOptions ( cursor , framework . isReadOnly ( ) ) ) ;
2017-08-28 10:25:05 +00:00
2018-12-10 10:57:39 +00:00
var cursorStopped = false ;
2018-12-14 15:40:15 +00:00
var cursorTo ;
2020-05-05 09:19:32 +00:00
var updateCursor = function ( ) {
2018-12-14 15:40:15 +00:00
if ( cursorTo ) { clearTimeout ( cursorTo ) ; }
// If we're receiving content
if ( cursorStopped ) { return void setTimeout ( updateCursor , 100 ) ; }
2020-05-05 09:19:32 +00:00
cursorTo = setTimeout ( function ( ) {
2018-12-14 15:40:15 +00:00
framework . updateCursor ( ) ;
} , 500 ) ; // 500ms to make sure it is sent after chainpad sync
2018-12-10 10:57:39 +00:00
} ;
2020-11-05 09:16:46 +00:00
var isAnchor = function ( el ) { return el . nodeName === 'A' ; } ;
var getAnchorName = function ( el ) {
return el . getAttribute ( 'id' ) ||
el . getAttribute ( 'data-cke-saved-name' ) ||
el . getAttribute ( 'name' ) ||
Util . stripTags ( $ ( el ) . text ( ) ) ;
} ;
2021-02-05 18:10:03 +00:00
var updatePageMode = function ( ) {
var md = Util . clone ( metadataMgr . getMetadata ( ) ) ;
var store = window . cryptpadStore ;
var key = 'pad-small-width' ;
var hideBtn = h ( 'button.btn.btn-default.cp-pad-hide.fa.fa-compress' ) ;
var showBtn = h ( 'button.btn.btn-default.cp-pad-show.fa.fa-expand' ) ;
var localHide ;
$ ( hideBtn ) . click ( function ( ) { // Expand
$contentContainer . addClass ( 'cke_body_width' ) ;
$resize . addClass ( 'hidden' ) ;
localHide = true ;
if ( store ) { store . put ( key , '1' ) ; }
} ) ;
$ ( showBtn ) . click ( function ( ) {
$contentContainer . removeClass ( 'cke_body_width' ) ;
$resize . removeClass ( 'hidden' ) ;
localHide = false ;
if ( store ) { store . put ( key , '0' ) ; }
} ) ;
var content = [
hideBtn ,
showBtn ,
] ;
$resize . html ( '' ) . append ( content ) ;
// Hidden or visible? check pad settings first, then browser otherwise hide
var hide = false ;
if ( typeof ( md . defaultWidth ) === "undefined" ) {
if ( typeof ( store . store [ key ] ) === 'undefined' ) {
hide = true ;
} else {
hide = store . store [ key ] === '1' ;
}
} else {
hide = md . defaultWidth === 0 ;
}
// If we've clicking on the show/hide buttons, always use our last value
if ( typeof ( localHide ) === "boolean" ) { hide = localHide ; }
$contentContainer . removeClass ( 'cke_body_width' ) ;
$resize . removeClass ( 'hidden' ) ;
if ( hide ) {
$resize . addClass ( 'hidden' ) ;
$contentContainer . addClass ( 'cke_body_width' ) ;
}
} ;
updatePageMode ( ) ;
2020-08-06 13:42:43 +00:00
var updateTOC = Util . throttle ( function ( ) {
2021-02-05 18:10:03 +00:00
var md = Util . clone ( metadataMgr . getMetadata ( ) ) ;
2020-08-06 10:27:25 +00:00
var toc = [ ] ;
2020-11-05 09:16:46 +00:00
$inner . find ( 'h1, h2, h3, a[id][data-cke-saved-name]' ) . each ( function ( i , el ) {
if ( isAnchor ( el ) ) {
return void toc . push ( {
level : 2 ,
el : el ,
title : getAnchorName ( el ) ,
} ) ;
}
2020-08-06 10:27:25 +00:00
toc . push ( {
level : Number ( el . tagName . slice ( 1 ) ) ,
el : el ,
title : Util . stripTags ( $ ( el ) . text ( ) )
} ) ;
} ) ;
2021-02-01 17:10:02 +00:00
var hideBtn = h ( 'button.btn.btn-default.cp-pad-hide.fa.fa-chevron-left' ) ;
var showBtn = h ( 'button.btn.btn-default.cp-pad-show' , {
title : Messages . pad _tocHide
} , [
h ( 'i.fa.fa-list-ul' )
] ) ;
var content = [
hideBtn ,
showBtn ,
h ( 'h2' , Messages . markdown _toc )
] ;
var store = window . cryptpadStore ;
var key = 'hide-pad-toc' ;
2021-02-05 18:10:03 +00:00
// Hidden or visible? check pad settings first, then browser otherwise hide
var hide = false ;
var localHide ;
if ( typeof ( md . defaultOutline ) === "undefined" ) {
if ( typeof ( store . store [ key ] ) === 'undefined' ) {
hide = true ;
} else {
hide = store . store [ key ] === '1' ;
}
} else {
hide = md . defaultOutline === 0 ;
2021-02-01 17:10:02 +00:00
}
2021-02-05 18:10:03 +00:00
// If we've clicking on the show/hide buttons, always use our last value
if ( typeof ( localHide ) === "boolean" ) { hide = localHide ; }
$toc . removeClass ( 'hidden' ) ;
if ( hide ) { $toc . addClass ( 'hidden' ) ; }
2021-02-01 17:10:02 +00:00
$ ( hideBtn ) . click ( function ( ) {
$toc . addClass ( 'hidden' ) ;
2021-02-05 18:10:03 +00:00
localHide = true ;
2021-02-01 17:10:02 +00:00
if ( store ) { store . put ( key , '1' ) ; }
} ) ;
$ ( showBtn ) . click ( function ( ) {
$toc . removeClass ( 'hidden' ) ;
2021-02-05 18:10:03 +00:00
localHide = false ;
2021-02-01 17:10:02 +00:00
if ( store ) { store . put ( key , '0' ) ; }
} ) ;
2020-08-06 10:27:25 +00:00
toc . forEach ( function ( obj ) {
2020-11-05 09:16:46 +00:00
var title = ( obj . title || "" ) . trim ( ) ;
if ( ! title ) { return ; }
2020-08-06 10:27:25 +00:00
// Only include level 2 headings
var level = obj . level ;
2020-08-06 11:35:24 +00:00
var a = h ( 'a.cp-pad-toc-link' , {
2020-08-06 10:27:25 +00:00
href : '#' ,
} ) ;
$ ( a ) . click ( function ( e ) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
2020-08-06 11:56:50 +00:00
if ( ! obj . el || UIElements . isVisible ( obj . el , $inner ) ) { return ; }
2020-08-06 11:35:24 +00:00
obj . el . scrollIntoView ( ) ;
2020-08-06 10:27:25 +00:00
} ) ;
2020-11-05 09:16:46 +00:00
a . innerHTML = title ;
2020-08-06 11:42:12 +00:00
content . push ( h ( 'p.cp-pad-toc-' + level , a ) ) ;
2020-08-06 10:27:25 +00:00
} ) ;
$toc . html ( '' ) . append ( content ) ;
2020-08-14 16:30:36 +00:00
} , 400 ) ;
2020-08-06 10:27:25 +00:00
2017-08-28 10:25:05 +00:00
// apply patches, and try not to lose the cursor in the process!
2020-05-05 09:19:32 +00:00
framework . onContentUpdate ( function ( hjson ) {
2017-10-02 12:06:01 +00:00
if ( ! Array . isArray ( hjson ) ) { throw new Error ( Messages . typeError ) ; }
2017-09-25 13:45:08 +00:00
var userDocStateDom = hjsonToDom ( hjson ) ;
2018-12-10 10:57:39 +00:00
cursorStopped = true ;
2017-09-25 13:45:08 +00:00
userDocStateDom . setAttribute ( "contenteditable" ,
inner . getAttribute ( 'contenteditable' ) ) ;
2017-08-28 10:25:05 +00:00
2017-09-27 09:58:14 +00:00
restoreMediaTags ( userDocStateDom , mediaTagMap ) ;
2017-10-09 11:13:11 +00:00
2020-03-30 14:41:47 +00:00
cursors . removeCursors ( inner ) ;
2017-10-09 11:13:11 +00:00
// Deal with adjasent text nodes
userDocStateDom . normalize ( ) ;
inner . normalize ( ) ;
2020-05-05 09:19:32 +00:00
$ ( userDocStateDom ) . find ( 'span[data-cke-display-name="media-tag"]:empty' ) . each ( function ( i , el ) {
2018-10-17 12:33:16 +00:00
$ ( el ) . remove ( ) ;
} ) ;
2018-11-26 15:23:16 +00:00
// Get cursor position
cursor . offsetUpdate ( ) ;
var oldText = inner . outerHTML ;
2021-01-07 15:21:05 +00:00
// Get scroll position
var sTop = $iframe . scrollTop ( ) ;
var sTopMax = $iframe . innerHeight ( ) - $ ( 'iframe' ) . innerHeight ( ) ;
2021-01-11 16:39:50 +00:00
var scrollMax = Math . abs ( sTop - sTopMax ) < 1 ;
2021-01-07 15:21:05 +00:00
2018-11-26 15:23:16 +00:00
// Apply the changes
2017-08-28 10:25:05 +00:00
var patch = ( DD ) . diff ( inner , userDocStateDom ) ;
( DD ) . apply ( inner , patch ) ;
2018-04-20 15:49:40 +00:00
2019-11-22 14:50:32 +00:00
editor . fire ( 'cp-wc' ) ; // Update word count
2018-11-26 15:23:16 +00:00
// Restore cursor position
var newText = inner . outerHTML ;
var ops = ChainPad . Diff . diff ( oldText , newText ) ;
cursor . restoreOffset ( ops ) ;
2020-05-05 09:19:32 +00:00
setTimeout ( function ( ) {
2018-12-14 15:40:15 +00:00
cursorStopped = false ;
updateCursor ( ) ;
} , 200 ) ;
2018-12-10 10:57:39 +00:00
2018-04-20 15:49:40 +00:00
// MEDIATAG: Migrate old mediatags to the widget system
2020-05-05 09:19:32 +00:00
$inner . find ( 'media-tag:not(.cke_widget_element)' ) . each ( function ( i , el ) {
2018-04-20 15:49:40 +00:00
var element = new window . CKEDITOR . dom . element ( el ) ;
2020-05-05 09:19:32 +00:00
editor . widgets . initOn ( element , 'mediatag' ) ;
2018-04-20 15:49:40 +00:00
} ) ;
2017-09-27 09:58:14 +00:00
displayMediaTags ( framework , inner , mediaTagMap ) ;
2018-04-20 15:49:40 +00:00
// MEDIATAG: Initialize mediatag widgets inserted in the document by other users
2018-04-19 15:45:08 +00:00
editor . widgets . checkWidgets ( ) ;
2018-04-20 15:49:40 +00:00
2017-09-25 13:45:08 +00:00
if ( framework . isReadOnly ( ) ) {
2018-12-13 12:22:19 +00:00
var $links = $inner . find ( 'a' ) ;
2017-08-28 10:25:05 +00:00
// off so that we don't end up with multiple identical handlers
$links . off ( 'click' , openLink ) . on ( 'click' , openLink ) ;
}
2020-04-20 13:22:45 +00:00
2020-04-22 14:23:45 +00:00
comments . onContentUpdate ( ) ;
2020-08-06 10:27:25 +00:00
updateTOC ( ) ;
2021-01-07 15:21:05 +00:00
if ( scrollMax ) {
$iframe . scrollTop ( $iframe . innerHeight ( ) ) ;
}
2017-09-25 13:45:08 +00:00
} ) ;
2017-08-28 10:25:05 +00:00
2020-05-05 09:19:32 +00:00
framework . setTextContentGetter ( function ( ) {
2018-02-01 09:09:08 +00:00
var innerCopy = inner . cloneNode ( true ) ;
displayMediaTags ( framework , innerCopy , mediaTagMap ) ;
innerCopy . normalize ( ) ;
2020-05-05 09:19:32 +00:00
$ ( innerCopy ) . find ( '*' ) . each ( function ( i , el ) {
2018-02-01 09:09:08 +00:00
$ ( el ) . append ( ' ' ) ;
} ) ;
var str = $ ( innerCopy ) . text ( ) ;
str = str . replace ( /\s\s+/g , ' ' ) ;
return str ;
} ) ;
2020-05-05 09:19:32 +00:00
framework . setContentGetter ( function ( ) {
$inner . find ( 'span[data-cke-display-name="media-tag"]:empty' ) . each ( function ( i , el ) {
2018-10-17 12:33:16 +00:00
$ ( el ) . remove ( ) ;
} ) ;
2018-12-10 10:57:39 +00:00
// We have to remove the cursors before getting the content because they split
// the text nodes and OT/ChainPad would freak out
2020-02-14 17:11:22 +00:00
cursors . removeCursors ( inner ) ;
2018-12-10 10:57:39 +00:00
2020-04-22 14:23:45 +00:00
comments . onContentUpdate ( ) ;
2017-09-27 09:58:14 +00:00
displayMediaTags ( framework , inner , mediaTagMap ) ;
2017-10-12 12:42:09 +00:00
inner . normalize ( ) ;
2018-12-10 10:57:39 +00:00
var hjson = Hyperjson . fromDOM ( inner , shouldSerialize , hjsonFilters ) ;
return hjson ;
2017-09-25 13:45:08 +00:00
} ) ;
2017-08-28 10:25:05 +00:00
2017-09-25 13:45:08 +00:00
if ( ! framework . isReadOnly ( ) ) {
2020-04-27 13:13:51 +00:00
addToolbarHideBtn ( framework , $ ( '.cke_toolbox_main' ) ) ;
2017-09-25 13:45:08 +00:00
} else {
$ ( '.cke_toolbox_main' ) . hide ( ) ;
}
2017-08-28 10:25:05 +00:00
2020-05-05 09:19:32 +00:00
framework . onReady ( function ( newPad ) {
2018-02-28 12:16:30 +00:00
editor . focus ( ) ;
2017-08-28 10:25:05 +00:00
if ( ! module . isMaximized ) {
module . isMaximized = true ;
$ ( 'iframe.cke_wysiwyg_frame' ) . css ( 'width' , '' ) ;
$ ( 'iframe.cke_wysiwyg_frame' ) . css ( 'height' , '' ) ;
}
$ ( 'body' ) . addClass ( 'app-pad' ) ;
if ( newPad ) {
cursor . setToEnd ( ) ;
2017-09-25 13:45:08 +00:00
} else if ( framework . isReadOnly ( ) ) {
2017-08-28 10:25:05 +00:00
cursor . setToStart ( ) ;
}
2017-09-27 09:58:14 +00:00
2017-12-20 11:15:43 +00:00
if ( framework . isReadOnly ( ) ) {
2018-12-13 12:22:19 +00:00
$inner . attr ( 'contenteditable' , 'false' ) ;
2017-12-20 11:15:43 +00:00
}
2017-09-18 08:59:40 +00:00
var fmConfig = {
ckeditor : editor ,
2020-07-09 10:40:31 +00:00
dropArea : $inner ,
2017-09-18 08:59:40 +00:00
body : $ ( 'body' ) ,
2020-05-05 09:19:32 +00:00
onUploaded : function ( ev , data ) {
2017-10-02 12:06:01 +00:00
var parsed = Hash . parsePadUrl ( data . url ) ;
2018-05-25 16:00:10 +00:00
var secret = Hash . getSecrets ( 'file' , parsed . hash , data . password ) ;
2019-08-27 13:31:22 +00:00
var fileHost = privateData . fileHost || privateData . origin ;
var src = fileHost + Hash . getBlobPathFromHex ( secret . channel ) ;
2018-05-25 16:00:10 +00:00
var key = Hash . encodeBase64 ( secret . keys . cryptKey ) ;
var mt = '<media-tag contenteditable="false" src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>' ;
2018-04-23 13:38:21 +00:00
// MEDIATAG
2018-04-23 12:33:39 +00:00
var element = window . CKEDITOR . dom . element . createFromHtml ( mt ) ;
2018-08-29 16:20:34 +00:00
if ( ev && ev . insertElement ) {
ev . insertElement ( element ) ;
} else {
editor . insertElement ( element ) ;
}
2020-05-05 09:19:32 +00:00
editor . widgets . initOn ( element , 'mediatag' ) ;
2017-09-18 08:59:40 +00:00
}
} ;
2020-07-09 10:40:31 +00:00
var FM = window . APP . FM = framework . _ . sfCommon . createFileManager ( fmConfig ) ;
editor . on ( 'paste' , function ( ev ) {
try {
var files = ev . data . dataTransfer . _ . files ;
files . forEach ( function ( f ) {
FM . handleFile ( f ) ;
} ) ;
// If the paste data contains files, don't use the ckeditor default handlers
// ==> they would try to include either a remote image URL or a base64 image
if ( files . length ) {
ev . cancel ( ) ;
ev . preventDefault ( ) ;
}
} catch ( e ) {
console . error ( e ) ;
}
} ) ;
2017-12-22 17:01:15 +00:00
2020-05-05 09:19:32 +00:00
framework . _ . sfCommon . getAttribute ( [ 'pad' , 'spellcheck' ] , function ( err , data ) {
2018-12-21 17:16:29 +00:00
if ( framework . isReadOnly ( ) ) { return ; }
if ( data ) {
$iframe . find ( 'body' ) . attr ( 'spellcheck' , true ) ;
}
} ) ;
2020-04-20 09:56:12 +00:00
2020-05-05 09:19:32 +00:00
framework . _ . sfCommon . isPadStored ( function ( err , val ) {
2020-02-05 12:33:32 +00:00
if ( ! val ) { return ; }
2018-12-13 12:22:19 +00:00
var b64images = $inner . find ( 'img[src^="data:image"]:not(.cke_reset)' ) ;
2020-02-05 12:33:32 +00:00
if ( b64images . length && framework . _ . sfCommon . isLoggedIn ( ) ) {
2018-08-30 09:21:33 +00:00
var no = h ( 'button.cp-corner-cancel' , Messages . cancel ) ;
var yes = h ( 'button.cp-corner-primary' , Messages . ok ) ;
2020-02-03 14:14:52 +00:00
var actions = h ( 'div' , [ no , yes ] ) ;
2020-05-05 09:19:32 +00:00
var modal = UI . cornerPopup ( Messages . pad _base64 , actions , '' , { big : true } ) ;
$ ( no ) . click ( function ( ) {
2018-08-30 09:21:33 +00:00
modal . delete ( ) ;
2018-08-29 16:20:34 +00:00
} ) ;
2020-05-05 09:19:32 +00:00
$ ( yes ) . click ( function ( ) {
2018-08-30 09:21:33 +00:00
modal . delete ( ) ;
2020-05-05 09:19:32 +00:00
b64images . each ( function ( i , el ) {
2018-08-30 09:21:33 +00:00
var src = $ ( el ) . attr ( 'src' ) ;
var blob = Util . dataURIToBlob ( src ) ;
var ext = '.' + ( blob . type . split ( '/' ) [ 1 ] || 'png' ) ;
2020-05-05 09:19:32 +00:00
var name = ( framework . _ . title . getTitle ( ) || 'Pad' ) + '_image' ;
2018-08-30 09:21:33 +00:00
blob . name = name + ext ;
var ev = {
2020-05-05 09:19:32 +00:00
insertElement : function ( newEl ) {
2018-08-30 09:21:33 +00:00
var element = new window . CKEDITOR . dom . element ( el ) ;
newEl . replace ( element ) ;
setTimeout ( framework . localChange ) ;
}
} ;
window . APP . FM . handleFile ( blob , ev ) ;
} ) ;
} ) ;
}
} ) ;
2020-04-22 14:23:45 +00:00
2021-02-05 18:10:03 +00:00
updateTOC ( ) ;
updatePageMode ( ) ;
2020-04-22 14:23:45 +00:00
comments . ready ( ) ;
2018-02-28 12:16:30 +00:00
/ * s e t T i m e o u t ( f u n c t i o n ( ) {
$ ( 'iframe.cke_wysiwyg_frame' ) . focus ( ) ;
editor . focus ( ) ;
console . log ( editor ) ;
console . log ( editor . focusManager ) ;
$ ( window ) . trigger ( 'resize' ) ;
} ) ; * /
2017-09-25 13:45:08 +00:00
} ) ;
2017-08-28 10:25:05 +00:00
2020-05-05 09:19:32 +00:00
framework . onDefaultContentNeeded ( function ( ) {
2018-02-28 12:16:30 +00:00
inner . innerHTML = '<p></p>' ;
} ) ;
2017-08-28 10:25:05 +00:00
2020-05-05 09:19:32 +00:00
var importMediaTags = function ( dom , cb ) {
2017-10-20 16:10:08 +00:00
var $dom = $ ( dom ) ;
2020-05-05 09:19:32 +00:00
$dom . find ( 'media-tag' ) . each ( function ( i , el ) {
2017-10-20 16:10:08 +00:00
$ ( el ) . empty ( ) ;
} ) ;
cb ( $dom [ 0 ] ) ;
} ;
2020-05-05 09:19:32 +00:00
framework . setFileImporter ( { accept : 'text/html' } , function ( content , f , cb ) {
importMediaTags ( domFromHTML ( content ) . body , function ( dom ) {
2017-10-20 16:10:08 +00:00
cb ( Hyperjson . fromDOM ( dom ) ) ;
} ) ;
} , true ) ;
2020-05-26 10:11:51 +00:00
framework . setFileExporter ( Exporter . exts , function ( cb , ext ) {
Exporter . main ( inner , cb , ext ) ;
2017-10-20 16:10:08 +00:00
} , true ) ;
2017-09-11 13:46:21 +00:00
2020-05-05 09:19:32 +00:00
framework . setNormalizer ( function ( hjson ) {
2017-09-25 13:45:08 +00:00
return [
'BODY' ,
{
"class" : "cke_editable cke_editable_themed cke_contents_ltr cke_show_borders" ,
"contenteditable" : "true" ,
2020-05-05 09:19:32 +00:00
"spellcheck" : "false"
2017-09-25 13:45:08 +00:00
} ,
hjson [ 2 ]
] ;
} ) ;
2017-08-28 10:25:05 +00:00
2018-12-10 10:57:39 +00:00
/* Display the cursor of other users and send our cursor */
2020-03-30 14:41:47 +00:00
framework . setCursorGetter ( cursors . cursorGetter ) ;
framework . onCursorUpdate ( cursors . onCursorUpdate ) ;
2018-12-14 15:40:15 +00:00
inner . addEventListener ( 'click' , updateCursor ) ;
inner . addEventListener ( 'keyup' , updateCursor ) ;
2018-12-10 10:57:39 +00:00
2018-11-22 13:45:12 +00:00
2017-08-28 10:25:05 +00:00
/ * h i t t i n g e n t e r m a k e s a n e w l i n e , b u t p l a c e s t h e c u r s o r i n s i d e
of the < br > instead of the < p > . This makes it such that you
cannot type until you click , which is rather unnacceptable .
If the cursor is ever inside such a < br > , you probably want
to push it out to the parent element , which ought to be a
paragraph tag . This needs to be done on keydown , otherwise
the first such keypress will not be inserted into the P . * /
inner . addEventListener ( 'keydown' , cursor . brFix ) ;
2018-11-22 13:44:46 +00:00
/ *
CkEditor emits a change event when it detects new content in the editable area .
Our problem is that this event is sent asynchronously and late after a keystroke .
The result is that between the keystroke and the change event , chainpad may
receive remote changes and so it can wipe the newly inserted content ( because
chainpad work synchronously ) , and the merged text is missing a few characters .
To fix this , we have to call ` framework.localChange ` sooner . We can ' t listen for
the "keypress" event because it is trigger before the character is inserted .
The solution is the "input" event , triggered by the browser as soon as the
character is inserted .
* /
2020-05-05 09:19:32 +00:00
inner . addEventListener ( 'input' , function ( ) {
2018-12-14 15:40:15 +00:00
framework . localChange ( ) ;
updateCursor ( ) ;
2019-11-22 14:50:32 +00:00
editor . fire ( 'cp-wc' ) ; // Update word count
2020-08-06 13:42:43 +00:00
updateTOC ( ) ;
2020-08-06 10:27:25 +00:00
} ) ;
editor . on ( 'change' , function ( ) {
framework . localChange ( ) ;
2020-08-06 13:42:43 +00:00
updateTOC ( ) ;
2018-12-14 15:40:15 +00:00
} ) ;
2017-08-28 10:25:05 +00:00
2019-11-22 14:50:32 +00:00
var wordCount = h ( 'span.cp-app-pad-wordCount' ) ;
$ ( '.cke_toolbox_main' ) . append ( wordCount ) ;
2020-05-05 09:19:32 +00:00
editor . on ( 'cp-wc-update' , function ( ) {
if ( ! editor . wordCount || typeof ( editor . wordCount . wordCount ) === "undefined" ) {
2019-11-22 14:50:32 +00:00
wordCount . innerText = '' ;
return ;
}
wordCount . innerText = Messages . _getKey ( 'pad_wordCount' , [ editor . wordCount . wordCount ] ) ;
} ) ;
2017-08-28 10:25:05 +00:00
// export the typing tests to the window.
// call like `test = easyTest()`
// terminate the test like `test.cancel()`
2020-05-05 09:19:32 +00:00
window . easyTest = function ( ) {
2017-08-28 10:25:05 +00:00
cursor . update ( ) ;
2018-11-16 10:28:05 +00:00
//var start = cursor.Range.start;
2018-11-08 09:46:07 +00:00
//var test = TypingTest.testInput(inner, start.el, start.offset, framework.localChange);
2018-11-22 13:45:12 +00:00
var test = TypingTest . testPad ( editor , framework . localChange ) ;
2017-09-25 13:45:08 +00:00
framework . localChange ( ) ;
2017-08-28 10:25:05 +00:00
return test ;
} ;
2018-11-15 17:24:01 +00:00
// Fix the scrollbar if it's reset when clicking on a button (firefox only?)
2018-11-16 10:28:05 +00:00
var buttonScrollTop ;
2020-05-05 09:19:32 +00:00
$ ( '.cke_toolbox_main' ) . find ( '.cke_button, .cke_combo_button' ) . mousedown ( function ( ) {
2018-11-15 17:24:01 +00:00
buttonScrollTop = $ ( 'iframe' ) . contents ( ) . scrollTop ( ) ;
2020-05-05 09:19:32 +00:00
setTimeout ( function ( ) {
2018-11-15 17:24:01 +00:00
$ ( 'iframe' ) . contents ( ) . scrollTop ( buttonScrollTop ) ;
} ) ;
} ) ;
2020-05-05 09:19:32 +00:00
$ ( '.cke_toolbox_main' ) . find ( '.cke_button' ) . click ( function ( ) {
2017-08-28 10:25:05 +00:00
var e = this ;
var classString = e . getAttribute ( 'class' ) ;
2020-05-05 09:19:32 +00:00
var classes = classString . split ( ' ' ) . filter ( function ( c ) {
2017-08-28 10:25:05 +00:00
return /cke_button__/ . test ( c ) ;
} ) ;
var id = classes [ 0 ] ;
if ( typeof ( id ) === 'string' ) {
2017-09-25 13:45:08 +00:00
framework . feedback ( id . toUpperCase ( ) ) ;
2017-08-28 10:25:05 +00:00
}
} ) ;
2017-09-25 13:45:08 +00:00
framework . start ( ) ;
2017-08-28 10:25:05 +00:00
} ;
2020-05-05 09:19:32 +00:00
var main = function ( ) {
2017-08-28 10:25:05 +00:00
var Ckeditor ;
var editor ;
2017-09-25 13:45:08 +00:00
var framework ;
2017-08-28 10:25:05 +00:00
2020-05-05 09:19:32 +00:00
nThen ( function ( waitFor ) {
2017-09-27 14:53:16 +00:00
Framework . create ( {
2020-04-21 14:07:04 +00:00
toolbarContainer : '#cp-app-pad-toolbar' ,
contentContainer : '#cp-app-pad-editor' ,
2017-11-09 14:36:49 +00:00
patchTransformer : ChainPad . NaiveJSONTransformer ,
2018-02-27 16:38:29 +00:00
/ * t h u m b n a i l : {
2017-10-26 10:31:16 +00:00
getContainer : function ( ) { return $ ( 'iframe' ) . contents ( ) . find ( 'html' ) [ 0 ] ; } ,
filter : function ( el , before ) {
if ( before ) {
2017-11-02 18:11:27 +00:00
module . cursor . update ( ) ;
2017-10-26 10:31:16 +00:00
$ ( el ) . parents ( ) . css ( 'overflow' , 'visible' ) ;
$ ( el ) . css ( 'max-width' , '1200px' ) ;
$ ( el ) . css ( 'max-height' , Math . max ( 600 , $ ( el ) . width ( ) ) + 'px' ) ;
$ ( el ) . css ( 'overflow' , 'hidden' ) ;
$ ( el ) . find ( 'body' ) . css ( 'background-color' , 'transparent' ) ;
return ;
}
$ ( el ) . parents ( ) . css ( 'overflow' , '' ) ;
$ ( el ) . css ( 'max-width' , '' ) ;
$ ( el ) . css ( 'max-height' , '' ) ;
$ ( el ) . css ( 'overflow' , '' ) ;
$ ( el ) . find ( 'body' ) . css ( 'background-color' , '#fff' ) ;
2017-11-02 18:11:27 +00:00
var sel = module . cursor . makeSelection ( ) ;
var range = module . cursor . makeRange ( ) ;
module . cursor . fixSelection ( sel , range ) ;
2017-10-26 10:31:16 +00:00
}
2018-02-27 16:38:29 +00:00
} * /
2020-05-05 09:19:32 +00:00
} , waitFor ( function ( fw ) { window . APP . framework = framework = fw ; } ) ) ;
2017-09-27 14:53:16 +00:00
2020-05-05 09:19:32 +00:00
nThen ( function ( waitFor ) {
ckEditorAvailable ( waitFor ( function ( ck ) {
2017-09-27 14:53:16 +00:00
Ckeditor = ck ;
require ( [ '/pad/wysiwygarea-plugin.js' ] , waitFor ( ) ) ;
} ) ) ;
$ ( waitFor ( ) ) ;
2020-05-05 09:19:32 +00:00
} ) . nThen ( function ( waitFor ) {
2017-09-27 14:53:16 +00:00
Ckeditor . config . toolbarCanCollapse = true ;
if ( screen . height < 800 ) {
Ckeditor . config . toolbarStartupExpanded = false ;
$ ( 'meta[name=viewport]' ) . attr ( 'content' ,
'width=device-width, initial-scale=1.0, user-scalable=no' ) ;
} else {
$ ( 'meta[name=viewport]' ) . attr ( 'content' ,
'width=device-width, initial-scale=1.0, user-scalable=yes' ) ;
}
// Used in ckeditor-config.js
Ckeditor . CRYPTPAD _URLARGS = ApiConfig . requireConf . urlArgs ;
2018-08-31 13:41:08 +00:00
Ckeditor . _mediatagTranslations = {
title : Messages . pad _mediatagTitle ,
width : Messages . pad _mediatagWidth ,
height : Messages . pad _mediatagHeight ,
ratio : Messages . pad _mediatagRatio ,
border : Messages . pad _mediatagBorder ,
preview : Messages . pad _mediatagPreview ,
'import' : Messages . pad _mediatagImport ,
2020-11-25 15:19:10 +00:00
download : Messages . download _mt _button ,
share : Messages . pad _mediatagShare ,
open : Messages . pad _mediatagOpen ,
2018-08-31 13:41:08 +00:00
options : Messages . pad _mediatagOptions
} ;
2020-04-24 10:49:21 +00:00
Ckeditor . _commentsTranslations = {
comment : Messages . comments _comment ,
} ;
2020-05-05 09:19:32 +00:00
Ckeditor . plugins . addExternal ( 'mediatag' , '/pad/' , 'mediatag-plugin.js' ) ;
Ckeditor . plugins . addExternal ( 'blockbase64' , '/pad/' , 'disable-base64.js' ) ;
Ckeditor . plugins . addExternal ( 'comments' , '/pad/' , 'comment.js' ) ;
Ckeditor . plugins . addExternal ( 'wordcount' , '/pad/wordcount/' , 'plugin.js' ) ;
2020-06-26 18:40:23 +00:00
/ * C K E d i t o r 4 i s , b y d e f a u l t , i n c o m p a t i b l e w i t h s t r o n g C S P s e t t i n g s d u e t o t h e
way it loads a variety of resources and event handlers by injecting HTML
via the innerHTML API .
In most cases those handlers just call a function with an id , so there ' s no
strong case for why it should be done this way except that lots of code depends
on this behaviour . These handlers all stop working when we enable our default CSP ,
but fortunately the code is simple enough that we can use regex to grab the id
from the inline code and call the relevant function directly , preserving the
intended behaviour while preventing malicious code injection .
Unfortunately , as long as the original code is still present the console
fills up with CSP warnings saying that inline scripts were blocked .
The code below overrides CKEditor ' s default ` setHtml ` method to include
a string . replace call which will rewrite various inline event handlers from
onevent to oonevent . . rendering them invalid as scripts and preventing
some needless noise from showing up in the console .
YAY !
* /
2020-07-01 10:10:08 +00:00
Ckeditor . dom . element . prototype . setHtml = function ( a ) {
2020-06-26 18:40:23 +00:00
if ( /callFunction/ . test ( a ) ) {
2020-11-05 09:16:46 +00:00
a = a . replace ( /on(mousedown|blur|keydown|focus|click|dragstart|mouseover|mouseout)/g , function ( value ) {
2020-06-26 18:40:23 +00:00
return 'o' + value ;
} ) ;
}
2020-07-01 10:10:08 +00:00
this . $ . innerHTML = a ;
return a ;
2020-06-26 18:40:23 +00:00
} ;
2017-09-27 14:53:16 +00:00
module . ckeditor = editor = Ckeditor . replace ( 'editor1' , {
customConfig : '/customize/ckeditor-config.js' ,
} ) ;
2020-04-27 11:01:42 +00:00
2017-09-27 14:53:16 +00:00
editor . on ( 'instanceReady' , waitFor ( ) ) ;
2020-05-05 09:19:32 +00:00
} ) . nThen ( function ( ) {
2020-05-05 16:11:00 +00:00
var _getPath = Ckeditor . plugins . getPath ;
Ckeditor . plugins . getPath = function ( name ) {
if ( name === 'preview' ) {
return window . location . origin + "/bower_components/ckeditor/plugins/preview/" ;
}
return _getPath ( name ) ;
} ;
2020-05-26 09:27:58 +00:00
window . _ _defineGetter _ _ ( '_cke_htmlToLoad' , function ( ) { } ) ;
2020-05-05 09:19:32 +00:00
editor . plugins . mediatag . import = function ( $mt ) {
2018-08-31 13:41:08 +00:00
framework . _ . sfCommon . importMediaTag ( $mt ) ;
2017-09-27 14:53:16 +00:00
} ;
2020-11-25 15:19:10 +00:00
editor . plugins . mediatag . download = function ( $mt ) {
var media = Util . find ( $mt , [ 0 , '_mediaObject' ] ) ;
2020-11-26 15:27:12 +00:00
if ( ! media ) { return void console . error ( 'no media' ) ; }
if ( ! media . complete ) { return void UI . warn ( Messages . mediatag _notReady ) ; }
2020-11-25 15:19:10 +00:00
if ( ! ( media && media . _blob ) ) { return void console . error ( $mt ) ; }
window . saveAs ( media . _blob . content , media . name ) ;
} ;
editor . plugins . mediatag . open = function ( $mt ) {
var hash = framework . _ . sfCommon . getHashFromMediaTag ( $mt ) ;
framework . _ . sfCommon . openURL ( Hash . hashToHref ( hash , 'file' ) ) ;
} ;
editor . plugins . mediatag . share = function ( $mt ) {
var data = {
file : true ,
pathname : '/file/' ,
hashes : {
fileHash : framework . _ . sfCommon . getHashFromMediaTag ( $mt )
} ,
title : Util . find ( $mt [ 0 ] , [ '_mediaObject' , 'name' ] ) || ''
} ;
framework . _ . sfCommon . getSframeChannel ( ) . event ( 'EV_SHARE_OPEN' , data ) ;
} ;
2020-04-24 11:17:56 +00:00
Links . init ( Ckeditor , editor ) ;
2020-05-05 09:19:32 +00:00
} ) . nThen ( function ( ) {
2018-02-27 16:38:29 +00:00
// Move ckeditor parts to have a structure like the other apps
var $contentContainer = $ ( '#cke_1_contents' ) ;
2020-04-21 14:07:04 +00:00
var $mainContainer = $ ( '#cke_editor1 > .cke_inner' ) ;
var $ckeToolbar = $ ( '#cke_1_top' ) . find ( '.cke_toolbox_main' ) ;
$mainContainer . prepend ( $ckeToolbar . addClass ( 'cke_reset_all' ) ) ;
2021-02-05 18:10:03 +00:00
$contentContainer . append ( h ( 'div#cp-app-pad-resize' ) ) ;
2020-04-21 14:07:04 +00:00
$contentContainer . append ( h ( 'div#cp-app-pad-comments' ) ) ;
2020-08-06 10:27:25 +00:00
$contentContainer . prepend ( h ( 'div#cp-app-pad-toc' ) ) ;
2020-04-24 16:46:29 +00:00
$ckeToolbar . find ( '.cke_button__image_icon' ) . parent ( ) . hide ( ) ;
2017-09-27 14:53:16 +00:00
} ) . nThen ( waitFor ( ) ) ;
2020-06-26 12:02:24 +00:00
} ) . nThen ( function ( waitFor ) {
require ( [ '/pad/csp.js' ] , waitFor ( ) ) ;
2020-05-05 09:19:32 +00:00
} ) . nThen ( function ( /*waitFor*/ ) {
2020-06-22 16:45:16 +00:00
2018-05-11 07:06:22 +00:00
function launchAnchorTest ( test ) {
// -------- anchor test: make sure the exported anchor contains <a name="..."> -------
console . log ( '---- anchor test: make sure the exported anchor contains <a name="..."> -----.' ) ;
function tryAndTestExport ( ) {
2018-08-13 19:04:24 +00:00
console . log ( "Starting tryAndTestExport." ) ;
2020-05-05 09:19:32 +00:00
editor . on ( 'dialogShow' , function ( evt ) {
2018-08-13 19:04:24 +00:00
console . log ( "Anchor dialog detected." ) ;
var dialog = evt . data ;
2020-05-05 09:19:32 +00:00
$ ( dialog . parts . contents . $ ) . find ( "input" ) . val ( 'xx-' + Math . round ( Math . random ( ) * 1000 ) ) ;
2018-08-14 15:11:08 +00:00
dialog . click ( window . CKEDITOR . dialog . okButton ( editor ) . id ) ;
2020-05-05 09:19:32 +00:00
} ) ;
2018-08-13 19:04:24 +00:00
var existingText = editor . getData ( ) ;
editor . insertText ( "A bit of text" ) ;
console . log ( "Launching anchor command." ) ;
editor . execCommand ( editor . ui . get ( 'Anchor' ) . command ) ;
console . log ( "Anchor command launched." ) ;
var waitH = window . setInterval ( function ( ) {
console . log ( "Waited 2s for the dialog to appear" ) ;
2018-08-14 15:11:08 +00:00
var anchors = window . CKEDITOR . plugins [ "link" ] . getEditorAnchors ( editor ) ;
2020-05-05 09:19:32 +00:00
if ( ! anchors || anchors . length === 0 ) {
2018-08-13 19:04:24 +00:00
test . fail ( "No anchors found. Please adjust document" ) ;
} else {
console . log ( anchors . length + " anchors found." ) ;
2018-10-18 16:50:38 +00:00
var exported = Exporter . getHTML ( window . inner ) ;
2018-08-13 19:04:24 +00:00
console . log ( "Obtained exported: " + exported ) ;
var allFound = true ;
2020-05-05 09:19:32 +00:00
for ( var i = 0 ; i < anchors . length ; i ++ ) {
2018-08-13 19:04:24 +00:00
var anchor = anchors [ i ] ;
console . log ( "Anchor " + anchor . name ) ;
var expected = "<a id=\"" + anchor . id + "\" name=\"" + anchor . name + "\" " ;
2020-05-05 09:19:32 +00:00
var found = exported . indexOf ( expected ) >= 0 ;
2018-08-13 19:04:24 +00:00
console . log ( "Found " + expected + " " + found + "." ) ;
allFound = allFound && found ;
}
console . log ( "Cleaning up." ) ;
2020-05-05 09:19:32 +00:00
if ( allFound ) {
2018-08-13 19:04:24 +00:00
// clean-up
editor . execCommand ( 'undo' ) ;
editor . execCommand ( 'undo' ) ;
2020-05-05 09:19:32 +00:00
var nint = window . setInterval ( function ( ) {
2018-08-14 15:11:08 +00:00
console . log ( "Waiting for undo to yield same result." ) ;
2020-05-05 09:19:32 +00:00
if ( existingText === editor . getData ( ) ) {
2018-08-13 19:04:24 +00:00
window . clearInterval ( nint ) ;
test . pass ( ) ;
}
} , 500 ) ;
2020-05-05 09:19:32 +00:00
} else {
2018-08-13 19:04:24 +00:00
test . fail ( "Not all expected a elements found for document at " + window . top . location + "." ) ;
}
2018-05-11 07:06:22 +00:00
}
2018-08-13 19:04:24 +00:00
window . clearInterval ( waitH ) ;
2020-05-05 09:19:32 +00:00
} , 2000 ) ;
2018-08-13 19:04:24 +00:00
2018-05-11 07:06:22 +00:00
}
var intervalHandle = window . setInterval ( function ( ) {
2020-05-05 09:19:32 +00:00
if ( editor . status === "ready" ) {
2018-05-11 07:06:22 +00:00
window . clearInterval ( intervalHandle ) ;
2018-08-13 19:04:24 +00:00
console . log ( "Editor is ready." ) ;
2018-05-11 07:06:22 +00:00
tryAndTestExport ( ) ;
} else {
console . log ( "Waiting for editor to be ready." ) ;
}
} , 100 ) ;
}
Test ( function ( test ) {
launchAnchorTest ( test ) ;
} ) ;
2017-09-25 13:45:08 +00:00
andThen2 ( editor , Ckeditor , framework ) ;
2017-08-28 10:25:05 +00:00
} ) ;
} ;
main ( ) ;
2020-05-05 16:11:00 +00:00
} ) ;