@ckeditor/ckeditor5-block-quote 35.4.0 → 36.0.0
Sign up to get free protection for your applications and to get access to all the features.
- package/LICENSE.md +1 -1
- package/build/block-quote.js +1 -1
- package/package.json +21 -17
- package/src/blockquote.js +13 -18
- package/src/blockquotecommand.js +154 -214
- package/src/blockquoteediting.js +99 -127
- package/src/blockquoteui.js +32 -42
- package/src/index.js +1 -3
- package/theme/blockquote.css +1 -1
package/LICENSE.md
CHANGED
@@ -2,7 +2,7 @@ Software License Agreement
|
|
2
2
|
==========================
|
3
3
|
|
4
4
|
**CKEditor 5 block quote feature** – https://github.com/ckeditor/ckeditor5-block-quote <br>
|
5
|
-
Copyright (c) 2003-
|
5
|
+
Copyright (c) 2003-2023, [CKSource Holding sp. z o.o.](https://cksource.com) All rights reserved.
|
6
6
|
|
7
7
|
Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html).
|
8
8
|
|
package/build/block-quote.js
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
!function(e){const t=e.en=e.en||{};t.dictionary=Object.assign(t.dictionary||{},{"Block quote":"Block quote"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={})),
|
2
2
|
/*!
|
3
|
-
* @license Copyright (c) 2003-
|
3
|
+
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
|
4
4
|
* For licensing, see LICENSE.md.
|
5
5
|
*/(()=>{var e={446:(e,t,o)=>{"use strict";o.d(t,{Z:()=>i});var n=o(609),r=o.n(n)()((function(e){return e[1]}));r.push([e.id,".ck-content blockquote{border-left:5px solid #ccc;font-style:italic;margin-left:0;margin-right:0;overflow:hidden;padding-left:1.5em;padding-right:1.5em}.ck-content[dir=rtl] blockquote{border-left:0;border-right:5px solid #ccc}",""]);const i=r},609:e=>{"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var o=e(t);return t[2]?"@media ".concat(t[2]," {").concat(o,"}"):o})).join("")},t.i=function(e,o,n){"string"==typeof e&&(e=[[null,e,""]]);var r={};if(n)for(var i=0;i<this.length;i++){var c=this[i][0];null!=c&&(r[c]=!0)}for(var s=0;s<e.length;s++){var l=[].concat(e[s]);n&&r[l[0]]||(o&&(l[2]?l[2]="".concat(o," and ").concat(l[2]):l[2]=o),t.push(l))}},t}},62:(e,t,o)=>{"use strict";var n,r=function(){return void 0===n&&(n=Boolean(window&&document&&document.all&&!window.atob)),n},i=function(){var e={};return function(t){if(void 0===e[t]){var o=document.querySelector(t);if(window.HTMLIFrameElement&&o instanceof window.HTMLIFrameElement)try{o=o.contentDocument.head}catch(e){o=null}e[t]=o}return e[t]}}(),c=[];function s(e){for(var t=-1,o=0;o<c.length;o++)if(c[o].identifier===e){t=o;break}return t}function l(e,t){for(var o={},n=[],r=0;r<e.length;r++){var i=e[r],l=t.base?i[0]+t.base:i[0],a=o[l]||0,u="".concat(l," ").concat(a);o[l]=a+1;var d=s(u),f={css:i[1],media:i[2],sourceMap:i[3]};-1!==d?(c[d].references++,c[d].updater(f)):c.push({identifier:u,updater:v(f,t),references:1}),n.push(u)}return n}function a(e){var t=document.createElement("style"),n=e.attributes||{};if(void 0===n.nonce){var r=o.nc;r&&(n.nonce=r)}if(Object.keys(n).forEach((function(e){t.setAttribute(e,n[e])})),"function"==typeof e.insert)e.insert(t);else{var c=i(e.insert||"head");if(!c)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");c.appendChild(t)}return t}var u,d=(u=[],function(e,t){return u[e]=t,u.filter(Boolean).join("\n")});function f(e,t,o,n){var r=o?"":n.media?"@media ".concat(n.media," {").concat(n.css,"}"):n.css;if(e.styleSheet)e.styleSheet.cssText=d(t,r);else{var i=document.createTextNode(r),c=e.childNodes;c[t]&&e.removeChild(c[t]),c.length?e.insertBefore(i,c[t]):e.appendChild(i)}}function p(e,t,o){var n=o.css,r=o.media,i=o.sourceMap;if(r?e.setAttribute("media",r):e.removeAttribute("media"),i&&"undefined"!=typeof btoa&&(n+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(i))))," */")),e.styleSheet)e.styleSheet.cssText=n;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(n))}}var m=null,h=0;function v(e,t){var o,n,r;if(t.singleton){var i=h++;o=m||(m=a(t)),n=f.bind(null,o,i,!1),r=f.bind(null,o,i,!0)}else o=a(t),n=p.bind(null,o,t),r=function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(o)};return n(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;n(e=t)}else r()}}e.exports=function(e,t){(t=t||{}).singleton||"boolean"==typeof t.singleton||(t.singleton=r());var o=l(e=e||[],t);return function(e){if(e=e||[],"[object Array]"===Object.prototype.toString.call(e)){for(var n=0;n<o.length;n++){var r=s(o[n]);c[r].references--}for(var i=l(e,t),a=0;a<o.length;a++){var u=s(o[a]);0===c[u].references&&(c[u].updater(),c.splice(u,1))}o=i}}}},704:(e,t,o)=>{e.exports=o(79)("./src/core.js")},331:(e,t,o)=>{e.exports=o(79)("./src/enter.js")},181:(e,t,o)=>{e.exports=o(79)("./src/typing.js")},273:(e,t,o)=>{e.exports=o(79)("./src/ui.js")},209:(e,t,o)=>{e.exports=o(79)("./src/utils.js")},79:e=>{"use strict";e.exports=CKEditor5.dll}},t={};function o(n){var r=t[n];if(void 0!==r)return r.exports;var i=t[n]={id:n,exports:{}};return e[n](i,i.exports,o),i.exports}o.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},o.d=(e,t)=>{for(var n in t)o.o(t,n)&&!o.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.nc=void 0;var n={};(()=>{"use strict";o.r(n),o.d(n,{BlockQuote:()=>g,BlockQuoteEditing:()=>u,BlockQuoteUI:()=>v});var e=o(704),t=o(331),r=o(181),i=o(209);class c extends e.Command{refresh(){this.value=this._getValue(),this.isEnabled=this._checkEnabled()}execute(e={}){const t=this.editor.model,o=t.schema,n=t.document.selection,r=Array.from(n.getSelectedBlocks()),i=void 0===e.forceValue?!this.value:e.forceValue;t.change((e=>{if(i){const t=r.filter((e=>s(e)||a(o,e)));this._applyQuote(e,t)}else this._removeQuote(e,r.filter(s))}))}_getValue(){const e=this.editor.model.document.selection,t=(0,i.first)(e.getSelectedBlocks());return!(!t||!s(t))}_checkEnabled(){if(this.value)return!0;const e=this.editor.model.document.selection,t=this.editor.model.schema,o=(0,i.first)(e.getSelectedBlocks());return!!o&&a(t,o)}_removeQuote(e,t){l(e,t).reverse().forEach((t=>{if(t.start.isAtStart&&t.end.isAtEnd)return void e.unwrap(t.start.parent);if(t.start.isAtStart){const o=e.createPositionBefore(t.start.parent);return void e.move(t,o)}t.end.isAtEnd||e.split(t.end);const o=e.createPositionAfter(t.end.parent);e.move(t,o)}))}_applyQuote(e,t){const o=[];l(e,t).reverse().forEach((t=>{let n=s(t.start);n||(n=e.createElement("blockQuote"),e.wrap(t,n)),o.push(n)})),o.reverse().reduce(((t,o)=>t.nextSibling==o?(e.merge(e.createPositionAfter(t)),t):o))}}function s(e){return"blockQuote"==e.parent.name?e.parent:null}function l(e,t){let o,n=0;const r=[];for(;n<t.length;){const i=t[n],c=t[n+1];o||(o=e.createPositionBefore(i)),c&&i.nextSibling==c||(r.push(e.createRange(o,e.createPositionAfter(i))),o=null),n++}return r}function a(e,t){const o=e.checkChild(t.parent,"blockQuote"),n=e.checkChild(["$root","blockQuote"],t);return o&&n}class u extends e.Plugin{static get pluginName(){return"BlockQuoteEditing"}static get requires(){return[t.Enter,r.Delete]}init(){const e=this.editor,t=e.model.schema;e.commands.add("blockQuote",new c(e)),t.register("blockQuote",{inheritAllFrom:"$container"}),e.conversion.elementToElement({model:"blockQuote",view:"blockquote"}),e.model.document.registerPostFixer((o=>{const n=e.model.document.differ.getChanges();for(const e of n)if("insert"==e.type){const n=e.position.nodeAfter;if(!n)continue;if(n.is("element","blockQuote")&&n.isEmpty)return o.remove(n),!0;if(n.is("element","blockQuote")&&!t.checkChild(e.position,n))return o.unwrap(n),!0;if(n.is("element")){const e=o.createRangeIn(n);for(const n of e.getItems())if(n.is("element","blockQuote")&&!t.checkChild(o.createPositionBefore(n),n))return o.unwrap(n),!0}}else if("remove"==e.type){const t=e.position.parent;if(t.is("element","blockQuote")&&t.isEmpty)return o.remove(t),!0}return!1}));const o=this.editor.editing.view.document,n=e.model.document.selection,r=e.commands.get("blockQuote");this.listenTo(o,"enter",((t,o)=>{if(!n.isCollapsed||!r.value)return;n.getLastPosition().parent.isEmpty&&(e.execute("blockQuote"),e.editing.view.scrollToTheSelection(),o.preventDefault(),t.stop())}),{context:"blockquote"}),this.listenTo(o,"delete",((t,o)=>{if("backward"!=o.direction||!n.isCollapsed||!r.value)return;const i=n.getLastPosition().parent;i.isEmpty&&!i.previousSibling&&(e.execute("blockQuote"),e.editing.view.scrollToTheSelection(),o.preventDefault(),t.stop())}),{context:"blockquote"})}}var d=o(273),f=o(62),p=o.n(f),m=o(446),h={injectType:"singletonStyleTag",attributes:{"data-cke":!0},insert:"head",singleton:!0};p()(m.Z,h);m.Z.locals;class v extends e.Plugin{static get pluginName(){return"BlockQuoteUI"}init(){const t=this.editor,o=t.t;t.ui.componentFactory.add("blockQuote",(n=>{const r=t.commands.get("blockQuote"),i=new d.ButtonView(n);return i.set({label:o("Block quote"),icon:e.icons.quote,tooltip:!0,isToggleable:!0}),i.bind("isOn","isEnabled").to(r,"value","isEnabled"),this.listenTo(i,"execute",(()=>{t.execute("blockQuote"),t.editing.view.focus()})),i}))}}class g extends e.Plugin{static get requires(){return[u,v]}static get pluginName(){return"BlockQuote"}}})(),(window.CKEditor5=window.CKEditor5||{}).blockQuote=n})();
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@ckeditor/ckeditor5-block-quote",
|
3
|
-
"version": "
|
3
|
+
"version": "36.0.0",
|
4
4
|
"description": "Block quote feature for CKEditor 5.",
|
5
5
|
"keywords": [
|
6
6
|
"ckeditor",
|
@@ -12,22 +12,23 @@
|
|
12
12
|
],
|
13
13
|
"main": "src/index.js",
|
14
14
|
"dependencies": {
|
15
|
-
"ckeditor5": "^
|
15
|
+
"ckeditor5": "^36.0.0"
|
16
16
|
},
|
17
17
|
"devDependencies": {
|
18
|
-
"@ckeditor/ckeditor5-basic-styles": "^
|
19
|
-
"@ckeditor/ckeditor5-core": "^
|
20
|
-
"@ckeditor/ckeditor5-dev-utils": "^
|
21
|
-
"@ckeditor/ckeditor5-editor-classic": "^
|
22
|
-
"@ckeditor/ckeditor5-engine": "^
|
23
|
-
"@ckeditor/ckeditor5-enter": "^
|
24
|
-
"@ckeditor/ckeditor5-heading": "^
|
25
|
-
"@ckeditor/ckeditor5-image": "^
|
26
|
-
"@ckeditor/ckeditor5-list": "^
|
27
|
-
"@ckeditor/ckeditor5-paragraph": "^
|
28
|
-
"@ckeditor/ckeditor5-table": "^
|
29
|
-
"@ckeditor/ckeditor5-theme-lark": "^
|
30
|
-
"@ckeditor/ckeditor5-typing": "^
|
18
|
+
"@ckeditor/ckeditor5-basic-styles": "^36.0.0",
|
19
|
+
"@ckeditor/ckeditor5-core": "^36.0.0",
|
20
|
+
"@ckeditor/ckeditor5-dev-utils": "^32.0.0",
|
21
|
+
"@ckeditor/ckeditor5-editor-classic": "^36.0.0",
|
22
|
+
"@ckeditor/ckeditor5-engine": "^36.0.0",
|
23
|
+
"@ckeditor/ckeditor5-enter": "^36.0.0",
|
24
|
+
"@ckeditor/ckeditor5-heading": "^36.0.0",
|
25
|
+
"@ckeditor/ckeditor5-image": "^36.0.0",
|
26
|
+
"@ckeditor/ckeditor5-list": "^36.0.0",
|
27
|
+
"@ckeditor/ckeditor5-paragraph": "^36.0.0",
|
28
|
+
"@ckeditor/ckeditor5-table": "^36.0.0",
|
29
|
+
"@ckeditor/ckeditor5-theme-lark": "^36.0.0",
|
30
|
+
"@ckeditor/ckeditor5-typing": "^36.0.0",
|
31
|
+
"typescript": "^4.8.4",
|
31
32
|
"webpack": "^5.58.1",
|
32
33
|
"webpack-cli": "^4.9.0"
|
33
34
|
},
|
@@ -46,13 +47,16 @@
|
|
46
47
|
},
|
47
48
|
"files": [
|
48
49
|
"lang",
|
49
|
-
"src",
|
50
|
+
"src/**/*.js",
|
51
|
+
"src/**/*.d.ts",
|
50
52
|
"theme",
|
51
53
|
"build",
|
52
54
|
"ckeditor5-metadata.json",
|
53
55
|
"CHANGELOG.md"
|
54
56
|
],
|
55
57
|
"scripts": {
|
56
|
-
"dll:build": "webpack"
|
58
|
+
"dll:build": "webpack",
|
59
|
+
"build": "tsc -p ./tsconfig.release.json",
|
60
|
+
"postversion": "npm run build"
|
57
61
|
}
|
58
62
|
}
|
package/src/blockquote.js
CHANGED
@@ -1,17 +1,13 @@
|
|
1
1
|
/**
|
2
|
-
* @license Copyright (c) 2003-
|
2
|
+
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
4
4
|
*/
|
5
|
-
|
6
5
|
/**
|
7
6
|
* @module block-quote/blockquote
|
8
7
|
*/
|
9
|
-
|
10
8
|
import { Plugin } from 'ckeditor5/src/core';
|
11
|
-
|
12
9
|
import BlockQuoteEditing from './blockquoteediting';
|
13
10
|
import BlockQuoteUI from './blockquoteui';
|
14
|
-
|
15
11
|
/**
|
16
12
|
* The block quote plugin.
|
17
13
|
*
|
@@ -23,17 +19,16 @@ import BlockQuoteUI from './blockquoteui';
|
|
23
19
|
* @extends module:core/plugin~Plugin
|
24
20
|
*/
|
25
21
|
export default class BlockQuote extends Plugin {
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
}
|
22
|
+
/**
|
23
|
+
* @inheritDoc
|
24
|
+
*/
|
25
|
+
static get requires() {
|
26
|
+
return [BlockQuoteEditing, BlockQuoteUI];
|
27
|
+
}
|
28
|
+
/**
|
29
|
+
* @inheritDoc
|
30
|
+
*/
|
31
|
+
static get pluginName() {
|
32
|
+
return 'BlockQuote';
|
33
|
+
}
|
39
34
|
}
|
package/src/blockquotecommand.js
CHANGED
@@ -1,232 +1,172 @@
|
|
1
1
|
/**
|
2
|
-
* @license Copyright (c) 2003-
|
2
|
+
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
4
4
|
*/
|
5
|
-
|
6
5
|
/**
|
7
6
|
* @module block-quote/blockquotecommand
|
8
7
|
*/
|
9
|
-
|
10
8
|
import { Command } from 'ckeditor5/src/core';
|
11
9
|
import { first } from 'ckeditor5/src/utils';
|
12
|
-
|
13
10
|
/**
|
14
11
|
* The block quote command plugin.
|
15
12
|
*
|
16
13
|
* @extends module:core/command~Command
|
17
14
|
*/
|
18
15
|
export default class BlockQuoteCommand extends Command {
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
if ( !groupRange.end.isAtEnd ) {
|
139
|
-
writer.split( groupRange.end );
|
140
|
-
}
|
141
|
-
|
142
|
-
// Now we are sure that groupRange.end.isAtEnd is true, so let's move the blocks right.
|
143
|
-
|
144
|
-
const positionAfter = writer.createPositionAfter( groupRange.end.parent );
|
145
|
-
|
146
|
-
writer.move( groupRange, positionAfter );
|
147
|
-
} );
|
148
|
-
}
|
149
|
-
|
150
|
-
/**
|
151
|
-
* Applies the quote to given blocks.
|
152
|
-
*
|
153
|
-
* @private
|
154
|
-
* @param {module:engine/model/writer~Writer} writer
|
155
|
-
* @param {Array.<module:engine/model/element~Element>} blocks
|
156
|
-
*/
|
157
|
-
_applyQuote( writer, blocks ) {
|
158
|
-
const quotesToMerge = [];
|
159
|
-
|
160
|
-
// Quote all groups of block. Iterate in the reverse order to not break following ranges.
|
161
|
-
getRangesOfBlockGroups( writer, blocks ).reverse().forEach( groupRange => {
|
162
|
-
let quote = findQuote( groupRange.start );
|
163
|
-
|
164
|
-
if ( !quote ) {
|
165
|
-
quote = writer.createElement( 'blockQuote' );
|
166
|
-
|
167
|
-
writer.wrap( groupRange, quote );
|
168
|
-
}
|
169
|
-
|
170
|
-
quotesToMerge.push( quote );
|
171
|
-
} );
|
172
|
-
|
173
|
-
// Merge subsequent <bQ> elements. Reverse the order again because this time we want to go through
|
174
|
-
// the <bQ> elements in the source order (due to how merge works – it moves the right element's content
|
175
|
-
// to the first element and removes the right one. Since we may need to merge a couple of subsequent `<bQ>` elements
|
176
|
-
// we want to keep the reference to the first (furthest left) one.
|
177
|
-
quotesToMerge.reverse().reduce( ( currentQuote, nextQuote ) => {
|
178
|
-
if ( currentQuote.nextSibling == nextQuote ) {
|
179
|
-
writer.merge( writer.createPositionAfter( currentQuote ) );
|
180
|
-
|
181
|
-
return currentQuote;
|
182
|
-
}
|
183
|
-
|
184
|
-
return nextQuote;
|
185
|
-
} );
|
186
|
-
}
|
16
|
+
/**
|
17
|
+
* @inheritDoc
|
18
|
+
*/
|
19
|
+
refresh() {
|
20
|
+
this.value = this._getValue();
|
21
|
+
this.isEnabled = this._checkEnabled();
|
22
|
+
}
|
23
|
+
/**
|
24
|
+
* Executes the command. When the command {@link #value is on}, all top-most block quotes within
|
25
|
+
* the selection will be removed. If it is off, all selected blocks will be wrapped with
|
26
|
+
* a block quote.
|
27
|
+
*
|
28
|
+
* @fires execute
|
29
|
+
* @param options Command options.
|
30
|
+
* @param options.forceValue If set, it will force the command behavior. If `true`, the command will apply a block quote,
|
31
|
+
* otherwise the command will remove the block quote. If not set, the command will act basing on its current value.
|
32
|
+
*/
|
33
|
+
execute(options = {}) {
|
34
|
+
const model = this.editor.model;
|
35
|
+
const schema = model.schema;
|
36
|
+
const selection = model.document.selection;
|
37
|
+
const blocks = Array.from(selection.getSelectedBlocks());
|
38
|
+
const value = (options.forceValue === undefined) ? !this.value : options.forceValue;
|
39
|
+
model.change(writer => {
|
40
|
+
if (!value) {
|
41
|
+
this._removeQuote(writer, blocks.filter(findQuote));
|
42
|
+
}
|
43
|
+
else {
|
44
|
+
const blocksToQuote = blocks.filter(block => {
|
45
|
+
// Already quoted blocks needs to be considered while quoting too
|
46
|
+
// in order to reuse their <bQ> elements.
|
47
|
+
return findQuote(block) || checkCanBeQuoted(schema, block);
|
48
|
+
});
|
49
|
+
this._applyQuote(writer, blocksToQuote);
|
50
|
+
}
|
51
|
+
});
|
52
|
+
}
|
53
|
+
/**
|
54
|
+
* Checks the command's {@link #value}.
|
55
|
+
*/
|
56
|
+
_getValue() {
|
57
|
+
const selection = this.editor.model.document.selection;
|
58
|
+
const firstBlock = first(selection.getSelectedBlocks());
|
59
|
+
// In the current implementation, the block quote must be an immediate parent of a block element.
|
60
|
+
return !!(firstBlock && findQuote(firstBlock));
|
61
|
+
}
|
62
|
+
/**
|
63
|
+
* Checks whether the command can be enabled in the current context.
|
64
|
+
*
|
65
|
+
* @returns Whether the command should be enabled.
|
66
|
+
*/
|
67
|
+
_checkEnabled() {
|
68
|
+
if (this.value) {
|
69
|
+
return true;
|
70
|
+
}
|
71
|
+
const selection = this.editor.model.document.selection;
|
72
|
+
const schema = this.editor.model.schema;
|
73
|
+
const firstBlock = first(selection.getSelectedBlocks());
|
74
|
+
if (!firstBlock) {
|
75
|
+
return false;
|
76
|
+
}
|
77
|
+
return checkCanBeQuoted(schema, firstBlock);
|
78
|
+
}
|
79
|
+
/**
|
80
|
+
* Removes the quote from given blocks.
|
81
|
+
*
|
82
|
+
* If blocks which are supposed to be "unquoted" are in the middle of a quote,
|
83
|
+
* start it or end it, then the quote will be split (if needed) and the blocks
|
84
|
+
* will be moved out of it, so other quoted blocks remained quoted.
|
85
|
+
*/
|
86
|
+
_removeQuote(writer, blocks) {
|
87
|
+
// Unquote all groups of block. Iterate in the reverse order to not break following ranges.
|
88
|
+
getRangesOfBlockGroups(writer, blocks).reverse().forEach(groupRange => {
|
89
|
+
if (groupRange.start.isAtStart && groupRange.end.isAtEnd) {
|
90
|
+
writer.unwrap(groupRange.start.parent);
|
91
|
+
return;
|
92
|
+
}
|
93
|
+
// The group of blocks are at the beginning of an <bQ> so let's move them left (out of the <bQ>).
|
94
|
+
if (groupRange.start.isAtStart) {
|
95
|
+
const positionBefore = writer.createPositionBefore(groupRange.start.parent);
|
96
|
+
writer.move(groupRange, positionBefore);
|
97
|
+
return;
|
98
|
+
}
|
99
|
+
// The blocks are in the middle of an <bQ> so we need to split the <bQ> after the last block
|
100
|
+
// so we move the items there.
|
101
|
+
if (!groupRange.end.isAtEnd) {
|
102
|
+
writer.split(groupRange.end);
|
103
|
+
}
|
104
|
+
// Now we are sure that groupRange.end.isAtEnd is true, so let's move the blocks right.
|
105
|
+
const positionAfter = writer.createPositionAfter(groupRange.end.parent);
|
106
|
+
writer.move(groupRange, positionAfter);
|
107
|
+
});
|
108
|
+
}
|
109
|
+
/**
|
110
|
+
* Applies the quote to given blocks.
|
111
|
+
*/
|
112
|
+
_applyQuote(writer, blocks) {
|
113
|
+
const quotesToMerge = [];
|
114
|
+
// Quote all groups of block. Iterate in the reverse order to not break following ranges.
|
115
|
+
getRangesOfBlockGroups(writer, blocks).reverse().forEach(groupRange => {
|
116
|
+
let quote = findQuote(groupRange.start);
|
117
|
+
if (!quote) {
|
118
|
+
quote = writer.createElement('blockQuote');
|
119
|
+
writer.wrap(groupRange, quote);
|
120
|
+
}
|
121
|
+
quotesToMerge.push(quote);
|
122
|
+
});
|
123
|
+
// Merge subsequent <bQ> elements. Reverse the order again because this time we want to go through
|
124
|
+
// the <bQ> elements in the source order (due to how merge works – it moves the right element's content
|
125
|
+
// to the first element and removes the right one. Since we may need to merge a couple of subsequent `<bQ>` elements
|
126
|
+
// we want to keep the reference to the first (furthest left) one.
|
127
|
+
quotesToMerge.reverse().reduce((currentQuote, nextQuote) => {
|
128
|
+
if (currentQuote.nextSibling == nextQuote) {
|
129
|
+
writer.merge(writer.createPositionAfter(currentQuote));
|
130
|
+
return currentQuote;
|
131
|
+
}
|
132
|
+
return nextQuote;
|
133
|
+
});
|
134
|
+
}
|
187
135
|
}
|
188
|
-
|
189
|
-
|
190
|
-
return elementOrPosition.parent.name == 'blockQuote' ? elementOrPosition.parent : null;
|
136
|
+
function findQuote(elementOrPosition) {
|
137
|
+
return elementOrPosition.parent.name == 'blockQuote' ? elementOrPosition.parent : null;
|
191
138
|
}
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
startPosition = null;
|
217
|
-
}
|
218
|
-
|
219
|
-
i++;
|
220
|
-
}
|
221
|
-
|
222
|
-
return ranges;
|
139
|
+
/**
|
140
|
+
* Returns a minimal array of ranges containing groups of subsequent blocks.
|
141
|
+
*
|
142
|
+
* content: abcdefgh
|
143
|
+
* blocks: [ a, b, d, f, g, h ]
|
144
|
+
* output ranges: [ab]c[d]e[fgh]
|
145
|
+
*/
|
146
|
+
function getRangesOfBlockGroups(writer, blocks) {
|
147
|
+
let startPosition;
|
148
|
+
let i = 0;
|
149
|
+
const ranges = [];
|
150
|
+
while (i < blocks.length) {
|
151
|
+
const block = blocks[i];
|
152
|
+
const nextBlock = blocks[i + 1];
|
153
|
+
if (!startPosition) {
|
154
|
+
startPosition = writer.createPositionBefore(block);
|
155
|
+
}
|
156
|
+
if (!nextBlock || block.nextSibling != nextBlock) {
|
157
|
+
ranges.push(writer.createRange(startPosition, writer.createPositionAfter(block)));
|
158
|
+
startPosition = null;
|
159
|
+
}
|
160
|
+
i++;
|
161
|
+
}
|
162
|
+
return ranges;
|
223
163
|
}
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
164
|
+
/**
|
165
|
+
* Checks whether <bQ> can wrap the block.
|
166
|
+
*/
|
167
|
+
function checkCanBeQuoted(schema, block) {
|
168
|
+
// TMP will be replaced with schema.checkWrap().
|
169
|
+
const isBQAllowed = schema.checkChild(block.parent, 'blockQuote');
|
170
|
+
const isBlockAllowedInBQ = schema.checkChild(['$root', 'blockQuote'], block);
|
171
|
+
return isBQAllowed && isBlockAllowedInBQ;
|
232
172
|
}
|
package/src/blockquoteediting.js
CHANGED
@@ -1,18 +1,14 @@
|
|
1
1
|
/**
|
2
|
-
* @license Copyright (c) 2003-
|
2
|
+
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
4
4
|
*/
|
5
|
-
|
6
5
|
/**
|
7
6
|
* @module block-quote/blockquoteediting
|
8
7
|
*/
|
9
|
-
|
10
8
|
import { Plugin } from 'ckeditor5/src/core';
|
11
9
|
import { Enter } from 'ckeditor5/src/enter';
|
12
10
|
import { Delete } from 'ckeditor5/src/typing';
|
13
|
-
|
14
11
|
import BlockQuoteCommand from './blockquotecommand';
|
15
|
-
|
16
12
|
/**
|
17
13
|
* The block quote editing.
|
18
14
|
*
|
@@ -21,126 +17,102 @@ import BlockQuoteCommand from './blockquotecommand';
|
|
21
17
|
* @extends module:core/plugin~Plugin
|
22
18
|
*/
|
23
19
|
export default class BlockQuoteEditing extends Plugin {
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
data.preventDefault();
|
124
|
-
evt.stop();
|
125
|
-
}
|
126
|
-
}, { context: 'blockquote' } );
|
127
|
-
|
128
|
-
// Overwrite default Backspace key behavior.
|
129
|
-
// If Backspace key is pressed with selection collapsed in first empty block inside a quote, break the quote.
|
130
|
-
this.listenTo( viewDocument, 'delete', ( evt, data ) => {
|
131
|
-
if ( data.direction != 'backward' || !selection.isCollapsed || !blockQuoteCommand.value ) {
|
132
|
-
return;
|
133
|
-
}
|
134
|
-
|
135
|
-
const positionParent = selection.getLastPosition().parent;
|
136
|
-
|
137
|
-
if ( positionParent.isEmpty && !positionParent.previousSibling ) {
|
138
|
-
editor.execute( 'blockQuote' );
|
139
|
-
editor.editing.view.scrollToTheSelection();
|
140
|
-
|
141
|
-
data.preventDefault();
|
142
|
-
evt.stop();
|
143
|
-
}
|
144
|
-
}, { context: 'blockquote' } );
|
145
|
-
}
|
20
|
+
/**
|
21
|
+
* @inheritDoc
|
22
|
+
*/
|
23
|
+
static get pluginName() {
|
24
|
+
return 'BlockQuoteEditing';
|
25
|
+
}
|
26
|
+
/**
|
27
|
+
* @inheritDoc
|
28
|
+
*/
|
29
|
+
static get requires() {
|
30
|
+
return [Enter, Delete];
|
31
|
+
}
|
32
|
+
/**
|
33
|
+
* @inheritDoc
|
34
|
+
*/
|
35
|
+
init() {
|
36
|
+
const editor = this.editor;
|
37
|
+
const schema = editor.model.schema;
|
38
|
+
editor.commands.add('blockQuote', new BlockQuoteCommand(editor));
|
39
|
+
schema.register('blockQuote', {
|
40
|
+
inheritAllFrom: '$container'
|
41
|
+
});
|
42
|
+
editor.conversion.elementToElement({ model: 'blockQuote', view: 'blockquote' });
|
43
|
+
// Postfixer which cleans incorrect model states connected with block quotes.
|
44
|
+
editor.model.document.registerPostFixer(writer => {
|
45
|
+
const changes = editor.model.document.differ.getChanges();
|
46
|
+
for (const entry of changes) {
|
47
|
+
if (entry.type == 'insert') {
|
48
|
+
const element = entry.position.nodeAfter;
|
49
|
+
if (!element) {
|
50
|
+
// We are inside a text node.
|
51
|
+
continue;
|
52
|
+
}
|
53
|
+
if (element.is('element', 'blockQuote') && element.isEmpty) {
|
54
|
+
// Added an empty blockQuote - remove it.
|
55
|
+
writer.remove(element);
|
56
|
+
return true;
|
57
|
+
}
|
58
|
+
else if (element.is('element', 'blockQuote') && !schema.checkChild(entry.position, element)) {
|
59
|
+
// Added a blockQuote in incorrect place. Unwrap it so the content inside is not lost.
|
60
|
+
writer.unwrap(element);
|
61
|
+
return true;
|
62
|
+
}
|
63
|
+
else if (element.is('element')) {
|
64
|
+
// Just added an element. Check that all children meet the scheme rules.
|
65
|
+
const range = writer.createRangeIn(element);
|
66
|
+
for (const child of range.getItems()) {
|
67
|
+
if (child.is('element', 'blockQuote') &&
|
68
|
+
!schema.checkChild(writer.createPositionBefore(child), child)) {
|
69
|
+
writer.unwrap(child);
|
70
|
+
return true;
|
71
|
+
}
|
72
|
+
}
|
73
|
+
}
|
74
|
+
}
|
75
|
+
else if (entry.type == 'remove') {
|
76
|
+
const parent = entry.position.parent;
|
77
|
+
if (parent.is('element', 'blockQuote') && parent.isEmpty) {
|
78
|
+
// Something got removed and now blockQuote is empty. Remove the blockQuote as well.
|
79
|
+
writer.remove(parent);
|
80
|
+
return true;
|
81
|
+
}
|
82
|
+
}
|
83
|
+
}
|
84
|
+
return false;
|
85
|
+
});
|
86
|
+
const viewDocument = this.editor.editing.view.document;
|
87
|
+
const selection = editor.model.document.selection;
|
88
|
+
const blockQuoteCommand = editor.commands.get('blockQuote');
|
89
|
+
// Overwrite default Enter key behavior.
|
90
|
+
// If Enter key is pressed with selection collapsed in empty block inside a quote, break the quote.
|
91
|
+
this.listenTo(viewDocument, 'enter', (evt, data) => {
|
92
|
+
if (!selection.isCollapsed || !blockQuoteCommand.value) {
|
93
|
+
return;
|
94
|
+
}
|
95
|
+
const positionParent = selection.getLastPosition().parent;
|
96
|
+
if (positionParent.isEmpty) {
|
97
|
+
editor.execute('blockQuote');
|
98
|
+
editor.editing.view.scrollToTheSelection();
|
99
|
+
data.preventDefault();
|
100
|
+
evt.stop();
|
101
|
+
}
|
102
|
+
}, { context: 'blockquote' });
|
103
|
+
// Overwrite default Backspace key behavior.
|
104
|
+
// If Backspace key is pressed with selection collapsed in first empty block inside a quote, break the quote.
|
105
|
+
this.listenTo(viewDocument, 'delete', (evt, data) => {
|
106
|
+
if (data.direction != 'backward' || !selection.isCollapsed || !blockQuoteCommand.value) {
|
107
|
+
return;
|
108
|
+
}
|
109
|
+
const positionParent = selection.getLastPosition().parent;
|
110
|
+
if (positionParent.isEmpty && !positionParent.previousSibling) {
|
111
|
+
editor.execute('blockQuote');
|
112
|
+
editor.editing.view.scrollToTheSelection();
|
113
|
+
data.preventDefault();
|
114
|
+
evt.stop();
|
115
|
+
}
|
116
|
+
}, { context: 'blockquote' });
|
117
|
+
}
|
146
118
|
}
|
package/src/blockquoteui.js
CHANGED
@@ -1,17 +1,13 @@
|
|
1
1
|
/**
|
2
|
-
* @license Copyright (c) 2003-
|
2
|
+
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
4
4
|
*/
|
5
|
-
|
6
5
|
/**
|
7
6
|
* @module block-quote/blockquoteui
|
8
7
|
*/
|
9
|
-
|
10
8
|
import { Plugin, icons } from 'ckeditor5/src/core';
|
11
9
|
import { ButtonView } from 'ckeditor5/src/ui';
|
12
|
-
|
13
10
|
import '../theme/blockquote.css';
|
14
|
-
|
15
11
|
/**
|
16
12
|
* The block quote UI plugin.
|
17
13
|
*
|
@@ -20,41 +16,35 @@ import '../theme/blockquote.css';
|
|
20
16
|
* @extends module:core/plugin~Plugin
|
21
17
|
*/
|
22
18
|
export default class BlockQuoteUI extends Plugin {
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
editor.editing.view.focus();
|
55
|
-
} );
|
56
|
-
|
57
|
-
return buttonView;
|
58
|
-
} );
|
59
|
-
}
|
19
|
+
/**
|
20
|
+
* @inheritDoc
|
21
|
+
*/
|
22
|
+
static get pluginName() {
|
23
|
+
return 'BlockQuoteUI';
|
24
|
+
}
|
25
|
+
/**
|
26
|
+
* @inheritDoc
|
27
|
+
*/
|
28
|
+
init() {
|
29
|
+
const editor = this.editor;
|
30
|
+
const t = editor.t;
|
31
|
+
editor.ui.componentFactory.add('blockQuote', locale => {
|
32
|
+
const command = editor.commands.get('blockQuote');
|
33
|
+
const buttonView = new ButtonView(locale);
|
34
|
+
buttonView.set({
|
35
|
+
label: t('Block quote'),
|
36
|
+
icon: icons.quote,
|
37
|
+
tooltip: true,
|
38
|
+
isToggleable: true
|
39
|
+
});
|
40
|
+
// Bind button model to command.
|
41
|
+
buttonView.bind('isOn', 'isEnabled').to(command, 'value', 'isEnabled');
|
42
|
+
// Execute command.
|
43
|
+
this.listenTo(buttonView, 'execute', () => {
|
44
|
+
editor.execute('blockQuote');
|
45
|
+
editor.editing.view.focus();
|
46
|
+
});
|
47
|
+
return buttonView;
|
48
|
+
});
|
49
|
+
}
|
60
50
|
}
|
package/src/index.js
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
/**
|
2
|
-
* @license Copyright (c) 2003-
|
2
|
+
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
4
4
|
*/
|
5
|
-
|
6
5
|
/**
|
7
6
|
* @module block-quote
|
8
7
|
*/
|
9
|
-
|
10
8
|
export { default as BlockQuote } from './blockquote';
|
11
9
|
export { default as BlockQuoteEditing } from './blockquoteediting';
|
12
10
|
export { default as BlockQuoteUI } from './blockquoteui';
|
package/theme/blockquote.css
CHANGED