Use data instead of config

This commit is contained in:
Peter Savchenko 2022-05-17 22:18:56 +03:00
parent 0ddc09de0c
commit 30c4b39f3a
No known key found for this signature in database
GPG key ID: E68306B1AB0F727C
8 changed files with 91 additions and 116 deletions

View file

@ -54,11 +54,6 @@ interface BlockConstructorOptions {
* Tunes data for current Block
*/
tunesData: { [name: string]: BlockTuneData };
/**
* May contain overrides for tool default config
*/
configOverrides: ToolConfig;
}
/**
@ -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;
@ -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> {
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] && blockData[propName] === propValue;
});
});
}
/**

View file

@ -242,7 +242,6 @@ export default class BlocksAPI extends Module {
index,
needToFocus,
replace,
config,
});
return new BlockAPI(insertedBlock);

View file

@ -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}
*/
@ -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);
@ -250,7 +248,6 @@ export default class BlockManager extends Module {
api: this.Editor.API,
readOnly,
tunesData,
configOverrides: config,
});
if (!readOnly) {
@ -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}
*/
@ -282,7 +278,6 @@ export default class BlockManager extends Module {
needToFocus = true,
replace = false,
tunes = {},
config,
}: {
id?: string;
tool?: string;
@ -291,7 +286,6 @@ export default class BlockManager extends Module {
needToFocus?: boolean;
replace?: boolean;
tunes?: {[name: string]: BlockTuneData};
config?: ToolConfig;
} = {}): Block {
let newIndex = index;
@ -303,7 +297,6 @@ export default class BlockManager extends Module {
tool,
data,
tunes,
config,
});
/**
@ -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,
});
}

View file

@ -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, ToolConfig, BlockToolData } from '../../../../types';
/**
* HTML Elements used for ConversionToolbar
@ -136,10 +136,10 @@ 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);
}));
@ -147,7 +147,7 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
if (_.isFunction(this.togglingCallback)) {
this.togglingCallback(true);
}
}, 50);
});
}
/**
@ -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
@ -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();
@ -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);
@ -305,9 +298,8 @@ 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
*/
@ -315,13 +307,6 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
return;
}
/**
* Skip tools that has not unique hash
*/
if (otherToolboxEntries.find(otherItem => otherItem.id === toolboxSettings.id)) {
return;
}
this.addTool(name, toolboxSettings);
}
@ -349,18 +334,29 @@ 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 + entry1.title === entry2.icon + entry2.title;
}
this.tools.forEach(tool => {
const isToolboxItemActive = currentBlock.activeToolboxEntry.id === tool.toolboxItem.id;
const isToolboxItemActive = isTheSameToolboxEntry(currentBlockActiveToolboxEntry, tool.toolboxItem);
const hidden = (tool.button.dataset.tool === currentBlock.name && isToolboxItemActive);
tool.button.hidden = hidden;

View file

@ -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;
@ -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 ||

View file

@ -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);
}
@ -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
*/
@ -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),
};
}
}

View file

@ -3,7 +3,7 @@ import { BlockToolAPI } from '../block';
import Shortcuts from '../utils/shortcuts';
import BlockTool from '../tools/block';
import ToolsCollection from '../tools/collection';
import { API, ToolboxConfig, ToolConfig } from '../../../types';
import {API, BlockToolData, ToolboxConfig, ToolConfig} from '../../../types';
import EventsDispatcher from '../utils/events';
import Popover, { PopoverEvent, PopoverItem } from '../utils/popover';
import I18n from '../i18n';
@ -175,10 +175,10 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
* Toolbox Tool's button click handler
*
* @param toolName - tool type to be activated
* @param config -
* @param blockDataOverrides - Block data predefined by the activated Toolbox item
*/
public toolButtonActivated(toolName: string, config: ToolConfig): void {
this.insertNewBlock(toolName, config);
public toolButtonActivated(toolName: string, blockDataOverrides: BlockToolData): void {
this.insertNewBlock(toolName, blockDataOverrides);
}
/**
@ -257,26 +257,16 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
const toolToolboxSettings = tool.toolbox;
if (Array.isArray(toolToolboxSettings)) {
const validToolboxSettings = toolToolboxSettings
.filter(item => this.areToolboxSetttingsValid(item, tool.name))
.filter((item, i) => {
const notUnique = toolToolboxSettings.slice(0, i).find(otherItem => otherItem.id === item.id);
if (notUnique) {
_.log('Toolbox entry has not unique combination of icon and title. Toolbox entry %o is skipped', 'warn', item.title);
return false;
}
return true;
});
const validToolboxSettings = toolToolboxSettings.filter(item => {
return this.areToolboxSettingsValid(item, tool.name);
});
result.push({
...tool,
toolbox: validToolboxSettings,
});
} else {
if (this.areToolboxSetttingsValid(toolToolboxSettings, tool.name)) {
if (this.areToolboxSettingsValid(toolToolboxSettings, tool.name)) {
result.push(tool);
}
}
@ -293,13 +283,13 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
/**
* Maps tool data to popover item structure
*/
const toPopoverItem = (config: ToolboxConfig, tool: BlockTool): PopoverItem => {
const toPopoverItem = (toolboxItem: ToolboxConfig, tool: BlockTool): PopoverItem => {
return {
icon: config.icon,
label: I18n.t(I18nInternalNS.toolNames, config.title || _.capitalize(tool.name)),
icon: toolboxItem.icon,
label: I18n.t(I18nInternalNS.toolNames, toolboxItem.title || _.capitalize(tool.name)),
name: tool.name,
onClick: (e): void => {
this.toolButtonActivated(tool.name, config);
this.toolButtonActivated(tool.name, toolboxItem.data);
},
secondaryLabel: tool.shortcut ? _.beautifyShortcut(tool.shortcut) : '',
};
@ -323,9 +313,9 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
* Validates tool's toolbox settings
*
* @param toolToolboxSettings - item to validate
* @param toolName - name of the tool used in consone warning if item is not valid
* @param toolName - name of the tool used in console warning if item is not valid
*/
private areToolboxSetttingsValid(toolToolboxSettings: ToolboxConfig, toolName: string): boolean {
private areToolboxSettingsValid(toolToolboxSettings: ToolboxConfig, toolName: string): boolean {
/**
* Skip tools that don't pass 'toolbox' property
*/
@ -391,8 +381,9 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
* Can be called when button clicked on Toolbox or by ShortcutData
*
* @param {string} toolName - Tool name
* @param blockDataOverrides - predefined Block data
*/
private insertNewBlock(toolName: string, toolboxConfig?): void {
private insertNewBlock(toolName: string, blockDataOverrides?: BlockToolData): void {
const currentBlockIndex = this.api.blocks.getCurrentBlockIndex();
const currentBlock = this.api.blocks.getBlockByIndex(currentBlockIndex);
@ -408,8 +399,8 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
const newBlock = this.api.blocks.insert(
toolName,
blockDataOverrides,
undefined,
toolboxConfig.config,
index,
undefined,
currentBlock.isEmpty

View file

@ -1,5 +1,5 @@
import {ToolConfig} from './tool-config';
import {ToolConstructable} from './index';
import {ToolConstructable, BlockToolData} from './index';
/**
* Tool's Toolbox settings
@ -15,15 +15,10 @@ export interface ToolboxConfig {
*/
icon?: string;
/**
* Toolbox item id for distinguishing one toolbox item from another
*/
id?: number;
/**
* May contain overrides for tool default config
*/
config?: ToolConfig
data?: BlockToolData
}
/**