@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,695 @@
1
+ import type { BlockTune, API, BlockAPI, BlockAddedEvent,ToolConfig } from '@ebl-vue/editorjs/types'
2
+
3
+ import type { MenuConfig } from '@ebl-vue/editorjs/types/tools'
4
+ import { IconIndentLeft,IconIndentRight } from '../../icons';
5
+
6
+ import '@ebl-vue/editor-render/styles/indent.css'
7
+
8
+
9
+
10
+ interface ConstructorArgs {
11
+ data: IndentData;
12
+ config?: ToolConfig;
13
+ api: API;
14
+ block?: any;
15
+ }
16
+ export type TextDirection = 'ltr' | "rtl"
17
+
18
+ export type IndentTuneConfig = Partial<IndentTuneConfigOptions>
19
+ export type IndentTuneConfigOptions = Record<'indentSize' | 'maxIndent' | 'minIndent', number> & {
20
+ /**
21
+ * Specify the editorjs version so that the styles will match your version
22
+ */
23
+ version?: string;
24
+ /**
25
+ * Enables auto indent if not null or `true`
26
+ * Default disabled.
27
+ */
28
+ autoIndent?: {
29
+ /**
30
+ * Tunes you want to apply auto indent for.
31
+ * Defaults to all.
32
+ */
33
+ tuneNames?: string[]
34
+ } | boolean;
35
+ /**
36
+ * Apply a highlight to the indent if not null
37
+ */
38
+ highlightIndent?: {
39
+ className?: string,
40
+ /**
41
+ * Tunes you want to apply highlight for.
42
+ * Defaults to all.
43
+ */
44
+ tuneNames?: string[]
45
+ };
46
+ orientation: 'horizontal' | 'vertical';
47
+ /**
48
+ * Example:
49
+ * {
50
+ * tableTuneName: { min: 2, max:8 },
51
+ * imageTuneName: { min:1 }
52
+ * }
53
+ */
54
+ customBlockIndentLimits: Record<string, Partial<Record<'min' | 'max', number>>>;
55
+ /**
56
+ * Custom keyboard indent handler.
57
+ * Return 'indent' or 'unindent' if you want to change the current indentation.
58
+ * Return 'undefined' or pass 'false' instead of a function to disable the shortcut entirely
59
+ * Return 'default' for default handling
60
+ */
61
+ handleShortcut?: ((e: KeyboardEvent, blockId: string) => 'indent' | 'unindent' | "default" | undefined) | undefined | false;
62
+ /**
63
+ * `ltr` | `rtl`
64
+ */
65
+ direction: TextDirection;
66
+ /**
67
+ * Handle dynamic direction change (on each block level)
68
+ */
69
+ directionChangeHandler: null | ((listener: (blockId: string, direction: TextDirection) => void) => void);
70
+ } & (
71
+ | {
72
+ tuneName: string
73
+ multiblock: true
74
+ }
75
+ | {
76
+ tuneName: null
77
+ multiblock: false
78
+ }
79
+ )
80
+ const warnings = { orientation: false };
81
+ export type IndentData = { indentLevel: number }
82
+ export default class IndentTune implements BlockTune {
83
+ public static get isTune() {
84
+ return true
85
+ }
86
+ public static DATA_WRAPPER_NAME = 'data-block-indent-wrapper'
87
+ public static DATA_FOCUSED = 'data-focused'
88
+ public static DATA_INDENT_LEVEL = "data-indent-level"
89
+ private api: API
90
+ private block: BlockAPI | undefined
91
+ private config: IndentTuneConfigOptions
92
+ public data: IndentData
93
+ private wrapper: HTMLElement = document.createElement('div')
94
+ private DEFAULT_INDENT_KEY = 'Tab' as const;
95
+ constructor({ api, data, config, block, ...other }: ConstructorArgs) {
96
+ this.api = api
97
+ this.block = block
98
+
99
+ const defaultConfig: IndentTuneConfigOptions = {
100
+ indentSize: 24,
101
+ maxIndent: 8,
102
+ minIndent: 0,
103
+ multiblock: false,
104
+ autoIndent: false,
105
+ tuneName: null,
106
+ orientation: 'horizontal',
107
+ customBlockIndentLimits: {},
108
+ handleShortcut: undefined,
109
+ direction: "ltr",
110
+ directionChangeHandler: null,
111
+ version: "2.30",
112
+ }
113
+ if (!config && "settings" in other)
114
+ // for older versions
115
+ config = other.settings as any ?? {}
116
+ this.config = {
117
+ ...defaultConfig,
118
+ ...(config ?? {}),
119
+ }
120
+
121
+ this.changeConfigBasedOnVersionIfNeeded();
122
+
123
+ if (this.config?.directionChangeHandler) {
124
+ this.config.directionChangeHandler(this.alignmentChangeListener.bind(this));
125
+ }
126
+
127
+ const defaultIndentLevel = this.config.customBlockIndentLimits[this.block?.name ?? '']?.min ?? this.config.minIndent
128
+ this.data = {
129
+ //@ts-ignore
130
+ indentLevel: defaultIndentLevel,
131
+ ...(data ?? {}),
132
+ }
133
+
134
+ if (this.config.multiblock && !this.config.tuneName)
135
+ console.error("IndentTune config 'tuneName' was not provided, this is required for multiblock option to work.")
136
+
137
+ window.addEventListener('resize', (e) => this.onResize.call(this, e))
138
+
139
+ // this is called after the indent tune constructor is created
140
+ this.api.events.on("block changed", ({ event }: { event: BlockAddedEvent }) => {
141
+ const targetId = event.detail.target.id;
142
+ const currentBlockId = this.block?.id;
143
+ const isSameTarget = currentBlockId === targetId
144
+ if (!isSameTarget) return;
145
+
146
+ if (!this.shouldApplyAutoIndent) return
147
+ queueMicrotask(() => this.autoIndentBlock())
148
+ })
149
+ }
150
+
151
+
152
+ public prepare?(): void | Promise<void> {
153
+
154
+ }
155
+ public reset?(): void | Promise<void> {
156
+
157
+ }
158
+
159
+
160
+ public render(): HTMLElement | MenuConfig {
161
+ //Disable items after they are rendered synchronously
162
+ const disableItemOnRender = () => {
163
+ if (this.data.indentLevel == this.maxIndent) {
164
+ const element = this.getTuneButton('indent');
165
+ element?.classList.add(this.CSS.disabledItem)
166
+ if (!element) return true;
167
+
168
+ }
169
+ if (this.data.indentLevel == this.minIndent) {
170
+ const element = this.getTuneButton('unindent');
171
+ element?.classList.add(this.CSS.disabledItem)
172
+ if (!element) return true;
173
+ }
174
+ }
175
+ queueMicrotask(() => {
176
+ const shouldUseMacroTask = disableItemOnRender();
177
+ if (shouldUseMacroTask)
178
+ setTimeout(disableItemOnRender, 300)
179
+ })
180
+
181
+
182
+ if (this.config.orientation === 'vertical') {
183
+ const leftElementName = `${this.TuneNames.indentLeft}-${this.block?.id}`;
184
+ const rightElementName = `${this.TuneNames.indentRight}-${this.block?.id}`
185
+ return [
186
+ {
187
+ title: this.rightText,
188
+ hint: {
189
+ title: this.api.i18n.t(this.rightText),
190
+ },
191
+ onActivate: (item, event) => {
192
+ this.handleIndentRight();
193
+ // override editorjs internal title copy
194
+ //@ts-ignore
195
+ item.title = this.rightText;
196
+ },
197
+ icon: IconIndentRight,
198
+ name: rightElementName,
199
+ },
200
+ {
201
+ title: this.leftText,
202
+ hint: {
203
+ title: this.api.i18n.t(this.leftText),
204
+ },
205
+ onActivate: (item, event) => {
206
+ this.handleIndentLeft()
207
+ //@ts-ignore
208
+ item.title = this.leftText;
209
+ },
210
+ icon: IconIndentLeft,
211
+ name: leftElementName,
212
+ },
213
+ ]
214
+ }
215
+
216
+ const html = /*html*/ `
217
+ <div class="${this.CSS.popoverItem} ${this.CSS.customPopoverItem}" data-item-name='indent' version=${this.config.version}>
218
+ <button type="button" class="${this.CSS.popoverItemIcon}" data-${this.TuneNames.indentLeft}>${IconIndentLeft}</button>
219
+ <span class="${this.CSS.popoverItemTitle}">${this.api.sanitizer.clean(this.api.i18n.t('Indent'), {})}</span>
220
+ <button type="button" class="${this.CSS.popoverItemIcon}" data-${this.TuneNames.indentRight} style="margin-left:10px;">${IconIndentRight}</button>
221
+ </div>
222
+ `
223
+
224
+ const item = this.createElementFromTemplate(html);
225
+ const rightBtn = item.querySelector(`[data-${this.TuneNames.indentRight}]`);
226
+ const leftBtn = item.querySelector(`[data-${this.TuneNames.indentLeft}]`);
227
+ if (rightBtn) {
228
+ rightBtn.addEventListener('click', () => this.handleIndentRight());
229
+ this.api.tooltip.onHover(rightBtn as HTMLElement, this.api.i18n.t('Indent right'), {
230
+ placement: 'top',
231
+ });
232
+ }
233
+ if (leftBtn) {
234
+ leftBtn.addEventListener('click', () => this.handleIndentLeft());
235
+ this.api.tooltip.onHover(leftBtn as HTMLElement, this.api.i18n.t('Indent left'), {
236
+ placement: 'top',
237
+ });
238
+ }
239
+ return item
240
+ }
241
+
242
+ public wrap(pluginsContent: HTMLElement): HTMLElement {
243
+ this.wrapper.appendChild(pluginsContent)
244
+ this.wrapper.setAttribute(IndentTune.DATA_WRAPPER_NAME, '')
245
+
246
+ let applyBlockHighlight = Boolean(this.config.highlightIndent)
247
+ if (this.config.highlightIndent?.tuneNames) {
248
+ const shouldIgnoreThisBlock = !this.config.highlightIndent.tuneNames.includes(this.block?.name ?? "")
249
+ if (shouldIgnoreThisBlock)
250
+ applyBlockHighlight = false
251
+ }
252
+ if (applyBlockHighlight) {
253
+ const highlightEl = this.createElementFromTemplate(/*html*/`
254
+ <div class="${this.config.highlightIndent?.className ?? ""} ${this.CSS.highlightIndent}">
255
+ </div>
256
+ `);
257
+ const contentEl = pluginsContent.classList.contains(this.EditorCSS.content) ? pluginsContent : pluginsContent.querySelector(`.${this.EditorCSS.content}`)
258
+ contentEl?.appendChild(highlightEl);
259
+ }
260
+
261
+ this.applyStylesToWrapper(this.wrapper, this.data.indentLevel)
262
+
263
+ // this.wrapper.addEventListener("keypress", this.handlePropagationForKeyEvent.bind(this), { capture: true })
264
+ // this.wrapper.addEventListener("keyup", this.handlePropagationForKeyEvent.bind(this), { capture: true })
265
+ this.wrapper.addEventListener('keydown', (...args) => this.onKeyDown.apply(this, args), { capture: true })
266
+ this.wrapper.addEventListener("focus", (e) => this.onFocus.call(this, e), { capture: true });
267
+ this.wrapper.addEventListener("blur", (e) => this.onBlur.call(this, e), { capture: true });
268
+
269
+ return this.wrapper
270
+ }
271
+
272
+ public save() {
273
+ return this.data
274
+ }
275
+
276
+ private get CSS() {
277
+ return {
278
+ customPopoverItem: 'ce-popover-indent-item',
279
+ popoverItem: 'ce-popover-item',
280
+ popoverItemIcon: 'ce-popover-item__icon',
281
+ popoverItemTitle: 'ce-popover-item__title',
282
+ disabledItem: 'ce-popover-item--disabled',
283
+ highlightIndent: "ce-highlight-indent",
284
+ }
285
+ }
286
+ private get EditorCSS() {
287
+ return {
288
+ block: "ce-block",
289
+ content: "ce-block__content",
290
+ redactor: "codex-editor__redactor",
291
+ }
292
+ }
293
+
294
+ private get TuneNames() {
295
+ return {
296
+ indentLeft: 'tune-indent-left',
297
+ indentRight: 'tune-indent-right',
298
+ }
299
+ }
300
+
301
+ private get customInterval() {
302
+ return this.config.customBlockIndentLimits[this.block?.name ?? ''] ?? {}
303
+ }
304
+
305
+ private get maxIndent() {
306
+ return this.customInterval.max ?? this.config.maxIndent
307
+ }
308
+
309
+ private get minIndent() {
310
+ return this.customInterval.min ?? this.config.minIndent
311
+ }
312
+
313
+ private get isDirectionInverted() {
314
+ return this.config.direction !== 'ltr'; // also ignore invalid directions
315
+ }
316
+
317
+ private get rightText() {
318
+ return this.api.i18n.t(this.isDirectionInverted ? 'Un Indent' : 'Indent')
319
+ }
320
+
321
+ private get leftText() {
322
+ return this.api.i18n.t(this.isDirectionInverted ? 'Indent' : 'Un Indent')
323
+ }
324
+
325
+ private get shouldApplyAutoIndent(): boolean {
326
+ if (!this.config.autoIndent) return false
327
+ if (typeof this.config.autoIndent === 'boolean') return this.config.autoIndent;
328
+
329
+ // the index is still on the previous block
330
+ const previousBlockIndex = this.api.blocks.getCurrentBlockIndex()
331
+ // const previousBlockIndex = currentBlockIndex// - 1;
332
+ if (previousBlockIndex < 0) return false;
333
+
334
+ const previousBlock = this.api.blocks.getBlockByIndex(previousBlockIndex)
335
+ if (!previousBlock) return false;
336
+
337
+ const previousBlockName = previousBlock.name;
338
+ return !this.config.autoIndent.tuneNames?.length || this.config.autoIndent.tuneNames.includes(previousBlockName)
339
+ }
340
+
341
+ private handlePropagationForKeyEvent(e: KeyboardEvent): { isIndent: boolean } | null {
342
+ if (!this.block?.id) return null;
343
+ // omit key shortcut entirely
344
+ if (this.config.handleShortcut === false) return null;
345
+
346
+ const isDefaultKeyPressed = e.key == this.DEFAULT_INDENT_KEY
347
+ const isCustomBehaviourDefined = typeof this.config.handleShortcut === 'function'
348
+
349
+ if (!isCustomBehaviourDefined && !isDefaultKeyPressed) return null
350
+
351
+ const handledCommand = this.config.handleShortcut?.(e, this.block.id)
352
+ const shouldIgnoreKeyPress = !handledCommand && isCustomBehaviourDefined
353
+ if (shouldIgnoreKeyPress) return null
354
+
355
+ let isIndent: boolean;
356
+ switch (handledCommand) {
357
+ case 'indent':
358
+ isIndent = true;
359
+ break
360
+ case 'unindent':
361
+ isIndent = false;
362
+ break;
363
+ case 'default':
364
+ default:
365
+ if (!isDefaultKeyPressed) return null;
366
+ isIndent = !e.shiftKey
367
+ }
368
+
369
+ e.stopPropagation()
370
+ e.preventDefault()
371
+
372
+ return { isIndent }
373
+ }
374
+
375
+ private onKeyDown(e: KeyboardEvent) {
376
+ if (!this.block?.id) return;
377
+
378
+ const handlingResult = this.handlePropagationForKeyEvent(e)
379
+ if (!handlingResult) return;
380
+ const { isIndent } = handlingResult
381
+
382
+ //this might be still open
383
+ this.api.inlineToolbar.close()
384
+ const selectedBlocks = this.getGlobalSelectedBlocks()
385
+ const isSingleLineBlock = !this.config.multiblock || selectedBlocks.length < 2
386
+ if (isSingleLineBlock) {
387
+ if (isIndent) this.indentBlock()
388
+ else this.unIndentBlock()
389
+ this.block.dispatchChange?.()
390
+ return
391
+ }
392
+
393
+ if (!Boolean(this.config.tuneName)) {
394
+ console.error(`'tuneName' is empty.`)
395
+ return
396
+ }
397
+
398
+ selectedBlocks.forEach(async (b) => {
399
+ //get block indent level
400
+ const savedData = await b.save()
401
+ if (!savedData) return
402
+
403
+ if (!('tunes' in savedData) || typeof savedData.tunes !== 'object' || !savedData.tunes) {
404
+ console.error('Multiblock indenting is not supported for this editor version. ')
405
+ return
406
+ }
407
+
408
+ //this somehow SAVES fine
409
+ const tune = (savedData.tunes as Record<string, IndentData>)[this.config.tuneName ?? ''] as IndentData | undefined
410
+ if (!tune) {
411
+ console.error(`'tuneName' is invalid, no tune was found for block ${b.name}`)
412
+ return
413
+ }
414
+ if (isIndent) tune.indentLevel = Math.min(this.config.maxIndent, (tune.indentLevel ?? 0) + 1)
415
+ else tune.indentLevel = Math.max(0, (tune.indentLevel ?? 0) - 1)
416
+ b.dispatchChange?.()
417
+
418
+ //apply visual feedback manually, since we can't make the tune update on other blocks
419
+ const blockWrapper = this.getWrapperBlockById(b.id)
420
+ if (blockWrapper instanceof HTMLElement) {
421
+ this.applyStylesToWrapper(blockWrapper, tune.indentLevel)
422
+ }
423
+ })
424
+ }
425
+
426
+ private handleIndentLeft() {
427
+ if (this.isDirectionInverted)
428
+ this.indentBlock();
429
+ else
430
+ this.unIndentBlock();
431
+ this.block?.dispatchChange?.()
432
+ }
433
+
434
+ private handleIndentRight() {
435
+ if (this.isDirectionInverted)
436
+ this.unIndentBlock();
437
+ else
438
+ this.indentBlock();
439
+ this.block?.dispatchChange?.()
440
+ }
441
+
442
+ private indentBlock() {
443
+ if (!this.wrapper) return
444
+ this.data.indentLevel = Math.min(this.data.indentLevel + 1, this.maxIndent)
445
+
446
+ this.applyStylesToWrapper(this.wrapper, this.data.indentLevel)
447
+
448
+ this.toggleDisableStateForButtons()
449
+ }
450
+
451
+ private unIndentBlock() {
452
+ if (!this.wrapper) return
453
+ this.data.indentLevel = Math.max(this.data.indentLevel - 1, this.minIndent)
454
+
455
+ this.applyStylesToWrapper(this.wrapper, this.data.indentLevel)
456
+
457
+ this.toggleDisableStateForButtons()
458
+ }
459
+
460
+ private autoIndentBlock() {
461
+ const currentBlockIndex = this.api.blocks.getBlockIndex(this.block!.id)
462
+ const previousBlock = this.api.blocks.getBlockByIndex(currentBlockIndex - 1)
463
+
464
+ if (!previousBlock) return
465
+
466
+ const previousBlockIndentLevelAttribute = previousBlock.holder?.querySelector(`[${IndentTune.DATA_WRAPPER_NAME}]`)
467
+ ?.getAttribute(IndentTune.DATA_INDENT_LEVEL);
468
+
469
+ const previousBlockIndentLevel = Number(
470
+ previousBlockIndentLevelAttribute ?? 0,
471
+ )
472
+
473
+ const currentBlockIndentLevel = Math.min(Math.max(previousBlockIndentLevel, this.minIndent), this.maxIndent)
474
+
475
+ this.data.indentLevel = currentBlockIndentLevel
476
+
477
+ this.applyStylesToWrapper(this.wrapper, this.data.indentLevel)
478
+ }
479
+
480
+ private toggleDisableStateForButtons() {
481
+ if (this.data.indentLevel === this.minIndent)
482
+ this.getTuneButton('unindent')?.classList.add(this.CSS.disabledItem)
483
+ else
484
+ this.getTuneButton('unindent')?.classList.remove(this.CSS.disabledItem)
485
+
486
+ if (this.data.indentLevel === this.maxIndent)
487
+ this.getTuneButton('indent')?.classList.add(this.CSS.disabledItem)
488
+ else
489
+ this.getTuneButton('indent')?.classList.remove(this.CSS.disabledItem)
490
+ }
491
+
492
+ private getTuneButton(indentType: 'indent' | 'unindent') {
493
+ let indentName: 'indentLeft' | "indentRight" = indentType === 'indent' ? "indentRight" : "indentLeft";
494
+ if (this.isDirectionInverted)
495
+ indentName = indentType == 'indent' ? "indentLeft" : "indentRight";
496
+
497
+ return this.config.orientation === 'vertical'
498
+ ? this.getTuneByName(`${this.TuneNames[indentName]}-${this.block?.id}`)
499
+ : document.querySelector(`.${this.CSS.popoverItemIcon}[data-${this.TuneNames[indentName]}]`)
500
+ }
501
+
502
+ private getTuneByName(name: string) {
503
+ return document.querySelector(`.${this.CSS.popoverItem}[data-item-name="${name}"]`)
504
+ }
505
+
506
+ private getTuneTitleByName(name: string) {
507
+ return this.getTuneByName(name)?.querySelector(`.${this.CSS.popoverItemTitle}`)
508
+ }
509
+
510
+ private applyStylesToWrapper(givenWrapper: HTMLElement, indentLevel: number = parseInt(givenWrapper.getAttribute(IndentTune.DATA_INDENT_LEVEL) || "0")) {
511
+ const indentValue = indentLevel * this.config.indentSize;
512
+ givenWrapper.setAttribute(IndentTune.DATA_INDENT_LEVEL, indentLevel.toString());
513
+
514
+ const contentElement = givenWrapper.querySelector(`.${this.EditorCSS.content}`);
515
+ const blockElement = this.getBlockForWrapper(givenWrapper) || document.querySelector(`.${this.EditorCSS.redactor}`);
516
+ if (!(contentElement instanceof HTMLElement) || !blockElement) return;
517
+
518
+ const blockWidth = blockElement.getBoundingClientRect().width;
519
+ if (blockWidth === 0) //block is not in DOM yet/redactor is hidden, depends on editorjs version
520
+ {
521
+ queueMicrotask(() => this.applyStylesToWrapper.bind(this)(givenWrapper, indentLevel))
522
+ return
523
+ }
524
+ const normalContentWidth = this.maxWidthForContent(givenWrapper);
525
+
526
+ // until margin inline == 0;
527
+ const maxApplyableIndent = (blockWidth - normalContentWidth) / 2
528
+
529
+ const indentToApply = Math.max(0, Math.min(maxApplyableIndent, indentValue));
530
+ //have to double the value because content inside has margin inline;
531
+ const indentValuePixels = `${indentToApply * 2}px`;
532
+ const indentValuePixelsForHighlight = `${indentToApply}px`;
533
+
534
+ // because the direction has been changed
535
+ // const omitTransitionTemporarily = givenWrapper.style[this.isDirectionInverted ? 'paddingLeft' : "paddingRight"] === "0px"
536
+ // if (omitTransitionTemporarily) this.omitTransitionTemporarily(givenWrapper)
537
+
538
+ if (this.isDirectionInverted) {
539
+ givenWrapper.style.paddingLeft = '0px';
540
+ givenWrapper.style.paddingRight = indentValuePixels;
541
+ } else {
542
+ givenWrapper.style.paddingLeft = indentValuePixels;
543
+ givenWrapper.style.paddingRight = "0px";
544
+ }
545
+
546
+ const highlightElement = givenWrapper.querySelector(`.${this.CSS.highlightIndent}`)
547
+ if (!(highlightElement instanceof HTMLElement)) return;
548
+
549
+ // if (omitTransitionTemporarily) this.omitTransitionTemporarily(highlightElement)
550
+
551
+ if (this.isDirectionInverted) {
552
+ highlightElement.style.width = indentValuePixelsForHighlight;
553
+ highlightElement.style.left = "100%";
554
+ highlightElement.style.right = '';
555
+ }
556
+ else {
557
+ highlightElement.style.width = indentValuePixelsForHighlight;
558
+ highlightElement.style.left = "";
559
+ highlightElement.style.right = '100%';
560
+ }
561
+ }
562
+
563
+ private onFocus(e: FocusEvent) {
564
+ if (!(e.target instanceof HTMLElement)) return;
565
+ const isInsideCurrentBlock = this.wrapper.contains(e.target);
566
+ if (!isInsideCurrentBlock) return;
567
+ this.wrapper.setAttribute(IndentTune.DATA_FOCUSED, '');
568
+ }
569
+
570
+ private onBlur(e: FocusEvent) {
571
+ if (!(e.target instanceof HTMLElement)) return;
572
+ const isInsideCurrentBlock = this.wrapper.contains(e.target);
573
+ if (!isInsideCurrentBlock) return;
574
+ this.wrapper.removeAttribute(IndentTune.DATA_FOCUSED);
575
+ }
576
+
577
+ private lastResizeTimeout: null | NodeJS.Timeout = null;
578
+ private onResize(e: UIEvent) {
579
+ const timeoutDelayMs = 500;
580
+ if (this.lastResizeTimeout)
581
+ clearTimeout(this.lastResizeTimeout)
582
+ this.lastResizeTimeout = setTimeout(() => {
583
+ const allWrappers = document.querySelectorAll(`[${IndentTune.DATA_WRAPPER_NAME}]`);
584
+ allWrappers.forEach((w) => {
585
+ if (!(w instanceof HTMLElement)) return;
586
+ this.applyStylesToWrapper(w);
587
+ });
588
+ }, timeoutDelayMs);
589
+ }
590
+
591
+ private getGlobalSelectedBlocks() {
592
+ const crossSelectedBlocks = new Array(this.api.blocks.getBlocksCount())
593
+ .fill(0)
594
+ .map((_, idx) => this.api.blocks.getBlockByIndex(idx))
595
+ .filter((b): b is BlockAPI => !!b?.selected)
596
+ return crossSelectedBlocks
597
+ }
598
+
599
+ private getWrapperBlockById(blockId: string) {
600
+ const selector = `.${this.EditorCSS.block}[data-id="${blockId}"] [${IndentTune.DATA_WRAPPER_NAME}]`
601
+ return document.querySelector(selector) ??
602
+ this.api.blocks.getById(blockId)?.holder.querySelector(`[${IndentTune.DATA_WRAPPER_NAME}]`)
603
+ ?? null;
604
+ }
605
+
606
+ private getBlockForWrapper(wrapper: HTMLElement): HTMLElement | null {
607
+ let current = wrapper;
608
+ while ((!current.classList.contains(this.EditorCSS.block))) {
609
+ if (!current.parentElement || (current instanceof HTMLHtmlElement)) return null;
610
+ current = current.parentElement;
611
+ }
612
+
613
+ return current
614
+ }
615
+
616
+ private alignmentChangeListener(blockId: string, direction: TextDirection) {
617
+ // across all blocks this function is called, so we got to filter out
618
+ if (blockId !== this.block?.id) return;
619
+ const hasDirectionChanged = direction !== this.config.direction
620
+ if (!hasDirectionChanged) return
621
+
622
+ this.config.direction = direction;
623
+ this.applyStylesToWrapper(this.wrapper, this.data.indentLevel)
624
+ this.toggleDisableStateForButtons()
625
+ if (this.config.orientation === 'vertical') {
626
+ // I have to update the text for the indent options 😪
627
+
628
+ const indentRightBtnTitle = this.getTuneTitleByName(`${this.TuneNames.indentRight}-${this.block?.id}`);
629
+ if (indentRightBtnTitle) indentRightBtnTitle.textContent = this.rightText
630
+
631
+ const indentLeftBtnTitle = this.getTuneTitleByName(`${this.TuneNames.indentLeft}-${this.block?.id}`);
632
+ if (indentLeftBtnTitle) indentLeftBtnTitle.textContent = this.leftText
633
+ }
634
+ }
635
+
636
+ private createElementFromTemplate(template: string): HTMLElement {
637
+ return new DOMParser().parseFromString(template, 'text/html').body.firstChild as HTMLElement;
638
+ }
639
+
640
+ // private omitTransitionTemporarily(element: HTMLElement) {
641
+ // element.style.transitionDuration = "0s";
642
+ // (() => {
643
+ // element.style.transitionDuration = "";
644
+ // })
645
+ // }
646
+
647
+ private changeConfigBasedOnVersionIfNeeded() {
648
+ if (!this.config.version) return;
649
+
650
+ if (this.config.version < '2.27' && this.config.orientation === 'vertical') {
651
+ this.config.orientation = 'horizontal';
652
+
653
+ if (!warnings.orientation)
654
+ console.warn("Current editor version does not support vertical indent tune 'orientation'. View your config input")
655
+ warnings.orientation = true;
656
+ }
657
+ }
658
+
659
+ private cachedMaxWidthForContent: number | null = null;
660
+ private maxWidthForContent(elementInsideEditor: HTMLElement): number {
661
+ const content = elementInsideEditor.querySelector(`.${this.EditorCSS.content}`);
662
+ if ((content instanceof HTMLElement)) {
663
+ const { maxWidth } = window.getComputedStyle(content);
664
+ if (maxWidth) {
665
+ this.cachedMaxWidthForContent = parseInt(maxWidth);
666
+ return this.cachedMaxWidthForContent
667
+ }
668
+ }
669
+
670
+ if (this.cachedMaxWidthForContent !== null) return this.cachedMaxWidthForContent
671
+ // Get value from stylesheet
672
+ // for (let i = 0; i < document.styleSheets.length; i++) {
673
+ // const styleSheet = document.styleSheets.item(i);
674
+ // if (!styleSheet || !(styleSheet.ownerNode instanceof HTMLStyleElement) || styleSheet.ownerNode.id !== "editor-js-styles") continue;
675
+
676
+ // for (let j = 0; j < styleSheet.cssRules.length; j++) {
677
+ // const rule = styleSheet.cssRules.item(j);
678
+ // if (!rule) continue;
679
+ // const selector = `.${this.EditorCSS.content}`
680
+ // if (!rule.cssText.startsWith(selector + " {") && (rule as { selectorText?: string }).selectorText !== selector)
681
+ // continue;
682
+ // const matches = /max-width: [\d]+px;/.exec(rule.cssText)
683
+ // if (!matches || !matches.length) continue;
684
+
685
+ // const maxWidth = parseInt(matches[0].replace("max-width:", ''));
686
+ // this.cachedMaxWidthForContent = maxWidth;
687
+ // return this.maxWidthForContent;
688
+ // }
689
+
690
+ // }
691
+ // console.warn("Cannot detect EditorJs max width for content. Please contact package author")
692
+ this.cachedMaxWidthForContent = 650;
693
+ return this.cachedMaxWidthForContent;
694
+ }
695
+ }