mirror of
https://github.com/codex-team/editor.js
synced 2024-05-19 06:47:16 +02:00
ac93017c70
* 2.16.0 * [Refactor] Separate internal and external settings (#845) * Enable flipping tools via standalone class (#830) * Enable flipping tools via standalone class * use flipper to refactor (#842) * use flipper to refactor * save changes * update * fix flipper on inline toolbar * ready for testing * requested changes * update doc * updates * destroy flippers * some requested changes * update * update * ready * update * last changes * update docs * Hghl active button of CT, simplify activate/deactivate * separate dom iterator * unhardcode directions * fixed a link in readme.md (#856) * Fix Block selection via CMD+A (#829) * Fix Block selection via CMD+A * Delete editor.js.map * update * update * Update CHANGELOG.md * Improve style of selected blocks (#858) * Cross-block-selection style improved * Update CHANGELOG.md * Fix case when property 'observer' in modificationObserver is not defined (#866) * Bump lodash.template from 4.4.0 to 4.5.0 (#885) Bumps [lodash.template](https://github.com/lodash/lodash) from 4.4.0 to 4.5.0. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.4.0...4.5.0) Signed-off-by: dependabot[bot] <support@github.com> * Bump eslint-utils from 1.3.1 to 1.4.2 (#886) Bumps [eslint-utils](https://github.com/mysticatea/eslint-utils) from 1.3.1 to 1.4.2. - [Release notes](https://github.com/mysticatea/eslint-utils/releases) - [Commits](https://github.com/mysticatea/eslint-utils/compare/v1.3.1...v1.4.2) Signed-off-by: dependabot[bot] <support@github.com> * Bump mixin-deep from 1.3.1 to 1.3.2 (#887) Bumps [mixin-deep](https://github.com/jonschlinkert/mixin-deep) from 1.3.1 to 1.3.2. - [Release notes](https://github.com/jonschlinkert/mixin-deep/releases) - [Commits](https://github.com/jonschlinkert/mixin-deep/compare/1.3.1...1.3.2) Signed-off-by: dependabot[bot] <support@github.com> * update bundle and readme * Update README.md * upd codeowners, fix funding * Minor Docs Fix according to main Readme (#916) * Inline Toolbar now contains Conversion Toolbar (#932) * Block lifecycle hooks (#906) * [Fix] Arrow selection (#964) * Fix arrow selection * Add docs * [issue-926]: fix dom iterator leafing when items are empty (#958) * [issue-926]: fix dom iterator leafing when items are empty * update Changelog * Issue 869 (#963) * Fix issue 943 (#965) * [Draft] Feature/tooltip enhancements (#907) * initial * update * make module standalone * use tooltips as external module * update * build via prod mode * add tooltips as external module * add declaration file and options param * add api tooltip * update * removed submodule * removed due to the incorrect setip * setup tooltips again * wip * update tooltip module * toolbox, inline toolbar * Tooltips in block tunes not uses shorthand * shorthand in a plus and block settings * fix doc * Update tools-inline.md * Delete tooltip.css * Update CHANGELOG.md * Update codex.tooltips * Update api.md * [issue-779]: Grammarly conflicts (#956) * grammarly conflicts * update * upd bundle * Submodule Header now on master * Submodule Marker now on master * Submodule Paragraph now on master * Submodule InlineCode now on master * Submodule Simple Image now on master * [issue-868]: Deleting multiple blocks triggers back button in Firefox (#967) * Deleting multiple blocks triggers back button in Firefox @evgenusov * Update editor.js * Update CHANGELOG.md * pass options on removeEventListener (#904) * pass options on removeEventListener by removeAll * rebuild * Merge branch 'release/2.16' into pr/904 * Update CHANGELOG.md * Update inline.ts * [Fix] Selection rangecount (#968) * Fix #952 (#969) * Update codex.tooltips * Selection bugfix (#970) * Selection bugfix * fix cross block selection * close inline toolbar when blocks selected via shift * remove inline toolbar closing on cross block selection mouse up due to the bug (#972) * [Feature] Log levels (#971) * Decrease margins (#973) * Decrease margins * Update editor.licenses.txt * Update src/components/domIterator.ts Co-Authored-By: Murod Khaydarov <murod.haydarov@gmail.com> * [Fix] Fix delete blocks api method (#974) * Update docs/usage.md Co-Authored-By: Murod Khaydarov <murod.haydarov@gmail.com> * rm unused * Update yarn.lock file * upd bundle, changelog
312 lines
8.8 KiB
TypeScript
312 lines
8.8 KiB
TypeScript
import Module from '../../__module';
|
|
import $ from '../../dom';
|
|
import {BlockToolConstructable} from '../../../../types';
|
|
import * as _ from '../../utils';
|
|
import {SavedData} from '../../../types-internal/block-data';
|
|
import Block from '../../block';
|
|
import Flipper from '../../flipper';
|
|
|
|
/**
|
|
* Block Converter
|
|
*/
|
|
export default class ConversionToolbar extends Module {
|
|
/**
|
|
* CSS getter
|
|
*/
|
|
public static get CSS(): { [key: string]: string } {
|
|
return {
|
|
conversionToolbarWrapper: 'ce-conversion-toolbar',
|
|
conversionToolbarShowed: 'ce-conversion-toolbar--showed',
|
|
conversionToolbarTools: 'ce-conversion-toolbar__tools',
|
|
conversionToolbarLabel: 'ce-conversion-toolbar__label',
|
|
conversionTool: 'ce-conversion-tool',
|
|
conversionToolHidden: 'ce-conversion-tool--hidden',
|
|
conversionToolIcon: 'ce-conversion-tool__icon',
|
|
|
|
conversionToolFocused : 'ce-conversion-tool--focused',
|
|
conversionToolActive : 'ce-conversion-tool--active',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* HTML Elements used for UI
|
|
*/
|
|
public nodes: { [key: string]: HTMLElement } = {
|
|
wrapper: null,
|
|
tools: null,
|
|
};
|
|
|
|
/**
|
|
* Conversion Toolbar open/close state
|
|
* @type {boolean}
|
|
*/
|
|
public opened: boolean = false;
|
|
|
|
/**
|
|
* Available tools
|
|
*/
|
|
private tools: { [key: string]: HTMLElement } = {};
|
|
|
|
/**
|
|
* Instance of class that responses for leafing buttons by arrows/tab
|
|
* @type {Flipper|null}
|
|
*/
|
|
private flipper: Flipper = null;
|
|
|
|
/**
|
|
* Callback that fill be fired on open/close and accepts an opening state
|
|
*/
|
|
private togglingCallback = null;
|
|
|
|
/**
|
|
* Create UI of Conversion Toolbar
|
|
*/
|
|
public make(): HTMLElement {
|
|
this.nodes.wrapper = $.make('div', ConversionToolbar.CSS.conversionToolbarWrapper);
|
|
this.nodes.tools = $.make('div', ConversionToolbar.CSS.conversionToolbarTools);
|
|
|
|
const label = $.make('div', ConversionToolbar.CSS.conversionToolbarLabel, {
|
|
textContent: 'Convert to',
|
|
});
|
|
|
|
/**
|
|
* Add Tools that has 'import' method
|
|
*/
|
|
this.addTools();
|
|
|
|
/**
|
|
* Prepare Flipper to be able to leaf tools by arrows/tab
|
|
*/
|
|
this.enableFlipper();
|
|
|
|
$.append(this.nodes.wrapper, label);
|
|
$.append(this.nodes.wrapper, this.nodes.tools);
|
|
|
|
return this.nodes.wrapper;
|
|
}
|
|
|
|
/**
|
|
* Toggle conversion dropdown visibility
|
|
* @param {function} [togglingCallback] — callback that will accept opening state
|
|
*/
|
|
public toggle(togglingCallback?: (openedState: boolean) => void): void {
|
|
if (!this.opened) {
|
|
this.open();
|
|
} else {
|
|
this.close();
|
|
}
|
|
|
|
if (typeof togglingCallback === 'function') {
|
|
this.togglingCallback = togglingCallback;
|
|
|
|
this.togglingCallback(this.opened);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shows Conversion Toolbar
|
|
*/
|
|
public open(): void {
|
|
this.filterTools();
|
|
|
|
this.opened = true;
|
|
this.nodes.wrapper.classList.add(ConversionToolbar.CSS.conversionToolbarShowed);
|
|
|
|
/**
|
|
* We use timeout to prevent bubbling Enter keydown on first dropdown item
|
|
* Conversion flipper will be activated after dropdown will open
|
|
*/
|
|
setTimeout(() => {
|
|
this.flipper.activate(Object.values(this.tools).filter((button) => {
|
|
return !button.classList.contains(ConversionToolbar.CSS.conversionToolHidden);
|
|
}));
|
|
this.flipper.focusFirst();
|
|
|
|
if (typeof this.togglingCallback === 'function') {
|
|
this.togglingCallback(true);
|
|
}
|
|
}, 50);
|
|
}
|
|
|
|
/**
|
|
* Closes Conversion Toolbar
|
|
*/
|
|
public close(): void {
|
|
this.opened = false;
|
|
this.flipper.deactivate();
|
|
this.nodes.wrapper.classList.remove(ConversionToolbar.CSS.conversionToolbarShowed);
|
|
|
|
if (typeof this.togglingCallback === 'function') {
|
|
this.togglingCallback(false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Replaces one Block with another
|
|
* For that Tools must provide import/export methods
|
|
*
|
|
* @param {string} replacingToolName
|
|
*/
|
|
public async replaceWithBlock(replacingToolName: string): Promise <void> {
|
|
/**
|
|
* At first, we get current Block data
|
|
* @type {BlockToolConstructable}
|
|
*/
|
|
const currentBlockClass = this.Editor.BlockManager.currentBlock.class;
|
|
const currentBlockName = this.Editor.BlockManager.currentBlock.name;
|
|
const savedBlock = await this.Editor.BlockManager.currentBlock.save() as SavedData;
|
|
const { INTERNAL_SETTINGS } = this.Editor.Tools;
|
|
const blockData = savedBlock.data;
|
|
|
|
/**
|
|
* When current Block name is equals to the replacing tool Name,
|
|
* than convert this Block back to the initial Block
|
|
*/
|
|
if (currentBlockName === replacingToolName) {
|
|
replacingToolName = this.config.initialBlock;
|
|
}
|
|
|
|
/**
|
|
* Getting a class of replacing Tool
|
|
* @type {BlockToolConstructable}
|
|
*/
|
|
const replacingTool = this.Editor.Tools.toolsClasses[replacingToolName] as BlockToolConstructable;
|
|
|
|
/**
|
|
* Export property can be:
|
|
* 1) Function — Tool defines which data to return
|
|
* 2) String — the name of saved property
|
|
*
|
|
* In both cases returning value must be a string
|
|
*/
|
|
let exportData: string = '';
|
|
const exportProp = currentBlockClass[INTERNAL_SETTINGS.CONVERSION_CONFIG].export;
|
|
|
|
if (typeof exportProp === 'function') {
|
|
exportData = exportProp(blockData);
|
|
} else if (typeof exportProp === 'string') {
|
|
exportData = blockData[exportProp];
|
|
} else {
|
|
_.log('Conversion «export» property must be a string or function. ' +
|
|
'String means key of saved data object to export. Function should export processed string to export.');
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Clean exported data with replacing sanitizer config
|
|
*/
|
|
const cleaned: string = this.Editor.Sanitizer.clean(
|
|
exportData,
|
|
replacingTool.sanitize,
|
|
);
|
|
|
|
/**
|
|
* «import» property can be Function or String
|
|
* function — accept imported string and compose tool data object
|
|
* string — the name of data field to import
|
|
*/
|
|
let newBlockData = {};
|
|
const importProp = replacingTool[INTERNAL_SETTINGS.CONVERSION_CONFIG].import;
|
|
|
|
if (typeof importProp === 'function') {
|
|
newBlockData = importProp(cleaned);
|
|
} else if (typeof importProp === 'string') {
|
|
newBlockData[importProp] = cleaned;
|
|
} else {
|
|
_.log('Conversion «import» property must be a string or function. ' +
|
|
'String means key of tool data to import. Function accepts a imported string and return composed tool data.');
|
|
return;
|
|
}
|
|
|
|
this.Editor.BlockManager.replace(replacingToolName, newBlockData);
|
|
this.Editor.BlockSelection.clearSelection();
|
|
|
|
this.close();
|
|
this.Editor.InlineToolbar.close();
|
|
|
|
_.delay(() => {
|
|
this.Editor.Caret.setToBlock(this.Editor.BlockManager.currentBlock);
|
|
}, 10)();
|
|
}
|
|
|
|
/**
|
|
* Iterates existing Tools and inserts to the ConversionToolbar
|
|
* if tools have ability to import
|
|
*/
|
|
private addTools(): void {
|
|
const tools = this.Editor.Tools.blockTools;
|
|
|
|
for (const toolName in tools) {
|
|
if (!tools.hasOwnProperty(toolName)) {
|
|
continue;
|
|
}
|
|
|
|
const internalSettings = this.Editor.Tools.INTERNAL_SETTINGS;
|
|
const toolClass = tools[toolName] as BlockToolConstructable;
|
|
const toolToolboxSettings = toolClass[internalSettings.TOOLBOX];
|
|
const conversionConfig = toolClass[internalSettings.CONVERSION_CONFIG];
|
|
|
|
/**
|
|
* Skip tools that don't pass 'toolbox' property
|
|
*/
|
|
if (_.isEmpty(toolToolboxSettings) || !toolToolboxSettings.icon) {
|
|
continue;
|
|
}
|
|
|
|
/**
|
|
* Skip tools without «import» rule specified
|
|
*/
|
|
if (!conversionConfig || !conversionConfig.import) {
|
|
continue;
|
|
}
|
|
|
|
this.addTool(toolName, toolToolboxSettings.icon, toolToolboxSettings.title);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add tool to the Conversion Toolbar
|
|
*/
|
|
private addTool(toolName: string, toolIcon: string, title: string): void {
|
|
const tool = $.make('div', [ ConversionToolbar.CSS.conversionTool ]);
|
|
const icon = $.make('div', [ ConversionToolbar.CSS.conversionToolIcon ]);
|
|
|
|
tool.dataset.tool = toolName;
|
|
icon.innerHTML = toolIcon;
|
|
|
|
$.append(tool, icon);
|
|
$.append(tool, $.text(title || _.capitalize(toolName)));
|
|
|
|
$.append(this.nodes.tools, tool);
|
|
this.tools[toolName] = tool;
|
|
|
|
this.Editor.Listeners.on(tool, 'click', async () => {
|
|
await this.replaceWithBlock(toolName);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Hide current Tool and show others
|
|
*/
|
|
private filterTools(): void {
|
|
const { currentBlock } = this.Editor.BlockManager;
|
|
|
|
/**
|
|
* Show previously hided
|
|
*/
|
|
Object.entries(this.tools).forEach(([name, button]) => {
|
|
button.hidden = false;
|
|
button.classList.toggle(ConversionToolbar.CSS.conversionToolHidden, name === currentBlock.name);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Prepare Flipper to be able to leaf tools by arrows/tab
|
|
*/
|
|
private enableFlipper(): void {
|
|
this.flipper = new Flipper({
|
|
focusedItemClass: ConversionToolbar.CSS.conversionToolFocused,
|
|
});
|
|
}
|
|
}
|