@ebl-vue/editor-full 2.31.35 → 2.31.36

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.
Files changed (97) hide show
  1. package/.postcssrc.yml +33 -0
  2. package/dist/index.d.ts +3 -7
  3. package/dist/index.mjs +183 -182
  4. package/dist/index.mjs.map +1 -0
  5. package/package.json +1 -1
  6. package/postcss.config.js +15 -0
  7. package/src/components/Editor/Editor.vue +293 -0
  8. package/src/components/index.ts +27 -0
  9. package/src/constants/index.ts +1 -0
  10. package/src/i18n/zh-cn.ts +160 -0
  11. package/src/icons/index.ts +93 -0
  12. package/src/index.ts +21 -0
  13. package/src/installer.ts +21 -0
  14. package/src/plugins/alert/index.ts +455 -0
  15. package/src/plugins/block-alignment/index.ts +117 -0
  16. package/src/plugins/block-alignment/readme.md +1 -0
  17. package/src/plugins/code/LICENSE +21 -0
  18. package/src/plugins/code/index.ts +619 -0
  19. package/src/plugins/code/utils/string.ts +34 -0
  20. package/src/plugins/color-picker/index.ts +132 -0
  21. package/src/plugins/delimiter/index.ts +121 -0
  22. package/src/plugins/drag-drop/index.css +19 -0
  23. package/src/plugins/drag-drop/index.ts +151 -0
  24. package/src/plugins/drag-drop/readme.md +1 -0
  25. package/src/plugins/header/H1.ts +404 -0
  26. package/src/plugins/header/H2.ts +403 -0
  27. package/src/plugins/header/H3.ts +404 -0
  28. package/src/plugins/header/H4.ts +404 -0
  29. package/src/plugins/header/H5.ts +403 -0
  30. package/src/plugins/header/H6.ts +404 -0
  31. package/src/plugins/header/index.ts +15 -0
  32. package/src/plugins/header/types.d.ts +46 -0
  33. package/src/plugins/imageResizeCrop/ImageTune.ts +635 -0
  34. package/src/plugins/imageResizeCrop/index.css +230 -0
  35. package/src/plugins/imageResizeCrop/index.ts +5 -0
  36. package/src/plugins/imageResizeCrop/types.d.ts +23 -0
  37. package/src/plugins/imageTool/index.ts +510 -0
  38. package/src/plugins/imageTool/types/codexteam__ajax.d.ts +89 -0
  39. package/src/plugins/imageTool/types/types.ts +236 -0
  40. package/src/plugins/imageTool/ui.ts +313 -0
  41. package/src/plugins/imageTool/uploader.ts +287 -0
  42. package/src/plugins/imageTool/utils/dom.ts +24 -0
  43. package/src/plugins/imageTool/utils/index.ts +73 -0
  44. package/src/plugins/imageTool/utils/isPromise.ts +10 -0
  45. package/src/plugins/indent/index.ts +695 -0
  46. package/src/plugins/inline-code/index.ts +203 -0
  47. package/src/plugins/list/ListRenderer/ChecklistRenderer.ts +208 -0
  48. package/src/plugins/list/ListRenderer/ListRenderer.ts +73 -0
  49. package/src/plugins/list/ListRenderer/OrderedListRenderer.ts +123 -0
  50. package/src/plugins/list/ListRenderer/UnorderedListRenderer.ts +123 -0
  51. package/src/plugins/list/ListRenderer/index.ts +6 -0
  52. package/src/plugins/list/ListTabulator/index.ts +1179 -0
  53. package/src/plugins/list/index.ts +488 -0
  54. package/src/plugins/list/styles/CssPrefix.ts +4 -0
  55. package/src/plugins/list/types/Elements.ts +14 -0
  56. package/src/plugins/list/types/ItemMeta.ts +40 -0
  57. package/src/plugins/list/types/ListParams.ts +102 -0
  58. package/src/plugins/list/types/ListRenderer.ts +6 -0
  59. package/src/plugins/list/types/OlCounterType.ts +63 -0
  60. package/src/plugins/list/types/index.ts +14 -0
  61. package/src/plugins/list/utils/focusItem.ts +18 -0
  62. package/src/plugins/list/utils/getChildItems.ts +40 -0
  63. package/src/plugins/list/utils/getItemChildWrapper.ts +10 -0
  64. package/src/plugins/list/utils/getItemContentElement.ts +10 -0
  65. package/src/plugins/list/utils/getSiblings.ts +52 -0
  66. package/src/plugins/list/utils/isLastItem.ts +9 -0
  67. package/src/plugins/list/utils/itemHasSublist.ts +10 -0
  68. package/src/plugins/list/utils/normalizeData.ts +83 -0
  69. package/src/plugins/list/utils/removeChildWrapperIfEmpty.ts +31 -0
  70. package/src/plugins/list/utils/renderToolboxInput.ts +113 -0
  71. package/src/plugins/list/utils/stripNumbers.ts +7 -0
  72. package/src/plugins/list/utils/type-guards.ts +8 -0
  73. package/src/plugins/marker/index.ts +199 -0
  74. package/src/plugins/outline/index.ts +62 -0
  75. package/src/plugins/outline/outline.css +52 -0
  76. package/src/plugins/paragraph/index.ts +384 -0
  77. package/src/plugins/paragraph/types/icons.d.ts +4 -0
  78. package/src/plugins/paragraph/utils/makeFragment.ts +17 -0
  79. package/src/plugins/quote/index.ts +203 -0
  80. package/src/plugins/table/index.ts +4 -0
  81. package/src/plugins/table/plugin.ts +255 -0
  82. package/src/plugins/table/table.ts +1202 -0
  83. package/src/plugins/table/toolbox.ts +166 -0
  84. package/src/plugins/table/utils/dom.ts +130 -0
  85. package/src/plugins/table/utils/popover.ts +185 -0
  86. package/src/plugins/table/utils/throttled.ts +22 -0
  87. package/src/plugins/underline/index.ts +214 -0
  88. package/src/plugins/undo/index.ts +526 -0
  89. package/src/plugins/undo/observer.ts +101 -0
  90. package/src/plugins/undo/vanilla-caret-js.ts +102 -0
  91. package/src/style.css +139 -0
  92. package/src/types.ts +3 -0
  93. package/src/utils/AxiosService.ts +87 -0
  94. package/src/utils/index.ts +15 -0
  95. package/src/utils/install.ts +19 -0
  96. package/tsconfig.json +37 -0
  97. package/vite.config.ts +81 -0
