diff --git a/packages/lexical-devtools/README.md b/packages/lexical-devtools/README.md index 7e1b7b579d8..74d27cb9925 100644 --- a/packages/lexical-devtools/README.md +++ b/packages/lexical-devtools/README.md @@ -16,6 +16,7 @@ $ npm run dev - Extension activity log: [chrome://extensions/?activity=eddfjidloofnnmloonifcjkpmfmlblab](chrome://extensions/?activity=eddfjidloofnnmloonifcjkpmfmlblab) - Status of ServiceWorkers: [chrome://serviceworker-internals/?devtools](chrome://serviceworker-internals/?devtools) - WXT Framework debugging: `DEBUG_WXT=1 npm run dev` +- If you detach the Dev Tools in a separate window, and press `Cmd+Option+I` while Dev Tools window is focused, you will invoke the Dev Tools for the Dev Tools window. ## Design diff --git a/packages/lexical-devtools/src/components/EditorsRefreshCTA.tsx b/packages/lexical-devtools/src/components/EditorsRefreshCTA.tsx index 22bb8d1373b..01986bf2b88 100644 --- a/packages/lexical-devtools/src/components/EditorsRefreshCTA.tsx +++ b/packages/lexical-devtools/src/components/EditorsRefreshCTA.tsx @@ -30,7 +30,7 @@ function EditorsRefreshCTA({tabID, setErrorMessage}: Props) { ); injectedPegasusService - .refreshLexicalEditorsForTabID() + .refreshLexicalEditors() .catch((err) => { setErrorMessage(err.message); console.error(err); diff --git a/packages/lexical-devtools/src/entrypoints/background/ActionIconWatchdog.ts b/packages/lexical-devtools/src/entrypoints/background/ActionIconWatchdog.ts new file mode 100644 index 00000000000..124d67f54b7 --- /dev/null +++ b/packages/lexical-devtools/src/entrypoints/background/ActionIconWatchdog.ts @@ -0,0 +1,100 @@ +/** + * 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. + * + */ +import type {Tabs} from 'wxt/browser'; +import type {StoreApi} from 'zustand'; + +import {IS_FIREFOX} from 'shared/environment'; + +import {ExtensionState} from '../../store'; + +export default class ActionIconWatchdog { + private constructor( + private readonly extensionStore: StoreApi, + ) {} + + static async start(store: StoreApi) { + return new ActionIconWatchdog(store).init(); + } + + async init() { + const tabs = await browser.tabs.query({}); + await Promise.all( + tabs.map(this.checkAndHandleRestrictedPageIfSo.bind(this)), + ); + + browser.tabs.onCreated.addListener((tab) => { + this.checkAndHandleRestrictedPageIfSo(tab); + }); + + // Listen to URL changes on the active tab and update the DevTools icon. + browser.tabs.onUpdated.addListener(this.handleTabsUpdatedEvent.bind(this)); + } + + private async setIcon( + lexicalBuildType: 'restricted' | 'enabled', + tabId: number, + ) { + const action = IS_FIREFOX ? browser.browserAction : browser.action; + + await action.setIcon({ + path: { + '128': browser.runtime.getURL( + lexicalBuildType === 'enabled' + ? '/icon/128.png' + : '/icon/128-restricted.png', + ), + '16': browser.runtime.getURL( + lexicalBuildType === 'enabled' + ? '/icon/16.png' + : '/icon/16-restricted.png', + ), + '32': browser.runtime.getURL( + lexicalBuildType === 'enabled' + ? '/icon/32.png' + : '/icon/32-restricted.png', + ), + '48': browser.runtime.getURL( + lexicalBuildType === 'enabled' + ? '/icon/48.png' + : '/icon/48-restricted.png', + ), + }, + tabId: tabId, + }); + + if (lexicalBuildType === 'restricted') { + this.extensionStore.getState().markTabAsRestricted(tabId); + } + } + + private handleTabsUpdatedEvent( + tabId: number, + _changeInfo: unknown, + tab: Tabs.Tab, + ): void { + this.checkAndHandleRestrictedPageIfSo(tab); + } + + private isRestrictedBrowserPage(url: string | undefined) { + return ( + !url || ['chrome:', 'about:', 'file:'].includes(new URL(url).protocol) + ); + } + + private async checkAndHandleRestrictedPageIfSo(tab: Tabs.Tab) { + if (tab.id == null) { + return; + } + + if (tab.id == null || this.isRestrictedBrowserPage(tab.url)) { + return this.setIcon('restricted', tab.id); + } + + return this.setIcon('enabled', tab.id); + } +} diff --git a/packages/lexical-devtools/src/entrypoints/background/index.ts b/packages/lexical-devtools/src/entrypoints/background/index.ts index d0c1de4e14e..4dd130799d4 100644 --- a/packages/lexical-devtools/src/entrypoints/background/index.ts +++ b/packages/lexical-devtools/src/entrypoints/background/index.ts @@ -9,7 +9,11 @@ import {registerRPCService} from '@webext-pegasus/rpc'; import {initPegasusTransport} from '@webext-pegasus/transport/background'; -import {initExtensionStoreBackend} from '../../store.ts'; +import { + initExtensionStoreBackend, + useExtensionStore as extensionStore, +} from '../../store.ts'; +import ActionIconWatchdog from './ActionIconWatchdog.ts'; import {getTabIDService} from './getTabIDService'; export default defineBackground(() => { @@ -20,4 +24,6 @@ export default defineBackground(() => { // Store initialization so other extension surfaces can use it // as all changes go through background SW initExtensionStoreBackend(); + + ActionIconWatchdog.start(extensionStore).catch(console.error); }); diff --git a/packages/lexical-devtools/src/entrypoints/devtools-panel/components/EditorInspectorButton.tsx b/packages/lexical-devtools/src/entrypoints/devtools-panel/components/EditorInspectorButton.tsx index fcbb980ffad..d1019b92303 100644 --- a/packages/lexical-devtools/src/entrypoints/devtools-panel/components/EditorInspectorButton.tsx +++ b/packages/lexical-devtools/src/entrypoints/devtools-panel/components/EditorInspectorButton.tsx @@ -24,10 +24,13 @@ export function EditorInspectorButton({tabID, setErrorMessage}: Props) { {context: 'window', tabId: tabID}, ); - injectedPegasusService.toggleEditorPicker().catch((err) => { - setErrorMessage(err.message); - console.error(err); - }); + injectedPegasusService + .refreshLexicalEditors() + .then(() => injectedPegasusService.toggleEditorPicker()) + .catch((err) => { + setErrorMessage(err.message); + console.error(err); + }); }; return ( diff --git a/packages/lexical-devtools/src/entrypoints/injected/InjectedPegasusService.ts b/packages/lexical-devtools/src/entrypoints/injected/InjectedPegasusService.ts index 176de0f1598..664006f0fb1 100644 --- a/packages/lexical-devtools/src/entrypoints/injected/InjectedPegasusService.ts +++ b/packages/lexical-devtools/src/entrypoints/injected/InjectedPegasusService.ts @@ -40,7 +40,7 @@ export class InjectedPegasusService private readonly commandLog: WeakMap, ) {} - refreshLexicalEditorsForTabID() { + refreshLexicalEditors() { scanAndListenForEditors(this.tabID, this.extensionStore, this.commandLog); } diff --git a/packages/lexical-devtools/src/entrypoints/popup/App.css b/packages/lexical-devtools/src/entrypoints/popup/App.css index 72a3cbc4bf1..e6bd89156df 100644 --- a/packages/lexical-devtools/src/entrypoints/popup/App.css +++ b/packages/lexical-devtools/src/entrypoints/popup/App.css @@ -17,17 +17,3 @@ body { margin: 0 auto; padding: 1rem; } - -.logo { - height: 2em; - /* padding: 1.5em; */ - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -.card { - padding-top: 2em; -} diff --git a/packages/lexical-devtools/src/entrypoints/popup/App.tsx b/packages/lexical-devtools/src/entrypoints/popup/App.tsx index 2887b4f1f04..05733be0c3d 100644 --- a/packages/lexical-devtools/src/entrypoints/popup/App.tsx +++ b/packages/lexical-devtools/src/entrypoints/popup/App.tsx @@ -7,11 +7,10 @@ */ import './App.css'; +import {Box, Flex} from '@chakra-ui/react'; import * as React from 'react'; import {useState} from 'react'; -import lexicalLogo from '@/public/lexical.svg'; - import EditorsRefreshCTA from '../../components/EditorsRefreshCTA'; import {useExtensionStore} from '../../store'; @@ -27,38 +26,46 @@ function App({tabID}: Props) { const lexicalCount = Object.keys(states ?? {}).length; return ( - <> -
- - Lexical logo - -
+ {errorMessage !== '' ? ( -
{errorMessage}
+ + {errorMessage} + ) : null} -
- {states === undefined ? ( - Loading... - ) : ( + + {states === null ? ( - Found {lexicalCount} editor{lexicalCount > 1 ? 's' : ''} on - the page - {lexicalCount > 0 ? ( - <> - {' '} - ✅ -
- Open the developer tools, and "Lexical" tab will appear to the - right. - - ) : null} + This is a restricted browser page. Lexical DevTools cannot access + this page.
+ ) : states === undefined ? ( + Loading... + ) : ( + <> + + Found {lexicalCount} editor + {lexicalCount > 1 || lexicalCount === 0 ? 's' : ''} on the page + {lexicalCount > 0 ? ( + <> + {' '} + ✅ +
+ Open the developer tools, and "Lexical" tab will appear to the + right. + + ) : null} +
+ + + + + )} -

- -

-
- + +
); } diff --git a/packages/lexical-devtools/src/lexicalForExtension.ts b/packages/lexical-devtools/src/lexicalForExtension.ts index bdd537f3d1e..3b45ed74f52 100644 --- a/packages/lexical-devtools/src/lexicalForExtension.ts +++ b/packages/lexical-devtools/src/lexicalForExtension.ts @@ -59,17 +59,25 @@ export function $getSelection(): null | lexical.BaseSelection { export function $isElementNode( node: lexical.LexicalNode | null | undefined, ): node is lexical.ElementNode { + if (node == null) { + return false; + } + const editor = getActiveEditor(); - const ElementNode = Object.getPrototypeOf( - editor._nodes.get('paragraph')!.klass, - ); + const ParagraphNode = editor._nodes.get('paragraph')!.klass; + const ElementNode = Object.getPrototypeOf(ParagraphNode.prototype); - return node instanceof ElementNode; + // eslint-disable-next-line no-prototype-builtins + return ElementNode.isPrototypeOf(node); } export function $isTextNode( node: lexical.LexicalNode | null | undefined, ): node is lexical.TextNode { + if (node == null) { + return false; + } + const editor = getActiveEditor(); const TextNode = editor._nodes.get('text')!.klass; diff --git a/packages/lexical-devtools/src/public/icon/128-restricted.png b/packages/lexical-devtools/src/public/icon/128-restricted.png new file mode 100644 index 00000000000..781567d2fbe Binary files /dev/null and b/packages/lexical-devtools/src/public/icon/128-restricted.png differ diff --git a/packages/lexical-devtools/src/public/icon/16-restricted.png b/packages/lexical-devtools/src/public/icon/16-restricted.png new file mode 100644 index 00000000000..c97f6ffce25 Binary files /dev/null and b/packages/lexical-devtools/src/public/icon/16-restricted.png differ diff --git a/packages/lexical-devtools/src/public/icon/32-restricted.png b/packages/lexical-devtools/src/public/icon/32-restricted.png new file mode 100644 index 00000000000..26d39aa852f Binary files /dev/null and b/packages/lexical-devtools/src/public/icon/32-restricted.png differ diff --git a/packages/lexical-devtools/src/public/icon/48-restricted.png b/packages/lexical-devtools/src/public/icon/48-restricted.png new file mode 100644 index 00000000000..dcde663ad7f Binary files /dev/null and b/packages/lexical-devtools/src/public/icon/48-restricted.png differ diff --git a/packages/lexical-devtools/src/store.ts b/packages/lexical-devtools/src/store.ts index 8b38b0a475b..3926e9830a6 100644 --- a/packages/lexical-devtools/src/store.ts +++ b/packages/lexical-devtools/src/store.ts @@ -17,11 +17,12 @@ import {SerializedRawEditorState} from './types'; export interface ExtensionState { lexicalState: { - [tabID: number]: {[editorKey: string]: SerializedRawEditorState}; + [tabID: number]: {[editorKey: string]: SerializedRawEditorState} | null; }; selectedEditorKey: { [tabID: number]: string | null; }; + markTabAsRestricted: (tabID: number) => void; setStatesForTab: ( id: number, states: {[editorKey: string]: SerializedRawEditorState}, @@ -32,6 +33,13 @@ export interface ExtensionState { export const useExtensionStore = create()( subscribeWithSelector((set) => ({ lexicalState: {}, + markTabAsRestricted: (tabID: number) => + set((state) => ({ + lexicalState: { + ...state.lexicalState, + [tabID]: null, + }, + })), selectedEditorKey: {}, setSelectedEditorKey: (tabID: number, editorKey: string | null) => set((state) => ({