Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Multiple toolbox items: using of data overrides instead of config overrides #2064

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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() || {};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's one problem with setting toggler's content this way: if none of the configured toolbox entries is active, toggler will contain only tool name

Screen.Recording.2022-05-18.at.09.46.01.mov

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, it is ok because other titles and icons does not correctly fit here.


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