@@ -0,0 +1,63 @@
1
+ import { IconNumber, IconLowerRoman, IconUpperRoman, IconLowerAlpha, IconUpperAlpha } from '../../../icons';
2
+
3
+ export type OlCounterType = 'numeric' | 'upper-roman' | 'lower-roman' | 'upper-alpha' | 'lower-alpha';
4
+
5
+ /**
6
+ * Map that represents all of the supported styles of the counters for ordered list
7
+ */
8
+ export const OlCounterTypesMap = new Map<string, string>([
9
+ /**
10
+ * Value that represents default arabic numbers for counters
11
+ */
12
+ ['Numeric', 'numeric'],
13
+
14
+ /**
15
+ * Value that represents lower roman numbers for counteres
16
+ */
17
+ ['Lower Roman', 'lower-roman'],
18
+
19
+ /**
20
+ * Value that represents upper roman numbers for counters
21
+ */
22
+ ['Upper Roman', 'upper-roman'],
23
+
24
+ /**
25
+ * Value that represents lower alpha characters for counters
26
+ */
27
+ ['Lower Alpha', 'lower-alpha'],
28
+
29
+ /**
30
+ * Value that represents upper alpha characters for counters
31
+ */
32
+ ['Upper Alpha', 'upper-alpha'],
33
+ ]);
34
+
35
+ /**
36
+ * Map that represents relation between supported counter types and theirs icons to be displayed in toolbox
37
+ */
38
+ export const OlCounterIconsMap = new Map<string, string>([
39
+ /**
40
+ * Value that represents Icon for Numeric counter type
41
+ */
42
+ ['numeric', IconNumber],
43
+
44
+ /**
45
+ * Value that represents Icon for Lower Roman counter type
46
+ */
47
+ ['lower-roman', IconLowerRoman],
48
+
49
+ /**
50
+ * Value that represents Icon for Upper Roman counter type
51
+ */
52
+ ['upper-roman', IconUpperRoman],
53
+
54
+ /**
55
+ * Value that represents Icon for Lower Alpha counter type
56
+ */
57
+ ['lower-alpha', IconLowerAlpha],
58
+
59
+ /**
60
+ * Value that represents Icon for Upper Alpha counter type
61
+ */
62
+ ['upper-alpha', IconUpperAlpha],
63
+ ]);
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Paste event for tag substitution, similar to editor.js PasteEvent but with a different data type
3
+ */
4
+ export interface PasteEvent extends CustomEvent {
5
+ /**
6
+ * Pasted element
7
+ */
8
+ detail: {
9
+ /**
10
+ * Supported elements fir the paste event
11
+ */
12
+ data: HTMLUListElement | HTMLOListElement | HTMLLIElement;
13
+ };
14
+ }
@@ -0,0 +1,18 @@
1
+ import { focus } from '@editorjs/caret';
2
+ import type { ItemElement } from '../types/Elements';
3
+ import { getItemContentElement } from './getItemContentElement';
4
+
5
+ /**
6
+ * Sets focus to the item's content
7
+ * @param item - item (<li>) to select
8
+ * @param atStart - where to set focus: at the start or at the end
9
+ */
10
+ export function focusItem(item: ItemElement, atStart: boolean = true): void {
11
+ const itemContent = getItemContentElement(item);
12
+
13
+ if (!itemContent) {
14
+ return;
15
+ }
16
+
17
+ focus(itemContent, atStart);
18
+ }
@@ -0,0 +1,40 @@
1
+ import { DefaultListCssClasses } from '../ListRenderer';
2
+ import type { ItemChildWrapperElement, ItemElement } from '../types/Elements';
3
+
4
+ /**
5
+ * Get child items of the passed element
6
+ * @param element - child items would be got from this element
7
+ * @param firstLevelChildren - if method should return all level child items or only first level ones
8
+ */
9
+ // eslint-disable-next-line @typescript-eslint/no-duplicate-type-constituents
10
+ export function getChildItems(element: ItemElement | ItemChildWrapperElement, firstLevelChildren: boolean = true): ItemElement[] {
11
+ let itemChildWrapper: HTMLElement = element;
12
+
13
+ /**
14
+ * If passed element is list item than get item's child wrapper
15
+ */
16
+ if (element.classList.contains(DefaultListCssClasses.item)) {
17
+ itemChildWrapper = element.querySelector(`.${DefaultListCssClasses.itemChildren}`) as HTMLElement;
18
+ }
19
+
20
+ /**
21
+ * Check if itemChildWrapper is not null
22
+ */
23
+ if (itemChildWrapper === null) {
24
+ return [];
25
+ }
26
+
27
+ if (firstLevelChildren) {
28
+ /**
29
+ * Filter first level child items of the curret child item wrapper
30
+ * In case that child could be not only list item
31
+ */
32
+ return Array.from(itemChildWrapper.querySelectorAll(`:scope > .${DefaultListCssClasses.item}`));
33
+ } else {
34
+ /**
35
+ * Filter all levels child items of the current child item wrapper
36
+ * In case that child could be not only list item
37
+ */
38
+ return Array.from(itemChildWrapper.querySelectorAll(`.${DefaultListCssClasses.item}`));
39
+ }
40
+ }
@@ -0,0 +1,10 @@
1
+ import type { ItemChildWrapperElement, ItemElement } from '../types/Elements';
2
+ import { DefaultListCssClasses } from '../ListRenderer';
3
+
4
+ /**
5
+ * Returns child wrapper element of the passed item
6
+ * @param item - wrapper element would be got from this item
7
+ */
8
+ export function getItemChildWrapper(item: ItemElement): ItemChildWrapperElement | null {
9
+ return item.querySelector(`.${DefaultListCssClasses.itemChildren}`);
10
+ }
@@ -0,0 +1,10 @@
1
+ import type { ItemContentElement, ItemElement } from '../types/Elements';
2
+ import { DefaultListCssClasses } from '../ListRenderer';
3
+
4
+ /**
5
+ * Returns content element of the passed item
6
+ * @param item - content element would be got from this item
7
+ */
8
+ export function getItemContentElement(item: ItemElement): ItemContentElement | null {
9
+ return item.querySelector(`.${DefaultListCssClasses.itemContent}`);
10
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Get all siblings before passed element, or after it
3
+ * @param element - html element whose siblings would be returned
4
+ * @param direction - wherever siblings would be returned, after element of before it
5
+ */
6
+ export function getSiblings(element: HTMLElement, direction: 'after' | 'before' = 'after'): Element[] | null {
7
+ const siblings: Element[] = [];
8
+
9
+ let nextElementSibling: HTMLElement;
10
+
11
+ /**
12
+ * Method that is responsible for getting next element sibling responsible to the direction variable
13
+ * @param el - current element
14
+ * @returns HTML element of the sibling
15
+ */
16
+ function getNextElementSibling(el: HTMLElement): HTMLElement {
17
+ /**
18
+ * Get first sibling element respectfully to passed direction
19
+ */
20
+ switch (direction) {
21
+ case 'after':
22
+ return el.nextElementSibling as HTMLElement;
23
+
24
+ case 'before':
25
+ return el.previousElementSibling as HTMLElement;
26
+ }
27
+ }
28
+
29
+ nextElementSibling = getNextElementSibling(element);
30
+
31
+ /**
32
+ * Iterate by all siblings elements
33
+ */
34
+ while (nextElementSibling !== null) {
35
+ siblings.push(nextElementSibling);
36
+
37
+ /**
38
+ * Get next element sibling
39
+ */
40
+ nextElementSibling = getNextElementSibling(nextElementSibling);
41
+ }
42
+
43
+ /**
44
+ * Check that formed siblings array is not empty
45
+ * If it is emtpy, return null
46
+ */
47
+ if (siblings.length !== 0) {
48
+ return siblings;
49
+ }
50
+
51
+ return null;
52
+ }
@@ -0,0 +1,9 @@
1
+ import type { ItemElement } from '../types/Elements';
2
+
3
+ /**
4
+ * Check that passed item element is last item of the list
5
+ * @param item - item to be checked, wherever it has next element sibling
6
+ */
7
+ export function isLastItem(item: ItemElement): boolean {
8
+ return item.nextElementSibling === null;
9
+ }
@@ -0,0 +1,10 @@
1
+ import type { ItemElement } from '../types/Elements';
2
+ import { DefaultListCssClasses } from '../ListRenderer';
3
+
4
+ /**
5
+ * Check if passed item has the sublist
6
+ * @param item - item to be checked wherever it has sublist
7
+ */
8
+ export function itemHasSublist(item: ItemElement): boolean {
9
+ return item.querySelector(`.${DefaultListCssClasses.itemChildren}`) !== null;
10
+ }
@@ -0,0 +1,83 @@
1
+ import type { OldListData, ListData, ListItem, OldChecklistData, OldNestedListData } from '../types/ListParams';
2
+
3
+ /**
4
+ * Method that checks if data is result of the Old list tool save mtehod
5
+ * @param data - data of the OldList, Checklist, OldNestedList or Editorjs List tool
6
+ * @returns true if data related to the List tool, false otherwise
7
+ */
8
+ function instanceOfOldListData(data: ListData | OldListData | OldChecklistData | OldNestedListData): data is OldListData {
9
+ return (typeof data.items[0] === 'string');
10
+ }
11
+
12
+ /**
13
+ * Method that checks if data is result of the Old nested list tool save method
14
+ * @param data - data of the OldList, Checklist, OldNestedList or Editorjs List tool
15
+ * @returns true if data is related to the Nested List tool, false otherwise
16
+ */
17
+ function instanceOfOldNestedListData(data: ListData | OldListData | OldChecklistData | OldNestedListData): data is OldNestedListData {
18
+ return !('meta' in data);
19
+ }
20
+
21
+ /**
22
+ * Method that checks if data is result of the Old checklist tool save method
23
+ * @param data - data of the Checklist, OldList, OldNestedList or Editorjs List tool
24
+ * @returns true if data is related to the Checklist tool, false otherwise
25
+ */
26
+ function instanceOfChecklistData(data: ListData | OldListData | OldChecklistData | OldNestedListData): data is OldChecklistData {
27
+ return (
28
+ typeof data.items[0] !== 'string'
29
+ && 'text' in data.items[0]
30
+ && 'checked' in data.items[0]
31
+ && typeof data.items[0].text === 'string'
32
+ && typeof data.items[0].checked === 'boolean'
33
+ );
34
+ }
35
+
36
+ /**
37
+ * Method that checks if passed data is related to the legacy format and normalizes it
38
+ * @param data - data to be checked
39
+ * @returns - normalized data, ready to be used by Editorjs List tool
40
+ */
41
+ export default function normalizeData(data: ListData | OldListData | OldChecklistData): ListData {
42
+ const normalizedDataItems: ListItem[] = [];
43
+
44
+ if (instanceOfOldListData(data)) {
45
+ data.items.forEach((item) => {
46
+ normalizedDataItems.push({
47
+ content: item,
48
+ meta: {},
49
+ items: [],
50
+ });
51
+ });
52
+
53
+ return {
54
+ style: data.style,
55
+ meta: {},
56
+ items: normalizedDataItems,
57
+ };
58
+ } else if (instanceOfChecklistData(data)) {
59
+ data.items.forEach((item) => {
60
+ normalizedDataItems.push({
61
+ content: item.text,
62
+ meta: {
63
+ checked: item.checked,
64
+ },
65
+ items: [],
66
+ });
67
+ });
68
+
69
+ return {
70
+ style: 'checklist',
71
+ meta: {},
72
+ items: normalizedDataItems,
73
+ };
74
+ } else if (instanceOfOldNestedListData(data)) {
75
+ return {
76
+ style: data.style,
77
+ meta: {},
78
+ items: data.items,
79
+ };
80
+ } else {
81
+ return structuredClone(data);
82
+ }
83
+ };
@@ -0,0 +1,31 @@
1
+ import { DefaultListCssClasses } from '../ListRenderer';
2
+ import type { ItemChildWrapperElement, ItemElement } from '../types/Elements';
3
+ import { getChildItems } from './getChildItems';
4
+ import { getItemChildWrapper } from './getItemChildWrapper';
5
+
6
+ /**
7
+ * Method that will remove passed child wrapper if it has no child items
8
+ * @param element - child wrapper or actual item, from where we get child wrapper
9
+ */
10
+ // eslint-disable-next-line @typescript-eslint/no-duplicate-type-constituents
11
+ export function removeChildWrapperIfEmpty(element: ItemChildWrapperElement | ItemElement): void {
12
+ let itemChildWrapper: HTMLElement | null = element;
13
+
14
+ /**
15
+ * If passed element is list item than get item's child wrapper
16
+ */
17
+ if (element.classList.contains(DefaultListCssClasses.item)) {
18
+ itemChildWrapper = getItemChildWrapper(element);
19
+ }
20
+
21
+ if (itemChildWrapper === null) {
22
+ return;
23
+ }
24
+
25
+ /**
26
+ * Check that there is at least one item
27
+ */
28
+ if (getChildItems(itemChildWrapper).length === 0) {
29
+ itemChildWrapper.remove();
30
+ }
31
+ }
@@ -0,0 +1,113 @@
1
+ import * as Dom from '@editorjs/dom';
2
+ import { CssPrefix } from '../styles/CssPrefix';
3
+
4
+ /**
5
+ * Options used in input rendering
6
+ */
7
+ interface InputOptions {
8
+ /**
9
+ * Placeholder, that will be displayed in input
10
+ */
11
+ placeholder: string;
12
+ /**
13
+ * Input will be rendered with this value inside
14
+ */
15
+ value?: string;
16
+ /**
17
+ * Html attributes, that would be added to the input element
18
+ */
19
+ attributes?: {
20
+ [key: string]: string;
21
+ };
22
+ /**
23
+ * Flag that represents special behavior that prevents you from entering anything other than numbers
24
+ */
25
+ sanitize?: (value: string) => string;
26
+ }
27
+
28
+ const css = {
29
+ wrapper: `${CssPrefix}-start-with-field`,
30
+ input: `${CssPrefix}-start-with-field__input`,
31
+ startWithElementWrapperInvalid: `${CssPrefix}-start-with-field--invalid`,
32
+ };
33
+
34
+ /**
35
+ * Method that renders html element for popover start with item
36
+ * @param inputCallback - callback method that could change list attributes on input
37
+ * @param inputOptions - options used in input rendering
38
+ * @param inputOptions.value - input will be rendered with this value inside
39
+ * @param inputOptions.placeholder - placeholder, that will be displayed in input
40
+ * @param inputOptions.attributes - html attributes, that would be added to the input element
41
+ * @returns - rendered html element
42
+ */
43
+ export function renderToolboxInput(startLabel:string,inputCallback: (index: string) => void,
44
+ { value, placeholder, attributes, sanitize }: InputOptions): HTMLElement {
45
+ const startWithElementWrapper = Dom.make('div', css.wrapper);
46
+ const startLabelTag = Dom.make('div', 'cdx-list-start-with-field__startlabel', {
47
+ innerText:startLabel
48
+ })
49
+ const inpuWrapper=Dom.make('div', 'cdx-list-start-with-field__input-wrapper');
50
+
51
+ const input = Dom.make('input', css.input, {
52
+ placeholder,
53
+ /**
54
+ * Used to prevent focusing on the input by Tab key
55
+ * (Popover in the Toolbar lays below the blocks,
56
+ * so Tab in the last block will focus this hidden input if this property is not set)
57
+ */
58
+ tabIndex: -1,
59
+ /**
60
+ * Value of the start property, if it is not specified, then it is set to one
61
+ */
62
+ value,
63
+ type: 'number'
64
+ }) as HTMLInputElement;
65
+
66
+ /**
67
+ * Add passed attributes to the input
68
+ */
69
+ for (const attribute in attributes) {
70
+ input.setAttribute(attribute, attributes[attribute]);
71
+ }
72
+ startWithElementWrapper.appendChild(startLabelTag);
73
+ inpuWrapper.appendChild(input);
74
+ startWithElementWrapper.appendChild(inpuWrapper);
75
+
76
+
77
+
78
+ input.addEventListener('input', () => {
79
+ /**
80
+ * If input sanitizer specified, then sanitize input value
81
+ */
82
+ if (sanitize !== undefined) {
83
+ input.value = sanitize(input.value);
84
+ }
85
+
86
+ const validInput = input.checkValidity();
87
+
88
+ /**
89
+ * If input is invalid and classlist does not contain invalid class, add it
90
+ */
91
+ if (!validInput && !startWithElementWrapper.classList.contains(css.startWithElementWrapperInvalid)) {
92
+ startWithElementWrapper.classList.add(css.startWithElementWrapperInvalid);
93
+ }
94
+
95
+ /**
96
+ * If input is valid and classlist contains invalid class, remove it
97
+ */
98
+ if (validInput && startWithElementWrapper.classList.contains(css.startWithElementWrapperInvalid)) {
99
+ startWithElementWrapper.classList.remove(css.startWithElementWrapperInvalid);
100
+ }
101
+
102
+ /**
103
+ * If input is invalid, than do not change start with attribute
104
+ */
105
+ if (!validInput) {
106
+ return;
107
+ }
108
+
109
+ inputCallback(input.value);
110
+ });
111
+
112
+ return startWithElementWrapper;
113
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Removes everything except numbers in passed string
3
+ * @param input - string to be striped
4
+ */
5
+ export default function stripNumbers(input: string): string {
6
+ return input.replace(/\D+/g, '');
7
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Type guard to check if a node is an HTMLElement, then we can safely use it as an HTMLElement
3
+ * @param node - Node to be checked, wherever it is an HTMLElement
4
+ */
5
+ export function isHtmlElement(node: Node): node is HTMLElement {
6
+ // node is an HTMLElement if it is an element node
7
+ return node.nodeType === Node.ELEMENT_NODE;
8
+ }
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Build styles
3
+ */
4
+ import '@ebl-vue/editor-render/styles/marker.css';
5
+ import type { API } from '@ebl-vue/editorjs';
6
+ import { IconMarker } from '../../icons';
7
+
8
+ /**
9
+ * Marker Tool for the Editor.js
10
+ *
11
+ * Allows to wrap inline fragment and style it somehow.
12
+ */
13
+ export default class Marker {
14
+ private api: API;
15
+ private button: HTMLElement | null;
16
+ private tag: string;
17
+ private iconClasses: {base: string; active: string};
18
+
19
+ static get toolboxIcon() {
20
+ return IconMarker;
21
+ }
22
+ /**
23
+ * Class name for term-tag
24
+ *
25
+ * @type {string}
26
+ */
27
+ static get CSS() {
28
+ return 'cdx-marker';
29
+ };
30
+
31
+ /**
32
+ * @param {{api: object}} - Editor.js API
33
+ */
34
+ constructor({api}: {api: any}) {
35
+ this.api = api;
36
+
37
+ /**
38
+ * Toolbar Button
39
+ *
40
+ * @type {HTMLElement|null}
41
+ */
42
+ this.button = null;
43
+
44
+ /**
45
+ * Tag represented the term
46
+ *
47
+ * @type {string}
48
+ */
49
+ this.tag = 'MARK';
50
+
51
+ /**
52
+ * CSS classes
53
+ */
54
+ this.iconClasses = {
55
+ base: this.api.styles.inlineToolButton,
56
+ active: this.api.styles.inlineToolButtonActive
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Specifies Tool as Inline Toolbar Tool
62
+ *
63
+ * @return {boolean}
64
+ */
65
+ static get isInline() {
66
+ return true;
67
+ }
68
+
69
+ /**
70
+ * Create button element for Toolbar
71
+ *
72
+ * @return {HTMLElement}
73
+ */
74
+ render() {
75
+ this.button = document.createElement('button');
76
+ //this.button.type = 'button';
77
+ this.button.setAttribute("type", "button");
78
+ this.button.classList.add(this.iconClasses.base);
79
+ this.button.innerHTML = this.toolboxIcon;
80
+
81
+ return this.button;
82
+ }
83
+
84
+ /**
85
+ * Wrap/Unwrap selected fragment
86
+ *
87
+ * @param {Range} range - selected fragment
88
+ */
89
+ surround(range:Range) {
90
+ if (!range) {
91
+ return;
92
+ }
93
+
94
+ let termWrapper = this.api.selection.findParentTag(this.tag, Marker.CSS);
95
+
96
+ /**
97
+ * If start or end of selection is in the highlighted block
98
+ */
99
+ if (termWrapper) {
100
+ this.unwrap(termWrapper);
101
+ } else {
102
+ this.wrap(range);
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Wrap selection with term-tag
108
+ *
109
+ * @param {Range} range - selected fragment
110
+ */
111
+ wrap(range:Range) {
112
+ /**
113
+ * Create a wrapper for highlighting
114
+ */
115
+ let marker = document.createElement(this.tag);
116
+
117
+ marker.classList.add(Marker.CSS);
118
+
119
+ /**
120
+ * SurroundContent throws an error if the Range splits a non-Text node with only one of its boundary points
121
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Range/surroundContents}
122
+ *
123
+ * // range.surroundContents(span);
124
+ */
125
+ marker.appendChild(range.extractContents());
126
+ range.insertNode(marker);
127
+
128
+ /**
129
+ * Expand (add) selection to highlighted block
130
+ */
131
+ this.api.selection.expandToTag(marker);
132
+ }
133
+
134
+ /**
135
+ * Unwrap term-tag
136
+ *
137
+ * @param {HTMLElement} termWrapper - term wrapper tag
138
+ */
139
+ unwrap(termWrapper: HTMLElement) {
140
+ /**
141
+ * Expand selection to all term-tag
142
+ */
143
+ this.api.selection.expandToTag(termWrapper);
144
+
145
+ let sel = window.getSelection();
146
+ let range = sel?.getRangeAt(0);
147
+
148
+ let unwrappedContent = range?.extractContents();
149
+
150
+ /**
151
+ * Remove empty term-tag
152
+ */
153
+ termWrapper?.parentNode?.removeChild(termWrapper);
154
+
155
+ /**
156
+ * Insert extracted content
157
+ */
158
+ if (unwrappedContent) {
159
+ range?.insertNode(unwrappedContent);
160
+ }
161
+ /**
162
+ * Restore selection
163
+ */
164
+ sel?.removeAllRanges();
165
+ if (range) {
166
+ sel?.addRange(range);
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Check and change Term's state for current selection
172
+ */
173
+ checkState() {
174
+ const termTag = this.api.selection.findParentTag(this.tag, Marker.CSS);
175
+
176
+ this.button?.classList.toggle(this.iconClasses.active, !!termTag);
177
+ }
178
+
179
+ /**
180
+ * Get Tool icon's SVG
181
+ * @return {string}
182
+ */
183
+ get toolboxIcon() {
184
+ return IconMarker;
185
+ }
186
+
187
+ /**
188
+ * Sanitizer rule
189
+ * @return {{mark: {class: string}}}
190
+ */
191
+ static get sanitize() {
192
+ return {
193
+ mark: {
194
+ class: Marker.CSS
195
+ }
196
+ };
197
+ }
198
+ }
199
+