diff --git a/lang/en/editor.php b/lang/en/editor.php index 752c6f3f7..0d250e9a7 100644 --- a/lang/en/editor.php +++ b/lang/en/editor.php @@ -48,6 +48,7 @@ return [ 'superscript' => 'Superscript', 'subscript' => 'Subscript', 'text_color' => 'Text color', + 'highlight_color' => 'Highlight color', 'custom_color' => 'Custom color', 'remove_color' => 'Remove color', 'background_color' => 'Background color', diff --git a/resources/js/wysiwyg/index.ts b/resources/js/wysiwyg/index.ts index f572f9de5..e01b4e8f4 100644 --- a/resources/js/wysiwyg/index.ts +++ b/resources/js/wysiwyg/index.ts @@ -19,6 +19,7 @@ import {contextToolbars, getBasicEditorToolbar, getMainEditorFullToolbar} from " import {modals} from "./ui/defaults/modals"; import {CodeBlockDecorator} from "./ui/decorators/code-block"; import {DiagramDecorator} from "./ui/decorators/diagram"; +import {registerMouseHandling} from "./services/mouse-handling"; const theme = { text: { @@ -51,6 +52,7 @@ export function createPageEditorInstance(container: HTMLElement, htmlContent: st registerHistory(editor, createEmptyHistoryState(), 300), registerShortcuts(context), registerKeyboardHandling(context), + registerMouseHandling(context), registerTableResizer(editor, context.scrollDOM), registerTableSelectionHandler(editor), registerTaskListHandler(editor, context.editorDOM), diff --git a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts index e18ef9756..fd87877ee 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts @@ -848,4 +848,20 @@ export function dispatchKeydownEventForSelectedNode(editor: LexicalEditor, key: dispatchKeydownEventForNode(node, editor, key); } }); +} + +export function dispatchEditorMouseClick(editor: LexicalEditor, clientX: number, clientY: number) { + const dom = editor.getRootElement(); + if (!dom) { + return; + } + + const event = new MouseEvent('click', { + clientX: clientX, + clientY: clientY, + bubbles: true, + cancelable: true, + }); + dom?.dispatchEvent(event); + editor.commitUpdates(); } \ No newline at end of file diff --git a/resources/js/wysiwyg/lexical/html/__tests__/unit/LexicalHtml.test.ts b/resources/js/wysiwyg/lexical/html/__tests__/unit/LexicalHtml.test.ts index e5064121a..b466ee34a 100644 --- a/resources/js/wysiwyg/lexical/html/__tests__/unit/LexicalHtml.test.ts +++ b/resources/js/wysiwyg/lexical/html/__tests__/unit/LexicalHtml.test.ts @@ -146,7 +146,7 @@ describe('HTML', () => { }); expect(html).toBe( - '

Hello

World

', + '

Hello

\n

World

', ); }); diff --git a/resources/js/wysiwyg/lexical/html/index.ts b/resources/js/wysiwyg/lexical/html/index.ts index 5018e10b4..de5e53bb8 100644 --- a/resources/js/wysiwyg/lexical/html/index.ts +++ b/resources/js/wysiwyg/lexical/html/index.ts @@ -85,7 +85,18 @@ export function $generateHtmlFromNodes( $appendNodesToHTML(editor, topLevelNode, container, selection); } - return container.innerHTML; + const nodeCode = []; + for (const node of container.childNodes) { + if ("outerHTML" in node) { + nodeCode.push(node.outerHTML) + } else { + const wrap = document.createElement('div'); + wrap.appendChild(node.cloneNode(true)); + nodeCode.push(wrap.innerHTML); + } + } + + return nodeCode.join('\n'); } function $appendNodesToHTML( diff --git a/resources/js/wysiwyg/lexical/link/__tests__/unit/LexicalAutoLinkNode.test.ts b/resources/js/wysiwyg/lexical/link/__tests__/unit/LexicalAutoLinkNode.test.ts index 0f3513682..1103f73d3 100644 --- a/resources/js/wysiwyg/lexical/link/__tests__/unit/LexicalAutoLinkNode.test.ts +++ b/resources/js/wysiwyg/lexical/link/__tests__/unit/LexicalAutoLinkNode.test.ts @@ -273,18 +273,6 @@ describe('LexicalAutoAutoLinkNode tests', () => { }); }); - test('AutoLinkNode.createDOM() sanitizes javascript: URLs', async () => { - const {editor} = testEnv; - - await editor.update(() => { - // eslint-disable-next-line no-script-url - const autoLinkNode = new AutoLinkNode('javascript:alert(0)'); - expect(autoLinkNode.createDOM(editorConfig).outerHTML).toBe( - '', - ); - }); - }); - test('AutoLinkNode.updateDOM()', async () => { const {editor} = testEnv; diff --git a/resources/js/wysiwyg/lexical/link/__tests__/unit/LexicalLinkNode.test.ts b/resources/js/wysiwyg/lexical/link/__tests__/unit/LexicalLinkNode.test.ts index 1aff91863..c50450302 100644 --- a/resources/js/wysiwyg/lexical/link/__tests__/unit/LexicalLinkNode.test.ts +++ b/resources/js/wysiwyg/lexical/link/__tests__/unit/LexicalLinkNode.test.ts @@ -218,18 +218,6 @@ describe('LexicalLinkNode tests', () => { }); }); - test('LinkNode.createDOM() sanitizes javascript: URLs', async () => { - const {editor} = testEnv; - - await editor.update(() => { - // eslint-disable-next-line no-script-url - const linkNode = new LinkNode('javascript:alert(0)'); - expect(linkNode.createDOM(editorConfig).outerHTML).toBe( - '', - ); - }); - }); - test('LinkNode.updateDOM()', async () => { const {editor} = testEnv; diff --git a/resources/js/wysiwyg/lexical/link/index.ts b/resources/js/wysiwyg/lexical/link/index.ts index 884fe9153..336bb1546 100644 --- a/resources/js/wysiwyg/lexical/link/index.ts +++ b/resources/js/wysiwyg/lexical/link/index.ts @@ -48,14 +48,6 @@ export type SerializedLinkNode = Spread< type LinkHTMLElementType = HTMLAnchorElement | HTMLSpanElement; -const SUPPORTED_URL_PROTOCOLS = new Set([ - 'http:', - 'https:', - 'mailto:', - 'sms:', - 'tel:', -]); - /** @noInheritDoc */ export class LinkNode extends ElementNode { /** @internal */ @@ -90,7 +82,7 @@ export class LinkNode extends ElementNode { createDOM(config: EditorConfig): LinkHTMLElementType { const element = document.createElement('a'); - element.href = this.sanitizeUrl(this.__url); + element.href = this.__url; if (this.__target !== null) { element.target = this.__target; } @@ -166,19 +158,6 @@ export class LinkNode extends ElementNode { return node; } - sanitizeUrl(url: string): string { - try { - const parsedUrl = new URL(url); - // eslint-disable-next-line no-script-url - if (!SUPPORTED_URL_PROTOCOLS.has(parsedUrl.protocol)) { - return 'about:blank'; - } - } catch { - return url; - } - return url; - } - exportJSON(): SerializedLinkNode | SerializedAutoLinkNode { return { ...super.exportJSON(), diff --git a/resources/js/wysiwyg/lexical/table/LexicalTableCellNode.ts b/resources/js/wysiwyg/lexical/table/LexicalTableCellNode.ts index 1fc6b42bb..1c9d7ecf6 100644 --- a/resources/js/wysiwyg/lexical/table/LexicalTableCellNode.ts +++ b/resources/js/wysiwyg/lexical/table/LexicalTableCellNode.ts @@ -353,10 +353,17 @@ export function $convertTableCellNodeElement( const hasUnderlineTextDecoration = textDecoration.includes('underline'); if (domNode instanceof HTMLElement) { - tableCellNode.setStyles(extractStyleMapFromElement(domNode)); + const styleMap = extractStyleMapFromElement(domNode); + styleMap.delete('background-color'); + tableCellNode.setStyles(styleMap); tableCellNode.setAlignment(extractAlignmentFromElement(domNode)); } + const background = style.backgroundColor || null; + if (background) { + tableCellNode.setBackgroundColor(background); + } + return { after: (childLexicalNodes) => { if (childLexicalNodes.length === 0) { diff --git a/resources/js/wysiwyg/lexical/utils/__tests__/unit/LexicalUtilsSplitNode.test.ts b/resources/js/wysiwyg/lexical/utils/__tests__/unit/LexicalUtilsSplitNode.test.ts index 54cd8b54f..6a415d008 100644 --- a/resources/js/wysiwyg/lexical/utils/__tests__/unit/LexicalUtilsSplitNode.test.ts +++ b/resources/js/wysiwyg/lexical/utils/__tests__/unit/LexicalUtilsSplitNode.test.ts @@ -38,7 +38,7 @@ describe('LexicalUtils#splitNode', () => { { _: 'split paragraph in between two text nodes', expectedHtml: - '

Hello

world

', + '

Hello

\n

world

', initialHtml: '

Helloworld

', splitOffset: 1, splitPath: [0], @@ -46,7 +46,7 @@ describe('LexicalUtils#splitNode', () => { { _: 'split paragraph before the first text node', expectedHtml: - '


Helloworld

', + '


\n

Helloworld

', initialHtml: '

Helloworld

', splitOffset: 0, splitPath: [0], @@ -54,7 +54,7 @@ describe('LexicalUtils#splitNode', () => { { _: 'split paragraph after the last text node', expectedHtml: - '

Helloworld


', + '

Helloworld

\n


', initialHtml: '

Helloworld

', splitOffset: 2, // Any offset that is higher than children size splitPath: [0], @@ -62,7 +62,7 @@ describe('LexicalUtils#splitNode', () => { { _: 'split list items between two text nodes', expectedHtml: - '' + + '\n' + '', initialHtml: '', splitOffset: 1, // Any offset that is higher than children size @@ -71,7 +71,7 @@ describe('LexicalUtils#splitNode', () => { { _: 'split list items before the first text node', expectedHtml: - '' + + '\n' + '', initialHtml: '', splitOffset: 0, // Any offset that is higher than children size @@ -83,7 +83,7 @@ describe('LexicalUtils#splitNode', () => { '' + + '\n' + '\n' + + '\n' + '