@ckeditor/ckeditor5-language 35.4.0 → 36.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +1 -1
- package/build/language.js +2 -2
- package/package.json +15 -11
- package/src/index.js +1 -3
- package/src/textpartlanguage.js +13 -63
- package/src/textpartlanguagecommand.js +79 -114
- package/src/textpartlanguageediting.js +71 -92
- package/src/textpartlanguageui.js +78 -101
- package/src/utils.js +16 -20
- package/theme/language.css +1 -1
package/LICENSE.md
CHANGED
|
@@ -2,7 +2,7 @@ Software License Agreement
|
|
|
2
2
|
==========================
|
|
3
3
|
|
|
4
4
|
**CKEditor 5 text part language feature** – https://github.com/ckeditor/ckeditor5-language <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/language.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
!function(e){const t=e.en=e.en||{};t.dictionary=Object.assign(t.dictionary||{},{"Choose language":"Choose language",Language:"Language","Remove language":"Remove language"})}(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
|
-
*/(()=>{var e={176:(e,t,n)=>{"use strict";n.d(t,{Z:()=>o});var a=n(609),r=n.n(a)()((function(e){return e[1]}));r.push([e.id,".ck-content span[lang]{font-style:italic}",""]);const o=r},609:e=>{"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n=e(t);return t[2]?"@media ".concat(t[2]," {").concat(n,"}"):n})).join("")},t.i=function(e,n,a){"string"==typeof e&&(e=[[null,e,""]]);var r={};if(a)for(var o=0;o<this.length;o++){var i=this[o][0];null!=i&&(r[i]=!0)}for(var s=0;s<e.length;s++){var u=[].concat(e[s]);a&&r[u[0]]||(n&&(u[2]?u[2]="".concat(n," and ").concat(u[2]):u[2]=n),t.push(u))}},t}},62:(e,t,n)=>{"use strict";var a,r=function(){return void 0===a&&(a=Boolean(window&&document&&document.all&&!window.atob)),a},o=function(){var e={};return function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}e[t]=n}return e[t]}}(),i=[];function s(e){for(var t=-1,n=0;n<i.length;n++)if(i[n].identifier===e){t=n;break}return t}function u(e,t){for(var n={},a=[],r=0;r<e.length;r++){var o=e[r],u=t.base?o[0]+t.base:o[0],l=n[u]||0,c="".concat(u," ").concat(l);n[u]=l+1;var d=s(c),g={css:o[1],media:o[2],sourceMap:o[3]};-1!==d?(i[d].references++,i[d].updater(g)):i.push({identifier:c,updater:h(g,t),references:1}),a.push(c)}return a}function l(e){var t=document.createElement("style"),a=e.attributes||{};if(void 0===a.nonce){var r=n.nc;r&&(a.nonce=r)}if(Object.keys(a).forEach((function(e){t.setAttribute(e,a[e])})),"function"==typeof e.insert)e.insert(t);else{var i=o(e.insert||"head");if(!i)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");i.appendChild(t)}return t}var c,d=(c=[],function(e,t){return c[e]=t,c.filter(Boolean).join("\n")});function g(e,t,n,a){var r=n?"":a.media?"@media ".concat(a.media," {").concat(a.css,"}"):a.css;if(e.styleSheet)e.styleSheet.cssText=d(t,r);else{var o=document.createTextNode(r),i=e.childNodes;i[t]&&e.removeChild(i[t]),i.length?e.insertBefore(o,i[t]):e.appendChild(o)}}function f(e,t,n){var a=n.css,r=n.media,o=n.sourceMap;if(r?e.setAttribute("media",r):e.removeAttribute("media"),o&&"undefined"!=typeof btoa&&(a+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(o))))," */")),e.styleSheet)e.styleSheet.cssText=a;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(a))}}var m=null,p=0;function h(e,t){var n,a,r;if(t.singleton){var o=p++;n=m||(m=l(t)),a=g.bind(null,n,o,!1),r=g.bind(null,n,o,!0)}else n=l(t),a=f.bind(null,n,t),r=function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(n)};return a(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;a(e=t)}else r()}}e.exports=function(e,t){(t=t||{}).singleton||"boolean"==typeof t.singleton||(t.singleton=r());var n=u(e=e||[],t);return function(e){if(e=e||[],"[object Array]"===Object.prototype.toString.call(e)){for(var a=0;a<n.length;a++){var r=s(n[a]);i[r].references--}for(var o=u(e,t),l=0;l<n.length;l++){var c=s(n[l]);0===i[c].references&&(i[c].updater(),i.splice(c,1))}n=o}}}},704:(e,t,n)=>{e.exports=n(79)("./src/core.js")},273:(e,t,n)=>{e.exports=n(79)("./src/ui.js")},209:(e,t,n)=>{e.exports=n(79)("./src/utils.js")},79:e=>{"use strict";e.exports=CKEditor5.dll}},t={};function n(a){var r=t[a];if(void 0!==r)return r.exports;var o=t[a]={id:a,exports:{}};return e[a](o,o.exports,n),o.exports}n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var a in t)n.o(t,a)&&!n.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.nc=void 0;var a={};(()=>{"use strict";n.r(a),n.d(a,{TextPartLanguage:()=>f,TextPartLanguageEditing:()=>i,TextPartLanguageUI:()=>g});var e=n(704),t=n(209);function r(e,n){return`${e}:${n=n||(0,t.getLanguageDirection)(e)}`}class o extends e.Command{refresh(){const e=this.editor.model,t=e.document;this.value=this._getValueFromFirstAllowedNode(),this.isEnabled=e.schema.checkAttributeInSelection(t.selection,"language")}execute({languageCode:e,textDirection:t}={}){const n=this.editor.model,a=n.document.selection,o=!!e&&r(e,t);n.change((e=>{if(a.isCollapsed)o?e.setSelectionAttribute("language",o):e.removeSelectionAttribute("language");else{const t=n.schema.getValidRanges(a.getRanges(),"language");for(const n of t)o?e.setAttribute("language",o,n):e.removeAttribute("language",n)}}))}_getValueFromFirstAllowedNode(){const e=this.editor.model,t=e.schema,n=e.document.selection;if(n.isCollapsed)return n.getAttribute("language")||!1;for(const e of n.getRanges())for(const n of e.getItems())if(t.checkAttribute(n,"language"))return n.getAttribute("language")||!1;return!1}}class i extends e.Plugin{static get pluginName(){return"TextPartLanguageEditing"}constructor(e){super(e),e.config.define("language",{textPartLanguage:[{title:"Arabic",languageCode:"ar"},{title:"French",languageCode:"fr"},{title:"Spanish",languageCode:"es"}]})}init(){const e=this.editor;e.model.schema.extend("$text",{allowAttributes:"language"}),e.model.schema.setAttributeProperties("language",{copyOnEnter:!0}),this._defineConverters(),e.commands.add("textPartLanguage",new o(e))}_defineConverters(){const e=this.editor.conversion;e.for("upcast").elementToAttribute({model:{key:"language",value:e=>r(e.getAttribute("lang"),e.getAttribute("dir"))},view:{name:"span",attributes:{lang:/[\s\S]+/}}}),e.for("downcast").attributeToElement({model:"language",view:(e,{writer:t},n)=>{if(!e)return;if(!n.item.is("$textProxy")&&!n.item.is("documentSelection"))return;const{languageCode:a,textDirection:r}=function(e){const[t,n]=e.split(":");return{languageCode:t,textDirection:n}}(e);return t.createAttributeElement("span",{lang:a,dir:r})}})}}var s=n(273),u=n(62),l=n.n(u),c=n(176),d={injectType:"singletonStyleTag",attributes:{"data-cke":!0},insert:"head",singleton:!0};l()(c.Z,d);c.Z.locals;class g extends e.Plugin{static get pluginName(){return"TextPartLanguageUI"}init(){const e=this.editor,n=e.t,a=e.config.get("language.textPartLanguage"),o=n("Choose language"),i=n("Remove language"),u=n("Language");e.ui.componentFactory.add("textPartLanguage",(n=>{const l=new t.Collection,c={},d=e.commands.get("textPartLanguage");l.add({type:"button",model:new s.Model({label:i,languageCode:!1,withText:!0})}),l.add({type:"separator"});for(const e of a){const t={type:"button",model:new s.Model({label:e.title,languageCode:e.languageCode,textDirection:e.textDirection,withText:!0})},n=r(e.languageCode,e.textDirection);t.model.bind("isOn").to(d,"value",(e=>e===n)),l.add(t),c[n]=e.title}const g=(0,s.createDropdown)(n);return(0,s.addListToDropdown)(g,l),g.buttonView.set({isOn:!1,withText:!0,tooltip:u}),g.extendTemplate({attributes:{class:["ck-text-fragment-language-dropdown"]}}),g.bind("isEnabled").to(d,"isEnabled"),g.buttonView.bind("label").to(d,"value",(e=>c[e]||o)),this.listenTo(g,"execute",(t=>{d.execute({languageCode:t.source.languageCode,textDirection:t.source.textDirection}),e.editing.view.focus()})),g}))}}class f extends e.Plugin{static get requires(){return[i,g]}static get pluginName(){return"TextPartLanguage"}}})(),(window.CKEditor5=window.CKEditor5||{}).language=a})();
|
|
5
|
+
*/(()=>{var e={176:(e,t,n)=>{"use strict";n.d(t,{Z:()=>o});var a=n(609),r=n.n(a)()((function(e){return e[1]}));r.push([e.id,".ck-content span[lang]{font-style:italic}",""]);const o=r},609:e=>{"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n=e(t);return t[2]?"@media ".concat(t[2]," {").concat(n,"}"):n})).join("")},t.i=function(e,n,a){"string"==typeof e&&(e=[[null,e,""]]);var r={};if(a)for(var o=0;o<this.length;o++){var i=this[o][0];null!=i&&(r[i]=!0)}for(var s=0;s<e.length;s++){var u=[].concat(e[s]);a&&r[u[0]]||(n&&(u[2]?u[2]="".concat(n," and ").concat(u[2]):u[2]=n),t.push(u))}},t}},62:(e,t,n)=>{"use strict";var a,r=function(){return void 0===a&&(a=Boolean(window&&document&&document.all&&!window.atob)),a},o=function(){var e={};return function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}e[t]=n}return e[t]}}(),i=[];function s(e){for(var t=-1,n=0;n<i.length;n++)if(i[n].identifier===e){t=n;break}return t}function u(e,t){for(var n={},a=[],r=0;r<e.length;r++){var o=e[r],u=t.base?o[0]+t.base:o[0],l=n[u]||0,c="".concat(u," ").concat(l);n[u]=l+1;var d=s(c),g={css:o[1],media:o[2],sourceMap:o[3]};-1!==d?(i[d].references++,i[d].updater(g)):i.push({identifier:c,updater:h(g,t),references:1}),a.push(c)}return a}function l(e){var t=document.createElement("style"),a=e.attributes||{};if(void 0===a.nonce){var r=n.nc;r&&(a.nonce=r)}if(Object.keys(a).forEach((function(e){t.setAttribute(e,a[e])})),"function"==typeof e.insert)e.insert(t);else{var i=o(e.insert||"head");if(!i)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");i.appendChild(t)}return t}var c,d=(c=[],function(e,t){return c[e]=t,c.filter(Boolean).join("\n")});function g(e,t,n,a){var r=n?"":a.media?"@media ".concat(a.media," {").concat(a.css,"}"):a.css;if(e.styleSheet)e.styleSheet.cssText=d(t,r);else{var o=document.createTextNode(r),i=e.childNodes;i[t]&&e.removeChild(i[t]),i.length?e.insertBefore(o,i[t]):e.appendChild(o)}}function f(e,t,n){var a=n.css,r=n.media,o=n.sourceMap;if(r?e.setAttribute("media",r):e.removeAttribute("media"),o&&"undefined"!=typeof btoa&&(a+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(o))))," */")),e.styleSheet)e.styleSheet.cssText=a;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(a))}}var m=null,p=0;function h(e,t){var n,a,r;if(t.singleton){var o=p++;n=m||(m=l(t)),a=g.bind(null,n,o,!1),r=g.bind(null,n,o,!0)}else n=l(t),a=f.bind(null,n,t),r=function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(n)};return a(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;a(e=t)}else r()}}e.exports=function(e,t){(t=t||{}).singleton||"boolean"==typeof t.singleton||(t.singleton=r());var n=u(e=e||[],t);return function(e){if(e=e||[],"[object Array]"===Object.prototype.toString.call(e)){for(var a=0;a<n.length;a++){var r=s(n[a]);i[r].references--}for(var o=u(e,t),l=0;l<n.length;l++){var c=s(n[l]);0===i[c].references&&(i[c].updater(),i.splice(c,1))}n=o}}}},704:(e,t,n)=>{e.exports=n(79)("./src/core.js")},273:(e,t,n)=>{e.exports=n(79)("./src/ui.js")},209:(e,t,n)=>{e.exports=n(79)("./src/utils.js")},79:e=>{"use strict";e.exports=CKEditor5.dll}},t={};function n(a){var r=t[a];if(void 0!==r)return r.exports;var o=t[a]={id:a,exports:{}};return e[a](o,o.exports,n),o.exports}n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var a in t)n.o(t,a)&&!n.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.nc=void 0;var a={};(()=>{"use strict";n.r(a),n.d(a,{TextPartLanguage:()=>f,TextPartLanguageEditing:()=>i,TextPartLanguageUI:()=>g});var e=n(704),t=n(209);function r(e,n){return`${e}:${n=n||(0,t.getLanguageDirection)(e)}`}class o extends e.Command{refresh(){const e=this.editor.model,t=e.document;this.value=this._getValueFromFirstAllowedNode(),this.isEnabled=e.schema.checkAttributeInSelection(t.selection,"language")}execute({languageCode:e,textDirection:t}={}){const n=this.editor.model,a=n.document.selection,o=!!e&&r(e,t);n.change((e=>{if(a.isCollapsed)o?e.setSelectionAttribute("language",o):e.removeSelectionAttribute("language");else{const t=n.schema.getValidRanges(a.getRanges(),"language");for(const n of t)o?e.setAttribute("language",o,n):e.removeAttribute("language",n)}}))}_getValueFromFirstAllowedNode(){const e=this.editor.model,t=e.schema,n=e.document.selection;if(n.isCollapsed)return n.getAttribute("language")||!1;for(const e of n.getRanges())for(const n of e.getItems())if(t.checkAttribute(n,"language"))return n.getAttribute("language")||!1;return!1}}class i extends e.Plugin{static get pluginName(){return"TextPartLanguageEditing"}constructor(e){super(e),e.config.define("language",{textPartLanguage:[{title:"Arabic",languageCode:"ar"},{title:"French",languageCode:"fr"},{title:"Spanish",languageCode:"es"}]})}init(){const e=this.editor;e.model.schema.extend("$text",{allowAttributes:"language"}),e.model.schema.setAttributeProperties("language",{copyOnEnter:!0}),this._defineConverters(),e.commands.add("textPartLanguage",new o(e))}_defineConverters(){const e=this.editor.conversion;e.for("upcast").elementToAttribute({model:{key:"language",value:e=>r(e.getAttribute("lang"),e.getAttribute("dir"))},view:{name:"span",attributes:{lang:/[\s\S]+/}}}),e.for("downcast").attributeToElement({model:"language",view:(e,{writer:t},n)=>{if(!e)return;if(!n.item.is("$textProxy")&&!n.item.is("documentSelection"))return;const{languageCode:a,textDirection:r}=function(e){const[t,n]=e.split(":");return{languageCode:t,textDirection:n}}(e);return t.createAttributeElement("span",{lang:a,dir:r})}})}}var s=n(273),u=n(62),l=n.n(u),c=n(176),d={injectType:"singletonStyleTag",attributes:{"data-cke":!0},insert:"head",singleton:!0};l()(c.Z,d);c.Z.locals;class g extends e.Plugin{static get pluginName(){return"TextPartLanguageUI"}init(){const e=this.editor,n=e.t,a=e.config.get("language.textPartLanguage"),o=n("Choose language"),i=n("Remove language"),u=n("Language");e.ui.componentFactory.add("textPartLanguage",(n=>{const l=new t.Collection,c={},d=e.commands.get("textPartLanguage");l.add({type:"button",model:new s.Model({label:i,languageCode:!1,withText:!0})}),l.add({type:"separator"});for(const e of a){const t={type:"button",model:new s.Model({label:e.title,languageCode:e.languageCode,textDirection:e.textDirection,withText:!0})},n=r(e.languageCode,e.textDirection);t.model.bind("isOn").to(d,"value",(e=>e===n)),l.add(t),c[n]=e.title}const g=(0,s.createDropdown)(n);return(0,s.addListToDropdown)(g,l),g.buttonView.set({isOn:!1,withText:!0,tooltip:u}),g.extendTemplate({attributes:{class:["ck-text-fragment-language-dropdown"]}}),g.bind("isEnabled").to(d,"isEnabled"),g.buttonView.bind("label").to(d,"value",(e=>e&&c[e]||o)),this.listenTo(g,"execute",(t=>{d.execute({languageCode:t.source.languageCode,textDirection:t.source.textDirection}),e.editing.view.focus()})),g}))}}class f extends e.Plugin{static get requires(){return[i,g]}static get pluginName(){return"TextPartLanguage"}}})(),(window.CKEditor5=window.CKEditor5||{}).language=a})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ckeditor/ckeditor5-language",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "36.0.1",
|
|
4
4
|
"description": "Text part language feature for CKEditor 5.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ckeditor",
|
|
@@ -12,16 +12,17 @@
|
|
|
12
12
|
],
|
|
13
13
|
"main": "src/index.js",
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"ckeditor5": "^
|
|
15
|
+
"ckeditor5": "^36.0.1"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
|
-
"@ckeditor/ckeditor5-core": "^
|
|
19
|
-
"@ckeditor/ckeditor5-dev-utils": "^
|
|
20
|
-
"@ckeditor/ckeditor5-editor-classic": "^
|
|
21
|
-
"@ckeditor/ckeditor5-engine": "^
|
|
22
|
-
"@ckeditor/ckeditor5-paragraph": "^
|
|
23
|
-
"@ckeditor/ckeditor5-theme-lark": "^
|
|
24
|
-
"@ckeditor/ckeditor5-ui": "^
|
|
18
|
+
"@ckeditor/ckeditor5-core": "^36.0.1",
|
|
19
|
+
"@ckeditor/ckeditor5-dev-utils": "^32.0.0",
|
|
20
|
+
"@ckeditor/ckeditor5-editor-classic": "^36.0.1",
|
|
21
|
+
"@ckeditor/ckeditor5-engine": "^36.0.1",
|
|
22
|
+
"@ckeditor/ckeditor5-paragraph": "^36.0.1",
|
|
23
|
+
"@ckeditor/ckeditor5-theme-lark": "^36.0.1",
|
|
24
|
+
"@ckeditor/ckeditor5-ui": "^36.0.1",
|
|
25
|
+
"typescript": "^4.8.4",
|
|
25
26
|
"webpack": "^5.58.1",
|
|
26
27
|
"webpack-cli": "^4.9.0"
|
|
27
28
|
},
|
|
@@ -40,13 +41,16 @@
|
|
|
40
41
|
},
|
|
41
42
|
"files": [
|
|
42
43
|
"lang",
|
|
43
|
-
"src",
|
|
44
|
+
"src/**/*.js",
|
|
45
|
+
"src/**/*.d.ts",
|
|
44
46
|
"theme",
|
|
45
47
|
"build",
|
|
46
48
|
"ckeditor5-metadata.json",
|
|
47
49
|
"CHANGELOG.md"
|
|
48
50
|
],
|
|
49
51
|
"scripts": {
|
|
50
|
-
"dll:build": "webpack"
|
|
52
|
+
"dll:build": "webpack",
|
|
53
|
+
"build": "tsc -p ./tsconfig.release.json",
|
|
54
|
+
"postversion": "npm run build"
|
|
51
55
|
}
|
|
52
56
|
}
|
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 language
|
|
8
7
|
*/
|
|
9
|
-
|
|
10
8
|
export { default as TextPartLanguage } from './textpartlanguage';
|
|
11
9
|
export { default as TextPartLanguageEditing } from './textpartlanguageediting';
|
|
12
10
|
export { default as TextPartLanguageUI } from './textpartlanguageui';
|
package/src/textpartlanguage.js
CHANGED
|
@@ -1,17 +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
|
-
/**
|
|
7
|
-
* @module language/textpartlanguage
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
5
|
import { Plugin } from 'ckeditor5/src/core';
|
|
11
|
-
|
|
12
6
|
import TextPartLanguageEditing from './textpartlanguageediting';
|
|
13
7
|
import TextPartLanguageUI from './textpartlanguageui';
|
|
14
|
-
|
|
15
8
|
/**
|
|
16
9
|
* The text part language feature.
|
|
17
10
|
*
|
|
@@ -26,61 +19,18 @@ import TextPartLanguageUI from './textpartlanguageui';
|
|
|
26
19
|
* This is a "glue" plugin which loads the
|
|
27
20
|
* {@link module:language/textpartlanguageediting~TextPartLanguageEditing text part language editing feature}
|
|
28
21
|
* and the {@link module:language/textpartlanguageui~TextPartLanguageUI text part language UI feature}.
|
|
29
|
-
*
|
|
30
|
-
* @extends module:core/plugin~Plugin
|
|
31
22
|
*/
|
|
32
23
|
export default class TextPartLanguage extends Plugin {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
24
|
+
/**
|
|
25
|
+
* @inheritDoc
|
|
26
|
+
*/
|
|
27
|
+
static get requires() {
|
|
28
|
+
return [TextPartLanguageEditing, TextPartLanguageUI];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* @inheritDoc
|
|
32
|
+
*/
|
|
33
|
+
static get pluginName() {
|
|
34
|
+
return 'TextPartLanguage';
|
|
35
|
+
}
|
|
46
36
|
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* The available {@link module:language/textpartlanguage~TextPartLanguage}
|
|
50
|
-
* options that allow setting the language of parts of the content.
|
|
51
|
-
*
|
|
52
|
-
* This configuration option is available only with the {@glink api/language text part language feature} enabled.
|
|
53
|
-
*
|
|
54
|
-
* Refer to [WCAG 3.1.2 Language of Parts](https://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-other-lang-id.html) specification
|
|
55
|
-
* to learn more.
|
|
56
|
-
*
|
|
57
|
-
* To change the editor's UI language, refer to the {@glink features/ui-language Setting the UI language} guide.
|
|
58
|
-
*
|
|
59
|
-
* The default value is:
|
|
60
|
-
*
|
|
61
|
-
* const config = [
|
|
62
|
-
* { title: 'Arabic', languageCode: 'ar' },
|
|
63
|
-
* { title: 'French', languageCode: 'fr' },
|
|
64
|
-
* { title: 'Spanish', languageCode: 'es' }
|
|
65
|
-
* ];
|
|
66
|
-
*
|
|
67
|
-
* The `title` property will be used by the text part language dropdown to render available options.
|
|
68
|
-
*
|
|
69
|
-
* The `languageCode` property is used for the `lang` attribute in [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
|
|
70
|
-
*
|
|
71
|
-
* You can also specify the optional `textDirection` property indicating the reading direction of the language.
|
|
72
|
-
* Correct values are `ltr` and `rtl`. When the `textDirection` property is missing, the text part language feature will
|
|
73
|
-
* specify the text direction by itself.
|
|
74
|
-
*
|
|
75
|
-
* @member {Array.<module:language/textpartlanguage~TextPartLanguageOption>}
|
|
76
|
-
* module:core/editor/editorconfig~LanguageConfig#textPartLanguage
|
|
77
|
-
*/
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* The text part language feature option descriptor.
|
|
81
|
-
*
|
|
82
|
-
* @typedef {Object} module:language/textpartlanguage~TextPartLanguageOption
|
|
83
|
-
* @property {String} title The user-readable title of the option.
|
|
84
|
-
* @property {String} languageCode The language code in the ISO 639 format.
|
|
85
|
-
* @property {'ltr'|'rtl'} [textDirection] The language text direction. Automatically detected if omitted.
|
|
86
|
-
*/
|
|
@@ -1,124 +1,89 @@
|
|
|
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
|
-
/**
|
|
7
|
-
* @module language/textpartlanguagecommand
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
5
|
import { Command } from 'ckeditor5/src/core';
|
|
11
6
|
import { stringifyLanguageAttribute } from './utils';
|
|
12
|
-
|
|
13
7
|
/**
|
|
14
8
|
* The text part language command plugin.
|
|
15
|
-
*
|
|
16
|
-
* @extends module:core/command~Command
|
|
17
9
|
*/
|
|
18
10
|
export default class TextPartLanguageCommand 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
|
-
* Returns the attribute value of the first node in the selection that allows the attribute.
|
|
100
|
-
* For a collapsed selection it returns the selection attribute.
|
|
101
|
-
*
|
|
102
|
-
* @private
|
|
103
|
-
* @returns {Boolean|String} The attribute value.
|
|
104
|
-
*/
|
|
105
|
-
_getValueFromFirstAllowedNode() {
|
|
106
|
-
const model = this.editor.model;
|
|
107
|
-
const schema = model.schema;
|
|
108
|
-
const selection = model.document.selection;
|
|
109
|
-
|
|
110
|
-
if ( selection.isCollapsed ) {
|
|
111
|
-
return selection.getAttribute( 'language' ) || false;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
for ( const range of selection.getRanges() ) {
|
|
115
|
-
for ( const item of range.getItems() ) {
|
|
116
|
-
if ( schema.checkAttribute( item, 'language' ) ) {
|
|
117
|
-
return item.getAttribute( 'language' ) || false;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
11
|
+
/**
|
|
12
|
+
* @inheritDoc
|
|
13
|
+
*/
|
|
14
|
+
refresh() {
|
|
15
|
+
const model = this.editor.model;
|
|
16
|
+
const doc = model.document;
|
|
17
|
+
this.value = this._getValueFromFirstAllowedNode();
|
|
18
|
+
this.isEnabled = model.schema.checkAttributeInSelection(doc.selection, 'language');
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Executes the command. Applies the attribute to the selection or removes it from the selection.
|
|
22
|
+
*
|
|
23
|
+
* If `languageCode` is set to `false` or a `null` value, it will remove attributes. Otherwise, it will set
|
|
24
|
+
* the attribute in the `{@link #value value}` format.
|
|
25
|
+
*
|
|
26
|
+
* The execution result differs, depending on the {@link module:engine/model/document~Document#selection}:
|
|
27
|
+
*
|
|
28
|
+
* * If the selection is on a range, the command applies the attribute to all nodes in that range
|
|
29
|
+
* (if they are allowed to have this attribute by the {@link module:engine/model/schema~Schema schema}).
|
|
30
|
+
* * If the selection is collapsed in a non-empty node, the command applies the attribute to the
|
|
31
|
+
* {@link module:engine/model/document~Document#selection} itself (note that typed characters copy attributes from the selection).
|
|
32
|
+
* * If the selection is collapsed in an empty node, the command applies the attribute to the parent node of the selection (note
|
|
33
|
+
* that the selection inherits all attributes from a node if it is in an empty node).
|
|
34
|
+
*
|
|
35
|
+
* @fires execute
|
|
36
|
+
* @param options Command options.
|
|
37
|
+
* @param options.languageCode The language code to be applied to the model.
|
|
38
|
+
* @param options.textDirection The language text direction.
|
|
39
|
+
*/
|
|
40
|
+
execute({ languageCode, textDirection } = {}) {
|
|
41
|
+
const model = this.editor.model;
|
|
42
|
+
const doc = model.document;
|
|
43
|
+
const selection = doc.selection;
|
|
44
|
+
const value = languageCode ? stringifyLanguageAttribute(languageCode, textDirection) : false;
|
|
45
|
+
model.change(writer => {
|
|
46
|
+
if (selection.isCollapsed) {
|
|
47
|
+
if (value) {
|
|
48
|
+
writer.setSelectionAttribute('language', value);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
writer.removeSelectionAttribute('language');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
const ranges = model.schema.getValidRanges(selection.getRanges(), 'language');
|
|
56
|
+
for (const range of ranges) {
|
|
57
|
+
if (value) {
|
|
58
|
+
writer.setAttribute('language', value, range);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
writer.removeAttribute('language', range);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Returns the attribute value of the first node in the selection that allows the attribute.
|
|
69
|
+
* For a collapsed selection it returns the selection attribute.
|
|
70
|
+
*
|
|
71
|
+
* @returns The attribute value.
|
|
72
|
+
*/
|
|
73
|
+
_getValueFromFirstAllowedNode() {
|
|
74
|
+
const model = this.editor.model;
|
|
75
|
+
const schema = model.schema;
|
|
76
|
+
const selection = model.document.selection;
|
|
77
|
+
if (selection.isCollapsed) {
|
|
78
|
+
return selection.getAttribute('language') || false;
|
|
79
|
+
}
|
|
80
|
+
for (const range of selection.getRanges()) {
|
|
81
|
+
for (const item of range.getItems()) {
|
|
82
|
+
if (schema.checkAttribute(item, 'language')) {
|
|
83
|
+
return item.getAttribute('language') || false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
124
89
|
}
|
|
@@ -1,105 +1,84 @@
|
|
|
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
|
-
/**
|
|
7
|
-
* @module language/textpartlanguageediting
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
5
|
import { Plugin } from 'ckeditor5/src/core';
|
|
11
6
|
import TextPartLanguageCommand from './textpartlanguagecommand';
|
|
12
7
|
import { stringifyLanguageAttribute, parseLanguageAttribute } from './utils';
|
|
13
|
-
|
|
14
8
|
/**
|
|
15
9
|
* The text part language editing.
|
|
16
10
|
*
|
|
17
11
|
* Introduces the `'textPartLanguage'` command and the `'language'` model element attribute.
|
|
18
|
-
*
|
|
19
|
-
* @extends module:core/plugin~Plugin
|
|
20
12
|
*/
|
|
21
13
|
export default class TextPartLanguageEditing extends Plugin {
|
|
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
|
-
if ( !data.item.is( '$textProxy' ) && !data.item.is( 'documentSelection' ) ) {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const { languageCode, textDirection } = parseLanguageAttribute( attributeValue );
|
|
97
|
-
|
|
98
|
-
return writer.createAttributeElement( 'span', {
|
|
99
|
-
lang: languageCode,
|
|
100
|
-
dir: textDirection
|
|
101
|
-
} );
|
|
102
|
-
}
|
|
103
|
-
} );
|
|
104
|
-
}
|
|
14
|
+
/**
|
|
15
|
+
* @inheritDoc
|
|
16
|
+
*/
|
|
17
|
+
static get pluginName() {
|
|
18
|
+
return 'TextPartLanguageEditing';
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* @inheritDoc
|
|
22
|
+
*/
|
|
23
|
+
constructor(editor) {
|
|
24
|
+
super(editor);
|
|
25
|
+
// Text part language options are only used to ensure that the feature works by default.
|
|
26
|
+
// In the real usage it should be reconfigured by a developer. We are not providing
|
|
27
|
+
// translations for `title` properties on purpose, as it's only an example configuration.
|
|
28
|
+
editor.config.define('language', {
|
|
29
|
+
textPartLanguage: [
|
|
30
|
+
{ title: 'Arabic', languageCode: 'ar' },
|
|
31
|
+
{ title: 'French', languageCode: 'fr' },
|
|
32
|
+
{ title: 'Spanish', languageCode: 'es' }
|
|
33
|
+
]
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* @inheritDoc
|
|
38
|
+
*/
|
|
39
|
+
init() {
|
|
40
|
+
const editor = this.editor;
|
|
41
|
+
editor.model.schema.extend('$text', { allowAttributes: 'language' });
|
|
42
|
+
editor.model.schema.setAttributeProperties('language', {
|
|
43
|
+
copyOnEnter: true
|
|
44
|
+
});
|
|
45
|
+
this._defineConverters();
|
|
46
|
+
editor.commands.add('textPartLanguage', new TextPartLanguageCommand(editor));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* @private
|
|
50
|
+
*/
|
|
51
|
+
_defineConverters() {
|
|
52
|
+
const conversion = this.editor.conversion;
|
|
53
|
+
conversion.for('upcast').elementToAttribute({
|
|
54
|
+
model: {
|
|
55
|
+
key: 'language',
|
|
56
|
+
value: (viewElement) => {
|
|
57
|
+
const languageCode = viewElement.getAttribute('lang');
|
|
58
|
+
const textDirection = viewElement.getAttribute('dir');
|
|
59
|
+
return stringifyLanguageAttribute(languageCode, textDirection);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
view: {
|
|
63
|
+
name: 'span',
|
|
64
|
+
attributes: { lang: /[\s\S]+/ }
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
conversion.for('downcast').attributeToElement({
|
|
68
|
+
model: 'language',
|
|
69
|
+
view: (attributeValue, { writer }, data) => {
|
|
70
|
+
if (!attributeValue) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!data.item.is('$textProxy') && !data.item.is('documentSelection')) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const { languageCode, textDirection } = parseLanguageAttribute(attributeValue);
|
|
77
|
+
return writer.createAttributeElement('span', {
|
|
78
|
+
lang: languageCode,
|
|
79
|
+
dir: textDirection
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
105
84
|
}
|
|
@@ -1,119 +1,96 @@
|
|
|
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 language/textpartlanguageui
|
|
8
7
|
*/
|
|
9
|
-
|
|
10
8
|
import { Plugin } from 'ckeditor5/src/core';
|
|
11
9
|
import { Model, createDropdown, addListToDropdown } from 'ckeditor5/src/ui';
|
|
12
10
|
import { Collection } from 'ckeditor5/src/utils';
|
|
13
11
|
import { stringifyLanguageAttribute } from './utils';
|
|
14
|
-
|
|
15
12
|
import '../theme/language.css';
|
|
16
|
-
|
|
17
13
|
/**
|
|
18
14
|
* The text part language UI plugin.
|
|
19
15
|
*
|
|
20
16
|
* It introduces the `'language'` dropdown.
|
|
21
|
-
*
|
|
22
|
-
* @extends module:core/plugin~Plugin
|
|
23
17
|
*/
|
|
24
18
|
export default class TextPartLanguageUI extends Plugin {
|
|
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
|
-
dropdownView.buttonView.bind( 'label' ).to( languageCommand, 'value', value => {
|
|
103
|
-
return titles[ value ] || defaultTitle;
|
|
104
|
-
} );
|
|
105
|
-
|
|
106
|
-
// Execute command when an item from the dropdown is selected.
|
|
107
|
-
this.listenTo( dropdownView, 'execute', evt => {
|
|
108
|
-
languageCommand.execute( {
|
|
109
|
-
languageCode: evt.source.languageCode,
|
|
110
|
-
textDirection: evt.source.textDirection
|
|
111
|
-
} );
|
|
112
|
-
|
|
113
|
-
editor.editing.view.focus();
|
|
114
|
-
} );
|
|
115
|
-
|
|
116
|
-
return dropdownView;
|
|
117
|
-
} );
|
|
118
|
-
}
|
|
19
|
+
/**
|
|
20
|
+
* @inheritDoc
|
|
21
|
+
*/
|
|
22
|
+
static get pluginName() {
|
|
23
|
+
return 'TextPartLanguageUI';
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* @inheritDoc
|
|
27
|
+
*/
|
|
28
|
+
init() {
|
|
29
|
+
const editor = this.editor;
|
|
30
|
+
const t = editor.t;
|
|
31
|
+
const options = editor.config.get('language.textPartLanguage');
|
|
32
|
+
const defaultTitle = t('Choose language');
|
|
33
|
+
const removeTitle = t('Remove language');
|
|
34
|
+
const dropdownTooltip = t('Language');
|
|
35
|
+
// Register UI component.
|
|
36
|
+
editor.ui.componentFactory.add('textPartLanguage', locale => {
|
|
37
|
+
const itemDefinitions = new Collection();
|
|
38
|
+
const titles = {};
|
|
39
|
+
const languageCommand = editor.commands.get('textPartLanguage');
|
|
40
|
+
// Item definition with false `languageCode` will behave as remove lang button.
|
|
41
|
+
itemDefinitions.add({
|
|
42
|
+
type: 'button',
|
|
43
|
+
model: new Model({
|
|
44
|
+
label: removeTitle,
|
|
45
|
+
languageCode: false,
|
|
46
|
+
withText: true
|
|
47
|
+
})
|
|
48
|
+
});
|
|
49
|
+
itemDefinitions.add({
|
|
50
|
+
type: 'separator'
|
|
51
|
+
});
|
|
52
|
+
for (const option of options) {
|
|
53
|
+
const def = {
|
|
54
|
+
type: 'button',
|
|
55
|
+
model: new Model({
|
|
56
|
+
label: option.title,
|
|
57
|
+
languageCode: option.languageCode,
|
|
58
|
+
textDirection: option.textDirection,
|
|
59
|
+
withText: true
|
|
60
|
+
})
|
|
61
|
+
};
|
|
62
|
+
const language = stringifyLanguageAttribute(option.languageCode, option.textDirection);
|
|
63
|
+
def.model.bind('isOn').to(languageCommand, 'value', value => value === language);
|
|
64
|
+
itemDefinitions.add(def);
|
|
65
|
+
titles[language] = option.title;
|
|
66
|
+
}
|
|
67
|
+
const dropdownView = createDropdown(locale);
|
|
68
|
+
addListToDropdown(dropdownView, itemDefinitions);
|
|
69
|
+
dropdownView.buttonView.set({
|
|
70
|
+
isOn: false,
|
|
71
|
+
withText: true,
|
|
72
|
+
tooltip: dropdownTooltip
|
|
73
|
+
});
|
|
74
|
+
dropdownView.extendTemplate({
|
|
75
|
+
attributes: {
|
|
76
|
+
class: [
|
|
77
|
+
'ck-text-fragment-language-dropdown'
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
dropdownView.bind('isEnabled').to(languageCommand, 'isEnabled');
|
|
82
|
+
dropdownView.buttonView.bind('label').to(languageCommand, 'value', value => {
|
|
83
|
+
return (value && titles[value]) || defaultTitle;
|
|
84
|
+
});
|
|
85
|
+
// Execute command when an item from the dropdown is selected.
|
|
86
|
+
this.listenTo(dropdownView, 'execute', evt => {
|
|
87
|
+
languageCommand.execute({
|
|
88
|
+
languageCode: evt.source.languageCode,
|
|
89
|
+
textDirection: evt.source.textDirection
|
|
90
|
+
});
|
|
91
|
+
editor.editing.view.focus();
|
|
92
|
+
});
|
|
93
|
+
return dropdownView;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
119
96
|
}
|
package/src/utils.js
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
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 language/utils
|
|
8
7
|
*/
|
|
9
|
-
|
|
10
8
|
import { getLanguageDirection } from 'ckeditor5/src/utils';
|
|
11
|
-
|
|
12
9
|
/**
|
|
13
10
|
* Returns the language attribute value in a human-readable text format:
|
|
14
11
|
*
|
|
15
|
-
*
|
|
12
|
+
* ```
|
|
13
|
+
* <languageCode>:<textDirection>
|
|
14
|
+
* ```
|
|
16
15
|
*
|
|
17
16
|
* * `languageCode` - The language code used for the `lang` attribute in the [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
|
|
18
17
|
* * `textDirection` - One of the following values: `rtl` or `ltr`, indicating the reading direction of the language.
|
|
@@ -22,26 +21,23 @@ import { getLanguageDirection } from 'ckeditor5/src/utils';
|
|
|
22
21
|
*
|
|
23
22
|
* If the `textDirection` argument is omitted, it will be automatically detected based on `languageCode`.
|
|
24
23
|
*
|
|
25
|
-
* @param
|
|
26
|
-
* @param
|
|
27
|
-
* @returns {String}
|
|
24
|
+
* @param languageCode The language code in the ISO 639-1 format.
|
|
25
|
+
* @param textDirection The language text direction. Automatically detected if omitted.
|
|
28
26
|
*/
|
|
29
|
-
export function stringifyLanguageAttribute(
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
export function stringifyLanguageAttribute(languageCode, textDirection) {
|
|
28
|
+
textDirection = textDirection || getLanguageDirection(languageCode);
|
|
29
|
+
return `${languageCode}:${textDirection}`;
|
|
32
30
|
}
|
|
33
|
-
|
|
34
31
|
/**
|
|
35
32
|
* Retrieves language properties converted to attribute value by the
|
|
36
33
|
* {@link module:language/utils~stringifyLanguageAttribute stringifyLanguageAttribute} function.
|
|
37
34
|
*
|
|
38
|
-
* @param
|
|
39
|
-
* @returns
|
|
40
|
-
*
|
|
41
|
-
*
|
|
35
|
+
* @param str The attribute value.
|
|
36
|
+
* @returns The object with properties:
|
|
37
|
+
* * languageCode - The language code in the ISO 639 format.
|
|
38
|
+
* * textDirection - The language text direction.
|
|
42
39
|
*/
|
|
43
|
-
export function parseLanguageAttribute(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return { languageCode, textDirection };
|
|
40
|
+
export function parseLanguageAttribute(str) {
|
|
41
|
+
const [languageCode, textDirection] = str.split(':');
|
|
42
|
+
return { languageCode, textDirection };
|
|
47
43
|
}
|
package/theme/language.css
CHANGED