From 2f21955980d1474dc2d3393df85dc06190d8c55e Mon Sep 17 00:00:00 2001 From: Will Gutierrez Date: Wed, 3 Aug 2022 10:41:06 -0700 Subject: [PATCH 1/6] move devtools code to injected script --- packages/lexical-devtools/public/inject.js | 89 +++++++++++++ .../lexical-devtools/public/manifest.json | 3 +- .../lexical-devtools/src/background/index.ts | 23 +++- .../lexical-devtools/src/content/index.ts | 47 +++++++ .../lexical-devtools/src/devtools/index.ts | 122 ++---------------- packages/lexical-devtools/vite.config.js | 11 ++ 6 files changed, 180 insertions(+), 115 deletions(-) create mode 100644 packages/lexical-devtools/public/inject.js diff --git a/packages/lexical-devtools/public/inject.js b/packages/lexical-devtools/public/inject.js new file mode 100644 index 00000000000..ba79065a9bb --- /dev/null +++ b/packages/lexical-devtools/public/inject.js @@ -0,0 +1,89 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ +'use strict'; + +let editorDOMNode, editorKey; + +// the existing editorState.toJSON() does not contain lexicalKeys +// therefore, we have a custom serializeEditorState helper +const serializeEditorState = (editorState) => { + const nodeMap = Object.fromEntries(editorState._nodeMap); // convert from Map structure to JSON-friendly object + return {nodeMap}; +}; + +const postEditorState = (editorState) => { + const serializedEditorState = serializeEditorState(editorState); + const data = {editorState: serializedEditorState}; + document.dispatchEvent(new CustomEvent('editorStateUpdate', {detail: data})); +}; + +document.addEventListener('loadEditorState', function (e) { + // query document for Lexical editor instance. + // TODO: add support multiple Lexical editors within the same page + editorDOMNode = document.querySelectorAll('div[data-lexical-editor]')[0]; + const editor = editorDOMNode.__lexicalEditor; + editorKey = editorDOMNode.__lexicalEditor._key; // dynamically generated upon each pageload, we need this to find DOM nodes for highlighting + + const initialEditorState = editor.getEditorState(); + postEditorState(initialEditorState); + + editor.registerUpdateListener(({editorState}) => { + postEditorState(editorState); + }); +}); + +// append highlight overlay
to document +const highlightOverlay = document.createElement('div'); +highlightOverlay.style.position = 'absolute'; +highlightOverlay.style.background = 'rgba(119, 182, 255, 0.5)'; +highlightOverlay.style.border = '1px dashed #77b6ff'; +document.body.appendChild(highlightOverlay); + +// functions to highlight/dehighlight DOM nodes onHover of DevTools nodes +document.addEventListener('highlight', function (e) { + highlight(e.detail.lexicalKey); +}); + +document.addEventListener('dehighlight', function (e) { + dehighlight(); +}); + +// depth first search to find DOM node with lexicalKey +const findDOMNode = (node, targetKey) => { + // each DOM node has its Lexical key stored at a particular key eg. DOMNode[__lexicalKey_pzely] + const key = '__lexicalKey_' + editorKey; + + if (targetKey === 'root') { + return editorDOMNode; + } + + if (node[key] && node[key] === targetKey) { + return node; + } + + for (let i = 0; i < node.childNodes.length; i++) { + const child = findDOMNode(node.childNodes[i], targetKey); + if (child) return child; + } + + return null; +}; + +const highlight = (targetKey) => { + const node = findDOMNode(editorDOMNode, targetKey); + const {width, height, top, left} = node.getBoundingClientRect(); + highlightOverlay.style.width = width + 'px'; + highlightOverlay.style.height = height + 'px'; + highlightOverlay.style.top = top + window.scrollY + 'px'; + highlightOverlay.style.left = left + window.scrollX + 'px'; + highlightOverlay.style.display = 'block'; +}; + +const dehighlight = () => { + highlightOverlay.style.display = 'none'; +}; diff --git a/packages/lexical-devtools/public/manifest.json b/packages/lexical-devtools/public/manifest.json index b59c36663a9..a0dee516960 100644 --- a/packages/lexical-devtools/public/manifest.json +++ b/packages/lexical-devtools/public/manifest.json @@ -21,5 +21,6 @@ "js": ["src/content/index.js"] } ], - "permissions": ["activeTab"] + "permissions": ["activeTab"], + "web_accessible_resources": ["inject.js"] } diff --git a/packages/lexical-devtools/src/background/index.ts b/packages/lexical-devtools/src/background/index.ts index 03e23cd5e7e..54f54c0d7e0 100644 --- a/packages/lexical-devtools/src/background/index.ts +++ b/packages/lexical-devtools/src/background/index.ts @@ -29,16 +29,27 @@ chrome.runtime.onConnect.addListener((port: chrome.runtime.Port) => { } if (message.name === 'init' && message.type === 'FROM_APP') { + tabsToPorts[tabId] = tabsToPorts[tabId] ? tabsToPorts[tabId] : {}; tabsToPorts[message.tabId].reactPort = port; return; } if (message.name === 'init' && message.type === 'FROM_CONTENT') { - tabsToPorts[tabId] = {}; + tabsToPorts[tabId] = tabsToPorts[tabId] ? tabsToPorts[tabId] : {}; tabsToPorts[tabId].contentScriptPort = port; return; } + // initial editorState requested from devtools panel + if (message.name === 'init' && message.type === 'FROM_DEVTOOLS') { + const contentScriptPort = tabsToPorts[tabId].contentScriptPort; + if (contentScriptPort) { + contentScriptPort.postMessage({ + name: 'loadEditorState', + }); + } + } + if (message.name === 'editor-update') { const reactPort = tabsToPorts[tabId].reactPort; if (reactPort) { @@ -47,5 +58,15 @@ chrome.runtime.onConnect.addListener((port: chrome.runtime.Port) => { }); } } + + if (message.name === 'highlight' || message.name === 'dehighlight') { + const contentScriptPort = tabsToPorts[tabId].contentScriptPort; + if (contentScriptPort) { + contentScriptPort.postMessage({ + lexicalKey: message.lexicalKey ? message.lexicalKey : null, + name: message.name, + }); + } + } }); }); diff --git a/packages/lexical-devtools/src/content/index.ts b/packages/lexical-devtools/src/content/index.ts index 4f4da0ec370..8220934caa2 100644 --- a/packages/lexical-devtools/src/content/index.ts +++ b/packages/lexical-devtools/src/content/index.ts @@ -5,6 +5,22 @@ * LICENSE file in the root directory of this source tree. * */ +import {IS_FIREFOX} from 'shared/environment'; + +// const CAN_USE_DOM = +// typeof window !== 'undefined' && +// typeof window.document !== 'undefined' && +// typeof window.document.createElement !== 'undefined'; +// const IS_FIREFOX = +// CAN_USE_DOM && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent); + +// for security reasons, content scripts cannot read Lexical's changes to the DOM +// in order to access the editorState, we inject this script directly into the page +const script = document.createElement('script'); +script.src = chrome.runtime.getURL('inject.js'); +document.documentElement.appendChild(script); +if (script.parentNode) script.parentNode.removeChild(script); + const port = chrome.runtime.connect(); port.postMessage({ @@ -31,3 +47,34 @@ window.addEventListener('message', function (event) { }); } }); + +document.addEventListener('editorStateUpdate', function (e) { + port.postMessage({ + editorState: e.detail.editorState, + name: 'editor-update', + type: 'FROM_CONTENT', + }); +}); + +port.onMessage.addListener((message) => { + if (message.name === 'highlight') { + const data = {lexicalKey: message.lexicalKey}; + const detail = IS_FIREFOX + ? // eslint-disable-next-line no-undef + cloneInto(data, document.defaultView) // see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts#cloneinto + : data; + document.dispatchEvent( + new CustomEvent('highlight', { + detail, + }), + ); + } + + if (message.name === 'dehighlight') { + document.dispatchEvent(new CustomEvent('dehighlight')); + } + + if (message.name === 'loadEditorState') { + document.dispatchEvent(new CustomEvent('loadEditorState')); + } +}); diff --git a/packages/lexical-devtools/src/devtools/index.ts b/packages/lexical-devtools/src/devtools/index.ts index 4fdb42a4a86..52d06154ae8 100644 --- a/packages/lexical-devtools/src/devtools/index.ts +++ b/packages/lexical-devtools/src/devtools/index.ts @@ -5,6 +5,8 @@ * LICENSE file in the root directory of this source tree. * */ +const port = chrome.runtime.connect(); + // Create the panel which appears within the browser's DevTools, loading the Lexical DevTools App within index.html. chrome.devtools.panels.create( 'Lexical', @@ -16,118 +18,12 @@ chrome.devtools.panels.create( }, ); -chrome.runtime.onConnect.addListener((port) => { - port.onMessage.addListener((message) => { - if (message.name === 'highlight') { - chrome.devtools.inspectedWindow.eval(` - highlight('${message.lexicalKey}'); - `); - } - - if (message.name === 'dehighlight') { - chrome.devtools.inspectedWindow.eval(` - dehighlight(); - `); - } - }); -}); - function handleShown() { - // Security concerns related to chrome.devtools.inspectedWindow.eval(): - // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts - // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/devtools/inspectedWindow/eval - - // query document for Lexical editor instance. - // TODO: add support multiple Lexical editors within the same page - // lexicalKey is attached to each DOM node like so: DOMNode[__lexicalKey_{editorKey}] - chrome.devtools.inspectedWindow.eval(` - const editorDOMNode = document.querySelectorAll( - 'div[data-lexical-editor]', - )[0]; - const editor = editorDOMNode.__lexicalEditor; - const editorKey = editorDOMNode.__lexicalEditor._key; - const lexicalKey = '__lexicalKey_' + editorKey; - `); - - // depth first search to find DOM node with lexicalKey - chrome.devtools.inspectedWindow.eval(` - const findDOMNode = (node, targetKey) => { - if (targetKey === 'root') { - return editorDOMNode; - } - - if (node[lexicalKey] && node[lexicalKey] === targetKey) { - return node; - } - - for (let i = 0; i < node.childNodes.length; i++) { - const child = findDOMNode(node.childNodes[i], targetKey); - if (child) return child; - } - - return null; - }; - `); - - // functions to highlight/dehighlight DOM nodes onHover of DevTools nodes - chrome.devtools.inspectedWindow.eval(` - const highlight = (lexicalKey) => { - const node = findDOMNode(editorDOMNode, lexicalKey); - const {width, height, top, left} = node.getBoundingClientRect(); - highlightOverlay.style.width = width + 'px'; - highlightOverlay.style.height = height + 'px'; - highlightOverlay.style.top = top + window.scrollY + 'px'; - highlightOverlay.style.left = left + window.scrollX + 'px'; - highlightOverlay.style.display = 'block'; - }; - - const dehighlight = () => { - highlightOverlay.style.display = 'none'; - }; - `); - - // append highlight overlay
to document - chrome.devtools.inspectedWindow.eval(` - const highlightOverlay = document.createElement('div'); - highlightOverlay.style.position = 'absolute'; - highlightOverlay.style.background = 'rgba(119, 182, 255, 0.5)'; - highlightOverlay.style.border = '1px dashed #77b6ff'; - document.body.appendChild(highlightOverlay); - `); - - // send initial editorState to devtools app through window.postMessage. - // the existing editorState.toJSON() does not contain lexicalKey - // therefore, we have a custom serializeEditorState helper - chrome.devtools.inspectedWindow.eval(` - const initialEditorState = editor.getEditorState(); - - const serializeEditorState = (editorState) => { - const nodeMap = Object.fromEntries(editorState._nodeMap); - return {nodeMap}; - }; - - window.postMessage( - { - editorState: serializeEditorState(initialEditorState), - name: 'editor-update', - type: 'FROM_PAGE', - }, - '*', - ); - `); - - // Attach a registerUpdateListener within the window to subscribe to changes in editorState. - // After the initial registration, all editorState updates are done via browser.runtime.onConnect & window.postMessage - chrome.devtools.inspectedWindow.eval(` - editor.registerUpdateListener(({editorState}) => { - window.postMessage( - { - editorState: serializeEditorState(editorState), - name: 'editor-update', - type: 'FROM_PAGE', - }, - '*', - ); - }); - `); + // init message goes → background script → content script + // content script handles initial load of editorState + port.postMessage({ + name: 'init', + tabId: chrome.devtools.inspectedWindow.tabId, + type: 'FROM_DEVTOOLS', + }); } diff --git a/packages/lexical-devtools/vite.config.js b/packages/lexical-devtools/vite.config.js index e128e620db2..aad513e838b 100644 --- a/packages/lexical-devtools/vite.config.js +++ b/packages/lexical-devtools/vite.config.js @@ -1,12 +1,23 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import { resolve } from 'path'; +import path from 'path'; const root = resolve(__dirname, 'src'); +const moduleResolution = [ + { + find: 'shared', + replacement: path.resolve('../shared/src'), + } +]; + // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], + resolve: { + alias: moduleResolution, + }, build: { outDir: 'build', rollupOptions: { From 8fdc069cb00a54035d0e6eef2785f1d82d5ee5e7 Mon Sep 17 00:00:00 2001 From: Will Gutierrez Date: Fri, 5 Aug 2022 18:32:06 -0700 Subject: [PATCH 2/6] correct typescript errors --- .../lexical-devtools/src/content/index.ts | 13 ++--- .../{public/inject.js => src/inject/index.ts} | 57 ++++++++++++------- packages/lexical-devtools/types.ts | 9 +++ packages/lexical-devtools/vite.config.js | 1 + 4 files changed, 53 insertions(+), 27 deletions(-) rename packages/lexical-devtools/{public/inject.js => src/inject/index.ts} (58%) diff --git a/packages/lexical-devtools/src/content/index.ts b/packages/lexical-devtools/src/content/index.ts index 8220934caa2..8d82211bca8 100644 --- a/packages/lexical-devtools/src/content/index.ts +++ b/packages/lexical-devtools/src/content/index.ts @@ -7,17 +7,16 @@ */ import {IS_FIREFOX} from 'shared/environment'; -// const CAN_USE_DOM = -// typeof window !== 'undefined' && -// typeof window.document !== 'undefined' && -// typeof window.document.createElement !== 'undefined'; -// const IS_FIREFOX = -// CAN_USE_DOM && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent); +declare global { + interface DocumentEventMap { + editorStateUpdate: CustomEvent; + } +} // for security reasons, content scripts cannot read Lexical's changes to the DOM // in order to access the editorState, we inject this script directly into the page const script = document.createElement('script'); -script.src = chrome.runtime.getURL('inject.js'); +script.src = chrome.runtime.getURL('./index/inject.js'); document.documentElement.appendChild(script); if (script.parentNode) script.parentNode.removeChild(script); diff --git a/packages/lexical-devtools/public/inject.js b/packages/lexical-devtools/src/inject/index.ts similarity index 58% rename from packages/lexical-devtools/public/inject.js rename to packages/lexical-devtools/src/inject/index.ts index ba79065a9bb..37012fedaf1 100644 --- a/packages/lexical-devtools/public/inject.js +++ b/packages/lexical-devtools/src/inject/index.ts @@ -5,18 +5,25 @@ * LICENSE file in the root directory of this source tree. * */ -'use strict'; +import {EditorState} from 'lexical'; +import {LexicalHTMLElement, LexicalKey} from 'packages/lexical-devtools/types'; -let editorDOMNode, editorKey; +declare global { + interface DocumentEventMap { + highlight: CustomEvent; + } +} + +let editorDOMNode: LexicalHTMLElement | null, editorKey: string | null; // the existing editorState.toJSON() does not contain lexicalKeys // therefore, we have a custom serializeEditorState helper -const serializeEditorState = (editorState) => { +const serializeEditorState = (editorState: EditorState) => { const nodeMap = Object.fromEntries(editorState._nodeMap); // convert from Map structure to JSON-friendly object return {nodeMap}; }; -const postEditorState = (editorState) => { +const postEditorState = (editorState: EditorState) => { const serializedEditorState = serializeEditorState(editorState); const data = {editorState: serializedEditorState}; document.dispatchEvent(new CustomEvent('editorStateUpdate', {detail: data})); @@ -25,7 +32,9 @@ const postEditorState = (editorState) => { document.addEventListener('loadEditorState', function (e) { // query document for Lexical editor instance. // TODO: add support multiple Lexical editors within the same page - editorDOMNode = document.querySelectorAll('div[data-lexical-editor]')[0]; + editorDOMNode = document.querySelectorAll( + 'div[data-lexical-editor]', + )[0] as LexicalHTMLElement; const editor = editorDOMNode.__lexicalEditor; editorKey = editorDOMNode.__lexicalEditor._key; // dynamically generated upon each pageload, we need this to find DOM nodes for highlighting @@ -45,18 +54,21 @@ highlightOverlay.style.border = '1px dashed #77b6ff'; document.body.appendChild(highlightOverlay); // functions to highlight/dehighlight DOM nodes onHover of DevTools nodes -document.addEventListener('highlight', function (e) { - highlight(e.detail.lexicalKey); +document.addEventListener('highlight', (evt: CustomEvent) => { + highlight(evt.detail.lexicalKey); }); -document.addEventListener('dehighlight', function (e) { +document.addEventListener('dehighlight', function () { dehighlight(); }); // depth first search to find DOM node with lexicalKey -const findDOMNode = (node, targetKey) => { +const findDOMNode = ( + node: LexicalHTMLElement, + targetKey: string, +): LexicalHTMLElement | null => { // each DOM node has its Lexical key stored at a particular key eg. DOMNode[__lexicalKey_pzely] - const key = '__lexicalKey_' + editorKey; + const key = ('__lexicalKey_' + editorKey) as LexicalKey; if (targetKey === 'root') { return editorDOMNode; @@ -67,21 +79,26 @@ const findDOMNode = (node, targetKey) => { } for (let i = 0; i < node.childNodes.length; i++) { - const child = findDOMNode(node.childNodes[i], targetKey); - if (child) return child; + const child = node.childNodes[i] as LexicalHTMLElement; + const childResults = findDOMNode(child, targetKey); + if (childResults) return child; } return null; }; -const highlight = (targetKey) => { - const node = findDOMNode(editorDOMNode, targetKey); - const {width, height, top, left} = node.getBoundingClientRect(); - highlightOverlay.style.width = width + 'px'; - highlightOverlay.style.height = height + 'px'; - highlightOverlay.style.top = top + window.scrollY + 'px'; - highlightOverlay.style.left = left + window.scrollX + 'px'; - highlightOverlay.style.display = 'block'; +const highlight = (targetKey: string) => { + if (editorDOMNode) { + const node = findDOMNode(editorDOMNode, targetKey); + if (node) { + const {width, height, top, left} = node.getBoundingClientRect(); + highlightOverlay.style.width = width + 'px'; + highlightOverlay.style.height = height + 'px'; + highlightOverlay.style.top = top + window.scrollY + 'px'; + highlightOverlay.style.left = left + window.scrollX + 'px'; + highlightOverlay.style.display = 'block'; + } + } }; const dehighlight = () => { diff --git a/packages/lexical-devtools/types.ts b/packages/lexical-devtools/types.ts index 70766502e68..a4fc595d744 100644 --- a/packages/lexical-devtools/types.ts +++ b/packages/lexical-devtools/types.ts @@ -5,6 +5,8 @@ * LICENSE file in the root directory of this source tree. * */ +import {LexicalEditor} from 'lexical'; + export interface DevToolsTree { [key: string]: DevToolsNode; } @@ -21,3 +23,10 @@ export interface DevToolsNode { lexicalKey: string; monospaceWidth: string; } + +export type LexicalKey = `__lexicalKey_${string}`; + +export interface LexicalHTMLElement extends HTMLElement { + [key: LexicalKey]: string; + __lexicalEditor: LexicalEditor; +} diff --git a/packages/lexical-devtools/vite.config.js b/packages/lexical-devtools/vite.config.js index aad513e838b..043acb26123 100644 --- a/packages/lexical-devtools/vite.config.js +++ b/packages/lexical-devtools/vite.config.js @@ -25,6 +25,7 @@ export default defineConfig({ background: resolve(root, 'background', 'index.ts'), content: resolve(root, 'content', 'index.ts'), devtools: resolve(root, 'devtools', 'index.html'), + inject: resolve(root, 'inject', 'index.ts'), panel: resolve(root, 'panel', 'index.html'), popup: resolve(root, 'popup', 'index.html') }, From 05cef8bd145524f9cd1e5855abc5193ac1ac3ad4 Mon Sep 17 00:00:00 2001 From: Will Gutierrez Date: Sat, 6 Aug 2022 14:40:59 -0700 Subject: [PATCH 3/6] write cloneInto type --- packages/lexical-devtools/src/content/index.ts | 4 +++- packages/lexical-devtools/types.ts | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/lexical-devtools/src/content/index.ts b/packages/lexical-devtools/src/content/index.ts index 8d82211bca8..9afde4dee16 100644 --- a/packages/lexical-devtools/src/content/index.ts +++ b/packages/lexical-devtools/src/content/index.ts @@ -5,6 +5,8 @@ * LICENSE file in the root directory of this source tree. * */ +import type {CloneInto} from '../../types'; + import {IS_FIREFOX} from 'shared/environment'; declare global { @@ -60,7 +62,7 @@ port.onMessage.addListener((message) => { const data = {lexicalKey: message.lexicalKey}; const detail = IS_FIREFOX ? // eslint-disable-next-line no-undef - cloneInto(data, document.defaultView) // see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts#cloneinto + (cloneInto(data, document.defaultView) as CloneInto) // see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts#cloneinto : data; document.dispatchEvent( new CustomEvent('highlight', { diff --git a/packages/lexical-devtools/types.ts b/packages/lexical-devtools/types.ts index a4fc595d744..12c753b1fa8 100644 --- a/packages/lexical-devtools/types.ts +++ b/packages/lexical-devtools/types.ts @@ -30,3 +30,8 @@ export interface LexicalHTMLElement extends HTMLElement { [key: LexicalKey]: string; __lexicalEditor: LexicalEditor; } + +export type cloneInto = ( + arg: {data: {lexicalKey: string}}, + arg2: WindowProxy, +) => {data: {lexicalKey: string}}; From 5fc5b28f8a941f76ceee347cb14f65b6f0c60684 Mon Sep 17 00:00:00 2001 From: Will Gutierrez Date: Sat, 6 Aug 2022 15:31:25 -0700 Subject: [PATCH 4/6] add injected script to web_accessible_resources --- packages/lexical-devtools/public/manifest.json | 2 +- packages/lexical-devtools/src/content/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/lexical-devtools/public/manifest.json b/packages/lexical-devtools/public/manifest.json index a0dee516960..c185d81489c 100644 --- a/packages/lexical-devtools/public/manifest.json +++ b/packages/lexical-devtools/public/manifest.json @@ -22,5 +22,5 @@ } ], "permissions": ["activeTab"], - "web_accessible_resources": ["inject.js"] + "web_accessible_resources": ["src/inject/index.js"] } diff --git a/packages/lexical-devtools/src/content/index.ts b/packages/lexical-devtools/src/content/index.ts index 9afde4dee16..e2b92cb748e 100644 --- a/packages/lexical-devtools/src/content/index.ts +++ b/packages/lexical-devtools/src/content/index.ts @@ -18,7 +18,7 @@ declare global { // for security reasons, content scripts cannot read Lexical's changes to the DOM // in order to access the editorState, we inject this script directly into the page const script = document.createElement('script'); -script.src = chrome.runtime.getURL('./index/inject.js'); +script.src = chrome.runtime.getURL('src/inject/index.js'); document.documentElement.appendChild(script); if (script.parentNode) script.parentNode.removeChild(script); From 2f3c540de2b9b8c478d2afd6f3c93757acc5df45 Mon Sep 17 00:00:00 2001 From: Will Gutierrez Date: Mon, 8 Aug 2022 16:46:58 -0700 Subject: [PATCH 5/6] fix highlighting bug --- packages/lexical-devtools/src/inject/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lexical-devtools/src/inject/index.ts b/packages/lexical-devtools/src/inject/index.ts index 37012fedaf1..3c9aea79f5e 100644 --- a/packages/lexical-devtools/src/inject/index.ts +++ b/packages/lexical-devtools/src/inject/index.ts @@ -81,7 +81,7 @@ const findDOMNode = ( for (let i = 0; i < node.childNodes.length; i++) { const child = node.childNodes[i] as LexicalHTMLElement; const childResults = findDOMNode(child, targetKey); - if (childResults) return child; + if (childResults) return childResults; } return null; From 81e0e1ab854ace27714b9688606bd7f25730c4d5 Mon Sep 17 00:00:00 2001 From: Will Gutierrez Date: Mon, 8 Aug 2022 17:09:13 -0700 Subject: [PATCH 6/6] wrapper function for cloneInto --- .../lexical-devtools/src/content/index.ts | 22 ++++++++++++++----- packages/lexical-devtools/src/inject/index.ts | 6 ----- packages/lexical-devtools/types.ts | 6 ++--- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/packages/lexical-devtools/src/content/index.ts b/packages/lexical-devtools/src/content/index.ts index e2b92cb748e..26b7ed37210 100644 --- a/packages/lexical-devtools/src/content/index.ts +++ b/packages/lexical-devtools/src/content/index.ts @@ -12,6 +12,7 @@ import {IS_FIREFOX} from 'shared/environment'; declare global { interface DocumentEventMap { editorStateUpdate: CustomEvent; + highlight: CustomEvent; } } @@ -57,13 +58,24 @@ document.addEventListener('editorStateUpdate', function (e) { }); }); +function getCloneInto(): CloneInto | null { + // @ts-ignore + if (typeof globalThis.cloneInto === 'function') { + // @ts-ignore + return globalThis.cloneInto; + } + return null; +} + +const cloneInto = getCloneInto(); + port.onMessage.addListener((message) => { if (message.name === 'highlight') { - const data = {lexicalKey: message.lexicalKey}; - const detail = IS_FIREFOX - ? // eslint-disable-next-line no-undef - (cloneInto(data, document.defaultView) as CloneInto) // see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts#cloneinto - : data; + const data = {lexicalKey: message.lexicalKey as string}; + const detail = + IS_FIREFOX && cloneInto && document && document.defaultView + ? cloneInto(data, document.defaultView) + : data; document.dispatchEvent( new CustomEvent('highlight', { detail, diff --git a/packages/lexical-devtools/src/inject/index.ts b/packages/lexical-devtools/src/inject/index.ts index 3c9aea79f5e..37fdef9ff94 100644 --- a/packages/lexical-devtools/src/inject/index.ts +++ b/packages/lexical-devtools/src/inject/index.ts @@ -8,12 +8,6 @@ import {EditorState} from 'lexical'; import {LexicalHTMLElement, LexicalKey} from 'packages/lexical-devtools/types'; -declare global { - interface DocumentEventMap { - highlight: CustomEvent; - } -} - let editorDOMNode: LexicalHTMLElement | null, editorKey: string | null; // the existing editorState.toJSON() does not contain lexicalKeys diff --git a/packages/lexical-devtools/types.ts b/packages/lexical-devtools/types.ts index 12c753b1fa8..5f71b849e02 100644 --- a/packages/lexical-devtools/types.ts +++ b/packages/lexical-devtools/types.ts @@ -31,7 +31,7 @@ export interface LexicalHTMLElement extends HTMLElement { __lexicalEditor: LexicalEditor; } -export type cloneInto = ( - arg: {data: {lexicalKey: string}}, - arg2: WindowProxy, +export type CloneInto = ( + arg: {lexicalKey: string}, + arg2: Window, ) => {data: {lexicalKey: string}};