Skip to content

Commit

Permalink
[Feature] Multiple toolbox items: using of data overrides instead of …
Browse files Browse the repository at this point in the history
…config overrides (#2064)

* Use data instead of config

* check if active toolbox entry exists

* comparison improved

* eslint fix
  • Loading branch information
neSpecc committed May 18, 2022
1 parent 0ddc09d commit 6b60cb1
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 131 deletions.
55 changes: 37 additions & 18 deletions src/components/block/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,6 @@ interface BlockConstructorOptions {
* Tunes data for current Block
*/
tunesData: { [name: string]: BlockTuneData };

/**
* May contain overrides for tool default config
*/
configOverrides: ToolConfig;
}

/**
Expand Down Expand Up @@ -259,15 +254,9 @@ export default class Block extends EventsDispatcher<BlockEvents> {
api,
readOnly,
tunesData,
configOverrides,
}: BlockConstructorOptions) {
super();

// Merge tool default settings with overrides
Object.entries(configOverrides).forEach(([prop, value]) => {
tool.settings[prop] = value;
});

this.name = tool.name;
this.id = id;
this.settings = tool.settings;
Expand Down Expand Up @@ -747,15 +736,45 @@ export default class Block extends EventsDispatcher<BlockEvents> {
}

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

return {
...entry,
id: _.getHash(entry.icon + entry.title),
};
/**
* If Tool specifies just the single entry, treat it like an active
*/
if (Array.isArray(toolboxSettings) === false) {
return Promise.resolve(this.tool.toolbox as ToolboxConfig);
}

/**
* If we have several entries with their own data overrides,
* find those who matches some current data property
*
* Example:
* Tools' toolbox: [
* {title: "Heading 1", data: {level: 1} },
* {title: "Heading 2", data: {level: 2} }
* ]
*
* the Block data: {
* text: "Heading text",
* level: 2
* }
*
* that means that for the current block, the second toolbox item (matched by "{level: 2}") is active
*/
const blockData = await this.data;
const toolboxItems = toolboxSettings as ToolboxConfig[];

return toolboxItems.find((item) => {
return Object.entries(item.data)
.some(([propName, propValue]) => {
return blockData[propName] && _.equals(blockData[propName], propValue);
});
});
}

/**
Expand Down
1 change: 0 additions & 1 deletion src/components/modules/api/blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,6 @@ export default class BlocksAPI extends Module {
index,
needToFocus,
replace,
config,
});

return new BlockAPI(insertedBlock);
Expand Down
10 changes: 0 additions & 10 deletions src/components/modules/blockManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,6 @@ export default class BlockManager extends Module {
* @param {string} options.tool - tools passed in editor config {@link EditorConfig#tools}
* @param {string} [options.id] - unique id for this block
* @param {BlockToolData} [options.data] - constructor params
* @param {ToolConfig} [options.config] - may contain tool default settings overrides
*
* @returns {Block}
*/
Expand All @@ -238,7 +237,6 @@ export default class BlockManager extends Module {
data = {},
id = undefined,
tunes: tunesData = {},
config = {},
}: {tool: string; id?: string; data?: BlockToolData; tunes?: {[name: string]: BlockTuneData}; config?: ToolConfig}): Block {
const readOnly = this.Editor.ReadOnly.isEnabled;
const tool = this.Editor.Tools.blockTools.get(name);
Expand All @@ -250,7 +248,6 @@ export default class BlockManager extends Module {
api: this.Editor.API,
readOnly,
tunesData,
configOverrides: config,
});

