Skip to content

Commit 0591aba

Browse files
authored
Merge pull request #86 from editor-js/fix/merge
fix(merge): incorrect caret position on merge
2 parents 65dbd4a + 31eb426 commit 0591aba

File tree

6 files changed

+66
-94
lines changed

6 files changed

+66
-94
lines changed

Diff for: .editorconfig

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = false
2+
3+
[*]
4+
charset = utf-8
5+
indent_style = space
6+
indent_size = 2
7+
end_of_line = lf
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true

Diff for: dist/paragraph.mjs

+17-42
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
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)}})();
22
const s = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="M8 9V7.2C8 7.08954 8.08954 7 8.2 7L12 7M16 9V7.2C16 7.08954 15.9105 7 15.8 7L12 7M12 7L12 17M12 17H10M12 17H14"/></svg>';
3+
function o(r) {
4+
const t = document.createElement("div");
5+
t.innerHTML = r.trim();
6+
const e = document.createDocumentFragment();
7+
return e.append(...Array.from(t.childNodes)), e;
8+
}
39
/**
410
* Base Paragraph Block for the Editor.js.
511
* Represents a regular text block
@@ -8,7 +14,7 @@ const s = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="
814
* @copyright CodeX 2018
915
* @license The MIT License (MIT)
1016
*/
11-
class a {
17+
class n {
1218
/**
1319
* Default placeholder for Paragraph Tool
1420
*
@@ -27,11 +33,11 @@ class a {
2733
* @param {object} params.api - editor.js api
2834
* @param {boolean} readOnly - read only mode flag
2935
*/
30-
constructor({ data: t, config: e, api: i, readOnly: n }) {
31-
this.api = i, this.readOnly = n, this._CSS = {
36+
constructor({ data: t, config: e, api: i, readOnly: a }) {
37+
this.api = i, this.readOnly = a, this._CSS = {
3238
block: this.api.styles.block,
3339
wrapper: "ce-paragraph"
34-
}, this.readOnly || (this.onKeyUp = this.onKeyUp.bind(this)), this._placeholder = e.placeholder ? e.placeholder : a.DEFAULT_PLACEHOLDER, this._data = {}, this._element = null, this._preserveBlank = e.preserveBlank !== void 0 ? e.preserveBlank : !1, this.data = t;
40+
}, this.readOnly || (this.onKeyUp = this.onKeyUp.bind(this)), this._placeholder = e.placeholder ? e.placeholder : n.DEFAULT_PLACEHOLDER, this._data = t ?? {}, this._element = null, this._preserveBlank = e.preserveBlank !== void 0 ? e.preserveBlank : !1;
3541
}
3642
/**
3743
* Check if text content is empty and set empty string to inner html.
@@ -71,10 +77,9 @@ class a {
7177
* @public
7278
*/
7379
merge(t) {
74-
const e = {
75-
text: this.data.text + t.text
76-
};
77-
this.data = e;
80+
this._data.text += t.text;
81+
const e = o(t.text);
82+
this._element.appendChild(e), this._element.normalize();
7883
}
7984
/**
8085
* Validate Paragraph block data:
@@ -108,7 +113,9 @@ class a {
108113
const e = {
109114
text: t.detail.data.innerHTML
110115
};
111-
this.data = e;
116+
this._data = e, window.requestAnimationFrame(() => {
117+
this._element.innerHTML = this._data.text || "";
118+
});
112119
}
113120
/**
114121
* Enable Conversion Toolbar. Paragraph can be converted to/from other tools
@@ -139,38 +146,6 @@ class a {
139146
static get isReadOnlySupported() {
140147
return !0;
141148
}
142-
/**
143-
* Get current Tools`s data
144-
*
145-
* @returns {ParagraphData} Current data
146-
* @private
147-
*/
148-
get data() {
149-
if (this._element !== null) {
150-
const t = this._element.innerHTML;
151-
this._data.text = t;
152-
}
153-
return this._data;
154-
}
155-
/**
156-
* Store data in plugin:
157-
* - at the this._data property
158-
* - at the HTML
159-
*
160-
* @param {ParagraphData} data — data to set
161-
* @private
162-
*/
163-
set data(t) {
164-
this._data = t || {}, this._element !== null && this.hydrate();
165-
}
166-
/**
167-
* Fill tool's view with data
168-
*/
169-
hydrate() {
170-
window.requestAnimationFrame(() => {
171-
this._element.innerHTML = this._data.text || "";
172-
});
173-
}
174149
/**
175150
* Used by Editor paste handling API.
176151
* Provides configuration to handle P tags.
@@ -195,5 +170,5 @@ class a {
195170
}
196171
}
197172
export {
198-
a as default
173+
n as default
199174
};

Diff for: dist/paragraph.umd.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@editorjs/paragraph",
3-
"version": "2.11.3",
3+
"version": "2.11.4",
44
"keywords": [
55
"codex editor",
66
"paragraph",

Diff for: src/index.js

+20-49
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import './index.css';
55

66
import { IconText } from '@codexteam/icons'
7+
import makeFragment from './utils/makeFragment';
78

89
/**
910
* Base Paragraph Block for the Editor.js.
@@ -64,11 +65,9 @@ export default class Paragraph {
6465
* @type {string}
6566
*/
6667
this._placeholder = config.placeholder ? config.placeholder : Paragraph.DEFAULT_PLACEHOLDER;
67-
this._data = {};
68+
this._data = data ?? {};
6869
this._element = null;
6970
this._preserveBlank = config.preserveBlank !== undefined ? config.preserveBlank : false;
70-
71-
this.data = data;
7271
}
7372

7473
/**
@@ -133,11 +132,17 @@ export default class Paragraph {
133132
* @public
134133
*/
135134
merge(data) {
136-
const newData = {
137-
text : this.data.text + data.text,
138-
};
135+
this._data.text += data.text;
136+
137+
/**
138+
* We use appendChild instead of innerHTML to keep the links of the existing nodes
139+
* (for example, shadow caret)
140+
*/
141+
const fragment = makeFragment(data.text);
139142

140-
this.data = newData;
143+
this._element.appendChild(fragment);
144+
145+
this._element.normalize();
141146
}
142147

143148
/**
@@ -179,7 +184,14 @@ export default class Paragraph {
179184
text: event.detail.data.innerHTML,
180185
};
181186

182-
this.data = data;
187+
this._data = data;
188+
189+
/**
190+
* We use requestAnimationFrame for performance purposes
191+
*/
192+
window.requestAnimationFrame(() => {
193+
this._element.innerHTML = this._data.text || '';
194+
});
183195
}
184196

185197
/**
@@ -212,47 +224,6 @@ export default class Paragraph {
212224
return true;
213225
}
214226

215-
/**
216-
* Get current Tools`s data
217-
*
218-
* @returns {ParagraphData} Current data
219-
* @private
220-
*/
221-
get data() {
222-
if (this._element !== null) {
223-
const text = this._element.innerHTML;
224-
225-
this._data.text = text;
226-
}
227-
228-
return this._data;
229-
}
230-
231-
/**
232-
* Store data in plugin:
233-
* - at the this._data property
234-
* - at the HTML
235-
*
236-
* @param {ParagraphData} data — data to set
237-
* @private
238-
*/
239-
set data(data) {
240-
this._data = data || {};
241-
242-
if (this._element !== null) {
243-
this.hydrate();
244-
}
245-
}
246-
247-
/**
248-
* Fill tool's view with data
249-
*/
250-
hydrate(){
251-
window.requestAnimationFrame(() => {
252-
this._element.innerHTML = this._data.text || '';
253-
});
254-
}
255-
256227
/**
257228
* Used by Editor paste handling API.
258229
* Provides configuration to handle P tags.

Diff for: src/utils/makeFragment.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* Create a DocumentFragment and fill it with HTML from a string
3+
*
4+
* @param {string} htmlString - A string of valid HTML
5+
* @returns {DocumentFragment}
6+
*/
7+
export default function makeFragment(htmlString) {
8+
const tempDiv = document.createElement('div');
9+
10+
tempDiv.innerHTML = htmlString.trim();
11+
12+
const fragment = document.createDocumentFragment();
13+
14+
fragment.append(...Array.from(tempDiv.childNodes));
15+
16+
return fragment;
17+
}

0 commit comments

Comments
 (0)