Skip to content

Commit 2367932

Browse files
committed
split file to EditorJSInline.ts
1 parent 186828b commit 2367932

File tree

3 files changed

+278
-272
lines changed

3 files changed

+278
-272
lines changed

Diff for: src/EditorJSInline.ts

+274
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
import type {
2+
API,
3+
EditorConfig,
4+
InlineTool,
5+
InlineToolConstructorOptions,
6+
OutputData,
7+
} from '@editorjs/editorjs';
8+
import { v4 as uuidv4 } from 'uuid';
9+
import type IframeWindow from './IframeWindow';
10+
import type MessageData from './MessageData';
11+
import type { MutatedMessageData, SavedMessageData } from './MessageData';
12+
// @ts-ignore
13+
import iframeWorker from '../dist/iframeWorker.js';
14+
15+
interface EditorJSInlineConfig {
16+
editorConfig: Omit<EditorConfig, 'data' | 'holder'>;
17+
}
18+
19+
interface EditorJSInlineConstructorOptions
20+
extends InlineToolConstructorOptions {
21+
config: EditorJSInlineConfig | object;
22+
}
23+
24+
class EditorJSInline implements InlineTool {
25+
static get isInline() {
26+
return true;
27+
}
28+
29+
static get sanitize() {
30+
return {
31+
span: true,
32+
};
33+
}
34+
35+
static get title() {
36+
return 'EditorJS';
37+
}
38+
39+
private api: API;
40+
private config!: EditorJSInlineConfig;
41+
42+
constructor({ api, config }: EditorJSInlineConstructorOptions) {
43+
this.api = api;
44+
45+
if (!('editorConfig' in config)) {
46+
return;
47+
}
48+
49+
this.config = config;
50+
}
51+
52+
get shortcut() {
53+
return 'CMD+E';
54+
}
55+
56+
checkState() {
57+
return false;
58+
}
59+
60+
render() {
61+
const button = document.createElement('button');
62+
63+
button.classList.add(this.api.styles.inlineToolButton);
64+
button.type = 'button';
65+
button.innerHTML = `
66+
<svg class="icon" viewBox="0 0 14 14">
67+
<g stroke="currentColor" stroke-width="2">
68+
<circle cx="7" cy="7" r="6" fill="none" />
69+
<line x1="4" y1="7" x2="10" y2="7" />
70+
<line x1="7" y1="4" x2="7" y2="10" />
71+
</g>
72+
</svg>
73+
`;
74+
75+
setTimeout(() => {
76+
const codexEditor = button.closest('.codex-editor');
77+
78+
if (!codexEditor) {
79+
throw new Error(
80+
"Couldn't find the parent Editor.js of editorjs-inline. "
81+
);
82+
}
83+
84+
const mutationObserver = new MutationObserver(() => {
85+
if (codexEditor.querySelector('.codex-editor__loader')) {
86+
return;
87+
}
88+
89+
codexEditor
90+
.querySelectorAll('span[data-editorjs-inline]')
91+
.forEach((element) => {
92+
const span = element as HTMLSpanElement;
93+
const data: OutputData = JSON.parse(
94+
span.dataset.editorjsInline ?? ''
95+
);
96+
const newSpan = this.createSpan({ data });
97+
98+
span.parentNode?.replaceChild(newSpan, span);
99+
});
100+
101+
mutationObserver.disconnect();
102+
});
103+
104+
mutationObserver.observe(codexEditor, { childList: true });
105+
106+
document.addEventListener('pointerdown', () => {
107+
codexEditor
108+
.querySelectorAll('span[data-editorjs-inline] iframe')
109+
.forEach((element) => {
110+
const iframe = element as HTMLIFrameElement;
111+
const iframeWorkerWindow = iframe.contentWindow as IframeWindow;
112+
113+
iframeWorkerWindow.editorJSInline.closeToolbars();
114+
});
115+
});
116+
117+
window.addEventListener(
118+
'message',
119+
(event) => {
120+
const messageData: MessageData = event.data;
121+
122+
if (
123+
typeof messageData !== 'object' ||
124+
!('editorJSInline' in messageData)
125+
) {
126+
return;
127+
}
128+
129+
const span = codexEditor.querySelector(
130+
`span[data-editorjs-inline-id="${messageData.id}"]`
131+
) as HTMLSpanElement | null;
132+
133+
const iframe = span?.querySelector('iframe');
134+
135+
({
136+
mutated: () => {
137+
if (!iframe) {
138+
return;
139+
}
140+
141+
const { scrollHeight } = messageData as MutatedMessageData;
142+
143+
iframe.style.height = `${scrollHeight}px`;
144+
},
145+
pointerdown: () => {
146+
codexEditor
147+
.querySelectorAll(
148+
`span[data-editorjs-inline]:not([data-editorjs-inline-id="${messageData.id}"]) iframe`
149+
)
150+
.forEach((element) => {
151+
const iframe = element as HTMLIFrameElement;
152+
const iframeWorkerWindow = iframe.contentWindow as IframeWindow;
153+
154+
iframeWorkerWindow.editorJSInline.closeToolbars();
155+
});
156+
},
157+
saved: () => {
158+
if (!span) {
159+
return;
160+
}
161+
162+
const { outputData } = messageData as SavedMessageData;
163+
164+
span.dataset.editorjsInline = JSON.stringify(outputData);
165+
},
166+
// https://lgtm.com/rules/1506750237676/
167+
}[messageData.type]?.());
168+
},
169+
false
170+
);
171+
});
172+
173+
return button;
174+
}
175+
176+
surround(range: Range) {
177+
const text: string = range.extractContents().textContent ?? '';
178+
179+
range.insertNode(
180+
this.createSpan({
181+
data: {
182+
blocks: [
183+
{
184+
type: 'paragraph',
185+
data: {
186+
text,
187+
},
188+
},
189+
],
190+
},
191+
})
192+
);
193+
}
194+
195+
private createSpan({ data }: { data: OutputData }) {
196+
const id = uuidv4();
197+
const span = document.createElement('span');
198+
199+
span.contentEditable = 'false';
200+
span.dataset.editorjsInline = JSON.stringify(data);
201+
span.dataset.editorjsInlineId = id;
202+
span.style.display = 'inline-block';
203+
204+
span.append('\u200b');
205+
206+
const iframe = document.createElement('iframe');
207+
208+
iframe.scrolling = 'no';
209+
iframe.style.border = 'none';
210+
iframe.style.width = '100%';
211+
iframe.title = 'editorjs-inline';
212+
213+
const styleHTML = Array.from(document.querySelectorAll('style'))
214+
.map((style) => style.outerHTML)
215+
.join('');
216+
217+
iframe.srcdoc = `
218+
<!doctype html>
219+
<html>
220+
<head>
221+
${styleHTML}
222+
223+
<style>
224+
body {
225+
margin: 0 16px;
226+
padding: 0;
227+
}
228+
229+
.ce-toolbox, .ce-inline-toolbar, .ce-conversion-toolbar {
230+
display: none;
231+
}
232+
233+
.ce-inline-toolbar--showed, .ce-conversion-toolbar--showed {
234+
display: block;
235+
}
236+
237+
.ce-toolbox--opened {
238+
display: -webkit-box;
239+
display: -ms-flexbox;
240+
display: flex;
241+
}
242+
</style>
243+
</head>
244+
245+
<body>
246+
<script>${iframeWorker}</script>
247+
</body>
248+
</html>
249+
`;
250+
251+
iframe.addEventListener('load', () => {
252+
if (!iframe.contentWindow) {
253+
throw new Error("Couldn't create iframe for editorjs-inline. ");
254+
}
255+
256+
const iframeWorkerWindow = iframe.contentWindow as IframeWindow;
257+
258+
iframeWorkerWindow.editorJSInline.load({
259+
id,
260+
editorConfig: {
261+
...this.config.editorConfig,
262+
data,
263+
},
264+
});
265+
});
266+
267+
span.append(iframe);
268+
269+
return span;
270+
}
271+
}
272+
273+
export default EditorJSInline;
274+
export type { EditorJSInlineConfig };

0 commit comments

Comments
 (0)