Skip to content

Commit 2275ddf

Browse files
authoredOct 8, 2024
feat(inline-toolbar): inline tools now can be used in the readonly mode (#2832)
* feat(inline-toolbar): inline tools now can be used in the readonly mode * tests added * docs improved
1 parent 3aa164d commit 2275ddf

File tree

11 files changed

+231
-82
lines changed

11 files changed

+231
-82
lines changed
 

‎docs/CHANGELOG.md

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
# Changelog
22

3+
### 2.31.0
4+
5+
- `New` - Inline tools (those with `isReadOnlySupported` specified) can now be used in read-only mode
6+
- `Fix` - Fix selection of first block in read-only initialization with "autofocus=true"
7+
38
### 2.30.6
49

5-
`Fix` – Fix the display of ‘Convert To’ near blocks that do not have the ‘conversionConfig.export’ rule specified
6-
`Fix` – The Plus button does not appear when the editor is loaded in an iframe in Chrome
10+
- `Fix` – Fix the display of ‘Convert To’ near blocks that do not have the ‘conversionConfig.export’ rule specified
11+
- `Fix` – The Plus button does not appear when the editor is loaded in an iframe in Chrome
712
- `Fix` - Prevent inline toolbar from closing in nested instance of editor
813

914
### 2.30.5
@@ -33,7 +38,7 @@
3338
- `New` – Block Tunes now supports nesting items
3439
- `New` – Block Tunes now supports separator items
3540
- `New`*Menu Config* – New item type – HTML
36-
`New`*Menu Config* – Default and HTML items now support hints
41+
- `New`*Menu Config* – Default and HTML items now support hints
3742
- `New` – Inline Toolbar has new look 💅
3843
- `New` – Inline Tool's `render()` now supports [Menu Config](https://editorjs.io/menu-config/) format
3944
- `New`*ToolsAPI* – All installed block tools now accessible via ToolsAPI `getBlockTools()` method

‎src/components/core.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export default class Core {
6161
UI.checkEmptiness();
6262
ModificationsObserver.enable();
6363

64-
if ((this.configuration as EditorConfig).autofocus) {
64+
if ((this.configuration as EditorConfig).autofocus === true && this.configuration.readOnly !== true) {
6565
Caret.setToBlock(BlockManager.blocks[0], Caret.positions.START);
6666
}
6767

‎src/components/modules/blockManager.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,7 @@ export default class BlockManager extends Module {
684684
*
685685
* @param {Node} element - html element to get Block by
686686
*/
687-
public getBlock(element: HTMLElement): Block {
687+
public getBlock(element: HTMLElement): Block | undefined {
688688
if (!$.isElement(element) as boolean) {
689689
element = element.parentNode as HTMLElement;
690690
}

‎src/components/modules/toolbar/inline.ts

+88-52
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { CommonInternalSettings } from '../../tools/base';
1212
import type { Popover, PopoverItemHtmlParams, PopoverItemParams, WithChildren } from '../../utils/popover';
1313
import { PopoverItemType } from '../../utils/popover';
1414
import { PopoverInline } from '../../utils/popover/popover-inline';
15+
import type InlineToolAdapter from 'src/components/tools/inline';
1516

1617
/**
1718
* Inline Toolbar elements
@@ -54,7 +55,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
5455
/**
5556
* Currently visible tools instances
5657
*/
57-
private toolsInstances: Map<string, IInlineTool> | null = new Map();
58+
private tools: Map<InlineToolAdapter, IInlineTool> = new Map();
5859

5960
/**
6061
* @param moduleConfiguration - Module Configuration
@@ -66,21 +67,10 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
6667
config,
6768
eventsDispatcher,
6869
});
69-
}
7070

71-
/**
72-
* Toggles read-only mode
73-
*
74-
* @param {boolean} readOnlyEnabled - read-only mode
75-
*/
76-
public toggleReadOnly(readOnlyEnabled: boolean): void {
77-
if (!readOnlyEnabled) {
78-
window.requestIdleCallback(() => {
79-
this.make();
80-
}, { timeout: 2000 });
81-
} else {
82-
this.destroy();
83-
}
71+
window.requestIdleCallback(() => {
72+
this.make();
73+
}, { timeout: 2000 });
8474
}
8575

8676
/**
@@ -116,14 +106,10 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
116106
return;
117107
}
118108

119-
if (this.Editor.ReadOnly.isEnabled) {
120-
return;
121-
}
122-
123-
Array.from(this.toolsInstances.entries()).forEach(([name, toolInstance]) => {
124-
const shortcut = this.getToolShortcut(name);
109+
for (const [tool, toolInstance] of this.tools) {
110+
const shortcut = this.getToolShortcut(tool.name);
125111

126-
if (shortcut) {
112+
if (shortcut !== undefined) {
127113
Shortcuts.remove(this.Editor.UI.nodes.redactor, shortcut);
128114
}
129115

@@ -133,9 +119,9 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
133119
if (_.isFunction(toolInstance.clear)) {
134120
toolInstance.clear();
135121
}
136-
});
122+
}
137123

138-
this.toolsInstances = null;
124+
this.tools = new Map();
139125

140126
this.reset();
141127
this.opened = false;
@@ -204,10 +190,12 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
204190
this.popover.destroy();
205191
}
206192

207-
const inlineTools = await this.getInlineTools();
193+
this.createToolsInstances();
194+
195+
const popoverItems = await this.getPopoverItems();
208196

209197
this.popover = new PopoverInline({
210-
items: inlineTools,
198+
items: popoverItems,
211199
scopeElement: this.Editor.API.methods.ui.nodes.redactor,
212200
messages: {
213201
nothingFound: I18n.ui(I18nInternalNS.ui.popover, 'Nothing found'),
@@ -290,25 +278,36 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
290278
return false;
291279
}
292280

293-
if (currentSelection && tagsConflictsWithSelection.includes(target.tagName)) {
281+
if (currentSelection !== null && tagsConflictsWithSelection.includes(target.tagName)) {
294282
return false;
295283
}
296284

297-
// The selection of the element only in contenteditable
298-
const contenteditable = target.closest('[contenteditable="true"]');
285+
/**
286+
* Check if there is at leas one tool enabled by current Block's Tool
287+
*/
288+
const currentBlock = this.Editor.BlockManager.getBlock(currentSelection.anchorNode as HTMLElement);
299289

300-
if (contenteditable === null) {
290+
if (!currentBlock) {
301291
return false;
302292
}
303293

304-
// is enabled by current Block's Tool
305-
const currentBlock = this.Editor.BlockManager.getBlock(currentSelection.anchorNode as HTMLElement);
294+
/**
295+
* Check that at least one tool is available for the current block
296+
*/
297+
const toolsAvailable = this.getTools();
298+
const isAtLeastOneToolAvailable = toolsAvailable.some((tool) => currentBlock.tool.inlineTools.has(tool.name));
306299

307-
if (!currentBlock) {
300+
if (isAtLeastOneToolAvailable === false) {
308301
return false;
309302
}
310303

311-
return currentBlock.tool.inlineTools.size !== 0;
304+
/**
305+
* Inline toolbar will be shown only if the target is contenteditable
306+
* In Read-Only mode, the target should be contenteditable with "false" value
307+
*/
308+
const contenteditable = target.closest('[contenteditable]');
309+
310+
return contenteditable !== null;
312311
}
313312

314313
/**
@@ -317,32 +316,63 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
317316
*/
318317

319318
/**
320-
* Returns Inline Tools segregated by their appearance type: popover items and custom html elements.
321-
* Sets this.toolsInstances map
319+
* Returns tools that are available for current block
320+
*
321+
* Used to check if Inline Toolbar could be shown
322+
* and to render tools in the Inline Toolbar
322323
*/
323-
private async getInlineTools(): Promise<PopoverItemParams[]> {
324-
const currentSelection = SelectionUtils.get();
325-
const currentBlock = this.Editor.BlockManager.getBlock(currentSelection.anchorNode as HTMLElement);
324+
private getTools(): InlineToolAdapter[] {
325+
const currentBlock = this.Editor.BlockManager.currentBlock;
326+
327+
if (!currentBlock) {
328+
return [];
329+
}
326330

327331
const inlineTools = Array.from(currentBlock.tool.inlineTools.values());
328332

329-
const popoverItems = [] as PopoverItemParams[];
333+
return inlineTools.filter((tool) => {
334+
/**
335+
* We support inline tools in read only mode.
336+
* Such tools should have isReadOnlySupported flag set to true
337+
*/
338+
if (this.Editor.ReadOnly.isEnabled && tool.isReadOnlySupported !== true) {
339+
return false;
340+
}
330341

331-
if (this.toolsInstances === null) {
332-
this.toolsInstances = new Map();
333-
}
342+
return true;
343+
});
344+
}
345+
346+
/**
347+
* Constructs tools instances and saves them to this.tools
348+
*/
349+
private createToolsInstances(): void {
350+
this.tools = new Map();
334351

335-
for (let i = 0; i < inlineTools.length; i++) {
336-
const tool = inlineTools[i];
352+
const tools = this.getTools();
353+
354+
tools.forEach((tool) => {
337355
const instance = tool.create();
338-
const renderedTool = await instance.render();
339356

340-
this.toolsInstances.set(tool.name, instance);
357+
this.tools.set(tool, instance);
358+
});
359+
}
360+
361+
/**
362+
* Returns Popover Items for tools segregated by their appearance type: regular items and custom html elements.
363+
*/
364+
private async getPopoverItems(): Promise<PopoverItemParams[]> {
365+
const popoverItems = [] as PopoverItemParams[];
366+
367+
let i = 0;
368+
369+
for (const [tool, instance] of this.tools) {
370+
const renderedTool = await instance.render();
341371

342372
/** Enable tool shortcut */
343373
const shortcut = this.getToolShortcut(tool.name);
344374

345-
if (shortcut) {
375+
if (shortcut !== undefined) {
346376
try {
347377
this.enableShortcuts(tool.name, shortcut);
348378
} catch (e) {}
@@ -429,7 +459,9 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
429459
type: PopoverItemType.Default,
430460
} as PopoverItemParams;
431461

432-
/** Prepend with separator if item has children and not the first one */
462+
/**
463+
* Prepend the separator if item has children and not the first one
464+
*/
433465
if ('children' in popoverItem && i !== 0) {
434466
popoverItems.push({
435467
type: PopoverItemType.Separator,
@@ -438,14 +470,18 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
438470

439471
popoverItems.push(popoverItem);
440472

441-
/** Append separator after the item is it has children and not the last one */
442-
if ('children' in popoverItem && i < inlineTools.length - 1) {
473+
/**
474+
* Append a separator after the item if it has children and not the last one
475+
*/
476+
if ('children' in popoverItem && i < this.tools.size - 1) {
443477
popoverItems.push({
444478
type: PopoverItemType.Separator,
445479
});
446480
}
447481
}
448482
});
483+
484+
i++;
449485
}
450486

451487
return popoverItems;
@@ -533,7 +569,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
533569
* Check Tools` state by selection
534570
*/
535571
private checkToolsState(): void {
536-
this.toolsInstances?.forEach((toolInstance) => {
572+
this.tools?.forEach((toolInstance) => {
537573
toolInstance.checkState?.(SelectionUtils.get());
538574
});
539575
}

0 commit comments

Comments
 (0)
Failed to load comments.