mirror of
https://github.com/codex-team/editor.js
synced 2024-05-02 14:52:57 +02:00
fix(toolbar): layout shrink after blocks removing (#2484)
This commit is contained in:
parent
77eb320203
commit
ec569f9981
|
@ -3,6 +3,9 @@
|
|||
### 2.29.0
|
||||
|
||||
- `Fix` — Passing an empty array via initial data or `blocks.render()` won't break the editor
|
||||
- `Fix` — Layout did not shrink when a large document cleared in Chrome
|
||||
- `Fix` — Multiple Tooltip elements creation fixed
|
||||
- `Fix` — When the focusing Block is out of the viewport, the page will be scrolled.
|
||||
|
||||
### 2.28.0
|
||||
|
||||
|
|
|
@ -98,6 +98,8 @@
|
|||
<script type="module">
|
||||
import EditorJS from './src/codex.ts';
|
||||
|
||||
window.EditorJS = EditorJS;
|
||||
|
||||
/**
|
||||
* To initialize the Editor, create a new instance with configuration object
|
||||
* @see docs/installation.md for mode details
|
||||
|
|
|
@ -10,6 +10,7 @@ import '@babel/register';
|
|||
import './components/polyfills';
|
||||
import Core from './components/core';
|
||||
import * as _ from './components/utils';
|
||||
import { destroy as destroyTooltip } from './components/utils/tooltip';
|
||||
|
||||
declare const VERSION: string;
|
||||
|
||||
|
@ -67,6 +68,9 @@ export default class EditorJS {
|
|||
*/
|
||||
this.isReady = editor.isReady.then(() => {
|
||||
this.exportAPI(editor);
|
||||
/**
|
||||
* @todo pass API as an argument. It will allow to use Editor's API when editor is ready
|
||||
*/
|
||||
onReady();
|
||||
});
|
||||
}
|
||||
|
@ -87,6 +91,8 @@ export default class EditorJS {
|
|||
moduleInstance.listeners.removeAll();
|
||||
});
|
||||
|
||||
destroyTooltip();
|
||||
|
||||
editor = null;
|
||||
|
||||
for (const field in this) {
|
||||
|
|
|
@ -2,16 +2,12 @@ import { Tooltip as ITooltip } from '../../../../types/api';
|
|||
import type { TooltipOptions, TooltipContent } from 'codex-tooltip/types';
|
||||
import Module from '../../__module';
|
||||
import { ModuleConfig } from '../../../types-internal/module-config';
|
||||
import Tooltip from '../../utils/tooltip';
|
||||
import * as tooltip from '../../utils/tooltip';
|
||||
/**
|
||||
* @class TooltipAPI
|
||||
* @classdesc Tooltip API
|
||||
*/
|
||||
export default class TooltipAPI extends Module {
|
||||
/**
|
||||
* Tooltip utility Instance
|
||||
*/
|
||||
private tooltip: Tooltip;
|
||||
/**
|
||||
* @class
|
||||
* @param moduleConfiguration - Module Configuration
|
||||
|
@ -23,15 +19,6 @@ export default class TooltipAPI extends Module {
|
|||
config,
|
||||
eventsDispatcher,
|
||||
});
|
||||
|
||||
this.tooltip = new Tooltip();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy Module
|
||||
*/
|
||||
public destroy(): void {
|
||||
this.tooltip.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,14 +46,14 @@ export default class TooltipAPI extends Module {
|
|||
* @param {TooltipOptions} options - tooltip options
|
||||
*/
|
||||
public show(element: HTMLElement, content: TooltipContent, options?: TooltipOptions): void {
|
||||
this.tooltip.show(element, content, options);
|
||||
tooltip.show(element, content, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method hides tooltip on HTML page
|
||||
*/
|
||||
public hide(): void {
|
||||
this.tooltip.hide();
|
||||
tooltip.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,6 +64,6 @@ export default class TooltipAPI extends Module {
|
|||
* @param {TooltipOptions} options - tooltip options
|
||||
*/
|
||||
public onHover(element: HTMLElement, content: TooltipContent, options?: TooltipOptions): void {
|
||||
this.tooltip.onHover(element, content, options);
|
||||
tooltip.onHover(element, content, options);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -304,16 +304,17 @@ export default class Caret extends Module {
|
|||
* @param {number} offset - offset
|
||||
*/
|
||||
public set(element: HTMLElement, offset = 0): void {
|
||||
const scrollOffset = 30;
|
||||
const { top, bottom } = Selection.setCursor(element, offset);
|
||||
|
||||
/** If new cursor position is not visible, scroll to it */
|
||||
const { innerHeight } = window;
|
||||
|
||||
/**
|
||||
* If new cursor position is not visible, scroll to it
|
||||
*/
|
||||
if (top < 0) {
|
||||
window.scrollBy(0, top);
|
||||
}
|
||||
if (bottom > innerHeight) {
|
||||
window.scrollBy(0, bottom - innerHeight);
|
||||
window.scrollBy(0, top - scrollOffset);
|
||||
} else if (bottom > innerHeight) {
|
||||
window.scrollBy(0, bottom - innerHeight + scrollOffset);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import $ from '../../dom';
|
|||
import * as _ from '../../utils';
|
||||
import I18n from '../../i18n';
|
||||
import { I18nInternalNS } from '../../i18n/namespace-internal';
|
||||
import Tooltip from '../../utils/tooltip';
|
||||
import * as tooltip from '../../utils/tooltip';
|
||||
import { ModuleConfig } from '../../../types-internal/module-config';
|
||||
import Block from '../../block';
|
||||
import Toolbox, { ToolboxEvent } from '../../ui/toolbox';
|
||||
|
@ -91,11 +91,6 @@ interface ToolbarNodes {
|
|||
* @property {Element} nodes.defaultSettings - Default Settings section of Settings Panel
|
||||
*/
|
||||
export default class Toolbar extends Module<ToolbarNodes> {
|
||||
/**
|
||||
* Tooltip utility Instance
|
||||
*/
|
||||
private tooltip: Tooltip;
|
||||
|
||||
/**
|
||||
* Block near which we display the Toolbox
|
||||
*/
|
||||
|
@ -118,7 +113,6 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
config,
|
||||
eventsDispatcher,
|
||||
});
|
||||
this.tooltip = new Tooltip();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -328,6 +322,14 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
this.blockActions.hide();
|
||||
this.toolboxInstance?.close();
|
||||
this.Editor.BlockSettings.close();
|
||||
this.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the Toolbar position to prevent DOM height growth, for example after blocks deletion
|
||||
*/
|
||||
private reset(): void {
|
||||
this.nodes.wrapper.style.top = 'unset';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -337,16 +339,13 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
* This flag allows to open Toolbar without Actions.
|
||||
*/
|
||||
private open(withBlockActions = true): void {
|
||||
_.delay(() => {
|
||||
this.nodes.wrapper.classList.add(this.CSS.toolbarOpened);
|
||||
this.nodes.wrapper.classList.add(this.CSS.toolbarOpened);
|
||||
|
||||
if (withBlockActions) {
|
||||
this.blockActions.show();
|
||||
} else {
|
||||
this.blockActions.hide();
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
||||
}, 50)();
|
||||
if (withBlockActions) {
|
||||
this.blockActions.show();
|
||||
} else {
|
||||
this.blockActions.hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -382,7 +381,7 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
$.append(this.nodes.actions, this.nodes.plusButton);
|
||||
|
||||
this.readOnlyMutableListeners.on(this.nodes.plusButton, 'click', () => {
|
||||
this.tooltip.hide(true);
|
||||
tooltip.hide(true);
|
||||
this.plusButtonClicked();
|
||||
}, false);
|
||||
|
||||
|
@ -396,7 +395,7 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
textContent: '⇥ Tab',
|
||||
}));
|
||||
|
||||
this.tooltip.onHover(this.nodes.plusButton, tooltipContent, {
|
||||
tooltip.onHover(this.nodes.plusButton, tooltipContent, {
|
||||
hidingDelay: 400,
|
||||
});
|
||||
|
||||
|
@ -412,7 +411,7 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
|
||||
$.append(this.nodes.actions, this.nodes.settingsToggler);
|
||||
|
||||
this.tooltip.onHover(
|
||||
tooltip.onHover(
|
||||
this.nodes.settingsToggler,
|
||||
I18n.ui(I18nInternalNS.ui.blockTunes.toggler, 'Click to tune'),
|
||||
{
|
||||
|
@ -512,7 +511,7 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
this.toolboxInstance.close();
|
||||
}
|
||||
|
||||
this.tooltip.hide(true);
|
||||
tooltip.hide(true);
|
||||
}, true);
|
||||
|
||||
/**
|
||||
|
@ -593,6 +592,5 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
if (this.toolboxInstance) {
|
||||
this.toolboxInstance.destroy();
|
||||
}
|
||||
this.tooltip.destroy();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import Flipper from '../../flipper';
|
|||
import I18n from '../../i18n';
|
||||
import { I18nInternalNS } from '../../i18n/namespace-internal';
|
||||
import Shortcuts from '../../utils/shortcuts';
|
||||
import Tooltip from '../../utils/tooltip';
|
||||
import * as tooltip from '../../utils/tooltip';
|
||||
import { ModuleConfig } from '../../../types-internal/module-config';
|
||||
import InlineTool from '../../tools/inline';
|
||||
import { CommonInternalSettings } from '../../tools/base';
|
||||
|
@ -97,10 +97,6 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
*/
|
||||
private flipper: Flipper = null;
|
||||
|
||||
/**
|
||||
* Tooltip utility Instance
|
||||
*/
|
||||
private tooltip: Tooltip;
|
||||
/**
|
||||
* @class
|
||||
* @param moduleConfiguration - Module Configuration
|
||||
|
@ -112,7 +108,6 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
config,
|
||||
eventsDispatcher,
|
||||
});
|
||||
this.tooltip = new Tooltip();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -157,52 +152,6 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
this.Editor.Toolbar.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move Toolbar to the selected text
|
||||
*/
|
||||
public move(): void {
|
||||
const selectionRect = SelectionUtils.rect as DOMRect;
|
||||
const wrapperOffset = this.Editor.UI.nodes.wrapper.getBoundingClientRect();
|
||||
const newCoords = {
|
||||
x: selectionRect.x - wrapperOffset.left,
|
||||
y: selectionRect.y +
|
||||
selectionRect.height -
|
||||
// + window.scrollY
|
||||
wrapperOffset.top +
|
||||
this.toolbarVerticalMargin,
|
||||
};
|
||||
|
||||
/**
|
||||
* If we know selections width, place InlineToolbar to center
|
||||
*/
|
||||
if (selectionRect.width) {
|
||||
newCoords.x += Math.floor(selectionRect.width / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inline Toolbar has -50% translateX, so we need to check real coords to prevent overflowing
|
||||
*/
|
||||
const realLeftCoord = newCoords.x - this.width / 2;
|
||||
const realRightCoord = newCoords.x + this.width / 2;
|
||||
|
||||
/**
|
||||
* By default, Inline Toolbar has top-corner at the center
|
||||
* We are adding a modifiers for to move corner to the left or right
|
||||
*/
|
||||
this.nodes.wrapper.classList.toggle(
|
||||
this.CSS.inlineToolbarLeftOriented,
|
||||
realLeftCoord < this.Editor.UI.contentRect.left
|
||||
);
|
||||
|
||||
this.nodes.wrapper.classList.toggle(
|
||||
this.CSS.inlineToolbarRightOriented,
|
||||
realRightCoord > this.Editor.UI.contentRect.right
|
||||
);
|
||||
|
||||
this.nodes.wrapper.style.left = Math.floor(newCoords.x) + 'px';
|
||||
this.nodes.wrapper.style.top = Math.floor(newCoords.y) + 'px';
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides Inline Toolbar
|
||||
*/
|
||||
|
@ -231,6 +180,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
}
|
||||
});
|
||||
|
||||
this.reset();
|
||||
this.opened = false;
|
||||
|
||||
this.flipper.deactivate();
|
||||
|
@ -304,7 +254,6 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
}
|
||||
|
||||
this.removeAllNodes();
|
||||
this.tooltip.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -374,6 +323,66 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
this.enableFlipper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move Toolbar to the selected text
|
||||
*/
|
||||
private move(): void {
|
||||
const selectionRect = SelectionUtils.rect as DOMRect;
|
||||
const wrapperOffset = this.Editor.UI.nodes.wrapper.getBoundingClientRect();
|
||||
const newCoords = {
|
||||
x: selectionRect.x - wrapperOffset.left,
|
||||
y: selectionRect.y +
|
||||
selectionRect.height -
|
||||
// + window.scrollY
|
||||
wrapperOffset.top +
|
||||
this.toolbarVerticalMargin,
|
||||
};
|
||||
|
||||
/**
|
||||
* If we know selections width, place InlineToolbar to center
|
||||
*/
|
||||
if (selectionRect.width) {
|
||||
newCoords.x += Math.floor(selectionRect.width / 2);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inline Toolbar has -50% translateX, so we need to check real coords to prevent overflowing
|
||||
*/
|
||||
const realLeftCoord = newCoords.x - this.width / 2;
|
||||
const realRightCoord = newCoords.x + this.width / 2;
|
||||
|
||||
/**
|
||||
* By default, Inline Toolbar has top-corner at the center
|
||||
* We are adding a modifiers for to move corner to the left or right
|
||||
*/
|
||||
this.nodes.wrapper.classList.toggle(
|
||||
this.CSS.inlineToolbarLeftOriented,
|
||||
realLeftCoord < this.Editor.UI.contentRect.left
|
||||
);
|
||||
|
||||
this.nodes.wrapper.classList.toggle(
|
||||
this.CSS.inlineToolbarRightOriented,
|
||||
realRightCoord > this.Editor.UI.contentRect.right
|
||||
);
|
||||
|
||||
this.nodes.wrapper.style.left = Math.floor(newCoords.x) + 'px';
|
||||
this.nodes.wrapper.style.top = Math.floor(newCoords.y) + 'px';
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear orientation classes and reset position
|
||||
*/
|
||||
private reset(): void {
|
||||
this.nodes.wrapper.classList.remove(
|
||||
this.CSS.inlineToolbarLeftOriented,
|
||||
this.CSS.inlineToolbarRightOriented
|
||||
);
|
||||
|
||||
this.nodes.wrapper.style.left = 'unset';
|
||||
this.nodes.wrapper.style.top = 'unset';
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to show Inline Toolbar or not
|
||||
*/
|
||||
|
@ -465,7 +474,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
});
|
||||
|
||||
if (_.isMobileScreen() === false ) {
|
||||
this.tooltip.onHover(this.nodes.conversionToggler, I18n.ui(I18nInternalNS.ui.inlineToolbar.converter, 'Convert to'), {
|
||||
tooltip.onHover(this.nodes.conversionToggler, I18n.ui(I18nInternalNS.ui.inlineToolbar.converter, 'Convert to'), {
|
||||
placement: 'top',
|
||||
hidingDelay: 100,
|
||||
});
|
||||
|
@ -594,7 +603,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
}
|
||||
|
||||
if (_.isMobileScreen() === false ) {
|
||||
this.tooltip.onHover(button, tooltipContent, {
|
||||
tooltip.onHover(button, tooltipContent, {
|
||||
placement: 'top',
|
||||
hidingDelay: 100,
|
||||
});
|
||||
|
|
|
@ -310,11 +310,17 @@ export default class UI extends Module<UINodes> {
|
|||
|
||||
this.readOnlyMutableListeners.on(this.nodes.redactor, 'mousedown', (event: MouseEvent | TouchEvent) => {
|
||||
this.documentTouched(event);
|
||||
}, true);
|
||||
}, {
|
||||
capture: true,
|
||||
passive: true,
|
||||
});
|
||||
|
||||
this.readOnlyMutableListeners.on(this.nodes.redactor, 'touchstart', (event: MouseEvent | TouchEvent) => {
|
||||
this.documentTouched(event);
|
||||
}, true);
|
||||
}, {
|
||||
capture: true,
|
||||
passive: true,
|
||||
});
|
||||
|
||||
this.readOnlyMutableListeners.on(document, 'keydown', (event: KeyboardEvent) => {
|
||||
this.documentKeydown(event);
|
||||
|
@ -479,7 +485,9 @@ export default class UI extends Module<UINodes> {
|
|||
if (BlockSelection.anyBlockSelected && !Selection.isSelectionExists) {
|
||||
const selectionPositionIndex = BlockManager.removeSelectedBlocks();
|
||||
|
||||
Caret.setToBlock(BlockManager.insertDefaultBlockAtIndex(selectionPositionIndex, true), Caret.positions.START);
|
||||
const newBlock = BlockManager.insertDefaultBlockAtIndex(selectionPositionIndex, true);
|
||||
|
||||
Caret.setToBlock(newBlock, Caret.positions.START);
|
||||
|
||||
/** Clear selection */
|
||||
BlockSelection.clearSelection(event);
|
||||
|
|
|
@ -6,53 +6,66 @@ import CodeXTooltips from 'codex-tooltip';
|
|||
import type { TooltipOptions, TooltipContent } from 'codex-tooltip/types';
|
||||
|
||||
/**
|
||||
* Tooltip
|
||||
* Tooltips lib: CodeX Tooltips
|
||||
*
|
||||
* Decorates any tooltip module like adapter
|
||||
* @see https://github.com/codex-team/codex.tooltips
|
||||
*/
|
||||
export default class Tooltip {
|
||||
/**
|
||||
* Tooltips lib: CodeX Tooltips
|
||||
*
|
||||
* @see https://github.com/codex-team/codex.tooltips
|
||||
*/
|
||||
private lib: CodeXTooltips = new CodeXTooltips();
|
||||
let lib: null | CodeXTooltips = null;
|
||||
|
||||
/**
|
||||
* Release the library
|
||||
*/
|
||||
public destroy(): void {
|
||||
this.lib.destroy();
|
||||
/**
|
||||
* If library is needed, but it is not initialized yet, this function will initialize it
|
||||
*
|
||||
* For example, if editor was destroyed and then initialized again
|
||||
*/
|
||||
function prepare(): void {
|
||||
if (lib) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows tooltip on element with passed HTML content
|
||||
*
|
||||
* @param {HTMLElement} element - any HTML element in DOM
|
||||
* @param content - tooltip's content
|
||||
* @param options - showing settings
|
||||
*/
|
||||
public show(element: HTMLElement, content: TooltipContent, options?: TooltipOptions): void {
|
||||
this.lib.show(element, content, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides tooltip
|
||||
*
|
||||
* @param skipHidingDelay — pass true to immediately hide the tooltip
|
||||
*/
|
||||
public hide(skipHidingDelay = false): void {
|
||||
this.lib.hide(skipHidingDelay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds 'mouseenter' and 'mouseleave' events that shows/hides the Tooltip
|
||||
*
|
||||
* @param {HTMLElement} element - any HTML element in DOM
|
||||
* @param content - tooltip's content
|
||||
* @param options - showing settings
|
||||
*/
|
||||
public onHover(element: HTMLElement, content: TooltipContent, options?: TooltipOptions): void {
|
||||
this.lib.onHover(element, content, options);
|
||||
}
|
||||
lib = new CodeXTooltips();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows tooltip on element with passed HTML content
|
||||
*
|
||||
* @param {HTMLElement} element - any HTML element in DOM
|
||||
* @param content - tooltip's content
|
||||
* @param options - showing settings
|
||||
*/
|
||||
export function show(element: HTMLElement, content: TooltipContent, options?: TooltipOptions): void {
|
||||
prepare();
|
||||
|
||||
lib?.show(element, content, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides tooltip
|
||||
*
|
||||
* @param skipHidingDelay — pass true to immediately hide the tooltip
|
||||
*/
|
||||
export function hide(skipHidingDelay = false): void {
|
||||
prepare();
|
||||
|
||||
lib?.hide(skipHidingDelay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds 'mouseenter' and 'mouseleave' events that shows/hides the Tooltip
|
||||
*
|
||||
* @param {HTMLElement} element - any HTML element in DOM
|
||||
* @param content - tooltip's content
|
||||
* @param options - showing settings
|
||||
*/
|
||||
export function onHover(element: HTMLElement, content: TooltipContent, options?: TooltipOptions): void {
|
||||
prepare();
|
||||
|
||||
lib?.onHover(element, content, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the library
|
||||
*/
|
||||
export function destroy(): void {
|
||||
lib?.destroy();
|
||||
lib = null;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue