diff --git a/src/content-scripts/content_script/global.ts b/src/content-scripts/content_script/global.ts index 4b1ebc2496..40e4cae1dc 100644 --- a/src/content-scripts/content_script/global.ts +++ b/src/content-scripts/content_script/global.ts @@ -36,7 +36,6 @@ import { PageAnnotationsCache } from 'src/annotations/cache' import type { AnalyticsEvent } from 'src/analytics/types' import analytics from 'src/analytics' import { main as highlightMain } from 'src/content-scripts/content_script/highlights' -import { main as InPageUIInjectionMain } from 'src/content-scripts/content_script/in-page-ui-injections' import type { PageIndexingInterface } from 'src/page-indexing/background/types' import { copyToClipboard } from 'src/annotations/content_script/utils' import { getUnderlyingResourceUrl } from 'src/util/uri-utils' @@ -469,16 +468,6 @@ export async function main( components.highlights.resolve() } - if (component === 'search') { - await contentScriptRegistry.registerInPageUIInjectionScript( - InPageUIInjectionMain, - { - searchDisplayProps, - }, - ) - return - } - if (!components[component]) { components[component] = resolvablePromise<void>() loadContentScript(component) @@ -501,16 +490,16 @@ export async function main( ): Promise<{ annotationId: AutoPk; createPromise: Promise<void> }> { const handleError = async (err: Error) => { captureException(err) - await contentScriptRegistry.registerInPageUIInjectionScript( - InPageUIInjectionMain, - { + inPageUI.loadOnDemandInPageUI({ + component: 'error-display', + options: { errorDisplayProps: { errorMessage: err.message, title: 'Error saving note', blockedBackground: true, }, }, - ) + }) } try { @@ -996,15 +985,16 @@ export async function main( }) components.tooltip?.resolve() }, - async registerInPageUIInjectionScript(execute, onDemandDisplay) { + async registerInPageUIInjectionScript(execute) { await execute({ + inPageUI, syncSettingsBG, syncSettings: createSyncSettingsStore({ syncSettingsBG }), requestSearcher: remoteFunction('search'), + searchDisplayProps, annotationsFunctions, - onDemandDisplay, - bgScriptBG, }) + components.in_page_ui_injections?.resolve() }, } @@ -1047,9 +1037,9 @@ export async function main( ) || window.location.href.includes('youtube.com') ) { - await contentScriptRegistry.registerInPageUIInjectionScript( - InPageUIInjectionMain, - ) + inPageUI.loadOnDemandInPageUI({ + component: 'search-engine-integration', + }) } const pageHasBookark = @@ -1137,9 +1127,9 @@ export async function main( ) || window.location.href.includes('youtube.com') ) { - await contentScriptRegistry.registerInPageUIInjectionScript( - InPageUIInjectionMain, - ) + inPageUI.loadOnDemandInPageUI({ + component: 'youtube-integration', + }) } await injectCustomUIperPage( @@ -1226,10 +1216,11 @@ export async function main( // so it is included in this global content script where it adds less than 500kb. await contentScriptRegistry.registerHighlightingScript(highlightMain) + await inPageUI.loadComponent('in_page_ui_injections') if (areHighlightsEnabled) { inPageUI.showHighlights() if (!annotationsCache.isEmpty) { - inPageUI.loadComponent('sidebar') + await inPageUI.loadComponent('sidebar') } } @@ -1239,7 +1230,7 @@ export async function main( showPageActivityIndicator: hasActivity, }) if (await tooltipUtils.getTooltipState()) { - await inPageUI.setupTooltip() + await inPageUI.loadComponent('tooltip') } } else { if (hasActivity) { @@ -1248,7 +1239,7 @@ export async function main( showPageActivityIndicator: hasActivity, }) if (await tooltipUtils.getTooltipState()) { - await inPageUI.setupTooltip() + await inPageUI.loadComponent('tooltip') } } } diff --git a/src/content-scripts/content_script/in-page-ui-injections.ts b/src/content-scripts/content_script/in-page-ui-injections.ts index f404c824f3..883be7c391 100644 --- a/src/content-scripts/content_script/in-page-ui-injections.ts +++ b/src/content-scripts/content_script/in-page-ui-injections.ts @@ -1,6 +1,65 @@ -import { initInPageUIInjections } from 'src/search-injection/content_script' -import type { InPageUIInjectionsMain } from 'src/content-scripts/content_script/types' +import * as constants from '../../search-injection/constants' +import * as utils from '../../search-injection/utils' +import { handleRenderSearchInjection } from '../../search-injection/searchInjection' +import { handleRenderYoutubeInterface } from '../../search-injection/youtubeInterface' +import { renderErrorDisplay } from '../../search-injection/error-display' +import { renderSearchDisplay } from '../../search-injection/search-display' +import type { ContentScriptRegistry, InPageUIInjectionsMain } from './types' -export const main: InPageUIInjectionsMain = async (...options) => { - initInPageUIInjections(...options) +export const main: InPageUIInjectionsMain = async ({ + inPageUI, + annotationsFunctions, + requestSearcher, + searchDisplayProps, + syncSettings, + syncSettingsBG, +}) => { + inPageUI.events.on( + 'injectOnDemandInPageUI', + async ({ component, options }) => { + if (component === 'error-display') { + if (options?.errorDisplayProps) { + renderErrorDisplay(options.errorDisplayProps) + } + } else if (component === 'dashboard') { + renderSearchDisplay(searchDisplayProps) + } else if (component === 'youtube-integration') { + const url = window.location.href + if (url.includes('youtube.com')) { + await handleRenderYoutubeInterface( + syncSettings, + syncSettingsBG, + annotationsFunctions, + ) + } + } else if (component === 'search-engine-integration') { + const url = window.location.href + const matched = utils.matchURL(url) + + if (matched) { + const searchInjection = + (await syncSettings.searchInjection.get( + 'searchEnginesEnabled', + )) ?? constants.SEARCH_INJECTION_DEFAULT + if (searchInjection[matched]) { + try { + const query = utils.fetchQuery(url) + + await handleRenderSearchInjection( + query, + requestSearcher, + matched, + syncSettings, + ) + } catch (err) { + console.error(err) + } + } + } + } + }, + ) } + +const registry = globalThis['contentScriptRegistry'] as ContentScriptRegistry +registry.registerInPageUIInjectionScript(main) diff --git a/src/content-scripts/content_script/tooltip.ts b/src/content-scripts/content_script/tooltip.ts index 3190eb0e9a..d7ebc425eb 100644 --- a/src/content-scripts/content_script/tooltip.ts +++ b/src/content-scripts/content_script/tooltip.ts @@ -1,5 +1,4 @@ import type { ContentScriptRegistry, TooltipScriptMain } from './types' - import { bodyLoader } from 'src/util/loader' import { runOnScriptShutdown } from 'src/in-page-ui/tooltip/utils' import { @@ -15,7 +14,7 @@ import { createInPageUI, destroyInPageUI } from 'src/in-page-ui/utils' import { IGNORE_CLICK_OUTSIDE_CLASS } from '../constants' export const main: TooltipScriptMain = async (options) => { - const cssFile = browser.runtime.getURL(`/content_script.css`) + const cssFile = browser.runtime.getURL(`/content_script_tooltip.css`) let mount: InPageUIRootMount | null = null const createMount = () => { if (!mount) { @@ -42,7 +41,7 @@ export const main: TooltipScriptMain = async (options) => { options.inPageUI.events.on('componentShouldDestroy', async (event) => { if (event.component === 'tooltip') { destroyInPageUI('tooltip') - await removeTooltip() + removeTooltip() } }) options.inPageUI.events.on('stateChanged', async (event) => { diff --git a/src/content-scripts/content_script/types.ts b/src/content-scripts/content_script/types.ts index d8ca847c1c..ba5f23af48 100644 --- a/src/content-scripts/content_script/types.ts +++ b/src/content-scripts/content_script/types.ts @@ -6,27 +6,19 @@ import type AnnotationsManager from 'src/annotations/annotations-manager' import type { AnnotationInterface } from 'src/annotations/background/types' import type { HighlightRendererInterface } from '@worldbrain/memex-common/lib/in-page-ui/highlighting/types' import type { ContentFingerprint } from '@worldbrain/memex-common/lib/personal-cloud/storage/types' -import type { RemoteSyncSettingsInterface } from 'src/sync-settings/background/types' import type { PageAnnotationsCacheInterface } from 'src/annotations/cache/types' import type { MaybePromise } from 'src/util/types' import type { AnalyticsCoreInterface } from '@worldbrain/memex-common/lib/analytics/types' -import type { SyncSettingsStore } from 'src/sync-settings/util' -import type { ErrorDisplayProps } from 'src/search-injection/error-display' import type { SearchDisplayProps } from 'src/search-injection/search-display' -import { RemoteBGScriptInterface } from 'src/background-script/types' +import type { RemoteSyncSettingsInterface } from 'src/sync-settings/background/types' +import type { SyncSettingsStore } from 'src/sync-settings/util' export interface ContentScriptRegistry { registerRibbonScript(main: RibbonScriptMain): Promise<void> registerSidebarScript(main: SidebarScriptMain): Promise<void> registerHighlightingScript(main: HighlightsScriptMain): Promise<void> registerTooltipScript(main: TooltipScriptMain): Promise<void> - registerInPageUIInjectionScript( - main: InPageUIInjectionsMain, - onDemandDisplay?: { - errorDisplayProps?: ErrorDisplayProps - searchDisplayProps?: SearchDisplayProps - }, - ): Promise<void> + registerInPageUIInjectionScript(main: InPageUIInjectionsMain): Promise<void> } export type SidebarScriptMain = ( @@ -60,6 +52,8 @@ export interface HighlightDependencies { } export interface InPageUIInjectionsDependencies { + inPageUI: SharedInPageUIInterface + searchDisplayProps: SearchDisplayProps requestSearcher: any syncSettingsBG: RemoteSyncSettingsInterface syncSettings: SyncSettingsStore< @@ -71,11 +65,6 @@ export interface InPageUIInjectionsDependencies { | 'dashboard' > annotationsFunctions: any - onDemandDisplay?: { - errorDisplayProps?: ErrorDisplayProps - searchDisplayProps?: SearchDisplayProps - } - bgScriptBG: RemoteBGScriptInterface } export type HighlightsScriptMain = ( @@ -90,8 +79,4 @@ export type InPageUIInjectionsMain = ( dependencies: InPageUIInjectionsDependencies, ) => Promise<void> -export type YoutubeInjectionMain = ( - dependencies: InPageUIInjectionsDependencies, -) => Promise<void> - export type GetContentFingerprints = () => Promise<ContentFingerprint[]> diff --git a/src/content-scripts/types.ts b/src/content-scripts/types.ts index c0388b6d28..54d6228353 100644 --- a/src/content-scripts/types.ts +++ b/src/content-scripts/types.ts @@ -3,4 +3,4 @@ export type ContentScriptComponent = | 'sidebar' | 'ribbon' | 'highlights' - | 'search_injection' + | 'in_page_ui_injections' diff --git a/src/in-page-ui/keyboard-shortcuts/content_script/index.ts b/src/in-page-ui/keyboard-shortcuts/content_script/index.ts index d9be9144ea..e001d59c7f 100644 --- a/src/in-page-ui/keyboard-shortcuts/content_script/index.ts +++ b/src/in-page-ui/keyboard-shortcuts/content_script/index.ts @@ -101,7 +101,8 @@ function getShortcutHandlers({ } }, createBookmark: () => inPageUI.showRibbon({ action: 'bookmark' }), - openDashboard: () => inPageUI.showSearch(), + openDashboard: async () => + inPageUI.loadOnDemandInPageUI({ component: 'dashboard' }), openDashboardInNewTab: () => runInBackground<InPageUIInterface<'caller'>>().openDashboard(), toggleSidebar: () => inPageUI.toggleSidebar(), diff --git a/src/in-page-ui/ribbon/react/containers/ribbon/logic.ts b/src/in-page-ui/ribbon/react/containers/ribbon/logic.ts index 6bf909bfb5..d6d34ab03c 100644 --- a/src/in-page-ui/ribbon/react/containers/ribbon/logic.ts +++ b/src/in-page-ui/ribbon/react/containers/ribbon/logic.ts @@ -384,11 +384,13 @@ export class RibbonContainerLogic extends UILogic< action: 'rabbit_hole_open', }) } - toggleQuickSearch: EventHandler<'toggleQuickSearch'> = async ({ - previousState, - }) => { - await this.dependencies.inPageUI.showSearch() + + toggleQuickSearch: EventHandler<'toggleQuickSearch'> = async ({}) => { + this.dependencies.inPageUI.loadOnDemandInPageUI({ + component: 'dashboard', + }) } + toggleTheme: EventHandler<'toggleTheme'> = async ({ previousState }) => { await browser.storage.local.set({ themeVariant: diff --git a/src/in-page-ui/shared-state/shared-in-page-ui-state.ts b/src/in-page-ui/shared-state/shared-in-page-ui-state.ts index eede003ffc..ef5a598bb2 100644 --- a/src/in-page-ui/shared-state/shared-in-page-ui-state.ts +++ b/src/in-page-ui/shared-state/shared-in-page-ui-state.ts @@ -33,25 +33,26 @@ export interface SharedInPageUIDependencies { * */ export class SharedInPageUIState implements SharedInPageUIInterface { - contentSharingEvents: TypedRemoteEventEmitter<'contentSharing'> - summarisePageEvents: TypedRemoteEventEmitter<'pageSummary'> + private contentSharingEvents: TypedRemoteEventEmitter<'contentSharing'> + private summarisePageEvents: TypedRemoteEventEmitter<'pageSummary'> + events = new EventEmitter() as TypedEventEmitter<SharedInPageUIEvents> componentsShown: InPageUIComponentShowState = { ribbon: false, - search: false, sidebar: false, tooltip: false, highlights: false, + in_page_ui_injections: false, } - componentsSetUp: InPageUIComponentShowState = { + private componentsSetUp: InPageUIComponentShowState = { ribbon: false, - search: false, sidebar: false, tooltip: false, highlights: false, + in_page_ui_injections: false, } - ribbonEnabled = null + private ribbonEnabled = null /** * Keep track of currently selected space for other UI elements to follow. @@ -68,7 +69,7 @@ export class SharedInPageUIState implements SharedInPageUIInterface { selectedList: SharedInPageUIInterface['selectedList'] = null cacheLoadPromise: SharedInPageUIInterface['cacheLoadPromise'] = resolvablePromise() - _pendingEvents: { + private _pendingEvents: { sidebarAction?: { emittedWhen: number } & SidebarActionOptions @@ -102,7 +103,7 @@ export class SharedInPageUIState implements SharedInPageUIInterface { }) } - _handleNewListener = ( + private _handleNewListener = ( eventName: keyof SharedInPageUIEvents, listener: (...args: any[]) => void, ) => { @@ -152,7 +153,14 @@ export class SharedInPageUIState implements SharedInPageUIInterface { maybeEmitAction() } - _emitAction( + loadOnDemandInPageUI: SharedInPageUIInterface['loadOnDemandInPageUI'] = ({ + component, + options, + }) => { + this.events.emit('injectOnDemandInPageUI', { component, options }) + } + + private _emitAction( params: | ({ type: 'sidebarAction' @@ -193,10 +201,6 @@ export class SharedInPageUIState implements SharedInPageUIInterface { return } - async showSearch() { - await this.options.loadComponent('search') - } - async showRibbon(options?: { action?: InPageUIRibbonAction }) { this.ribbonEnabled = await sidebarUtils.getSidebarState() const maybeEmitAction = () => { @@ -249,10 +253,6 @@ export class SharedInPageUIState implements SharedInPageUIInterface { this.events.emit('ribbonUpdate') } - async setupTooltip() { - await this.loadComponent('tooltip') - } - async showTooltip() { await this._setState('tooltip', true) } diff --git a/src/in-page-ui/shared-state/types.ts b/src/in-page-ui/shared-state/types.ts index 61d6bf7d6c..f395f7d3e7 100644 --- a/src/in-page-ui/shared-state/types.ts +++ b/src/in-page-ui/shared-state/types.ts @@ -6,6 +6,11 @@ import type { UnifiedList, } from 'src/annotations/cache/types' import type { Resolvable } from 'src/util/resolvable' +import type { + OnDemandInPageUIComponents, + OnDemandInPageUIProps, +} from 'src/search-injection/types' +import type { ContentScriptComponent } from 'src/content-scripts/types' export type InPageUISidebarAction = | 'comment' @@ -29,12 +34,7 @@ export type InPageUISidebarAction = | 'save_image_as_new_note' export type InPageUIRibbonAction = 'comment' | 'tag' | 'list' | 'bookmark' -export type InPageUIComponent = - | 'ribbon' - | 'sidebar' - | 'tooltip' - | 'highlights' - | 'search' +export type InPageUIComponent = ContentScriptComponent export type InPageUIComponentShowState = { [Component in InPageUIComponent]: boolean @@ -79,6 +79,10 @@ export interface SharedInPageUIEvents { options?: ShouldSetUpOptions }) => void componentShouldDestroy: (event: { component: InPageUIComponent }) => void + injectOnDemandInPageUI: (event: { + component: OnDemandInPageUIComponents + options?: OnDemandInPageUIProps + }) => void } export interface ShouldSetUpOptions { @@ -112,7 +116,6 @@ export interface SharedInPageUIInterface { // Tooltip showTooltip(): Promise<void> hideTooltip(): Promise<void> - setupTooltip(): Promise<void> removeTooltip(): Promise<void> toggleTooltip(): Promise<void> @@ -122,5 +125,8 @@ export interface SharedInPageUIInterface { toggleHighlights(): Promise<void> // On-demand in-page UIs - showSearch(): Promise<void> + loadOnDemandInPageUI(params: { + component: OnDemandInPageUIComponents + options?: OnDemandInPageUIProps + }): void } diff --git a/src/in-page-ui/tooltip/types.ts b/src/in-page-ui/tooltip/types.ts index 35541cdad5..167648ddf2 100644 --- a/src/in-page-ui/tooltip/types.ts +++ b/src/in-page-ui/tooltip/types.ts @@ -1,11 +1,6 @@ import type { SharedInPageUIInterface } from 'src/in-page-ui/shared-state/types' import type { AnnotationFunctions } from '@worldbrain/memex-common/lib/in-page-ui/types' -export type TooltipInPageUIInterface = Pick< - SharedInPageUIInterface, - 'events' | 'hideTooltip' | 'showTooltip' | 'removeTooltip' | 'showSidebar' -> - export interface TooltipDependencies extends AnnotationFunctions { inPageUI: SharedInPageUIInterface } diff --git a/src/manifest-v3.json b/src/manifest-v3.json index bbbb4527b6..6769a89b2e 100644 --- a/src/manifest-v3.json +++ b/src/manifest-v3.json @@ -23,7 +23,7 @@ { "matches": ["<all_urls>"], "js": ["lib/browser-polyfill.js", "content_script.js"], - "css": ["/content_script.css"], + "css": [], "run_at": "document_end" } ], diff --git a/src/manifest.json b/src/manifest.json index 0d9143d672..6186f978cf 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -17,7 +17,7 @@ { "matches": ["<all_urls>"], "js": ["lib/browser-polyfill.js", "content_script.js"], - "css": ["/content_script.css"], + "css": [], "run_at": "document_end" } ], diff --git a/src/search-injection/content_script.ts b/src/search-injection/content_script.ts deleted file mode 100644 index c303d67c10..0000000000 --- a/src/search-injection/content_script.ts +++ /dev/null @@ -1,61 +0,0 @@ -import * as constants from './constants' -import * as utils from './utils' -import { handleRenderSearchInjection } from './searchInjection' -import { handleRenderYoutubeInterface } from './youtubeInterface' -import { renderErrorDisplay } from './error-display' -import { renderSearchDisplay } from './search-display' -import type { InPageUIInjectionsDependencies } from 'src/content-scripts/content_script/types' - -const url = window.location.href -const matched = utils.matchURL(url) - -/** - * Fetches SearchInjection user preferance from storage. - * If set, proceed with matching URL and fetching search query - */ -export async function initInPageUIInjections({ - syncSettings, - syncSettingsBG, - requestSearcher, - annotationsFunctions, - onDemandDisplay, -}: InPageUIInjectionsDependencies) { - if (onDemandDisplay?.errorDisplayProps != null) { - renderErrorDisplay(onDemandDisplay.errorDisplayProps) - return - } - - if (onDemandDisplay?.searchDisplayProps != null) { - renderSearchDisplay(onDemandDisplay.searchDisplayProps) - return - } - - if (url.includes('youtube.com')) { - await handleRenderYoutubeInterface( - syncSettings, - syncSettingsBG, - annotationsFunctions, - ) - return - } - - if (matched) { - const searchInjection = - (await syncSettings.searchInjection.get('searchEnginesEnabled')) ?? - constants.SEARCH_INJECTION_DEFAULT - if (searchInjection[matched]) { - try { - const query = utils.fetchQuery(url) - - await handleRenderSearchInjection( - query, - requestSearcher, - matched, - syncSettings, - ) - } catch (err) { - console.error(err) - } - } - } -} diff --git a/src/search-injection/types.ts b/src/search-injection/types.ts index 639207d5d7..52fd3b6fd5 100644 --- a/src/search-injection/types.ts +++ b/src/search-injection/types.ts @@ -1,3 +1,5 @@ +import type { ErrorDisplayProps } from './error-display' + export type SearchEngineName = 'google' | 'duckduckgo' | 'brave' | 'bing' export interface SearchEngineInfo { regex: RegExp // Regular Expression to match the url @@ -21,3 +23,13 @@ export interface ResultItemProps { tags: [] onLinkClick: React.MouseEventHandler } + +export interface OnDemandInPageUIProps { + errorDisplayProps?: ErrorDisplayProps +} + +export type OnDemandInPageUIComponents = + | 'youtube-integration' + | 'search-engine-integration' + | 'dashboard' + | 'error-display'