diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0665d09 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = false + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/dist/paragraph.mjs b/dist/paragraph.mjs index 8db20b2..3375175 100644 --- a/dist/paragraph.mjs +++ b/dist/paragraph.mjs @@ -1,5 +1,11 @@ (function(){"use strict";try{if(typeof document<"u"){var e=document.createElement("style");e.appendChild(document.createTextNode(".ce-paragraph{line-height:1.6em;outline:none}.ce-paragraph[data-placeholder]:empty:before{content:attr(data-placeholder);color:#707684;font-weight:400;opacity:0}.codex-editor--empty .ce-block:first-child .ce-paragraph[data-placeholder]:empty:before{opacity:1}.codex-editor--toolbox-opened .ce-block:first-child .ce-paragraph[data-placeholder]:empty:before,.codex-editor--empty .ce-block:first-child .ce-paragraph[data-placeholder]:empty:focus:before{opacity:0}.ce-paragraph p:first-of-type{margin-top:0}.ce-paragraph p:last-of-type{margin-bottom:0}")),document.head.appendChild(e)}}catch(t){console.error("vite-plugin-css-injected-by-js",t)}})(); const s = ''; +function o(r) { + const t = document.createElement("div"); + t.innerHTML = r.trim(); + const e = document.createDocumentFragment(); + return e.append(...Array.from(t.childNodes)), e; +} /** * Base Paragraph Block for the Editor.js. * Represents a regular text block @@ -8,7 +14,7 @@ const s = ' { + this._element.innerHTML = this._data.text || ""; + }); } /** * Enable Conversion Toolbar. Paragraph can be converted to/from other tools @@ -139,38 +146,6 @@ class a { static get isReadOnlySupported() { return !0; } - /** - * Get current Tools`s data - * - * @returns {ParagraphData} Current data - * @private - */ - get data() { - if (this._element !== null) { - const t = this._element.innerHTML; - this._data.text = t; - } - return this._data; - } - /** - * Store data in plugin: - * - at the this._data property - * - at the HTML - * - * @param {ParagraphData} data — data to set - * @private - */ - set data(t) { - this._data = t || {}, this._element !== null && this.hydrate(); - } - /** - * Fill tool's view with data - */ - hydrate() { - window.requestAnimationFrame(() => { - this._element.innerHTML = this._data.text || ""; - }); - } /** * Used by Editor paste handling API. * Provides configuration to handle P tags. @@ -195,5 +170,5 @@ class a { } } export { - a as default + n as default }; diff --git a/dist/paragraph.umd.js b/dist/paragraph.umd.js index 522de30..17f2864 100644 --- a/dist/paragraph.umd.js +++ b/dist/paragraph.umd.js @@ -1,9 +1,9 @@ (function(){"use strict";try{if(typeof document<"u"){var e=document.createElement("style");e.appendChild(document.createTextNode(".ce-paragraph{line-height:1.6em;outline:none}.ce-paragraph[data-placeholder]:empty:before{content:attr(data-placeholder);color:#707684;font-weight:400;opacity:0}.codex-editor--empty .ce-block:first-child .ce-paragraph[data-placeholder]:empty:before{opacity:1}.codex-editor--toolbox-opened .ce-block:first-child .ce-paragraph[data-placeholder]:empty:before,.codex-editor--empty .ce-block:first-child .ce-paragraph[data-placeholder]:empty:focus:before{opacity:0}.ce-paragraph p:first-of-type{margin-top:0}.ce-paragraph p:last-of-type{margin-bottom:0}")),document.head.appendChild(e)}}catch(t){console.error("vite-plugin-css-injected-by-js",t)}})(); -(function(n,i){typeof exports=="object"&&typeof module<"u"?module.exports=i():typeof define=="function"&&define.amd?define(i):(n=typeof globalThis<"u"?globalThis:n||self,n.Paragraph=i())})(this,function(){"use strict";const n="",i='';/** +(function(i,n){typeof exports=="object"&&typeof module<"u"?module.exports=n():typeof define=="function"&&define.amd?define(n):(i=typeof globalThis<"u"?globalThis:i||self,i.Paragraph=n())})(this,function(){"use strict";const i="",n='';function s(a){const e=document.createElement("div");e.innerHTML=a.trim();const t=document.createDocumentFragment();return t.append(...Array.from(e.childNodes)),t}/** * Base Paragraph Block for the Editor.js. * Represents a regular text block * * @author CodeX (team@codex.so) * @copyright CodeX 2018 * @license The MIT License (MIT) - */class s{static get DEFAULT_PLACEHOLDER(){return""}constructor({data:t,config:e,api:a,readOnly:r}){this.api=a,this.readOnly=r,this._CSS={block:this.api.styles.block,wrapper:"ce-paragraph"},this.readOnly||(this.onKeyUp=this.onKeyUp.bind(this)),this._placeholder=e.placeholder?e.placeholder:s.DEFAULT_PLACEHOLDER,this._data={},this._element=null,this._preserveBlank=e.preserveBlank!==void 0?e.preserveBlank:!1,this.data=t}onKeyUp(t){if(t.code!=="Backspace"&&t.code!=="Delete")return;const{textContent:e}=this._element;e===""&&(this._element.innerHTML="")}drawView(){const t=document.createElement("DIV");return t.classList.add(this._CSS.wrapper,this._CSS.block),t.contentEditable=!1,t.dataset.placeholder=this.api.i18n.t(this._placeholder),this._data.text&&(t.innerHTML=this._data.text),this.readOnly||(t.contentEditable=!0,t.addEventListener("keyup",this.onKeyUp)),t}render(){return this._element=this.drawView(),this._element}merge(t){const e={text:this.data.text+t.text};this.data=e}validate(t){return!(t.text.trim()===""&&!this._preserveBlank)}save(t){return{text:t.innerHTML}}onPaste(t){const e={text:t.detail.data.innerHTML};this.data=e}static get conversionConfig(){return{export:"text",import:"text"}}static get sanitize(){return{text:{br:!0}}}static get isReadOnlySupported(){return!0}get data(){if(this._element!==null){const t=this._element.innerHTML;this._data.text=t}return this._data}set data(t){this._data=t||{},this._element!==null&&this.hydrate()}hydrate(){window.requestAnimationFrame(()=>{this._element.innerHTML=this._data.text||""})}static get pasteConfig(){return{tags:["P"]}}static get toolbox(){return{icon:i,title:"Text"}}}return s}); + */class r{static get DEFAULT_PLACEHOLDER(){return""}constructor({data:e,config:t,api:o,readOnly:d}){this.api=o,this.readOnly=d,this._CSS={block:this.api.styles.block,wrapper:"ce-paragraph"},this.readOnly||(this.onKeyUp=this.onKeyUp.bind(this)),this._placeholder=t.placeholder?t.placeholder:r.DEFAULT_PLACEHOLDER,this._data=e??{},this._element=null,this._preserveBlank=t.preserveBlank!==void 0?t.preserveBlank:!1}onKeyUp(e){if(e.code!=="Backspace"&&e.code!=="Delete")return;const{textContent:t}=this._element;t===""&&(this._element.innerHTML="")}drawView(){const e=document.createElement("DIV");return e.classList.add(this._CSS.wrapper,this._CSS.block),e.contentEditable=!1,e.dataset.placeholder=this.api.i18n.t(this._placeholder),this._data.text&&(e.innerHTML=this._data.text),this.readOnly||(e.contentEditable=!0,e.addEventListener("keyup",this.onKeyUp)),e}render(){return this._element=this.drawView(),this._element}merge(e){this._data.text+=e.text;const t=s(e.text);this._element.appendChild(t),this._element.normalize()}validate(e){return!(e.text.trim()===""&&!this._preserveBlank)}save(e){return{text:e.innerHTML}}onPaste(e){const t={text:e.detail.data.innerHTML};this._data=t,window.requestAnimationFrame(()=>{this._element.innerHTML=this._data.text||""})}static get conversionConfig(){return{export:"text",import:"text"}}static get sanitize(){return{text:{br:!0}}}static get isReadOnlySupported(){return!0}static get pasteConfig(){return{tags:["P"]}}static get toolbox(){return{icon:n,title:"Text"}}}return r}); diff --git a/package.json b/package.json index 76c5c90..ba08362 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@editorjs/paragraph", - "version": "2.11.3", + "version": "2.11.4", "keywords": [ "codex editor", "paragraph", diff --git a/src/index.js b/src/index.js index 48f4921..39ccf67 100644 --- a/src/index.js +++ b/src/index.js @@ -4,6 +4,7 @@ import './index.css'; import { IconText } from '@codexteam/icons' +import makeFragment from './utils/makeFragment'; /** * Base Paragraph Block for the Editor.js. @@ -64,11 +65,9 @@ export default class Paragraph { * @type {string} */ this._placeholder = config.placeholder ? config.placeholder : Paragraph.DEFAULT_PLACEHOLDER; - this._data = {}; + this._data = data ?? {}; this._element = null; this._preserveBlank = config.preserveBlank !== undefined ? config.preserveBlank : false; - - this.data = data; } /** @@ -133,11 +132,17 @@ export default class Paragraph { * @public */ merge(data) { - const newData = { - text : this.data.text + data.text, - }; + this._data.text += data.text; + + /** + * We use appendChild instead of innerHTML to keep the links of the existing nodes + * (for example, shadow caret) + */ + const fragment = makeFragment(data.text); - this.data = newData; + this._element.appendChild(fragment); + + this._element.normalize(); } /** @@ -179,7 +184,14 @@ export default class Paragraph { text: event.detail.data.innerHTML, }; - this.data = data; + this._data = data; + + /** + * We use requestAnimationFrame for performance purposes + */ + window.requestAnimationFrame(() => { + this._element.innerHTML = this._data.text || ''; + }); } /** @@ -212,47 +224,6 @@ export default class Paragraph { return true; } - /** - * Get current Tools`s data - * - * @returns {ParagraphData} Current data - * @private - */ - get data() { - if (this._element !== null) { - const text = this._element.innerHTML; - - this._data.text = text; - } - - return this._data; - } - - /** - * Store data in plugin: - * - at the this._data property - * - at the HTML - * - * @param {ParagraphData} data — data to set - * @private - */ - set data(data) { - this._data = data || {}; - - if (this._element !== null) { - this.hydrate(); - } - } - - /** - * Fill tool's view with data - */ - hydrate(){ - window.requestAnimationFrame(() => { - this._element.innerHTML = this._data.text || ''; - }); - } - /** * Used by Editor paste handling API. * Provides configuration to handle P tags. diff --git a/src/utils/makeFragment.js b/src/utils/makeFragment.js new file mode 100644 index 0000000..ca013af --- /dev/null +++ b/src/utils/makeFragment.js @@ -0,0 +1,17 @@ +/** + * Create a DocumentFragment and fill it with HTML from a string + * + * @param {string} htmlString - A string of valid HTML + * @returns {DocumentFragment} + */ +export default function makeFragment(htmlString) { + const tempDiv = document.createElement('div'); + + tempDiv.innerHTML = htmlString.trim(); + + const fragment = document.createDocumentFragment(); + + fragment.append(...Array.from(tempDiv.childNodes)); + + return fragment; +}