if (!readOnly) {
Expand All @@ -270,7 +267,6 @@ export default class BlockManager extends Module {
* @param {number} [options.index] - index where to insert new Block
* @param {boolean} [options.needToFocus] - flag shows if needed to update current Block index
* @param {boolean} [options.replace] - flag shows if block by passed index should be replaced with inserted one
* @param {ToolConfig} [options.config] - may contain tool default settings overrides
*
* @returns {Block}
*/
Expand All @@ -282,7 +278,6 @@ export default class BlockManager extends Module {
needToFocus = true,
replace = false,
tunes = {},
config,
}: {
id?: string;
tool?: string;
Expand All @@ -291,7 +286,6 @@ export default class BlockManager extends Module {
needToFocus?: boolean;
replace?: boolean;
tunes?: {[name: string]: BlockTuneData};
config?: ToolConfig;
} = {}): Block {
let newIndex = index;

Expand All @@ -303,7 +297,6 @@ export default class BlockManager extends Module {
tool,
data,
tunes,
config,
});

/**
Expand Down Expand Up @@ -340,21 +333,18 @@ export default class BlockManager extends Module {
* @param {object} options - replace options
* @param {string} options.tool — plugin name
* @param {BlockToolData} options.data — plugin data
* @param {ToolConfig} options.config - may contain tool default settings overrides-
*
* @returns {Block}
*/
public replace({
tool = this.config.defaultBlock,
data = {},
config = {},
}): Block {
return this.insert({
tool,
data,
index: this.currentBlockIndex,
replace: true,
config,
});
}

Expand Down
73 changes: 37 additions & 36 deletions src/components/modules/toolbar/conversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Flipper from '../../flipper';
import I18n from '../../i18n';
import { I18nInternalNS } from '../../i18n/namespace-internal';
import { clean } from '../../utils/sanitizer';
import { ToolboxConfig, ToolConfig } from '../../../../types';
import { ToolboxConfig, BlockToolData } from '../../../../types';

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

/**
* We use timeout to prevent bubbling Enter keydown on first dropdown item
* We use RAF to prevent bubbling Enter keydown on first dropdown item
* Conversion flipper will be activated after dropdown will open
*/
setTimeout(() => {
window.requestAnimationFrame(() => {
this.flipper.activate(this.tools.map(tool => tool.button).filter((button) => {
return !button.classList.contains(ConversionToolbar.CSS.conversionToolHidden);
}));
this.flipper.focusFirst();
if (_.isFunction(this.togglingCallback)) {
this.togglingCallback(true);
}
}, 50);
});
}

/**
Expand Down Expand Up @@ -175,27 +175,17 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
* For that Tools must provide import/export methods
*
* @param {string} replacingToolName - name of Tool which replaces current
* @param {ToolConfig} [config] -
* @param blockDataOverrides - Block data overrides. Could be passed in case if Multiple Toolbox items specified
*/
public async replaceWithBlock(replacingToolName: string, config?: ToolConfig): Promise<void> {
public async replaceWithBlock(replacingToolName: string, blockDataOverrides?: BlockToolData): Promise<void> {
/**
* At first, we get current Block data
*
* @type {BlockToolConstructable}
*/
const currentBlockTool = this.Editor.BlockManager.currentBlock.tool;
const currentBlockName = this.Editor.BlockManager.currentBlock.name;
const savedBlock = await this.Editor.BlockManager.currentBlock.save() as SavedData;
const blockData = savedBlock.data;
const isToolboxItemActive = this.Editor.BlockManager.currentBlock.activeToolboxEntry.id === config?.id;

/**
* When current Block name is equals to the replacing tool Name,
* than convert this Block back to the default Block
*/
if (currentBlockName === replacingToolName && isToolboxItemActive) {
replacingToolName = this.config.defaultBlock;
}

/**
* Getting a class of replacing Tool
Expand Down Expand Up @@ -252,10 +242,17 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
return;
}

/**
* If this conversion fired by the one of multiple Toolbox items,
* extend converted data with this item's "data" overrides
*/
if (blockDataOverrides) {
newBlockData = Object.assign(newBlockData, blockDataOverrides);
}

this.Editor.BlockManager.replace({
tool: replacingToolName,
data: newBlockData,
config,
});
this.Editor.BlockSelection.clearSelection();

Expand Down Expand Up @@ -287,12 +284,8 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
}

if (Array.isArray(tool.toolbox)) {
tool.toolbox.forEach((configItem, i) =>
this.addToolIfValid(
name,
configItem,
(tool.toolbox as ToolboxConfig[]).slice(0, i)
)
tool.toolbox.forEach((toolboxItem) =>
this.addToolIfValid(name, toolboxItem)
);
} else {
this.addToolIfValid(name, tool.toolbox);
Expand All @@ -305,23 +298,15 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
*
* @param name - tool's name
* @param toolboxSettings - tool's single toolbox setting
* @param otherToolboxEntries - other entries in tool's toolbox config (if any)
*/
private addToolIfValid(name: string, toolboxSettings: ToolboxConfig, otherToolboxEntries: ToolboxConfig[] = []): void {
private addToolIfValid(name: string, toolboxSettings: ToolboxConfig): void {
/**
* Skip tools that don't pass 'toolbox' property
*/
if (_.isEmpty(toolboxSettings) || !toolboxSettings.icon) {
return;
}

/**
* Skip tools that has not unique hash
*/
if (otherToolboxEntries.find(otherItem => otherItem.id === toolboxSettings.id)) {
return;
}

this.addTool(name, toolboxSettings);
}

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

this.listeners.on(tool, 'click', async () => {
await this.replaceWithBlock(toolName, toolboxItem.config);
await this.replaceWithBlock(toolName, toolboxItem.data);
});
}

/**
* Hide current Tool and show others
*/
private filterTools(): void {
private async filterTools(): Promise<void> {
const { currentBlock } = this.Editor.BlockManager;
const currentBlockActiveToolboxEntry = await currentBlock.getActiveToolboxEntry();

/**
* Compares two Toolbox entries
*
* @param entry1 - entry to compare
* @param entry2 - entry to compare with
*/
function isTheSameToolboxEntry(entry1, entry2): boolean {
return entry1.icon === entry2.icon && entry1.title === entry2.title;
}

this.tools.forEach(tool => {
const isToolboxItemActive = currentBlock.activeToolboxEntry.id === tool.toolboxItem.id;
const hidden = (tool.button.dataset.tool === currentBlock.name && isToolboxItemActive);
let hidden = false;

if (currentBlockActiveToolboxEntry) {
const isToolboxItemActive = isTheSameToolboxEntry(currentBlockActiveToolboxEntry, tool.toolboxItem);

hidden = (tool.button.dataset.tool === currentBlock.name && isToolboxItemActive);
}

tool.button.hidden = hidden;
tool.button.classList.toggle(ConversionToolbar.CSS.conversionToolHidden, hidden);
Expand Down
4 changes: 2 additions & 2 deletions src/components/modules/toolbar/inline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
/**
* Changes Conversion Dropdown content for current block's Tool
*/
private setConversionTogglerContent(): void {
private async setConversionTogglerContent(): Promise<void> {
const { BlockManager } = this.Editor;
const { currentBlock } = BlockManager;
const toolName = currentBlock.name;
Expand All @@ -480,7 +480,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
/**
* Get icon or title for dropdown
*/
const toolboxSettings = currentBlock.activeToolboxEntry || {};
const toolboxSettings = await currentBlock.getActiveToolboxEntry() || {};

this.nodes.conversionTogglerContent.innerHTML =
toolboxSettings.icon ||
Expand Down
19 changes: 2 additions & 17 deletions src/components/tools/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,7 @@ export default class BlockTool extends BaseTool<IBlockTool> {
const toolToolboxSettings = this.constructable[InternalBlockToolSettings.Toolbox] as ToolboxConfig;

if (Array.isArray(toolToolboxSettings)) {
return toolToolboxSettings
.map(item => this.getActualToolboxSettings(item))
.map(item => this.addIdToToolboxConfig(item));
return toolToolboxSettings.map(item => this.getActualToolboxSettings(item));
} else {
return this.getActualToolboxSettings(toolToolboxSettings);
}
Expand Down Expand Up @@ -165,7 +163,7 @@ export default class BlockTool extends BaseTool<IBlockTool> {
}

/**
* Returns toolbox items settings merged with user defined settings
* Returns toolbox item's settings merged with user defined settings
*
* @param toolboxItemSettings - toolbox item settings to merge
*/
Expand All @@ -182,17 +180,4 @@ export default class BlockTool extends BaseTool<IBlockTool> {

return Object.assign({}, toolboxItemSettings, userToolboxSettings);
}

/**
* Returns toolbox config entry with apended id field which is used later for
* identifying an entry in case the tool has multiple toolbox entries configured.
*
* @param config - toolbox config entry
*/
private addIdToToolboxConfig(config: ToolboxConfig): ToolboxConfig {
return {
...config,
id: _.getHash(config.icon + config.title),
};
}
}
Loading

0 comments on commit 6b60cb1

Please sign in to comment.