@cloudea/yang-editor 1.0.0

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.
@@ -0,0 +1,795 @@
1
+ import { YangEditor } from "./index";
2
+
3
+ export interface EditorElement {
4
+ }
5
+
6
+ export interface EditorComponent extends EditorElement {
7
+ getElement(): HTMLElement;
8
+ onMount(): HTMLElement;
9
+ onMounted(): void;
10
+ }
11
+
12
+ export class EditorBody implements EditorComponent {
13
+ public readonly element: HTMLDivElement;
14
+ public readonly context: YangEditor;
15
+ public readonly content: EditorContent;
16
+ public readonly menuButton: ContentBeforeMenu;
17
+
18
+ constructor(editor: YangEditor) {
19
+ this.element = document.createElement("div");
20
+ this.context = editor;
21
+ this.content = new EditorContent(this.context);
22
+ this.menuButton = new ContentBeforeMenu(this.context);
23
+ }
24
+
25
+ onMount(): HTMLElement {
26
+ this.element.classList.add("yang-editor-body");
27
+ this.element.appendChild(this.content.onMount());
28
+ this.element.appendChild(this.menuButton.onMount());
29
+ this.element.style.height = this.context.options.height;
30
+ return this.element;
31
+ }
32
+
33
+ onMounted(): void {
34
+ throw new Error("Method not implemented.");
35
+ }
36
+
37
+ getElement(): HTMLElement {
38
+ return this.element;
39
+ }
40
+
41
+ }
42
+
43
+ export class EditorContent implements EditorComponent {
44
+ public readonly element: HTMLDivElement;
45
+ public readonly context: YangEditor;
46
+
47
+ constructor(editor: YangEditor) {
48
+ this.element = document.createElement("div");
49
+ this.context = editor;
50
+ }
51
+
52
+ onMount(): HTMLElement {
53
+ this.element.classList.add("yang-editor-content");
54
+ if(this.context.options.mode === "edit") {
55
+ this.element.contentEditable = "true";
56
+ }
57
+
58
+ const observer = new MutationObserver((mutations) => {
59
+ if (this.context.options.events.onContentChange) {
60
+ this.context.options.events.onContentChange(this.element.innerHTML);
61
+ }
62
+ let childNodes = this.element.childNodes;
63
+ // transform text nodes or non-paragraph elements to paragraphs
64
+ for(let node of childNodes) {
65
+ if(node.nodeType === Node.TEXT_NODE) {
66
+ let p = document.createElement("p");
67
+ p.innerText = node.textContent || "";
68
+ node.replaceWith(p);
69
+ } else if(node.nodeType === Node.ELEMENT_NODE) {
70
+ let nodeElem = node as HTMLElement;
71
+ let tag = nodeElem.tagName.toLowerCase();
72
+ if(tag !== 'p' && tag !== 'div') {
73
+ let p = document.createElement("p");
74
+ p.innerHTML = nodeElem.outerHTML;
75
+ node.replaceWith(p);
76
+ }
77
+ }
78
+ }
79
+
80
+ // insert empty paragraph
81
+ let lastElement = this.element.children[this.element.children.length - 1];
82
+ if (lastElement === undefined || lastElement.childNodes.length > 0) {
83
+ this.insertDefaultParagraph();
84
+ }
85
+
86
+ // tag every childnodes
87
+ for(let node of this.element.children) {
88
+ if(node.nodeType === Node.ELEMENT_NODE) {
89
+ let elem = node as HTMLElement;
90
+ if(!elem.classList.contains(ComponentFactory.PARAGRAPH_STYLE)) {
91
+ elem.classList.add(ComponentFactory.PARAGRAPH_STYLE);
92
+ }
93
+ }
94
+ }
95
+
96
+ // add mousemove and mouseleave event to every child nodes
97
+ for(let node of this.element.children) {
98
+ if(node.nodeType === Node.ELEMENT_NODE) {
99
+ if(this.context.options.mode === "edit") {
100
+ let elem = node as HTMLElement;
101
+ elem.onmousemove = () => {
102
+ this.context.body.menuButton.show(elem);
103
+ };
104
+ elem.onmouseleave = () => {
105
+ this.context.body.menuButton.hide();
106
+ };
107
+ }
108
+ }
109
+ }
110
+
111
+ // activate all components
112
+ for(let node of this.element.children) {
113
+ if(node.nodeType === Node.ELEMENT_NODE) {
114
+ for (let className of node.classList) {
115
+ if (ComponentFactory.STYLES.indexOf(className) !== -1) {
116
+ this.context.componentFactory.createComponent(className, node as HTMLElement).onMount();
117
+ break;
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ });
124
+
125
+ // 开始观察
126
+ observer.observe(this.element, {
127
+ childList: true, // 观察直接子节点的变动
128
+ subtree: true, // 观察所有后代节点的变动
129
+ characterData: true, // 观察节点内容或文本的变动
130
+ attributes: false, // 观察属性的变动
131
+ // attributeFilter: ['style', 'class'] // 只观察特定属性
132
+ });
133
+
134
+ this.element.oncopy = (e) => {
135
+ // TODO: handle copy event
136
+ // e.preventDefault();
137
+ // let selection = window.getSelection();
138
+ // if (selection) {
139
+ // let range = selection.getRangeAt(0);
140
+ // let cloned = range.cloneContents();
141
+ // // console.log(cloned);
142
+ // // console.log(e.clipboardData)
143
+ // // console.log(e.clipboardData?.getData("text/html"));
144
+ // // e.clipboardData?.setData("text/html", cloned.innerHTML);
145
+ // }
146
+ }
147
+
148
+ this.element.onpaste = (e) => {
149
+ // TODO: handle paste event
150
+ //e.preventDefault();
151
+ // let clipboardData = e.clipboardData;
152
+ // if (clipboardData) {
153
+ // let html = clipboardData.getData("text/html");
154
+ // let text = clipboardData.getData("text/plain");
155
+ // if (html) {
156
+ // let div = document.createElement("div");
157
+ // div.innerHTML = html;
158
+ // this.element.appendChild(div);
159
+ // } else if (text) {
160
+ // this.element.appendChild(document.createTextNode(text));
161
+ // }
162
+ // }
163
+ }
164
+
165
+ this.insertDefaultParagraph();
166
+
167
+ return this.element;
168
+ }
169
+
170
+ insertDefaultParagraph() {
171
+ this.element.appendChild(document.createElement('p'));
172
+ }
173
+
174
+ insertCollapse() {
175
+ let paragraphElement = this.context.selectionUtils.getSelectedParagraph();
176
+ if(paragraphElement?.innerText.trim() === "") {
177
+ paragraphElement.replaceWith(new EditorCollapse(this.context).onMount());
178
+ } else {
179
+ for(let child of this.element.children) {
180
+ if(child === paragraphElement) {
181
+ child.after(new EditorCollapse(this.context).onMount());
182
+ break;
183
+ }
184
+ }
185
+ }
186
+ }
187
+
188
+ onMounted(): void {
189
+ throw new Error("Method not implemented.");
190
+ }
191
+
192
+ getElement(): HTMLElement {
193
+ return this.element;
194
+ }
195
+
196
+ }
197
+
198
+ class ContentBeforeMenu implements EditorComponent {
199
+ public readonly element: HTMLDivElement;
200
+ public readonly context: YangEditor;
201
+ public readonly sideMenu: EditorParagraphMenu;
202
+ private target: HTMLElement | undefined;
203
+
204
+ constructor(editor: YangEditor) {
205
+ this.context = editor;
206
+ this.element = document.createElement("div");
207
+ this.sideMenu = new EditorParagraphMenu(this.context);
208
+ }
209
+
210
+ onMount(): HTMLElement {
211
+ this.element.classList.add("yang-editor-content-before-menu");
212
+
213
+
214
+ let button = document.createElement("button");
215
+ button.classList.add("yang-editor-content-before-menu-button");
216
+ button.style.backgroundImage = `url(${this.context.options.images.menu})`;
217
+
218
+ this.element.onclick = () => this.sideMenu.show(this.target);
219
+ this.element.onmouseenter = () => this.show(this.target);
220
+ this.element.appendChild(button);
221
+ this.element.appendChild(this.sideMenu.onMount());
222
+ return this.element;
223
+ }
224
+
225
+ onMounted(): void {
226
+ throw new Error("Method not implemented.");
227
+ }
228
+
229
+ getElement(): HTMLElement {
230
+ return this.element;
231
+ }
232
+
233
+ show(target?: HTMLElement) {
234
+ this.element.style.display = "flex";
235
+ if(target !== this.target) {
236
+ this.sideMenu.hide();
237
+ }
238
+ if(target) {
239
+ this.target = target;
240
+ this.element.style.left = `24px`;
241
+ this.element.style.top = target.offsetTop + `px`;
242
+ }
243
+ }
244
+
245
+ hide() {
246
+ this.element.style.display = "none";
247
+ }
248
+ }
249
+
250
+ class ButtonAdd implements EditorComponent {
251
+ public readonly element: HTMLButtonElement;
252
+ public readonly context: YangEditor;
253
+
254
+ constructor(editor: YangEditor) {
255
+ this.context = editor;
256
+ this.element = document.createElement("button");
257
+ this.element.innerText = "";
258
+ this.element.style.width = "16px";
259
+ this.element.style.height = "16px";
260
+ this.element.style.height = "16px";
261
+ this.element.style.backgroundImage = `url(${this.context.options.images.add})`;
262
+ this.element.style.backgroundSize = "contain";
263
+ this.element.style.backgroundRepeat = "no-repeat";
264
+ this.element.style.cursor = "pointer";
265
+ this.element.title = "Add Collapsed Panel";
266
+ this.element.onclick = () => this.context.body.content.insertCollapse();
267
+
268
+ }
269
+
270
+ onMount(): HTMLElement {
271
+ return this.element;
272
+ }
273
+
274
+ onMounted(): void {
275
+ throw new Error("Method not implemented.");
276
+ }
277
+
278
+ getElement(): HTMLElement {
279
+ return this.element;
280
+ }
281
+
282
+ }
283
+
284
+ class BackGroundColorStrip implements EditorComponent {
285
+ public readonly element: HTMLDivElement;
286
+ public readonly context: YangEditor;
287
+ public readonly colors: Array<string>;
288
+ public readonly titles: Array<string>;
289
+
290
+ constructor(editor: YangEditor) {
291
+ this.context = editor;
292
+ this.element = document.createElement("div");
293
+ this.colors = ["rgba(228, 73, 91, 1)", "rgba(255, 140, 0, 1)", "rgba(255, 215, 0, 1)", "rgba(34, 139, 34, 1)", "rgba(30, 144, 255, 1)", "rgba(138, 43, 226, 1)", "rgba(255, 20, 147, 1)"];
294
+ this.titles = ["red", "orange", "yellow", "green", "blue", "purple", "pink"];
295
+ }
296
+
297
+ onMount(): HTMLElement {
298
+ this.element.classList.add("yang-editor-color-strip");
299
+ this.element.style.display = "flex";
300
+ this.element.style.alignItems = "center";
301
+ for(let i = 0; i < this.colors.length; i++) {
302
+ let color = this.colors[i];
303
+ let title = this.titles[i];
304
+ let btn = document.createElement("button");
305
+ if(color != undefined && title != undefined) {
306
+ btn.style.backgroundColor = color;
307
+ btn.style.width = "16px";
308
+ btn.style.height = "16px";
309
+ btn.style.borderRadius = "2px";
310
+ btn.style.cursor = "pointer";
311
+ btn.title = title;
312
+ btn.onclick = () => {
313
+ document.execCommand('backColor', false, color);
314
+ this.context.selectionUtils.getSelectionRange()?.collapse();
315
+ // eqauls to:
316
+ // document.execCommand('styleWithCSS', false, true);
317
+ // document.execCommand('foreColor', false, this.value);
318
+ }
319
+ }
320
+ this.element.appendChild(btn);
321
+ }
322
+ return this.element;
323
+ }
324
+
325
+ onMounted(): void {
326
+ throw new Error("Method not implemented.");
327
+ }
328
+
329
+ getElement(): HTMLElement {
330
+ return this.element;
331
+ }
332
+ }
333
+
334
+ class ForeGroundColorStrip implements EditorComponent {
335
+ public readonly element: HTMLDivElement;
336
+ public readonly context: YangEditor;
337
+ public readonly colors: Array<string>;
338
+ public readonly titles: Array<string>;
339
+
340
+ constructor(editor: YangEditor) {
341
+ this.context = editor;
342
+ this.element = document.createElement("div");
343
+ this.colors = ["rgba(228, 73, 91, 1)", "rgba(255, 140, 0, 1)", "rgba(255, 215, 0, 1)", "rgba(34, 139, 34, 1)", "rgba(30, 144, 255, 1)", "rgba(138, 43, 226, 1)", "rgba(255, 20, 147, 1)"];
344
+ this.titles = ["red", "orange", "yellow", "green", "blue", "purple", "pink"];
345
+ }
346
+
347
+ onMount(): HTMLElement {
348
+ this.element.classList.add("yang-editor-fgcolor-strip");
349
+ this.element.style.display = "flex";
350
+ this.element.style.alignItems = "center";
351
+ for(let i = 0; i < this.colors.length; i++) {
352
+ let color = this.colors[i];
353
+ let title = this.titles[i];
354
+ let btn = document.createElement("button");
355
+ if(color != undefined && title != undefined) {
356
+ // btn.style.backgroundColor = "transparent";
357
+ // btn.style.backgroundImage = `url(${this.context.options.images.fgColor})`;
358
+ // btn.style.backgroundSize = "16px 16px";
359
+ // btn.style.backgroundRepeat = "no-repeat";
360
+ // btn.style.backgroundPosition = "center";
361
+ btn.style.mask = `url(${this.context.options.images.fgColor}) no-repeat center`;
362
+ btn.style.webkitMask = `url(${this.context.options.images.fgColor}) no-repeat center`;
363
+ btn.style.backgroundColor = color;
364
+ btn.style.width = "17px";
365
+ btn.style.height = "17px";
366
+ btn.style.borderRadius = "2px";
367
+ btn.style.cursor = "pointer";
368
+ btn.title = title;
369
+ btn.onclick = () => {
370
+ document.execCommand('foreColor', false, color);
371
+ this.context.selectionUtils.getSelectionRange()?.collapse();
372
+ }
373
+ }
374
+ this.element.appendChild(btn);
375
+ }
376
+ return this.element;
377
+ }
378
+
379
+ onMounted(): void {
380
+ throw new Error("Method not implemented.");
381
+ }
382
+
383
+ getElement(): HTMLElement {
384
+ return this.element;
385
+ }
386
+ }
387
+
388
+ class IconButton implements EditorComponent {
389
+ public readonly element: HTMLButtonElement;
390
+ public readonly context: YangEditor;
391
+
392
+ constructor(editor: YangEditor, icon: string, tooltip: string, onclick?:() => void) {
393
+ this.context = editor;
394
+ this.element = document.createElement("button");
395
+ this.element.classList.add("yang-editor-icon-button");
396
+ this.element.style.backgroundImage = `url(${icon})`;
397
+ this.element.style.backgroundColor = "transparent";
398
+ this.element.style.width = "26px";
399
+ this.element.style.height = "26px";
400
+ this.element.style.borderRadius = "3px";
401
+ this.element.style.cursor = "pointer";
402
+ this.element.style.backgroundSize = "16px 16px";
403
+ this.element.style.backgroundRepeat = "no-repeat";
404
+ this.element.style.backgroundPosition = "center center";
405
+ this.element.title = tooltip;
406
+ this.element.onclick = () => {
407
+ if(onclick) {
408
+ onclick();
409
+ }
410
+ };
411
+ this.element.onmouseover = () => {
412
+ this.element.style.backgroundColor = "rgba(231, 233, 232, 1)";
413
+ };
414
+ this.element.onmouseleave = () => {
415
+ this.element.style.backgroundColor = "transparent";
416
+ }
417
+
418
+ }
419
+
420
+ onMount(): HTMLElement {
421
+ return this.element;
422
+ }
423
+
424
+ onMounted(): void {
425
+ throw new Error("Method not implemented.");
426
+ }
427
+
428
+ getElement(): HTMLElement {
429
+ return this.element;
430
+ }
431
+ }
432
+
433
+ export class EditorToolbar implements EditorComponent {
434
+ public readonly element: HTMLDivElement;
435
+ public readonly context: YangEditor;
436
+
437
+ constructor(editor: YangEditor) {
438
+ this.context = editor;
439
+ this.element = document.createElement("div");
440
+ }
441
+
442
+ onMount(): HTMLElement {
443
+ this.element.classList.add("yang-editor-toolbar");
444
+ this.element.appendChild(new ButtonAdd(this.context).onMount());
445
+ this.element.appendChild(new BackGroundColorStrip(this.context).onMount());
446
+ this.element.appendChild(new ForeGroundColorStrip(this.context).onMount());
447
+ this.element.appendChild(new IconButton(this.context, this.context.options.images.bold, "Bold", this.boldSelection.bind(this)).onMount());
448
+ this.element.appendChild(new IconButton(this.context, this.context.options.images.italic, "Italic", this.italicSelection.bind(this)).onMount());
449
+ this.element.appendChild(new IconButton(this.context, this.context.options.images.underline, "Underline", this.underlineSelection.bind(this)).onMount());
450
+ this.element.appendChild(new IconButton(this.context, this.context.options.images.deleteline, "Delete Line", this.strokeThroughSelection.bind(this)).onMount());
451
+ this.element.appendChild(new IconButton(this.context, this.context.options.images.link, "Link", this.insertLink.bind(this)).onMount());
452
+ this.element.appendChild(new IconButton(this.context, this.context.options.images.clear, "Clear", this.clearFormatSelection.bind(this)).onMount());
453
+ return this.element;
454
+ }
455
+
456
+ onMounted(): void {
457
+ throw new Error("Method not implemented.");
458
+ }
459
+
460
+ getElement(): HTMLElement {
461
+ return this.element;
462
+ }
463
+
464
+ insertLink() {
465
+ let range = this.context.selectionUtils.getSelectionRange();
466
+ if (range == null || range.collapsed) {return;}
467
+ const url = prompt("请输入链接地址");
468
+ if (url) {
469
+ const selectedContent = range.extractContents();
470
+ const linkElement = document.createElement('a');
471
+ linkElement.href = url;
472
+ linkElement.appendChild(selectedContent);
473
+ range.insertNode(linkElement);
474
+ }
475
+ }
476
+
477
+ boldSelection() {
478
+
479
+ // let range = this.context.selectionUtils.getSelectionRange();
480
+
481
+ // if (range == null || range.collapsed) {return;}
482
+
483
+ // // 检查是否已经在加粗标签内
484
+ // let parentElement = range.commonAncestorContainer;
485
+ // while (parentElement && parentElement.nodeType !== Node.ELEMENT_NODE && parentElement.parentElement !== null) {
486
+ // parentElement = parentElement.parentElement;
487
+ // }
488
+
489
+ // let tagName = parentElement.nodeName.toUpperCase();
490
+
491
+ // if (parentElement && (tagName === 'STRONG' || tagName === 'B')) {
492
+ // // 如果已经在加粗标签内,则取消加粗
493
+ // if(parentElement.textContent != null && parentElement.parentNode !== null) {
494
+ // const textNode = document.createTextNode(parentElement.textContent);
495
+ // parentElement.parentNode.replaceChild(textNode, parentElement);
496
+ // }
497
+ // } else {
498
+ // // 执行加粗
499
+ // //document.execCommand('bold', false);
500
+ // const selectedContent = range.extractContents();
501
+ // // 创建加粗元素并包裹选中内容
502
+ // const boldElement = document.createElement('strong');
503
+ // boldElement.appendChild(selectedContent);
504
+ // // 将加粗的内容插入回文档
505
+ // range.insertNode(boldElement);
506
+ // }
507
+
508
+ document.execCommand('bold', false, undefined);
509
+ }
510
+
511
+
512
+ italicSelection() {
513
+ document.execCommand('italic', false, undefined);
514
+ }
515
+
516
+ underlineSelection() {
517
+ document.execCommand('underline', false, undefined);
518
+ }
519
+
520
+ strokeThroughSelection() {
521
+ document.execCommand('strikeThrough', false, undefined);
522
+ }
523
+
524
+ clearFormatSelection() {
525
+ document.execCommand('removeFormat', false, undefined);
526
+ document.execCommand('unlink', false, undefined);
527
+ }
528
+
529
+ }
530
+
531
+ export class EditorCollapse implements EditorComponent {
532
+
533
+ public static readonly COLLAPSE_STYLE = "collapsed";
534
+ public readonly element: HTMLElement;
535
+ public readonly context: YangEditor;
536
+ title: HTMLDivElement;
537
+ header: HTMLDivElement;
538
+ button: HTMLButtonElement;
539
+ body: HTMLDivElement;
540
+ container: HTMLDivElement;
541
+
542
+ constructor(editor: YangEditor, element?: HTMLElement) {
543
+ this.context = editor;
544
+ if(element) {
545
+ this.element = element;
546
+ this.container = element.querySelector('.yang-editor-collapse-container') as HTMLDivElement;
547
+ this.header = element.querySelector('.yang-editor-collapse-header') as HTMLDivElement;
548
+ this.body = element.querySelector('.yang-editor-collapse-body') as HTMLDivElement;
549
+ this.button = element.querySelector('.yang-editor-collapse-button') as HTMLButtonElement;
550
+ this.title = element.querySelector('.yang-editor-collapse-title') as HTMLDivElement;
551
+ } else {
552
+ this.element = document.createElement("div");
553
+ this.container = document.createElement('div');
554
+ this.header = document.createElement('div');
555
+ this.body = document.createElement('div');
556
+ this.button = document.createElement('button');
557
+ this.title = document.createElement('div');
558
+
559
+ this.element.classList.add(ComponentFactory.COLLAPSE_STYLE)
560
+ this.container.classList.add("yang-editor-collapse-container");
561
+ this.header.classList.add("yang-editor-collapse-header");
562
+ this.body.classList.add("yang-editor-collapse-body");
563
+ this.button.classList.add("yang-editor-collapse-button");
564
+ this.title.classList.add("yang-editor-collapse-title");
565
+ this.title.innerText = "折叠面板标题";
566
+
567
+ this.button.innerText = "";
568
+
569
+ this.header.appendChild(this.button);
570
+ this.header.appendChild(this.title);
571
+ this.container.appendChild(this.header);
572
+ this.container.appendChild(this.body);
573
+ this.element.appendChild(this.container);
574
+
575
+ this.container.contentEditable = "false";
576
+ this.button.style.backgroundImage = `url(${this.context.options.images.down})`;
577
+ }
578
+ }
579
+
580
+ getElement(): HTMLElement {
581
+ return this.element;
582
+ }
583
+
584
+ onMount(): HTMLElement {
585
+ if(this.context.options.mode === "edit") {
586
+ this.title.contentEditable = "true";
587
+ this.body.contentEditable = "true";
588
+ } else {
589
+ this.title.contentEditable = "false";
590
+ this.body.contentEditable = "false";
591
+ }
592
+ this.button.onclick = () => {
593
+ this.switchState();
594
+ this.renderState();
595
+ }
596
+ this.renderState();
597
+ return this.getElement();
598
+ }
599
+
600
+ switchState() {
601
+ if(this.element.classList.contains(EditorCollapse.COLLAPSE_STYLE)) {
602
+ this.element.classList.remove(EditorCollapse.COLLAPSE_STYLE)
603
+ } else {
604
+ this.element.classList.add(EditorCollapse.COLLAPSE_STYLE);
605
+ }
606
+ }
607
+
608
+ isCollapsed() {
609
+ return this.element.classList.contains(EditorCollapse.COLLAPSE_STYLE);
610
+ }
611
+
612
+ renderState() {
613
+ if(this.isCollapsed()) {
614
+ this.button.classList.add(EditorCollapse.COLLAPSE_STYLE);
615
+ this.body.classList.add(EditorCollapse.COLLAPSE_STYLE);
616
+ this.header.classList.add(EditorCollapse.COLLAPSE_STYLE);
617
+ } else {
618
+ this.button.classList.remove(EditorCollapse.COLLAPSE_STYLE);
619
+ this.body.classList.remove(EditorCollapse.COLLAPSE_STYLE);
620
+ this.header.classList.remove(EditorCollapse.COLLAPSE_STYLE);
621
+ }
622
+ }
623
+
624
+ onMounted(): void {
625
+ throw new Error("Method not implemented.");
626
+ }
627
+
628
+ }
629
+
630
+ export class EditorParagraphMenu implements EditorComponent {
631
+ public readonly element: HTMLDivElement;
632
+ public readonly context: YangEditor;
633
+ private target: HTMLElement | undefined;
634
+
635
+ constructor(editor: YangEditor) {
636
+ this.context = editor;
637
+ this.element = document.createElement("div");
638
+ }
639
+
640
+ onMounted(): void {
641
+ throw new Error("Method not implemented.");
642
+ }
643
+
644
+ getElement(): HTMLElement {
645
+ return this.element;
646
+ }
647
+
648
+ onMount(): HTMLElement {
649
+ this.element.classList.add("yang-editor-paragraph-menu");
650
+ let ul = document.createElement("ul");
651
+ let lis = new Array<HTMLLIElement>();
652
+ let images = [this.context.options.images.delete, this.context.options.images.copy, this.context.options.images.cut];
653
+ let texts = ["Delete", "Copy", "Cut"];
654
+
655
+ for(let i = 0; i < images.length; i++) {
656
+ let li = document.createElement("li");
657
+ let icon = document.createElement("button");
658
+ let text = document.createElement("p");
659
+ li.classList.add("yang-editor-paragraph-menu-item");
660
+ icon.classList.add("yang-editor-paragraph-menu-item-icon");
661
+ text.classList.add("yang-editor-paragraph-menu-item-text");
662
+ let title = texts[i];
663
+ if(title !== undefined) {
664
+ icon.style.backgroundImage = `url(${images[i]})`;
665
+ icon.style.backgroundSize = "16px 16px";
666
+ icon.style.backgroundRepeat = "no-repeat";
667
+ icon.style.backgroundPosition = "center";
668
+ icon.style.width = "20px";
669
+ icon.style.height = "20px";
670
+ icon.style.cursor = "pointer";
671
+ icon.style.border = "none";
672
+ icon.style.outline = "none";
673
+ li.style.display = "flex";
674
+ li.style.alignItems = "center";
675
+ text.innerText = title;
676
+ li.appendChild(icon);
677
+ li.appendChild(text);
678
+ }
679
+ ul.appendChild(li);
680
+ lis.push(li);
681
+ }
682
+
683
+ if (lis[0]) {
684
+ lis[0].onclick = this.delete.bind(this);
685
+ }
686
+
687
+ if (lis[1]) {
688
+ lis[1].onclick = this.copy.bind(this);
689
+ }
690
+
691
+ if (lis[2]) {
692
+ lis[2].onclick = this.cut.bind(this);
693
+ }
694
+
695
+ this.element.onmouseleave = this.hide.bind(this);
696
+
697
+ this.element.appendChild(ul);
698
+ return this.getElement();
699
+ }
700
+
701
+
702
+ hide() {
703
+ this.element.style.display = "none";
704
+ }
705
+
706
+ show(target?: HTMLElement) {
707
+ this.element.style.display = "block";
708
+ if(target) {
709
+ this.target = target;
710
+ }
711
+ }
712
+
713
+ delete(ev: MouseEvent) {
714
+ if(this.target !== undefined) {
715
+ this.target.remove();
716
+ this.hide();
717
+ ev.stopPropagation();
718
+ }
719
+ }
720
+
721
+ copy(ev: MouseEvent) {
722
+ if(this.target !== undefined) {
723
+ navigator.clipboard.write([new ClipboardItem({
724
+ "text/html": this.target.outerHTML
725
+ })]).then(() => {
726
+ this.hide();
727
+ });
728
+ }
729
+ }
730
+
731
+ cut(ev: MouseEvent) {
732
+ if(this.target !== undefined) {
733
+ navigator.clipboard.write([new ClipboardItem({
734
+ "text/html": this.target.outerHTML
735
+ })]).then(() => {
736
+ this.target?.remove();
737
+ this.hide();
738
+ });
739
+ }
740
+ }
741
+ }
742
+
743
+ export class EditorParagraph implements EditorComponent {
744
+ public context: YangEditor;
745
+ public element: HTMLElement;
746
+
747
+ constructor(context: YangEditor, element?: HTMLElement) {
748
+
749
+ this.context = context;
750
+ if (element !== undefined) {
751
+ this.element = element;
752
+ } else {
753
+ this.element = document.createElement("p");
754
+ this.element.classList.add(ComponentFactory.PARAGRAPH_STYLE);
755
+ }
756
+ }
757
+
758
+ onMounted(): void {
759
+ throw new Error("Method not implemented.");
760
+ }
761
+
762
+ onMount() : HTMLElement {
763
+ return this.element;
764
+ }
765
+
766
+ getElement(): HTMLElement {
767
+ return this.element;
768
+ }
769
+ }
770
+
771
+
772
+ export class ComponentFactory {
773
+
774
+ static PARAGRAPH_STYLE = "yang-editor-paragraph";
775
+ static COLLAPSE_STYLE = "yang-editor-collapse";
776
+ static STYLES = [this.PARAGRAPH_STYLE, this.COLLAPSE_STYLE];
777
+
778
+ private context: YangEditor;
779
+
780
+ constructor(context: YangEditor) {
781
+ this.context = context;
782
+ }
783
+
784
+ createComponent(typeClassName: string, element?: HTMLElement): EditorComponent {
785
+ switch (typeClassName) {
786
+ case ComponentFactory.PARAGRAPH_STYLE:
787
+ return new EditorParagraph(this.context, element);
788
+ case ComponentFactory.COLLAPSE_STYLE:
789
+ return new EditorCollapse(this.context, element);
790
+ default:
791
+ throw new Error(`Unknown component type: ${typeClassName}`);
792
+ }
793
+ }
794
+
795
+ }