Skip to content

Commit 6b60cb1

Browse files
authored
[Feature] Multiple toolbox items: using of data overrides instead of config overrides (#2064)
* Use data instead of config * check if active toolbox entry exists * comparison improved * eslint fix
1 parent 0ddc09d commit 6b60cb1

File tree

10 files changed

+109
-131
lines changed

10 files changed

+109
-131
lines changed

src/components/block/index.ts

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,6 @@ interface BlockConstructorOptions {
5454
* Tunes data for current Block
5555
*/
5656
tunesData: { [name: string]: BlockTuneData };
57-
58-
/**
59-
* May contain overrides for tool default config
60-
*/
61-
configOverrides: ToolConfig;
6257
}
6358

6459
/**
@@ -259,15 +254,9 @@ export default class Block extends EventsDispatcher<BlockEvents> {
259254
api,
260255
readOnly,
261256
tunesData,
262-
configOverrides,
263257
}: BlockConstructorOptions) {
264258
super();
265259

266-
// Merge tool default settings with overrides
267-
Object.entries(configOverrides).forEach(([prop, value]) => {
268-
tool.settings[prop] = value;
269-
});
270-
271260
this.name = tool.name;
272261
this.id = id;
273262
this.settings = tool.settings;
@@ -747,15 +736,45 @@ export default class Block extends EventsDispatcher<BlockEvents> {
747736
}
748737

749738
/**
750-
* Returns current active toolbox entry
739+
* Tool could specify several entries to be displayed at the Toolbox (for example, "Heading 1", "Heading 2", "Heading 3")
740+
* This method returns the entry that is related to the Block (depended on the Block data)
751741
*/
752-
public get activeToolboxEntry(): ToolboxConfig {
753-
const entry = Array.isArray(this.tool.toolbox) ? this.toolInstance.activeToolboxEntry : this.tool.toolbox;
742+
public async getActiveToolboxEntry(): Promise<ToolboxConfig | undefined> {
743+
const toolboxSettings = this.tool.toolbox;
754744

755-
return {
756-
...entry,
757-
id: _.getHash(entry.icon + entry.title),
758-
};
745+
/**
746+
* If Tool specifies just the single entry, treat it like an active
747+
*/
748+
if (Array.isArray(toolboxSettings) === false) {
749+
return Promise.resolve(this.tool.toolbox as ToolboxConfig);
750+
}
751+
752+
/**
753+
* If we have several entries with their own data overrides,
754+
* find those who matches some current data property
755+
*
756+
* Example:
757+
* Tools' toolbox: [
758+
* {title: "Heading 1", data: {level: 1} },
759+
* {title: "Heading 2", data: {level: 2} }
760+
* ]
761+
*
762+
* the Block data: {
763+
* text: "Heading text",
764+
* level: 2
765+
* }
766+
*
767+
* that means that for the current block, the second toolbox item (matched by "{level: 2}") is active
768+
*/
769+
const blockData = await this.data;
770+
const toolboxItems = toolboxSettings as ToolboxConfig[];
771+
772+
return toolboxItems.find((item) => {
773+
return Object.entries(item.data)
774+
.some(([propName, propValue]) => {
775+
return blockData[propName] && _.equals(blockData[propName], propValue);
776+
});
777+
});
759778
}
760779

761780
/**

src/components/modules/api/blocks.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,6 @@ export default class BlocksAPI extends Module {
242242
index,
243243
needToFocus,
244244
replace,
245-
config,
246245
});
247246

248247
return new BlockAPI(insertedBlock);

src/components/modules/blockManager.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,6 @@ export default class BlockManager extends Module {
229229
* @param {string} options.tool - tools passed in editor config {@link EditorConfig#tools}
230230
* @param {string} [options.id] - unique id for this block
231231
* @param {BlockToolData} [options.data] - constructor params
232-
* @param {ToolConfig} [options.config] - may contain tool default settings overrides
233232
*
234233
* @returns {Block}
235234
*/
@@ -238,7 +237,6 @@ export default class BlockManager extends Module {
238237
data = {},
239238
id = undefined,
240239
tunes: tunesData = {},
241-
config = {},
242240
}: {tool: string; id?: string; data?: BlockToolData; tunes?: {[name: string]: BlockTuneData}; config?: ToolConfig}): Block {
243241
const readOnly = this.Editor.ReadOnly.isEnabled;
244242
const tool = this.Editor.Tools.blockTools.get(name);
@@ -250,7 +248,6 @@ export default class BlockManager extends Module {
250248
api: this.Editor.API,
251249
readOnly,
252250
tunesData,
253-
configOverrides: config,
254251
});
255252

256253
if (!readOnly) {
@@ -270,7 +267,6 @@ export default class BlockManager extends Module {
270267
* @param {number} [options.index] - index where to insert new Block
271268
* @param {boolean} [options.needToFocus] - flag shows if needed to update current Block index
272269
* @param {boolean} [options.replace] - flag shows if block by passed index should be replaced with inserted one
273-
* @param {ToolConfig} [options.config] - may contain tool default settings overrides
274270
*
275271
* @returns {Block}
276272
*/
@@ -282,7 +278,6 @@ export default class BlockManager extends Module {
282278
needToFocus = true,
283279
replace = false,
284280
tunes = {},
285-
config,
286281
}: {
287282
id?: string;
288283
tool?: string;
@@ -291,7 +286,6 @@ export default class BlockManager extends Module {
291286
needToFocus?: boolean;
292287
replace?: boolean;
293288
tunes?: {[name: string]: BlockTuneData};
294-
config?: ToolConfig;
295289
} = {}): Block {
296290
let newIndex = index;
297291

@@ -303,7 +297,6 @@ export default class BlockManager extends Module {
303297
tool,
304298
data,
305299
tunes,
306-
config,
307300
});
308301

309302
/**
@@ -340,21 +333,18 @@ export default class BlockManager extends Module {
340333
* @param {object} options - replace options
341334
* @param {string} options.tool — plugin name
342335
* @param {BlockToolData} options.data — plugin data
343-
* @param {ToolConfig} options.config - may contain tool default settings overrides-
344336
*
345337
* @returns {Block}
346338
*/
347339
public replace({
348340
tool = this.config.defaultBlock,
349341
data = {},
350-
config = {},
351342
}): Block {
352343
return this.insert({
353344
tool,
354345
data,
355346
index: this.currentBlockIndex,
356347
replace: true,
357-
config,
358348
});
359349
}
360350

src/components/modules/toolbar/conversion.ts

Lines changed: 37 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Flipper from '../../flipper';
66
import I18n from '../../i18n';
77
import { I18nInternalNS } from '../../i18n/namespace-internal';
88
import { clean } from '../../utils/sanitizer';
9-
import { ToolboxConfig, ToolConfig } from '../../../../types';
9+
import { ToolboxConfig, BlockToolData } from '../../../../types';
1010

1111
/**
1212
* HTML Elements used for ConversionToolbar
@@ -136,18 +136,18 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
136136
this.nodes.wrapper.classList.add(ConversionToolbar.CSS.conversionToolbarShowed);
137137

138138
/**
139-
* We use timeout to prevent bubbling Enter keydown on first dropdown item
139+
* We use RAF to prevent bubbling Enter keydown on first dropdown item
140140
* Conversion flipper will be activated after dropdown will open
141141
*/
142-
setTimeout(() => {
142+
window.requestAnimationFrame(() => {
143143
this.flipper.activate(this.tools.map(tool => tool.button).filter((button) => {
144144
return !button.classList.contains(ConversionToolbar.CSS.conversionToolHidden);
145145
}));
146146
this.flipper.focusFirst();
147147
if (_.isFunction(this.togglingCallback)) {
148148
this.togglingCallback(true);
149149
}
150-
}, 50);
150+
});
151151
}
152152

153153
/**
@@ -175,27 +175,17 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
175175
* For that Tools must provide import/export methods
176176
*
177177
* @param {string} replacingToolName - name of Tool which replaces current
178-
* @param {ToolConfig} [config] -
178+
* @param blockDataOverrides - Block data overrides. Could be passed in case if Multiple Toolbox items specified
179179
*/
180-
public async replaceWithBlock(replacingToolName: string, config?: ToolConfig): Promise<void> {
180+
public async replaceWithBlock(replacingToolName: string, blockDataOverrides?: BlockToolData): Promise<void> {
181181
/**
182182
* At first, we get current Block data
183183
*
184184
* @type {BlockToolConstructable}
185185
*/
186186
const currentBlockTool = this.Editor.BlockManager.currentBlock.tool;
187-
const currentBlockName = this.Editor.BlockManager.currentBlock.name;
188187
const savedBlock = await this.Editor.BlockManager.currentBlock.save() as SavedData;
189188
const blockData = savedBlock.data;
190-
const isToolboxItemActive = this.Editor.BlockManager.currentBlock.activeToolboxEntry.id === config?.id;
191-
192-
/**
193-
* When current Block name is equals to the replacing tool Name,
194-
* than convert this Block back to the default Block
195-
*/
196-
if (currentBlockName === replacingToolName && isToolboxItemActive) {
197-
replacingToolName = this.config.defaultBlock;
198-
}
199189

200190
/**
201191
* Getting a class of replacing Tool
@@ -252,10 +242,17 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
252242
return;
253243
}
254244

245+
/**
246+
* If this conversion fired by the one of multiple Toolbox items,
247+
* extend converted data with this item's "data" overrides
248+
*/
249+
if (blockDataOverrides) {
250+
newBlockData = Object.assign(newBlockData, blockDataOverrides);
251+
}
252+
255253
this.Editor.BlockManager.replace({
256254
tool: replacingToolName,
257255
data: newBlockData,
258-
config,
259256
});
260257
this.Editor.BlockSelection.clearSelection();
261258

@@ -287,12 +284,8 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
287284
}
288285

289286
if (Array.isArray(tool.toolbox)) {
290-
tool.toolbox.forEach((configItem, i) =>
291-
this.addToolIfValid(
292-
name,
293-
configItem,
294-
(tool.toolbox as ToolboxConfig[]).slice(0, i)
295-
)
287+
tool.toolbox.forEach((toolboxItem) =>
288+
this.addToolIfValid(name, toolboxItem)
296289
);
297290
} else {
298291
this.addToolIfValid(name, tool.toolbox);
@@ -305,23 +298,15 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
305298
*
306299
* @param name - tool's name
307300
* @param toolboxSettings - tool's single toolbox setting
308-
* @param otherToolboxEntries - other entries in tool's toolbox config (if any)
309301
*/
310-
private addToolIfValid(name: string, toolboxSettings: ToolboxConfig, otherToolboxEntries: ToolboxConfig[] = []): void {
302+
private addToolIfValid(name: string, toolboxSettings: ToolboxConfig): void {
311303
/**
312304
* Skip tools that don't pass 'toolbox' property
313305
*/
314306
if (_.isEmpty(toolboxSettings) || !toolboxSettings.icon) {
315307
return;
316308
}
317309

318-
/**
319-
* Skip tools that has not unique hash
320-
*/
321-
if (otherToolboxEntries.find(otherItem => otherItem.id === toolboxSettings.id)) {
322-
return;
323-
}
324-
325310
this.addTool(name, toolboxSettings);
326311
}
327312

@@ -349,19 +334,35 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
349334
});
350335

351336
this.listeners.on(tool, 'click', async () => {
352-
await this.replaceWithBlock(toolName, toolboxItem.config);
337+
await this.replaceWithBlock(toolName, toolboxItem.data);
353338
});
354339
}
355340

356341
/**
357342
* Hide current Tool and show others
358343
*/
359-
private filterTools(): void {
344+
private async filterTools(): Promise<void> {
360345
const { currentBlock } = this.Editor.BlockManager;
346+
const currentBlockActiveToolboxEntry = await currentBlock.getActiveToolboxEntry();
347+
348+
/**
349+
* Compares two Toolbox entries
350+
*
351+
* @param entry1 - entry to compare
352+
* @param entry2 - entry to compare with
353+
*/
354+
function isTheSameToolboxEntry(entry1, entry2): boolean {
355+
return entry1.icon === entry2.icon && entry1.title === entry2.title;
356+
}
361357

362358
this.tools.forEach(tool => {
363-
const isToolboxItemActive = currentBlock.activeToolboxEntry.id === tool.toolboxItem.id;
364-
const hidden = (tool.button.dataset.tool === currentBlock.name && isToolboxItemActive);
359+
let hidden = false;
360+
361+
if (currentBlockActiveToolboxEntry) {
362+
const isToolboxItemActive = isTheSameToolboxEntry(currentBlockActiveToolboxEntry, tool.toolboxItem);
363+
364+
hidden = (tool.button.dataset.tool === currentBlock.name && isToolboxItemActive);
365+
}
365366

366367
tool.button.hidden = hidden;
367368
tool.button.classList.toggle(ConversionToolbar.CSS.conversionToolHidden, hidden);

src/components/modules/toolbar/inline.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
463463
/**
464464
* Changes Conversion Dropdown content for current block's Tool
465465
*/
466-
private setConversionTogglerContent(): void {
466+
private async setConversionTogglerContent(): Promise<void> {
467467
const { BlockManager } = this.Editor;
468468
const { currentBlock } = BlockManager;
469469
const toolName = currentBlock.name;
@@ -480,7 +480,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
480480
/**
481481
* Get icon or title for dropdown
482482
*/
483-
const toolboxSettings = currentBlock.activeToolboxEntry || {};
483+
const toolboxSettings = await currentBlock.getActiveToolboxEntry() || {};
484484

485485
this.nodes.conversionTogglerContent.innerHTML =
486486
toolboxSettings.icon ||

src/components/tools/block.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,7 @@ export default class BlockTool extends BaseTool<IBlockTool> {
7676
const toolToolboxSettings = this.constructable[InternalBlockToolSettings.Toolbox] as ToolboxConfig;
7777

7878
if (Array.isArray(toolToolboxSettings)) {
79-
return toolToolboxSettings
80-
.map(item => this.getActualToolboxSettings(item))
81-
.map(item => this.addIdToToolboxConfig(item));
79+
return toolToolboxSettings.map(item => this.getActualToolboxSettings(item));
8280
} else {
8381
return this.getActualToolboxSettings(toolToolboxSettings);
8482
}
@@ -165,7 +163,7 @@ export default class BlockTool extends BaseTool<IBlockTool> {
165163
}
166164

167165
/**
168-
* Returns toolbox items settings merged with user defined settings
166+
* Returns toolbox item's settings merged with user defined settings
169167
*
170168
* @param toolboxItemSettings - toolbox item settings to merge
171169
*/
@@ -182,17 +180,4 @@ export default class BlockTool extends BaseTool<IBlockTool> {
182180

183181
return Object.assign({}, toolboxItemSettings, userToolboxSettings);
184182
}
185-
186-
/**
187-
* Returns toolbox config entry with apended id field which is used later for
188-
* identifying an entry in case the tool has multiple toolbox entries configured.
189-
*
190-
* @param config - toolbox config entry
191-
*/
192-
private addIdToToolboxConfig(config: ToolboxConfig): ToolboxConfig {
193-
return {
194-
...config,
195-
id: _.getHash(config.icon + config.title),
196-
};
197-
}
198183
}

0 commit comments

Comments
 (0)