}
});
- const context: EditorUiContext = buildEditorUI(container, editArea, editor, options);
+ const context: EditorUiContext = buildEditorUI(container, editArea, editWrap, editor, options);
registerCommonNodeMutationListeners(context);
return new SimpleWysiwygEditorInterface(editor);
- Alignments: Use existing classes for blocks
- Alignments: Handle inline block content (image, video)
- Add Type: Video/media/embed
-- Handle toolbars on scroll
- Table features
- Image paste upload
- Keyboard shortcuts support
## Bugs
- Image resizing currently bugged, maybe change to ghost resizer in decorator instead of updating core node.
-- Table resize bars often floating around in wrong place, and shows on hover or interrupts mouse actions.
\ No newline at end of file
+- Table resize bars often floating around in wrong place, and shows on hover or interrupts mouse actions.
+- Removing link around image via button deletes image, not just link
\ No newline at end of file
editor: LexicalEditor; // Lexical editor instance
editorDOM: HTMLElement; // DOM element the editor is bound to
containerDOM: HTMLElement; // DOM element which contains all editor elements
+ scrollDOM: HTMLElement; // DOM element which is the main content scroll container
translate: (text: string) => string; // Translate function
manager: EditorUIManager; // UI Manager instance for this editor
lastSelection: BaseSelection|null; // The last tracked selection made by the user
setContext(context: EditorUiContext) {
this.context = context;
+ this.setupEventListeners(context);
this.setupEditor(context.editor);
}
}
protected updateContextToolbars(update: EditorUiStateUpdate): void {
- for (const toolbar of this.activeContextToolbars) {
- toolbar.empty();
- toolbar.getDOMElement().remove();
+ for (let i = this.activeContextToolbars.length - 1; i >= 0; i--) {
+ const toolbar = this.activeContextToolbars[i];
+ toolbar.destroy();
+ this.activeContextToolbars.splice(i, 1);
}
const node = (update.selection?.getNodes() || [])[0] || null;
}
for (const [target, contents] of contentByTarget) {
- const toolbar = new EditorContextToolbar(contents);
+ const toolbar = new EditorContextToolbar(target, contents);
toolbar.setContext(this.getContext());
this.activeContextToolbars.push(toolbar);
this.getContext().containerDOM.append(toolbar.getDOMElement());
- toolbar.attachTo(target);
+ toolbar.updatePosition();
}
}
}
editor.registerDecoratorListener(domDecorateListener);
}
+
+ protected setupEventListeners(context: EditorUiContext) {
+ const updateToolbars = (event: Event) => {
+ for (const toolbar of this.activeContextToolbars) {
+ toolbar.updatePosition();
+ }
+ };
+
+ window.addEventListener('scroll', updateToolbars, {capture: true, passive: true});
+ window.addEventListener('resize', updateToolbars, {passive: true});
+ }
}
\ No newline at end of file
export class EditorContextToolbar extends EditorContainerUiElement {
+ protected target: HTMLElement;
+
+ constructor(target: HTMLElement, children: EditorUiElement[]) {
+ super(children);
+ this.target = target;
+ }
+
protected buildDOM(): HTMLElement {
return el('div', {
class: 'editor-context-toolbar',
}, this.getChildren().map(child => child.getDOMElement()));
}
- attachTo(target: HTMLElement) {
- const targetBounds = target.getBoundingClientRect();
+ updatePosition() {
+ const editorBounds = this.getContext().scrollDOM.getBoundingClientRect();
+ const targetBounds = this.target.getBoundingClientRect();
const dom = this.getDOMElement();
const domBounds = dom.getBoundingClientRect();
+ const showing = targetBounds.bottom > editorBounds.top
+ && targetBounds.top < editorBounds.bottom;
+
+ dom.hidden = !showing;
+
+ if (!showing) {
+ return;
+ }
+
+ const showAbove: boolean = targetBounds.bottom + 6 + domBounds.height > editorBounds.bottom;
+ dom.classList.toggle('is-above', showAbove);
+
const targetMid = targetBounds.left + (targetBounds.width / 2);
const targetLeft = targetMid - (domBounds.width / 2);
- dom.style.top = (targetBounds.bottom + 6) + 'px';
+ if (showAbove) {
+ dom.style.top = (targetBounds.top - 6 - domBounds.height) + 'px';
+ } else {
+ dom.style.top = (targetBounds.bottom + 6) + 'px';
+ }
dom.style.left = targetLeft + 'px';
}
dom.append(...children.map(child => child.getDOMElement()));
}
- empty() {
+ protected empty() {
const children = this.getChildren();
for (const child of children) {
child.getDOMElement().remove();
}
this.removeChildren(...children);
}
+
+ destroy() {
+ this.empty();
+ this.getDOMElement().remove();
+ }
}
\ No newline at end of file
import {CodeBlockDecorator} from "./decorators/code-block";
import {DiagramDecorator} from "./decorators/diagram";
-export function buildEditorUI(container: HTMLElement, element: HTMLElement, editor: LexicalEditor, options: Record<string, any>): EditorUiContext {
+export function buildEditorUI(container: HTMLElement, element: HTMLElement, scrollContainer: HTMLElement, editor: LexicalEditor, options: Record<string, any>): EditorUiContext {
const manager = new EditorUIManager();
const context: EditorUiContext = {
editor,
containerDOM: container,
editorDOM: element,
+ scrollDOM: scrollContainer,
manager,
translate: (text: string): string => text,
lastSelection: null,
manager.registerContextToolbar('image', {
selector: 'img:not([drawio-diagram] img)',
content: getImageToolbarContent(),
- displayTargetLocator(originalTarget: HTMLElement) {
- return originalTarget.closest('a') || originalTarget;
- }
});
manager.registerContextToolbar('link', {
selector: 'a',
content: getLinkToolbarContent(),
+ displayTargetLocator(originalTarget: HTMLElement): HTMLElement {
+ const image = originalTarget.querySelector('img');
+ return image || originalTarget;
+ }
});
manager.registerContextToolbar('code', {
selector: '.editor-code-block-wrap',
margin-left: -4px;
top: -5px;
}
+ &.is-above:before {
+ top: calc(100% - 5px);
+ transform: rotate(225deg);
+ }
}
// Modals