From 2cf73961b3615cd55a15396158ccf78fe7aabb83 Mon Sep 17 00:00:00 2001 From: Tymoteusz Motylewski Date: Mon, 15 Jun 2020 16:38:04 +0200 Subject: [PATCH 1/6] Content of the plugin from CKEditor website https://ckeditor.com/cke4/addon/contents --- dialogs/contents.js | 61 ++++++++++++++++ icons/contents.png | Bin 0 -> 312 bytes plugin.js | 168 ++++++++++++++++++++++++++++++++++++++++++++ styles/styles.css | 34 +++++++++ 4 files changed, 263 insertions(+) create mode 100644 dialogs/contents.js create mode 100644 icons/contents.png create mode 100644 plugin.js create mode 100644 styles/styles.css diff --git a/dialogs/contents.js b/dialogs/contents.js new file mode 100644 index 0000000..9cb5be2 --- /dev/null +++ b/dialogs/contents.js @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2014-2016, CKSource - Frederico Knabben. All rights reserved. + * Licensed under the terms of the MIT License (see LICENSE.md). + */ + +// Note: This automatic widget to dialog window binding (the fact that every field is set up from the widget +// and is committed to the widget) is only possible when the dialog is opened by the Widgets System +// (i.e. the widgetDef.dialog property is set). +// When you are opening the dialog window by yourself, you need to take care of this by yourself too. + +CKEDITOR.dialog.add( 'contents', function( editor ) { + return { + title: 'Edit', + minWidth: 200, + minHeight: 100, + contents: [ + { + id: 'info', + elements: [ + { + id: 'align', + type: 'select', + label: 'Align', + items: [ + [ editor.lang.common.notSet, '' ], + [ editor.lang.common.alignLeft, 'float-left' ], + [ editor.lang.common.alignRight, 'float-right' ], + ], + 'default': editor.lang.common.notSet, + // When setting up this field, set its value to the "align" value from widget data. + // Note: Align values used in the widget need to be the same as those defined in the "items" array above. + setup: function( widget ) { + widget.data.align === undefined ? this.setValue( '' ) : this.setValue( widget.data.align ); + }, + // When committing (saving) this field, set its value to the widget data. + commit: function( widget ) { + widget.setData( 'align', this.getValue() ); + } + }, + + { + id: 'chkInsertOpt', + type: 'checkbox', + label: 'Ignore nested headers', + 'default': true, + setup: function( widget ) { + widget.data.chkInsertOpt === undefined ? this.setValue( false ) : this.setValue( widget.data.chkInsertOpt ); + }, + + + commit: function( widget ) { + widget.setData( 'chkInsertOpt', this.getValue()); + } + } + + ] + }, + + ] + }; +} ); \ No newline at end of file diff --git a/icons/contents.png b/icons/contents.png new file mode 100644 index 0000000000000000000000000000000000000000..c7a061a0bfe2637ea15da699fb0b4fd1da4a2a01 GIT binary patch literal 312 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc!Lyz&jv*HQS0~wWwHOGr7W48yyr*pbL2Fv3 zm`Ki^#<^?v&h^z>bi^rOOJK_nnY;T9Hr;*~?t5AzFQwB) zS@S+PT`x_Ie01}BZD?rqM@D@UU+3ty{eri)&3!J}%', + + + allowedContent: + 'div(!widget-toc,float-left,float-right,align-center);' + + 'p(!toc-title);', + + + dialog: 'contents', + + upcast: function( element ) { + return element.name == 'div' + && element.hasClass( 'widget-toc' ); + }, + + init: function() { + + + + + + editor.on('saveSnapshot', function(evt) { + buildToc(this.element) + }.bind(this)); + + + + + this.on('focus', function(evt) { + buildToc(this.element) + }.bind(this)); + + buildToc(this.element); + + console.log(this.element.hasClass( 'float-right' ) ); + + + if ( this.element.hasClass( 'float-left' ) ) + this.setData( 'align', 'float-left' ); + if ( this.element.hasClass( 'float-right' ) ) + this.setData( 'align', 'float-right' ); + if ( this.element.hasClass( 'toc_root' ) ) + this.setData( 'chkInsertOpt', true); + + + }, + + data: function() { + + this.element.removeClass( 'float-left' ); + this.element.removeClass( 'float-right' ); + this.element.removeClass( 'toc_root' ); + + + + if ( this.data.align ) + this.element.addClass(this.data.align ); + + if ( this.data.chkInsertOpt ) + this.element.addClass('toc_root'); + + + }, + + } ); + +function buildToc(element){ + + + //set everything up + + + + element.setHtml('

