diff --git a/resources/icons/editor/table-delete-column.svg b/resources/icons/editor/table-delete-column.svg
new file mode 100644
index 000000000..428fc29a6
--- /dev/null
+++ b/resources/icons/editor/table-delete-column.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/icons/editor/table-delete-row.svg b/resources/icons/editor/table-delete-row.svg
new file mode 100644
index 000000000..ee2f8a00d
--- /dev/null
+++ b/resources/icons/editor/table-delete-row.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/icons/editor/table-delete.svg b/resources/icons/editor/table-delete.svg
new file mode 100644
index 000000000..412cf0732
--- /dev/null
+++ b/resources/icons/editor/table-delete.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/icons/editor/table-insert-column-after.svg b/resources/icons/editor/table-insert-column-after.svg
new file mode 100644
index 000000000..75abd9a85
--- /dev/null
+++ b/resources/icons/editor/table-insert-column-after.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/icons/editor/table-insert-column-before.svg b/resources/icons/editor/table-insert-column-before.svg
new file mode 100644
index 000000000..5bb38cd29
--- /dev/null
+++ b/resources/icons/editor/table-insert-column-before.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/icons/editor/table-insert-row-above.svg b/resources/icons/editor/table-insert-row-above.svg
new file mode 100644
index 000000000..df951485a
--- /dev/null
+++ b/resources/icons/editor/table-insert-row-above.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/icons/editor/table-insert-row-below.svg b/resources/icons/editor/table-insert-row-below.svg
new file mode 100644
index 000000000..b2af77592
--- /dev/null
+++ b/resources/icons/editor/table-insert-row-below.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/js/wysiwyg/helpers.ts b/resources/js/wysiwyg/helpers.ts
index a7c3e4453..6a55c429c 100644
--- a/resources/js/wysiwyg/helpers.ts
+++ b/resources/js/wysiwyg/helpers.ts
@@ -44,10 +44,19 @@ export function $getNodeFromSelection(selection: BaseSelection|null, matcher: Le
return node;
}
- for (const parent of node.getParents()) {
- if (matcher(parent)) {
- return parent;
- }
+ const matchedParent = $getParentOfType(node, matcher);
+ if (matchedParent) {
+ return matchedParent;
+ }
+ }
+
+ return null;
+}
+
+export function $getParentOfType(node: LexicalNode, matcher: LexicalNodeMatcher): LexicalNode|null {
+ for (const parent of node.getParents()) {
+ if (matcher(parent)) {
+ return parent;
}
}
diff --git a/resources/js/wysiwyg/todo.md b/resources/js/wysiwyg/todo.md
index 9950254df..7b9588194 100644
--- a/resources/js/wysiwyg/todo.md
+++ b/resources/js/wysiwyg/todo.md
@@ -2,12 +2,22 @@
## In progress
+- Add Type: Video/media/embed
+ - TinyMce media embed supported:
+ - iframe
+ - embed
+ - object
+ - video - Can take sources
+ - audio - Can take sources
+ - Pretty much all attributes look like they were supported.
+ - Core old logic seen here: https://github.com/tinymce/tinymce/blob/main/modules/tinymce/src/plugins/media/main/ts/core/DataToHtml.ts
+ - Copy/store attributes on node based on allow list?
+ - width, height, src, controls, etc... Take valid values from MDN
## Main Todo
- Alignments: Use existing classes for blocks
- Alignments: Handle inline block content (image, video)
-- Add Type: Video/media/embed
- Table features
- Image paste upload
- Keyboard shortcuts support
diff --git a/resources/js/wysiwyg/ui/defaults/button-definitions.ts b/resources/js/wysiwyg/ui/defaults/button-definitions.ts
deleted file mode 100644
index 5316dacf7..000000000
--- a/resources/js/wysiwyg/ui/defaults/button-definitions.ts
+++ /dev/null
@@ -1,528 +0,0 @@
-import {EditorBasicButtonDefinition, EditorButton, EditorButtonDefinition} from "../framework/buttons";
-import {
- $createNodeSelection,
- $createParagraphNode,
- $createTextNode,
- $getRoot,
- $getSelection,
- $isParagraphNode,
- $isTextNode,
- $setSelection,
- BaseSelection,
- CAN_REDO_COMMAND,
- CAN_UNDO_COMMAND,
- COMMAND_PRIORITY_LOW,
- ElementFormatType,
- ElementNode,
- FORMAT_TEXT_COMMAND,
- LexicalNode,
- REDO_COMMAND,
- TextFormatType,
- UNDO_COMMAND
-} from "lexical";
-import {
- $getBlockElementNodesInSelection,
- $getNodeFromSelection, $insertNewBlockNodeAtSelection, $selectionContainsElementFormat,
- $selectionContainsNodeType,
- $selectionContainsTextFormat,
- $toggleSelectionBlockNodeType
-} from "../../helpers";
-import {$createCalloutNode, $isCalloutNodeOfCategory, CalloutCategory} from "../../nodes/callout";
-import {
- $createHeadingNode,
- $createQuoteNode,
- $isHeadingNode,
- $isQuoteNode,
- HeadingNode,
- HeadingTagType
-} from "@lexical/rich-text";
-import {$isLinkNode, LinkNode} from "@lexical/link";
-import {EditorUiContext} from "../framework/core";
-import {$isImageNode, ImageNode} from "../../nodes/image";
-import {$createDetailsNode, $isDetailsNode} from "../../nodes/details";
-import {getEditorContentAsHtml} from "../../actions";
-import {$isListNode, insertList, ListNode, ListType, removeList} from "@lexical/list";
-import undoIcon from "@icons/editor/undo.svg";
-import redoIcon from "@icons/editor/redo.svg";
-import boldIcon from "@icons/editor/bold.svg";
-import italicIcon from "@icons/editor/italic.svg";
-import underlinedIcon from "@icons/editor/underlined.svg";
-import textColorIcon from "@icons/editor/text-color.svg";
-import highlightIcon from "@icons/editor/highlighter.svg";
-import strikethroughIcon from "@icons/editor/strikethrough.svg";
-import superscriptIcon from "@icons/editor/superscript.svg";
-import subscriptIcon from "@icons/editor/subscript.svg";
-import codeIcon from "@icons/editor/code.svg";
-import formatClearIcon from "@icons/editor/format-clear.svg";
-import alignLeftIcon from "@icons/editor/align-left.svg";
-import alignCenterIcon from "@icons/editor/align-center.svg";
-import alignRightIcon from "@icons/editor/align-right.svg";
-import alignJustifyIcon from "@icons/editor/align-justify.svg";
-import listBulletIcon from "@icons/editor/list-bullet.svg";
-import listNumberedIcon from "@icons/editor/list-numbered.svg";
-import listCheckIcon from "@icons/editor/list-check.svg";
-import linkIcon from "@icons/editor/link.svg";
-import unlinkIcon from "@icons/editor/unlink.svg";
-import tableIcon from "@icons/editor/table.svg";
-import imageIcon from "@icons/editor/image.svg";
-import horizontalRuleIcon from "@icons/editor/horizontal-rule.svg";
-import codeBlockIcon from "@icons/editor/code-block.svg";
-import diagramIcon from "@icons/editor/diagram.svg";
-import detailsIcon from "@icons/editor/details.svg";
-import sourceIcon from "@icons/editor/source-view.svg";
-import fullscreenIcon from "@icons/editor/fullscreen.svg";
-import editIcon from "@icons/edit.svg";
-import {$createHorizontalRuleNode, $isHorizontalRuleNode} from "../../nodes/horizontal-rule";
-import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../../nodes/code-block";
-import {$createDiagramNode, $isDiagramNode, $openDrawingEditorForNode, DiagramNode} from "../../nodes/diagram";
-
-export const undo: EditorButtonDefinition = {
- label: 'Undo',
- icon: undoIcon,
- action(context: EditorUiContext) {
- context.editor.dispatchCommand(UNDO_COMMAND, undefined);
- },
- isActive(selection: BaseSelection|null): boolean {
- return false;
- },
- setup(context: EditorUiContext, button: EditorButton) {
- button.toggleDisabled(true);
-
- context.editor.registerCommand(CAN_UNDO_COMMAND, (payload: boolean): boolean => {
- button.toggleDisabled(!payload)
- return false;
- }, COMMAND_PRIORITY_LOW);
- }
-}
-
-export const redo: EditorButtonDefinition = {
- label: 'Redo',
- icon: redoIcon,
- action(context: EditorUiContext) {
- context.editor.dispatchCommand(REDO_COMMAND, undefined);
- },
- isActive(selection: BaseSelection|null): boolean {
- return false;
- },
- setup(context: EditorUiContext, button: EditorButton) {
- button.toggleDisabled(true);
-
- context.editor.registerCommand(CAN_REDO_COMMAND, (payload: boolean): boolean => {
- button.toggleDisabled(!payload)
- return false;
- }, COMMAND_PRIORITY_LOW);
- }
-}
-
-function buildCalloutButton(category: CalloutCategory, name: string): EditorButtonDefinition {
- return {
- label: `${name} Callout`,
- action(context: EditorUiContext) {
- context.editor.update(() => {
- $toggleSelectionBlockNodeType(
- (node) => $isCalloutNodeOfCategory(node, category),
- () => $createCalloutNode(category),
- )
- });
- },
- isActive(selection: BaseSelection|null): boolean {
- return $selectionContainsNodeType(selection, (node) => $isCalloutNodeOfCategory(node, category));
- }
- };
-}
-
-export const infoCallout: EditorButtonDefinition = buildCalloutButton('info', 'Info');
-export const dangerCallout: EditorButtonDefinition = buildCalloutButton('danger', 'Danger');
-export const warningCallout: EditorButtonDefinition = buildCalloutButton('warning', 'Warning');
-export const successCallout: EditorButtonDefinition = buildCalloutButton('success', 'Success');
-
-const isHeaderNodeOfTag = (node: LexicalNode | null | undefined, tag: HeadingTagType) => {
- return $isHeadingNode(node) && (node as HeadingNode).getTag() === tag;
-};
-
-function buildHeaderButton(tag: HeadingTagType, name: string): EditorButtonDefinition {
- return {
- label: name,
- action(context: EditorUiContext) {
- context.editor.update(() => {
- $toggleSelectionBlockNodeType(
- (node) => isHeaderNodeOfTag(node, tag),
- () => $createHeadingNode(tag),
- )
- });
- },
- isActive(selection: BaseSelection|null): boolean {
- return $selectionContainsNodeType(selection, (node) => isHeaderNodeOfTag(node, tag));
- }
- };
-}
-
-export const h2: EditorButtonDefinition = buildHeaderButton('h2', 'Large Header');
-export const h3: EditorButtonDefinition = buildHeaderButton('h3', 'Medium Header');
-export const h4: EditorButtonDefinition = buildHeaderButton('h4', 'Small Header');
-export const h5: EditorButtonDefinition = buildHeaderButton('h5', 'Tiny Header');
-
-export const blockquote: EditorButtonDefinition = {
- label: 'Blockquote',
- action(context: EditorUiContext) {
- context.editor.update(() => {
- $toggleSelectionBlockNodeType($isQuoteNode, $createQuoteNode);
- });
- },
- isActive(selection: BaseSelection|null): boolean {
- return $selectionContainsNodeType(selection, $isQuoteNode);
- }
-};
-
-export const paragraph: EditorButtonDefinition = {
- label: 'Paragraph',
- action(context: EditorUiContext) {
- context.editor.update(() => {
- $toggleSelectionBlockNodeType($isParagraphNode, $createParagraphNode);
- });
- },
- isActive(selection: BaseSelection|null): boolean {
- return $selectionContainsNodeType(selection, $isParagraphNode);
- }
-}
-
-function buildFormatButton(label: string, format: TextFormatType, icon: string): EditorButtonDefinition {
- return {
- label: label,
- icon,
- action(context: EditorUiContext) {
- context.editor.dispatchCommand(FORMAT_TEXT_COMMAND, format);
- },
- isActive(selection: BaseSelection|null): boolean {
- return $selectionContainsTextFormat(selection, format);
- }
- };
-}
-
-export const bold: EditorButtonDefinition = buildFormatButton('Bold', 'bold', boldIcon);
-export const italic: EditorButtonDefinition = buildFormatButton('Italic', 'italic', italicIcon);
-export const underline: EditorButtonDefinition = buildFormatButton('Underline', 'underline', underlinedIcon);
-export const textColor: EditorBasicButtonDefinition = {label: 'Text color', icon: textColorIcon};
-export const highlightColor: EditorBasicButtonDefinition = {label: 'Highlight color', icon: highlightIcon};
-
-export const strikethrough: EditorButtonDefinition = buildFormatButton('Strikethrough', 'strikethrough', strikethroughIcon);
-export const superscript: EditorButtonDefinition = buildFormatButton('Superscript', 'superscript', superscriptIcon);
-export const subscript: EditorButtonDefinition = buildFormatButton('Subscript', 'subscript', subscriptIcon);
-export const code: EditorButtonDefinition = buildFormatButton('Inline Code', 'code', codeIcon);
-export const clearFormating: EditorButtonDefinition = {
- label: 'Clear formatting',
- icon: formatClearIcon,
- action(context: EditorUiContext) {
- context.editor.update(() => {
- const selection = $getSelection();
- for (const node of selection?.getNodes() || []) {
- if ($isTextNode(node)) {
- node.setFormat(0);
- node.setStyle('');
- }
- }
- });
- },
- isActive() {
- return false;
- }
-};
-
-function setAlignmentForSection(alignment: ElementFormatType): void {
- const selection = $getSelection();
- const elements = $getBlockElementNodesInSelection(selection);
- for (const node of elements) {
- node.setFormat(alignment);
- }
-}
-
-export const alignLeft: EditorButtonDefinition = {
- label: 'Align left',
- icon: alignLeftIcon,
- action(context: EditorUiContext) {
- context.editor.update(() => setAlignmentForSection('left'));
- },
- isActive(selection: BaseSelection|null) {
- return $selectionContainsElementFormat(selection, 'left');
- }
-};
-
-export const alignCenter: EditorButtonDefinition = {
- label: 'Align center',
- icon: alignCenterIcon,
- action(context: EditorUiContext) {
- context.editor.update(() => setAlignmentForSection('center'));
- },
- isActive(selection: BaseSelection|null) {
- return $selectionContainsElementFormat(selection, 'center');
- }
-};
-
-export const alignRight: EditorButtonDefinition = {
- label: 'Align right',
- icon: alignRightIcon,
- action(context: EditorUiContext) {
- context.editor.update(() => setAlignmentForSection('right'));
- },
- isActive(selection: BaseSelection|null) {
- return $selectionContainsElementFormat(selection, 'right');
- }
-};
-
-export const alignJustify: EditorButtonDefinition = {
- label: 'Align justify',
- icon: alignJustifyIcon,
- action(context: EditorUiContext) {
- context.editor.update(() => setAlignmentForSection('justify'));
- },
- isActive(selection: BaseSelection|null) {
- return $selectionContainsElementFormat(selection, 'justify');
- }
-};
-
-
-function buildListButton(label: string, type: ListType, icon: string): EditorButtonDefinition {
- return {
- label,
- icon,
- action(context: EditorUiContext) {
- context.editor.getEditorState().read(() => {
- const selection = $getSelection();
- if (this.isActive(selection, context)) {
- removeList(context.editor);
- } else {
- insertList(context.editor, type);
- }
- });
- },
- isActive(selection: BaseSelection|null): boolean {
- return $selectionContainsNodeType(selection, (node: LexicalNode | null | undefined): boolean => {
- return $isListNode(node) && (node as ListNode).getListType() === type;
- });
- }
- };
-}
-
-export const bulletList: EditorButtonDefinition = buildListButton('Bullet list', 'bullet', listBulletIcon);
-export const numberList: EditorButtonDefinition = buildListButton('Numbered list', 'number', listNumberedIcon);
-export const taskList: EditorButtonDefinition = buildListButton('Task list', 'check', listCheckIcon);
-
-
-export const link: EditorButtonDefinition = {
- label: 'Insert/edit link',
- icon: linkIcon,
- action(context: EditorUiContext) {
- const linkModal = context.manager.createModal('link');
- context.editor.getEditorState().read(() => {
- const selection = $getSelection();
- const selectedLink = $getNodeFromSelection(selection, $isLinkNode) as LinkNode|null;
-
- let formDefaults = {};
- if (selectedLink) {
- formDefaults = {
- url: selectedLink.getURL(),
- text: selectedLink.getTextContent(),
- title: selectedLink.getTitle(),
- target: selectedLink.getTarget(),
- }
-
- context.editor.update(() => {
- const selection = $createNodeSelection();
- selection.add(selectedLink.getKey());
- $setSelection(selection);
- });
- }
-
- linkModal.show(formDefaults);
- });
- },
- isActive(selection: BaseSelection|null): boolean {
- return $selectionContainsNodeType(selection, $isLinkNode);
- }
-};
-
-export const unlink: EditorButtonDefinition = {
- label: 'Remove link',
- icon: unlinkIcon,
- action(context: EditorUiContext) {
- context.editor.update(() => {
- const selection = context.lastSelection;
- const selectedLink = $getNodeFromSelection(selection, $isLinkNode) as LinkNode|null;
- const selectionPoints = selection?.getStartEndPoints();
-
- if (selectedLink) {
- const newNode = $createTextNode(selectedLink.getTextContent());
- selectedLink.replace(newNode);
- if (selectionPoints?.length === 2) {
- newNode.select(selectionPoints[0].offset, selectionPoints[1].offset);
- } else {
- newNode.select();
- }
- }
- });
- },
- isActive(selection: BaseSelection|null): boolean {
- return false;
- }
-};
-
-export const table: EditorBasicButtonDefinition = {
- label: 'Table',
- icon: tableIcon,
-};
-
-export const image: EditorButtonDefinition = {
- label: 'Insert/Edit Image',
- icon: imageIcon,
- action(context: EditorUiContext) {
- const imageModal = context.manager.createModal('image');
- const selection = context.lastSelection;
- const selectedImage = $getNodeFromSelection(selection, $isImageNode) as ImageNode|null;
-
- context.editor.getEditorState().read(() => {
- let formDefaults = {};
- if (selectedImage) {
- formDefaults = {
- src: selectedImage.getSrc(),
- alt: selectedImage.getAltText(),
- height: selectedImage.getHeight(),
- width: selectedImage.getWidth(),
- }
-
- context.editor.update(() => {
- const selection = $createNodeSelection();
- selection.add(selectedImage.getKey());
- $setSelection(selection);
- });
- }
-
- imageModal.show(formDefaults);
- });
- },
- isActive(selection: BaseSelection|null): boolean {
- return $selectionContainsNodeType(selection, $isImageNode);
- }
-};
-
-export const horizontalRule: EditorButtonDefinition = {
- label: 'Insert horizontal line',
- icon: horizontalRuleIcon,
- action(context: EditorUiContext) {
- context.editor.update(() => {
- $insertNewBlockNodeAtSelection($createHorizontalRuleNode(), false);
- });
- },
- isActive(selection: BaseSelection|null): boolean {
- return $selectionContainsNodeType(selection, $isHorizontalRuleNode);
- }
-};
-
-export const codeBlock: EditorButtonDefinition = {
- label: 'Insert code block',
- icon: codeBlockIcon,
- action(context: EditorUiContext) {
- context.editor.getEditorState().read(() => {
- const selection = $getSelection();
- const codeBlock = $getNodeFromSelection(context.lastSelection, $isCodeBlockNode) as (CodeBlockNode|null);
- if (codeBlock === null) {
- context.editor.update(() => {
- const codeBlock = $createCodeBlockNode();
- codeBlock.setCode(selection?.getTextContent() || '');
- $insertNewBlockNodeAtSelection(codeBlock, true);
- $openCodeEditorForNode(context.editor, codeBlock);
- codeBlock.selectStart();
- });
- } else {
- $openCodeEditorForNode(context.editor, codeBlock);
- }
- });
- },
- isActive(selection: BaseSelection|null): boolean {
- return $selectionContainsNodeType(selection, $isCodeBlockNode);
- }
-};
-
-export const editCodeBlock: EditorButtonDefinition = Object.assign({}, codeBlock, {
- label: 'Edit code block',
- icon: editIcon,
-});
-
-export const diagram: EditorButtonDefinition = {
- label: 'Insert/edit drawing',
- icon: diagramIcon,
- action(context: EditorUiContext) {
- context.editor.getEditorState().read(() => {
- const selection = $getSelection();
- const diagramNode = $getNodeFromSelection(context.lastSelection, $isDiagramNode) as (DiagramNode|null);
- if (diagramNode === null) {
- context.editor.update(() => {
- const diagram = $createDiagramNode();
- $insertNewBlockNodeAtSelection(diagram, true);
- $openDrawingEditorForNode(context, diagram);
- diagram.selectStart();
- });
- } else {
- $openDrawingEditorForNode(context, diagramNode);
- }
- });
- },
- isActive(selection: BaseSelection|null): boolean {
- return $selectionContainsNodeType(selection, $isDiagramNode);
- }
-};
-
-
-export const details: EditorButtonDefinition = {
- label: 'Insert collapsible block',
- icon: detailsIcon,
- action(context: EditorUiContext) {
- context.editor.update(() => {
- const selection = $getSelection();
- const detailsNode = $createDetailsNode();
- const selectionNodes = selection?.getNodes() || [];
- const topLevels = selectionNodes.map(n => n.getTopLevelElement())
- .filter(n => n !== null) as ElementNode[];
- const uniqueTopLevels = [...new Set(topLevels)];
-
- if (uniqueTopLevels.length > 0) {
- uniqueTopLevels[0].insertAfter(detailsNode);
- } else {
- $getRoot().append(detailsNode);
- }
-
- for (const node of uniqueTopLevels) {
- detailsNode.append(node);
- }
- });
- },
- isActive(selection: BaseSelection|null): boolean {
- return $selectionContainsNodeType(selection, $isDetailsNode);
- }
-}
-
-export const source: EditorButtonDefinition = {
- label: 'Source code',
- icon: sourceIcon,
- async action(context: EditorUiContext) {
- const modal = context.manager.createModal('source');
- const source = await getEditorContentAsHtml(context.editor);
- modal.show({source});
- },
- isActive() {
- return false;
- }
-};
-
-export const fullscreen: EditorButtonDefinition = {
- label: 'Fullscreen',
- icon: fullscreenIcon,
- async action(context: EditorUiContext, button: EditorButton) {
- const isFullScreen = context.containerDOM.classList.contains('fullscreen');
- context.containerDOM.classList.toggle('fullscreen', !isFullScreen);
- (context.containerDOM.closest('body') as HTMLElement).classList.toggle('editor-is-fullscreen', !isFullScreen);
- button.setActiveState(!isFullScreen);
- },
- isActive(selection, context: EditorUiContext) {
- return context.containerDOM.classList.contains('fullscreen');
- }
-};
\ No newline at end of file
diff --git a/resources/js/wysiwyg/ui/defaults/buttons/alignments.ts b/resources/js/wysiwyg/ui/defaults/buttons/alignments.ts
new file mode 100644
index 000000000..2b441e5da
--- /dev/null
+++ b/resources/js/wysiwyg/ui/defaults/buttons/alignments.ts
@@ -0,0 +1,61 @@
+import {$getSelection, BaseSelection, ElementFormatType} from "lexical";
+import {$getBlockElementNodesInSelection, $selectionContainsElementFormat} from "../../../helpers";
+import {EditorButtonDefinition} from "../../framework/buttons";
+import alignLeftIcon from "@icons/editor/align-left.svg";
+import {EditorUiContext} from "../../framework/core";
+import alignCenterIcon from "@icons/editor/align-center.svg";
+import alignRightIcon from "@icons/editor/align-right.svg";
+import alignJustifyIcon from "@icons/editor/align-justify.svg";
+
+
+function setAlignmentForSection(alignment: ElementFormatType): void {
+ const selection = $getSelection();
+ const elements = $getBlockElementNodesInSelection(selection);
+ for (const node of elements) {
+ node.setFormat(alignment);
+ }
+}
+
+export const alignLeft: EditorButtonDefinition = {
+ label: 'Align left',
+ icon: alignLeftIcon,
+ action(context: EditorUiContext) {
+ context.editor.update(() => setAlignmentForSection('left'));
+ },
+ isActive(selection: BaseSelection|null) {
+ return $selectionContainsElementFormat(selection, 'left');
+ }
+};
+
+export const alignCenter: EditorButtonDefinition = {
+ label: 'Align center',
+ icon: alignCenterIcon,
+ action(context: EditorUiContext) {
+ context.editor.update(() => setAlignmentForSection('center'));
+ },
+ isActive(selection: BaseSelection|null) {
+ return $selectionContainsElementFormat(selection, 'center');
+ }
+};
+
+export const alignRight: EditorButtonDefinition = {
+ label: 'Align right',
+ icon: alignRightIcon,
+ action(context: EditorUiContext) {
+ context.editor.update(() => setAlignmentForSection('right'));
+ },
+ isActive(selection: BaseSelection|null) {
+ return $selectionContainsElementFormat(selection, 'right');
+ }
+};
+
+export const alignJustify: EditorButtonDefinition = {
+ label: 'Align justify',
+ icon: alignJustifyIcon,
+ action(context: EditorUiContext) {
+ context.editor.update(() => setAlignmentForSection('justify'));
+ },
+ isActive(selection: BaseSelection|null) {
+ return $selectionContainsElementFormat(selection, 'justify');
+ }
+};
diff --git a/resources/js/wysiwyg/ui/defaults/buttons/block-formats.ts b/resources/js/wysiwyg/ui/defaults/buttons/block-formats.ts
new file mode 100644
index 000000000..0eb07ecf1
--- /dev/null
+++ b/resources/js/wysiwyg/ui/defaults/buttons/block-formats.ts
@@ -0,0 +1,85 @@
+import {$createCalloutNode, $isCalloutNodeOfCategory, CalloutCategory} from "../../../nodes/callout";
+import {EditorButtonDefinition} from "../../framework/buttons";
+import {EditorUiContext} from "../../framework/core";
+import {$selectionContainsNodeType, $toggleSelectionBlockNodeType} from "../../../helpers";
+import {$createParagraphNode, $isParagraphNode, BaseSelection, LexicalNode} from "lexical";
+import {
+ $createHeadingNode,
+ $createQuoteNode,
+ $isHeadingNode,
+ $isQuoteNode,
+ HeadingNode,
+ HeadingTagType
+} from "@lexical/rich-text";
+
+function buildCalloutButton(category: CalloutCategory, name: string): EditorButtonDefinition {
+ return {
+ label: `${name} Callout`,
+ action(context: EditorUiContext) {
+ context.editor.update(() => {
+ $toggleSelectionBlockNodeType(
+ (node) => $isCalloutNodeOfCategory(node, category),
+ () => $createCalloutNode(category),
+ )
+ });
+ },
+ isActive(selection: BaseSelection|null): boolean {
+ return $selectionContainsNodeType(selection, (node) => $isCalloutNodeOfCategory(node, category));
+ }
+ };
+}
+
+export const infoCallout: EditorButtonDefinition = buildCalloutButton('info', 'Info');
+export const dangerCallout: EditorButtonDefinition = buildCalloutButton('danger', 'Danger');
+export const warningCallout: EditorButtonDefinition = buildCalloutButton('warning', 'Warning');
+export const successCallout: EditorButtonDefinition = buildCalloutButton('success', 'Success');
+
+const isHeaderNodeOfTag = (node: LexicalNode | null | undefined, tag: HeadingTagType) => {
+ return $isHeadingNode(node) && (node as HeadingNode).getTag() === tag;
+};
+
+function buildHeaderButton(tag: HeadingTagType, name: string): EditorButtonDefinition {
+ return {
+ label: name,
+ action(context: EditorUiContext) {
+ context.editor.update(() => {
+ $toggleSelectionBlockNodeType(
+ (node) => isHeaderNodeOfTag(node, tag),
+ () => $createHeadingNode(tag),
+ )
+ });
+ },
+ isActive(selection: BaseSelection|null): boolean {
+ return $selectionContainsNodeType(selection, (node) => isHeaderNodeOfTag(node, tag));
+ }
+ };
+}
+
+export const h2: EditorButtonDefinition = buildHeaderButton('h2', 'Large Header');
+export const h3: EditorButtonDefinition = buildHeaderButton('h3', 'Medium Header');
+export const h4: EditorButtonDefinition = buildHeaderButton('h4', 'Small Header');
+export const h5: EditorButtonDefinition = buildHeaderButton('h5', 'Tiny Header');
+
+export const blockquote: EditorButtonDefinition = {
+ label: 'Blockquote',
+ action(context: EditorUiContext) {
+ context.editor.update(() => {
+ $toggleSelectionBlockNodeType($isQuoteNode, $createQuoteNode);
+ });
+ },
+ isActive(selection: BaseSelection|null): boolean {
+ return $selectionContainsNodeType(selection, $isQuoteNode);
+ }
+};
+
+export const paragraph: EditorButtonDefinition = {
+ label: 'Paragraph',
+ action(context: EditorUiContext) {
+ context.editor.update(() => {
+ $toggleSelectionBlockNodeType($isParagraphNode, $createParagraphNode);
+ });
+ },
+ isActive(selection: BaseSelection|null): boolean {
+ return $selectionContainsNodeType(selection, $isParagraphNode);
+ }
+}
\ No newline at end of file
diff --git a/resources/js/wysiwyg/ui/defaults/buttons/controls.ts b/resources/js/wysiwyg/ui/defaults/buttons/controls.ts
new file mode 100644
index 000000000..ad69d69d1
--- /dev/null
+++ b/resources/js/wysiwyg/ui/defaults/buttons/controls.ts
@@ -0,0 +1,81 @@
+import {EditorButton, EditorButtonDefinition} from "../../framework/buttons";
+import undoIcon from "@icons/editor/undo.svg";
+import {EditorUiContext} from "../../framework/core";
+import {
+ BaseSelection,
+ CAN_REDO_COMMAND,
+ CAN_UNDO_COMMAND,
+ COMMAND_PRIORITY_LOW,
+ REDO_COMMAND,
+ UNDO_COMMAND
+} from "lexical";
+import redoIcon from "@icons/editor/redo.svg";
+import sourceIcon from "@icons/editor/source-view.svg";
+import {getEditorContentAsHtml} from "../../../actions";
+import fullscreenIcon from "@icons/editor/fullscreen.svg";
+
+export const undo: EditorButtonDefinition = {
+ label: 'Undo',
+ icon: undoIcon,
+ action(context: EditorUiContext) {
+ context.editor.dispatchCommand(UNDO_COMMAND, undefined);
+ },
+ isActive(selection: BaseSelection|null): boolean {
+ return false;
+ },
+ setup(context: EditorUiContext, button: EditorButton) {
+ button.toggleDisabled(true);
+
+ context.editor.registerCommand(CAN_UNDO_COMMAND, (payload: boolean): boolean => {
+ button.toggleDisabled(!payload)
+ return false;
+ }, COMMAND_PRIORITY_LOW);
+ }
+}
+
+export const redo: EditorButtonDefinition = {
+ label: 'Redo',
+ icon: redoIcon,
+ action(context: EditorUiContext) {
+ context.editor.dispatchCommand(REDO_COMMAND, undefined);
+ },
+ isActive(selection: BaseSelection|null): boolean {
+ return false;
+ },
+ setup(context: EditorUiContext, button: EditorButton) {
+ button.toggleDisabled(true);
+
+ context.editor.registerCommand(CAN_REDO_COMMAND, (payload: boolean): boolean => {
+ button.toggleDisabled(!payload)
+ return false;
+ }, COMMAND_PRIORITY_LOW);
+ }
+}
+
+
+export const source: EditorButtonDefinition = {
+ label: 'Source code',
+ icon: sourceIcon,
+ async action(context: EditorUiContext) {
+ const modal = context.manager.createModal('source');
+ const source = await getEditorContentAsHtml(context.editor);
+ modal.show({source});
+ },
+ isActive() {
+ return false;
+ }
+};
+
+export const fullscreen: EditorButtonDefinition = {
+ label: 'Fullscreen',
+ icon: fullscreenIcon,
+ async action(context: EditorUiContext, button: EditorButton) {
+ const isFullScreen = context.containerDOM.classList.contains('fullscreen');
+ context.containerDOM.classList.toggle('fullscreen', !isFullScreen);
+ (context.containerDOM.closest('body') as HTMLElement).classList.toggle('editor-is-fullscreen', !isFullScreen);
+ button.setActiveState(!isFullScreen);
+ },
+ isActive(selection, context: EditorUiContext) {
+ return context.containerDOM.classList.contains('fullscreen');
+ }
+};
\ No newline at end of file
diff --git a/resources/js/wysiwyg/ui/defaults/buttons/inline-formats.ts b/resources/js/wysiwyg/ui/defaults/buttons/inline-formats.ts
new file mode 100644
index 000000000..d04f72a2e
--- /dev/null
+++ b/resources/js/wysiwyg/ui/defaults/buttons/inline-formats.ts
@@ -0,0 +1,56 @@
+import {$getSelection, $isTextNode, BaseSelection, FORMAT_TEXT_COMMAND, TextFormatType} from "lexical";
+import {EditorBasicButtonDefinition, EditorButtonDefinition} from "../../framework/buttons";
+import {EditorUiContext} from "../../framework/core";
+import {$selectionContainsTextFormat} from "../../../helpers";
+import boldIcon from "@icons/editor/bold.svg";
+import italicIcon from "@icons/editor/italic.svg";
+import underlinedIcon from "@icons/editor/underlined.svg";
+import textColorIcon from "@icons/editor/text-color.svg";
+import highlightIcon from "@icons/editor/highlighter.svg";
+import strikethroughIcon from "@icons/editor/strikethrough.svg";
+import superscriptIcon from "@icons/editor/superscript.svg";
+import subscriptIcon from "@icons/editor/subscript.svg";
+import codeIcon from "@icons/editor/code.svg";
+import formatClearIcon from "@icons/editor/format-clear.svg";
+
+function buildFormatButton(label: string, format: TextFormatType, icon: string): EditorButtonDefinition {
+ return {
+ label: label,
+ icon,
+ action(context: EditorUiContext) {
+ context.editor.dispatchCommand(FORMAT_TEXT_COMMAND, format);
+ },
+ isActive(selection: BaseSelection|null): boolean {
+ return $selectionContainsTextFormat(selection, format);
+ }
+ };
+}
+
+export const bold: EditorButtonDefinition = buildFormatButton('Bold', 'bold', boldIcon);
+export const italic: EditorButtonDefinition = buildFormatButton('Italic', 'italic', italicIcon);
+export const underline: EditorButtonDefinition = buildFormatButton('Underline', 'underline', underlinedIcon);
+export const textColor: EditorBasicButtonDefinition = {label: 'Text color', icon: textColorIcon};
+export const highlightColor: EditorBasicButtonDefinition = {label: 'Highlight color', icon: highlightIcon};
+
+export const strikethrough: EditorButtonDefinition = buildFormatButton('Strikethrough', 'strikethrough', strikethroughIcon);
+export const superscript: EditorButtonDefinition = buildFormatButton('Superscript', 'superscript', superscriptIcon);
+export const subscript: EditorButtonDefinition = buildFormatButton('Subscript', 'subscript', subscriptIcon);
+export const code: EditorButtonDefinition = buildFormatButton('Inline Code', 'code', codeIcon);
+export const clearFormating: EditorButtonDefinition = {
+ label: 'Clear formatting',
+ icon: formatClearIcon,
+ action(context: EditorUiContext) {
+ context.editor.update(() => {
+ const selection = $getSelection();
+ for (const node of selection?.getNodes() || []) {
+ if ($isTextNode(node)) {
+ node.setFormat(0);
+ node.setStyle('');
+ }
+ }
+ });
+ },
+ isActive() {
+ return false;
+ }
+};
\ No newline at end of file
diff --git a/resources/js/wysiwyg/ui/defaults/buttons/lists.ts b/resources/js/wysiwyg/ui/defaults/buttons/lists.ts
new file mode 100644
index 000000000..ecda290a1
--- /dev/null
+++ b/resources/js/wysiwyg/ui/defaults/buttons/lists.ts
@@ -0,0 +1,35 @@
+import {$isListNode, insertList, ListNode, ListType, removeList} from "@lexical/list";
+import {EditorButtonDefinition} from "../../framework/buttons";
+import {EditorUiContext} from "../../framework/core";
+import {$getSelection, BaseSelection, LexicalNode} from "lexical";
+import {$selectionContainsNodeType} from "../../../helpers";
+import listBulletIcon from "@icons/editor/list-bullet.svg";
+import listNumberedIcon from "@icons/editor/list-numbered.svg";
+import listCheckIcon from "@icons/editor/list-check.svg";
+
+
+function buildListButton(label: string, type: ListType, icon: string): EditorButtonDefinition {
+ return {
+ label,
+ icon,
+ action(context: EditorUiContext) {
+ context.editor.getEditorState().read(() => {
+ const selection = $getSelection();
+ if (this.isActive(selection, context)) {
+ removeList(context.editor);
+ } else {
+ insertList(context.editor, type);
+ }
+ });
+ },
+ isActive(selection: BaseSelection|null): boolean {
+ return $selectionContainsNodeType(selection, (node: LexicalNode | null | undefined): boolean => {
+ return $isListNode(node) && (node as ListNode).getListType() === type;
+ });
+ }
+ };
+}
+
+export const bulletList: EditorButtonDefinition = buildListButton('Bullet list', 'bullet', listBulletIcon);
+export const numberList: EditorButtonDefinition = buildListButton('Numbered list', 'number', listNumberedIcon);
+export const taskList: EditorButtonDefinition = buildListButton('Task list', 'check', listCheckIcon);
diff --git a/resources/js/wysiwyg/ui/defaults/buttons/objects.ts b/resources/js/wysiwyg/ui/defaults/buttons/objects.ts
new file mode 100644
index 000000000..88241e926
--- /dev/null
+++ b/resources/js/wysiwyg/ui/defaults/buttons/objects.ts
@@ -0,0 +1,215 @@
+import {EditorButtonDefinition} from "../../framework/buttons";
+import linkIcon from "@icons/editor/link.svg";
+import {EditorUiContext} from "../../framework/core";
+import {
+ $createNodeSelection,
+ $createTextNode,
+ $getRoot,
+ $getSelection,
+ $setSelection,
+ BaseSelection,
+ ElementNode
+} from "lexical";
+import {$getNodeFromSelection, $insertNewBlockNodeAtSelection, $selectionContainsNodeType} from "../../../helpers";
+import {$isLinkNode, LinkNode} from "@lexical/link";
+import unlinkIcon from "@icons/editor/unlink.svg";
+import imageIcon from "@icons/editor/image.svg";
+import {$isImageNode, ImageNode} from "../../../nodes/image";
+import horizontalRuleIcon from "@icons/editor/horizontal-rule.svg";
+import {$createHorizontalRuleNode, $isHorizontalRuleNode} from "../../../nodes/horizontal-rule";
+import codeBlockIcon from "@icons/editor/code-block.svg";
+import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../../../nodes/code-block";
+import editIcon from "@icons/edit.svg";
+import diagramIcon from "@icons/editor/diagram.svg";
+import {$createDiagramNode, $isDiagramNode, $openDrawingEditorForNode, DiagramNode} from "../../../nodes/diagram";
+import detailsIcon from "@icons/editor/details.svg";
+import {$createDetailsNode, $isDetailsNode} from "../../../nodes/details";
+
+export const link: EditorButtonDefinition = {
+ label: 'Insert/edit link',
+ icon: linkIcon,
+ action(context: EditorUiContext) {
+ const linkModal = context.manager.createModal('link');
+ context.editor.getEditorState().read(() => {
+ const selection = $getSelection();
+ const selectedLink = $getNodeFromSelection(selection, $isLinkNode) as LinkNode|null;
+
+ let formDefaults = {};
+ if (selectedLink) {
+ formDefaults = {
+ url: selectedLink.getURL(),
+ text: selectedLink.getTextContent(),
+ title: selectedLink.getTitle(),
+ target: selectedLink.getTarget(),
+ }
+
+ context.editor.update(() => {
+ const selection = $createNodeSelection();
+ selection.add(selectedLink.getKey());
+ $setSelection(selection);
+ });
+ }
+
+ linkModal.show(formDefaults);
+ });
+ },
+ isActive(selection: BaseSelection|null): boolean {
+ return $selectionContainsNodeType(selection, $isLinkNode);
+ }
+};
+
+export const unlink: EditorButtonDefinition = {
+ label: 'Remove link',
+ icon: unlinkIcon,
+ action(context: EditorUiContext) {
+ context.editor.update(() => {
+ const selection = context.lastSelection;
+ const selectedLink = $getNodeFromSelection(selection, $isLinkNode) as LinkNode|null;
+ const selectionPoints = selection?.getStartEndPoints();
+
+ if (selectedLink) {
+ const newNode = $createTextNode(selectedLink.getTextContent());
+ selectedLink.replace(newNode);
+ if (selectionPoints?.length === 2) {
+ newNode.select(selectionPoints[0].offset, selectionPoints[1].offset);
+ } else {
+ newNode.select();
+ }
+ }
+ });
+ },
+ isActive(selection: BaseSelection|null): boolean {
+ return false;
+ }
+};
+
+
+
+export const image: EditorButtonDefinition = {
+ label: 'Insert/Edit Image',
+ icon: imageIcon,
+ action(context: EditorUiContext) {
+ const imageModal = context.manager.createModal('image');
+ const selection = context.lastSelection;
+ const selectedImage = $getNodeFromSelection(selection, $isImageNode) as ImageNode|null;
+
+ context.editor.getEditorState().read(() => {
+ let formDefaults = {};
+ if (selectedImage) {
+ formDefaults = {
+ src: selectedImage.getSrc(),
+ alt: selectedImage.getAltText(),
+ height: selectedImage.getHeight(),
+ width: selectedImage.getWidth(),
+ }
+
+ context.editor.update(() => {
+ const selection = $createNodeSelection();
+ selection.add(selectedImage.getKey());
+ $setSelection(selection);
+ });
+ }
+
+ imageModal.show(formDefaults);
+ });
+ },
+ isActive(selection: BaseSelection|null): boolean {
+ return $selectionContainsNodeType(selection, $isImageNode);
+ }
+};
+
+export const horizontalRule: EditorButtonDefinition = {
+ label: 'Insert horizontal line',
+ icon: horizontalRuleIcon,
+ action(context: EditorUiContext) {
+ context.editor.update(() => {
+ $insertNewBlockNodeAtSelection($createHorizontalRuleNode(), false);
+ });
+ },
+ isActive(selection: BaseSelection|null): boolean {
+ return $selectionContainsNodeType(selection, $isHorizontalRuleNode);
+ }
+};
+
+export const codeBlock: EditorButtonDefinition = {
+ label: 'Insert code block',
+ icon: codeBlockIcon,
+ action(context: EditorUiContext) {
+ context.editor.getEditorState().read(() => {
+ const selection = $getSelection();
+ const codeBlock = $getNodeFromSelection(context.lastSelection, $isCodeBlockNode) as (CodeBlockNode|null);
+ if (codeBlock === null) {
+ context.editor.update(() => {
+ const codeBlock = $createCodeBlockNode();
+ codeBlock.setCode(selection?.getTextContent() || '');
+ $insertNewBlockNodeAtSelection(codeBlock, true);
+ $openCodeEditorForNode(context.editor, codeBlock);
+ codeBlock.selectStart();
+ });
+ } else {
+ $openCodeEditorForNode(context.editor, codeBlock);
+ }
+ });
+ },
+ isActive(selection: BaseSelection|null): boolean {
+ return $selectionContainsNodeType(selection, $isCodeBlockNode);
+ }
+};
+
+export const editCodeBlock: EditorButtonDefinition = Object.assign({}, codeBlock, {
+ label: 'Edit code block',
+ icon: editIcon,
+});
+
+export const diagram: EditorButtonDefinition = {
+ label: 'Insert/edit drawing',
+ icon: diagramIcon,
+ action(context: EditorUiContext) {
+ context.editor.getEditorState().read(() => {
+ const selection = $getSelection();
+ const diagramNode = $getNodeFromSelection(context.lastSelection, $isDiagramNode) as (DiagramNode|null);
+ if (diagramNode === null) {
+ context.editor.update(() => {
+ const diagram = $createDiagramNode();
+ $insertNewBlockNodeAtSelection(diagram, true);
+ $openDrawingEditorForNode(context, diagram);
+ diagram.selectStart();
+ });
+ } else {
+ $openDrawingEditorForNode(context, diagramNode);
+ }
+ });
+ },
+ isActive(selection: BaseSelection|null): boolean {
+ return $selectionContainsNodeType(selection, $isDiagramNode);
+ }
+};
+
+
+export const details: EditorButtonDefinition = {
+ label: 'Insert collapsible block',
+ icon: detailsIcon,
+ action(context: EditorUiContext) {
+ context.editor.update(() => {
+ const selection = $getSelection();
+ const detailsNode = $createDetailsNode();
+ const selectionNodes = selection?.getNodes() || [];
+ const topLevels = selectionNodes.map(n => n.getTopLevelElement())
+ .filter(n => n !== null) as ElementNode[];
+ const uniqueTopLevels = [...new Set(topLevels)];
+
+ if (uniqueTopLevels.length > 0) {
+ uniqueTopLevels[0].insertAfter(detailsNode);
+ } else {
+ $getRoot().append(detailsNode);
+ }
+
+ for (const node of uniqueTopLevels) {
+ detailsNode.append(node);
+ }
+ });
+ },
+ isActive(selection: BaseSelection|null): boolean {
+ return $selectionContainsNodeType(selection, $isDetailsNode);
+ }
+}
\ No newline at end of file
diff --git a/resources/js/wysiwyg/ui/defaults/buttons/tables.ts b/resources/js/wysiwyg/ui/defaults/buttons/tables.ts
new file mode 100644
index 000000000..32fa49f88
--- /dev/null
+++ b/resources/js/wysiwyg/ui/defaults/buttons/tables.ts
@@ -0,0 +1,122 @@
+import {EditorBasicButtonDefinition, EditorButtonDefinition} from "../../framework/buttons";
+import tableIcon from "@icons/editor/table.svg";
+import deleteIcon from "@icons/editor/table-delete.svg";
+import deleteColumnIcon from "@icons/editor/table-delete-column.svg";
+import deleteRowIcon from "@icons/editor/table-delete-row.svg";
+import insertColumnAfterIcon from "@icons/editor/table-insert-column-after.svg";
+import insertColumnBeforeIcon from "@icons/editor/table-insert-column-before.svg";
+import insertRowAboveIcon from "@icons/editor/table-insert-row-above.svg";
+import insertRowBelowIcon from "@icons/editor/table-insert-row-below.svg";
+import {EditorUiContext} from "../../framework/core";
+import {$getBlockElementNodesInSelection, $getNodeFromSelection, $getParentOfType} from "../../../helpers";
+import {$getSelection} from "lexical";
+import {$isCustomTableNode, CustomTableNode} from "../../../nodes/custom-table";
+import {
+ $deleteTableColumn, $deleteTableColumn__EXPERIMENTAL,
+ $deleteTableRow__EXPERIMENTAL,
+ $getTableRowIndexFromTableCellNode, $insertTableColumn, $insertTableColumn__EXPERIMENTAL,
+ $insertTableRow, $insertTableRow__EXPERIMENTAL,
+ $isTableCellNode,
+ $isTableRowNode,
+ TableCellNode
+} from "@lexical/table";
+
+
+export const table: EditorBasicButtonDefinition = {
+ label: 'Table',
+ icon: tableIcon,
+};
+
+export const deleteTable: EditorButtonDefinition = {
+ label: 'Delete table',
+ icon: deleteIcon,
+ action(context: EditorUiContext) {
+ context.editor.update(() => {
+ const table = $getNodeFromSelection($getSelection(), $isCustomTableNode);
+ if (table) {
+ table.remove();
+ }
+ });
+ },
+ isActive() {
+ return false;
+ }
+};
+
+export const insertRowAbove: EditorButtonDefinition = {
+ label: 'Insert row above',
+ icon: insertRowAboveIcon,
+ action(context: EditorUiContext) {
+ context.editor.update(() => {
+ $insertTableRow__EXPERIMENTAL(false);
+ });
+ },
+ isActive() {
+ return false;
+ }
+};
+
+export const insertRowBelow: EditorButtonDefinition = {
+ label: 'Insert row below',
+ icon: insertRowBelowIcon,
+ action(context: EditorUiContext) {
+ context.editor.update(() => {
+ $insertTableRow__EXPERIMENTAL(true);
+ });
+ },
+ isActive() {
+ return false;
+ }
+};
+
+export const deleteRow: EditorButtonDefinition = {
+ label: 'Delete row',
+ icon: deleteRowIcon,
+ action(context: EditorUiContext) {
+ context.editor.update(() => {
+ $deleteTableRow__EXPERIMENTAL();
+ });
+ },
+ isActive() {
+ return false;
+ }
+};
+
+export const insertColumnBefore: EditorButtonDefinition = {
+ label: 'Insert column before',
+ icon: insertColumnBeforeIcon,
+ action(context: EditorUiContext) {
+ context.editor.update(() => {
+ $insertTableColumn__EXPERIMENTAL(false);
+ });
+ },
+ isActive() {
+ return false;
+ }
+};
+
+export const insertColumnAfter: EditorButtonDefinition = {
+ label: 'Insert column after',
+ icon: insertColumnAfterIcon,
+ action(context: EditorUiContext) {
+ context.editor.update(() => {
+ $insertTableColumn__EXPERIMENTAL(true);
+ });
+ },
+ isActive() {
+ return false;
+ }
+};
+
+export const deleteColumn: EditorButtonDefinition = {
+ label: 'Delete column',
+ icon: deleteColumnIcon,
+ action(context: EditorUiContext) {
+ context.editor.update(() => {
+ $deleteTableColumn__EXPERIMENTAL();
+ });
+ },
+ isActive() {
+ return false;
+ }
+};
\ No newline at end of file
diff --git a/resources/js/wysiwyg/ui/index.ts b/resources/js/wysiwyg/ui/index.ts
index f728ae48f..5dee6b62b 100644
--- a/resources/js/wysiwyg/ui/index.ts
+++ b/resources/js/wysiwyg/ui/index.ts
@@ -3,7 +3,7 @@ import {
getCodeToolbarContent,
getImageToolbarContent,
getLinkToolbarContent,
- getMainEditorFullToolbar
+ getMainEditorFullToolbar, getTableToolbarContent
} from "./toolbars";
import {EditorUIManager} from "./framework/manager";
import {image as imageFormDefinition, link as linkFormDefinition, source as sourceFormDefinition} from "./defaults/form-definitions";
@@ -61,6 +61,14 @@ export function buildEditorUI(container: HTMLElement, element: HTMLElement, scro
content: getCodeToolbarContent(),
});
+ manager.registerContextToolbar('table', {
+ selector: 'td,th',
+ content: getTableToolbarContent(),
+ displayTargetLocator(originalTarget: HTMLElement): HTMLElement {
+ return originalTarget.closest('table') as HTMLTableElement;
+ }
+ });
+
// Register image decorator listener
manager.registerDecoratorType('image', ImageDecorator);
manager.registerDecoratorType('code', CodeBlockDecorator);
diff --git a/resources/js/wysiwyg/ui/toolbars.ts b/resources/js/wysiwyg/ui/toolbars.ts
index f5eae6b21..5d40578a5 100644
--- a/resources/js/wysiwyg/ui/toolbars.ts
+++ b/resources/js/wysiwyg/ui/toolbars.ts
@@ -1,17 +1,4 @@
import {EditorButton} from "./framework/buttons";
-import {
- alignCenter, alignJustify,
- alignLeft,
- alignRight,
- blockquote, bold, bulletList, clearFormating, code, codeBlock,
- dangerCallout, details, diagram, editCodeBlock, fullscreen,
- h2, h3, h4, h5, highlightColor, horizontalRule, image,
- infoCallout, italic, link, numberList, paragraph,
- redo, source, strikethrough, subscript,
- successCallout, superscript, table, taskList, textColor, underline,
- undo, unlink,
- warningCallout
-} from "./defaults/button-definitions";
import {EditorContainerUiElement, EditorSimpleClassContainer, EditorUiElement} from "./framework/core";
import {el} from "../helpers";
import {EditorFormatMenu} from "./framework/blocks/format-menu";
@@ -21,6 +8,48 @@ import {EditorColorPicker} from "./framework/blocks/color-picker";
import {EditorTableCreator} from "./framework/blocks/table-creator";
import {EditorColorButton} from "./framework/blocks/color-button";
import {EditorOverflowContainer} from "./framework/blocks/overflow-container";
+import {
+ deleteColumn,
+ deleteRow,
+ deleteTable, insertColumnAfter,
+ insertColumnBefore,
+ insertRowAbove,
+ insertRowBelow,
+ table
+} from "./defaults/buttons/tables";
+import {fullscreen, redo, source, undo} from "./defaults/buttons/controls";
+import {
+ blockquote, dangerCallout,
+ h2,
+ h3,
+ h4,
+ h5,
+ infoCallout,
+ paragraph,
+ successCallout,
+ warningCallout
+} from "./defaults/buttons/block-formats";
+import {
+ bold, clearFormating, code,
+ highlightColor,
+ italic,
+ strikethrough, subscript,
+ superscript,
+ textColor,
+ underline
+} from "./defaults/buttons/inline-formats";
+import {alignCenter, alignJustify, alignLeft, alignRight} from "./defaults/buttons/alignments";
+import {bulletList, numberList, taskList} from "./defaults/buttons/lists";
+import {
+ codeBlock,
+ details,
+ diagram,
+ editCodeBlock,
+ horizontalRule,
+ image,
+ link,
+ unlink
+} from "./defaults/buttons/objects";
export function getMainEditorFullToolbar(): EditorContainerUiElement {
return new EditorSimpleClassContainer('editor-toolbar-main', [
@@ -129,4 +158,23 @@ export function getCodeToolbarContent(): EditorUiElement[] {
return [
new EditorButton(editCodeBlock),
];
+}
+
+export function getTableToolbarContent(): EditorUiElement[] {
+ return [
+ new EditorOverflowContainer(2, [
+ // Todo - Table properties
+ new EditorButton(deleteTable),
+ ]),
+ new EditorOverflowContainer(3, [
+ new EditorButton(insertRowAbove),
+ new EditorButton(insertRowBelow),
+ new EditorButton(deleteRow),
+ ]),
+ new EditorOverflowContainer(3, [
+ new EditorButton(insertColumnBefore),
+ new EditorButton(insertColumnAfter),
+ new EditorButton(deleteColumn),
+ ]),
+ ];
}
\ No newline at end of file