Contents

'); + Container = new CKEDITOR.dom.element( 'ol' ); + Container.appendTo(element); + + if (element.hasClass( 'toc_root' )){ + findRoot = '> h1,> h2,> h3,> h4,> h5,> h6,'; + }else{ + findRoot = 'h1,h2,h3,h4,h5,h6,'; + } + + + var headings = editor.editable().find(findRoot), + parentLevel = 1, + length = headings.count(); + + //get each heading + for (var i = 0 ; i < length ; ++i) { + + var currentHeading = headings.getItem( i ), + text = currentHeading.getText( ), + newLevel = parseInt(currentHeading.getName().substr(1,1)); + var diff = (newLevel - parentLevel); + + + + + //set the start level incase it is not h1 + if(i === 0){diff = 0; parentLevel = newLevel;} + + //we need a new ul if the new level has a higher number than its parents number + + + + + if (diff > 0) { + var containerLiNode = Container.getLast(); + + + var ulNode = new CKEDITOR.dom.element( 'ol' ); + ulNode.appendTo(containerLiNode); + Container = ulNode; + parentLevel = newLevel; + } + + + //we need to get a previous ul if the new level has a lower number than its parents number + if (diff < 0) { + while (0 !== diff++) { + parent = Container.getParent().getParent(); + Container = (parent.getName() === 'ol' ? parent : Container); + } + parentLevel = newLevel; + } + + //we can add the list item if there is no difference + + //if(text === ''){text = 'empty'} + + + if (text == null || text.trim() === ''){ + text = ' ' + } + + + var id = text.replace(/ /g, "+"); + currentHeading.setAttribute( 'id', id ); + + var liNode = CKEDITOR.dom.element.createFromHtml( '
  • '+text+'
  • ' ); + liNode.appendTo(Container); + } + + + +} + + + } +} ); + + + + diff --git a/styles/styles.css b/styles/styles.css new file mode 100644 index 0000000..8a4eee7 --- /dev/null +++ b/styles/styles.css @@ -0,0 +1,34 @@ +.widget-toc{ + + display: table; + border: 1px solid #a2a9b1; + background-color: #f8f9fa; + padding-right: 1rem; + font-size: 95%; +} + +.widget-toc ol { + padding-right: 0px; + counter-reset: item; +} +.widget-toc ol li { + display: block; + position: relative; +} +.widget-toc ol li:before { + content: counters(item, "."); + counter-increment: item; + position: absolute; + margin-right: 100%; + right: 0.5rem; +} + + +.toc-title{ + + text-align: center; + font-weight: 700; + margin: 0; + padding: 0; + +} \ No newline at end of file From 7501bea9418d400903e2f0299f01589f89863441 Mon Sep 17 00:00:00 2001 From: Tymoteusz Motylewski Date: Mon, 15 Jun 2020 16:43:28 +0200 Subject: [PATCH 2/6] Reformat code --- dialogs/contents.js | 77 ++++++++------- plugin.js | 225 ++++++++++++++++++-------------------------- 2 files changed, 130 insertions(+), 172 deletions(-) diff --git a/dialogs/contents.js b/dialogs/contents.js index 9cb5be2..3e89e71 100644 --- a/dialogs/contents.js +++ b/dialogs/contents.js @@ -8,54 +8,51 @@ // (i.e. the widgetDef.dialog property is set). // When you are opening the dialog window by yourself, you need to take care of this by yourself too. -CKEDITOR.dialog.add( 'contents', function( editor ) { - return { - title: 'Edit', - minWidth: 200, - minHeight: 100, - contents: [ - { - id: 'info', - elements: [ - { - id: 'align', - type: 'select', - label: 'Align', - items: [ - [ editor.lang.common.notSet, '' ], - [ editor.lang.common.alignLeft, 'float-left' ], - [ editor.lang.common.alignRight, 'float-right' ], - ], +CKEDITOR.dialog.add('contents', function (editor) { + return { + title: 'Edit', + minWidth: 200, + minHeight: 100, + contents: [ + { + id: 'info', + elements: [ + { + id: 'align', + type: 'select', + label: 'Align', + items: [ + [editor.lang.common.notSet, ''], + [editor.lang.common.alignLeft, 'float-left'], + [editor.lang.common.alignRight, 'float-right'], + ], 'default': editor.lang.common.notSet, - // When setting up this field, set its value to the "align" value from widget data. - // Note: Align values used in the widget need to be the same as those defined in the "items" array above. - setup: function( widget ) { - widget.data.align === undefined ? this.setValue( '' ) : this.setValue( widget.data.align ); - }, - // When committing (saving) this field, set its value to the widget data. - commit: function( widget ) { - widget.setData( 'align', this.getValue() ); - } - }, + // When setting up this field, set its value to the "align" value from widget data. + // Note: Align values used in the widget need to be the same as those defined in the "items" array above. + setup: function (widget) { + widget.data.align === undefined ? this.setValue('') : this.setValue(widget.data.align); + }, + // When committing (saving) this field, set its value to the widget data. + commit: function (widget) { + widget.setData('align', this.getValue()); + } + }, { id: 'chkInsertOpt', type: 'checkbox', label: 'Ignore nested headers', 'default': true, - setup: function( widget ) { - widget.data.chkInsertOpt === undefined ? this.setValue( false ) : this.setValue( widget.data.chkInsertOpt ); + setup: function (widget) { + widget.data.chkInsertOpt === undefined ? this.setValue(false) : this.setValue(widget.data.chkInsertOpt); }, - - commit: function( widget ) { - widget.setData( 'chkInsertOpt', this.getValue()); + commit: function (widget) { + widget.setData('chkInsertOpt', this.getValue()); } } - - ] - }, - - ] - }; -} ); \ No newline at end of file + ] + }, + ] + }; +}); \ No newline at end of file diff --git a/plugin.js b/plugin.js index 1a94f58..4fb6def 100644 --- a/plugin.js +++ b/plugin.js @@ -1,167 +1,128 @@ -CKEDITOR.plugins.add( 'contents', { +CKEDITOR.plugins.add('contents', { requires: 'widget', - icons: 'contents', + icons: 'contents', - init: function( editor ) { - editor.addContentsCss( this.path + 'styles/styles.css' ); - CKEDITOR.dialog.add( 'contents', this.path + 'dialogs/contents.js' ); + init: function (editor) { + editor.addContentsCss(this.path + 'styles/styles.css'); + CKEDITOR.dialog.add('contents', this.path + 'dialogs/contents.js'); - editor.widgets.add( 'contents', { - button: 'Insert Table of Contents', + editor.widgets.add('contents', { + button: 'Insert Table of Contents', - template: + template: '
    ', - allowedContent: - 'div(!widget-toc,float-left,float-right,align-center);' + - 'p(!toc-title);', - + 'div(!widget-toc,float-left,float-right,align-center);' + + 'p(!toc-title);', dialog: 'contents', - upcast: function( element ) { - return element.name == 'div' - && element.hasClass( 'widget-toc' ); - }, - - init: function() { - - - - + upcast: function (element) { + return element.name == 'div' + && element.hasClass('widget-toc'); + }, - editor.on('saveSnapshot', function(evt) { + init: function () { + editor.on('saveSnapshot', function (evt) { buildToc(this.element) }.bind(this)); - - - - this.on('focus', function(evt) { + this.on('focus', function (evt) { buildToc(this.element) }.bind(this)); buildToc(this.element); - console.log(this.element.hasClass( 'float-right' ) ); - - - if ( this.element.hasClass( 'float-left' ) ) - this.setData( 'align', 'float-left' ); - if ( this.element.hasClass( 'float-right' ) ) - this.setData( 'align', 'float-right' ); - if ( this.element.hasClass( 'toc_root' ) ) - this.setData( 'chkInsertOpt', true); - - - }, - - data: function() { - - this.element.removeClass( 'float-left' ); - this.element.removeClass( 'float-right' ); - this.element.removeClass( 'toc_root' ); - + console.log(this.element.hasClass('float-right')); + if (this.element.hasClass('float-left')) + this.setData('align', 'float-left'); + if (this.element.hasClass('float-right')) + this.setData('align', 'float-right'); + if (this.element.hasClass('toc_root')) + this.setData('chkInsertOpt', true); + }, - if ( this.data.align ) - this.element.addClass(this.data.align ); + data: function () { + this.element.removeClass('float-left'); + this.element.removeClass('float-right'); + this.element.removeClass('toc_root'); + if (this.data.align) + this.element.addClass(this.data.align); - if ( this.data.chkInsertOpt ) + if (this.data.chkInsertOpt) this.element.addClass('toc_root'); - - }, + }); - } ); - -function buildToc(element){ - - - //set everything up - - - - element.setHtml('

    Contents

    '); - Container = new CKEDITOR.dom.element( 'ol' ); - Container.appendTo(element); - - if (element.hasClass( 'toc_root' )){ - findRoot = '> h1,> h2,> h3,> h4,> h5,> h6,'; - }else{ - findRoot = 'h1,h2,h3,h4,h5,h6,'; - } - + function buildToc(element) { - var headings = editor.editable().find(findRoot), - parentLevel = 1, - length = headings.count(); + //set everything up + element.setHtml('

    Contents

    '); + Container = new CKEDITOR.dom.element('ol'); + Container.appendTo(element); - //get each heading - for (var i = 0 ; i < length ; ++i) { - - var currentHeading = headings.getItem( i ), - text = currentHeading.getText( ), - newLevel = parseInt(currentHeading.getName().substr(1,1)); - var diff = (newLevel - parentLevel); - - - - - //set the start level incase it is not h1 - if(i === 0){diff = 0; parentLevel = newLevel;} - - //we need a new ul if the new level has a higher number than its parents number - - - - - if (diff > 0) { - var containerLiNode = Container.getLast(); - - - var ulNode = new CKEDITOR.dom.element( 'ol' ); - ulNode.appendTo(containerLiNode); - Container = ulNode; - parentLevel = newLevel; - } - - - //we need to get a previous ul if the new level has a lower number than its parents number - if (diff < 0) { - while (0 !== diff++) { - parent = Container.getParent().getParent(); - Container = (parent.getName() === 'ol' ? parent : Container); + if (element.hasClass('toc_root')) { + findRoot = '> h1,> h2,> h3,> h4,> h5,> h6,'; + } else { + findRoot = 'h1,h2,h3,h4,h5,h6,'; } - parentLevel = newLevel; - } - - //we can add the list item if there is no difference - - //if(text === ''){text = 'empty'} - - if (text == null || text.trim() === ''){ - text = ' ' + var headings = editor.editable().find(findRoot), + parentLevel = 1, + length = headings.count(); + + //get each heading + for (var i = 0; i < length; ++i) { + + var currentHeading = headings.getItem(i), + text = currentHeading.getText(), + newLevel = parseInt(currentHeading.getName().substr(1, 1)); + var diff = (newLevel - parentLevel); + + //set the start level incase it is not h1 + if (i === 0) { + diff = 0; + parentLevel = newLevel; + } + + //we need a new ul if the new level has a higher number than its parents number + if (diff > 0) { + var containerLiNode = Container.getLast(); + var ulNode = new CKEDITOR.dom.element('ol'); + ulNode.appendTo(containerLiNode); + Container = ulNode; + parentLevel = newLevel; + } + + //we need to get a previous ul if the new level has a lower number than its parents number + if (diff < 0) { + while (0 !== diff++) { + parent = Container.getParent().getParent(); + Container = (parent.getName() === 'ol' ? parent : Container); + } + parentLevel = newLevel; + } + + //we can add the list item if there is no difference + + //if(text === ''){text = 'empty'} + + if (text == null || text.trim() === '') { + text = ' ' + } + + var id = text.replace(/ /g, "+"); + currentHeading.setAttribute('id', id); + + var liNode = CKEDITOR.dom.element.createFromHtml('
  • ' + text + '
  • '); + liNode.appendTo(Container); + } } - - - var id = text.replace(/ /g, "+"); - currentHeading.setAttribute( 'id', id ); - - var liNode = CKEDITOR.dom.element.createFromHtml( '
  • '+text+'
  • ' ); - liNode.appendTo(Container); - } - - - -} - - } -} ); +}); From 7d6e38d668dfdf9ed42253d25fca4831df4f6e47 Mon Sep 17 00:00:00 2001 From: Tymoteusz Motylewski Date: Tue, 16 Jun 2020 09:39:57 +0200 Subject: [PATCH 3/6] Make table of contents header configurable also remove some console.log and unneded comments --- plugin.js | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/plugin.js b/plugin.js index 4fb6def..b6e7d00 100644 --- a/plugin.js +++ b/plugin.js @@ -7,6 +7,14 @@ CKEDITOR.plugins.add('contents', { editor.addContentsCss(this.path + 'styles/styles.css'); CKEDITOR.dialog.add('contents', this.path + 'dialogs/contents.js'); + // Default Config + var defaultConfig = { + header: '

    Contents

    ', + }; + + // Get Config & Lang + var config = CKEDITOR.tools.extend(defaultConfig, editor.config.contents || {}, true); + editor.widgets.add('contents', { button: 'Insert Table of Contents', @@ -35,8 +43,6 @@ CKEDITOR.plugins.add('contents', { buildToc(this.element); - console.log(this.element.hasClass('float-right')); - if (this.element.hasClass('float-left')) this.setData('align', 'float-left'); if (this.element.hasClass('float-right')) @@ -59,8 +65,8 @@ CKEDITOR.plugins.add('contents', { function buildToc(element) { - //set everything up - element.setHtml('

    Contents

    '); + element.setHtml(config.header); + Container = new CKEDITOR.dom.element('ol'); Container.appendTo(element); @@ -82,7 +88,7 @@ CKEDITOR.plugins.add('contents', { newLevel = parseInt(currentHeading.getName().substr(1, 1)); var diff = (newLevel - parentLevel); - //set the start level incase it is not h1 + //set the start level in case it is not h1 if (i === 0) { diff = 0; parentLevel = newLevel; @@ -106,10 +112,6 @@ CKEDITOR.plugins.add('contents', { parentLevel = newLevel; } - //we can add the list item if there is no difference - - //if(text === ''){text = 'empty'} - if (text == null || text.trim() === '') { text = ' ' } @@ -122,8 +124,4 @@ CKEDITOR.plugins.add('contents', { } } } -}); - - - - +}); \ No newline at end of file From f4b5fa193d46d989f3142ff66ab5449f768294ec Mon Sep 17 00:00:00 2001 From: Tymoteusz Motylewski Date: Tue, 16 Jun 2020 10:43:28 +0200 Subject: [PATCH 4/6] Make list type configurable (ol/ul) --- plugin.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plugin.js b/plugin.js index b6e7d00..5ef7461 100644 --- a/plugin.js +++ b/plugin.js @@ -10,9 +10,11 @@ CKEDITOR.plugins.add('contents', { // Default Config var defaultConfig = { header: '

    Contents

    ', + //ol or ul + listType: 'ol', }; - // Get Config & Lang + // Get Config var config = CKEDITOR.tools.extend(defaultConfig, editor.config.contents || {}, true); editor.widgets.add('contents', { @@ -67,7 +69,7 @@ CKEDITOR.plugins.add('contents', { element.setHtml(config.header); - Container = new CKEDITOR.dom.element('ol'); + Container = new CKEDITOR.dom.element(config.listType); Container.appendTo(element); if (element.hasClass('toc_root')) { @@ -97,7 +99,7 @@ CKEDITOR.plugins.add('contents', { //we need a new ul if the new level has a higher number than its parents number if (diff > 0) { var containerLiNode = Container.getLast(); - var ulNode = new CKEDITOR.dom.element('ol'); + var ulNode = new CKEDITOR.dom.element(config.listType); ulNode.appendTo(containerLiNode); Container = ulNode; parentLevel = newLevel; @@ -107,7 +109,7 @@ CKEDITOR.plugins.add('contents', { if (diff < 0) { while (0 !== diff++) { parent = Container.getParent().getParent(); - Container = (parent.getName() === 'ol' ? parent : Container); + Container = (parent.getName() === config.listType ? parent : Container); } parentLevel = newLevel; } @@ -120,6 +122,7 @@ CKEDITOR.plugins.add('contents', { currentHeading.setAttribute('id', id); var liNode = CKEDITOR.dom.element.createFromHtml('
  • ' + text + '
  • '); + liNode.appendTo(Container); } } From 73b50a91bb42647dc486119decf854b5442c4289 Mon Sep 17 00:00:00 2001 From: Tymoteusz Motylewski Date: Tue, 16 Jun 2020 10:43:54 +0200 Subject: [PATCH 5/6] Properly sanitize double quotes to avoid XSS --- plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.js b/plugin.js index 5ef7461..af47661 100644 --- a/plugin.js +++ b/plugin.js @@ -118,7 +118,7 @@ CKEDITOR.plugins.add('contents', { text = ' ' } - var id = text.replace(/ /g, "+"); + var id = text.replace(/[^A-Za-z0-9_\-]+/g, '+'); currentHeading.setAttribute('id', id); var liNode = CKEDITOR.dom.element.createFromHtml('
  • ' + text + '
  • '); From 1438ec6f685442a5909af89b2265d107456d32dd Mon Sep 17 00:00:00 2001 From: Tymoteusz Motylewski Date: Tue, 23 Jun 2020 09:55:12 +0200 Subject: [PATCH 6/6] Make header selectors configurable This way you can configure e.g. to only take h2 for TOC --- plugin.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugin.js b/plugin.js index af47661..eea27c5 100644 --- a/plugin.js +++ b/plugin.js @@ -12,6 +12,8 @@ CKEDITOR.plugins.add('contents', { header: '

    Contents

    ', //ol or ul listType: 'ol', + headersSelector: '> h1,> h2,> h3,> h4,> h5,> h6,', + nestedHeadersSelector: 'h1,h2,h3,h4,h5,h6,' }; // Get Config @@ -73,9 +75,9 @@ CKEDITOR.plugins.add('contents', { Container.appendTo(element); if (element.hasClass('toc_root')) { - findRoot = '> h1,> h2,> h3,> h4,> h5,> h6,'; + findRoot = config.headersSelector; } else { - findRoot = 'h1,h2,h3,h4,h5,h6,'; + findRoot = config.nestedHeadersSelector; } var headings = editor.editable().find(findRoot),