Register AppContext & Format style cell

This commit is contained in:
johnathan 2025-06-25 14:59:37 +08:00
parent 105bfadfa3
commit da5e76aae7
20 changed files with 39 additions and 100 deletions

4
shim.d.ts vendored
View File

@ -17,7 +17,3 @@ declare module 'webext-bridge' {
>; >;
} }
} }
declare global {
type AppContext = 'options' | 'sidepanel';
}

View File

@ -11,6 +11,8 @@ if (import.meta.hot) {
import('./contentScriptHMR'); import('./contentScriptHMR');
} }
Object.assign(self, { appContext: 'background' });
// remove or turn this off if you don't use side panel // remove or turn this off if you don't use side panel
const USE_SIDE_PANEL = true; const USE_SIDE_PANEL = true;

View File

@ -36,7 +36,7 @@ defineProps<{ model: AmazonDetailItem }>();
</div> </div>
</template> </template>
<style lang="scss" scoped> <style scoped lang="scss">
.review-item-footer { .review-item-footer {
display: flex; display: flex;
flex-direction: row-reverse; flex-direction: row-reverse;

View File

@ -62,7 +62,7 @@ onUnmounted(() => {
</div> </div>
</template> </template>
<style lang="scss" scoped> <style scoped lang="scss">
.link-text { .link-text {
cursor: default; cursor: default;
font-family: v-mono; font-family: v-mono;

View File

@ -10,7 +10,7 @@
</a> </a>
</template> </template>
<style lang="scss" scoped> <style scoped lang="scss">
.icon { .icon {
display: block; display: block;
color: #333; color: #333;

View File

@ -18,7 +18,7 @@ defineProps<{
</div> </div>
</template> </template>
<style lang="scss" scoped> <style scoped lang="scss">
.image-link-container { .image-link-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -155,7 +155,7 @@ const handleExport = () => {
</div> </div>
</template> </template>
<style lang="scss" scoped> <style scoped lang="scss">
.review-list { .review-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -1,4 +0,0 @@
export function useAppContext() {
const appContext = document.location.pathname.split('/')[2] as AppContext;
return { appContext };
}

View File

@ -6,7 +6,6 @@ import { storage } from 'webextension-polyfill';
import type { RemovableRef, StorageLikeAsync, UseStorageAsyncOptions } from '@vueuse/core'; import type { RemovableRef, StorageLikeAsync, UseStorageAsyncOptions } from '@vueuse/core';
import type { Ref } from 'vue-demi'; import type { Ref } from 'vue-demi';
import type { Storage } from 'webextension-polyfill'; import type { Storage } from 'webextension-polyfill';
import { useAppContext } from './useAppContext';
export type WebExtensionStorageOptions<T> = UseStorageAsyncOptions<T>; export type WebExtensionStorageOptions<T> = UseStorageAsyncOptions<T>;
@ -121,8 +120,7 @@ export function useWebExtensionStorage<T>(
return; return;
} }
if (typeof listenToStorageChanges === 'string') { if (typeof listenToStorageChanges === 'string') {
const { appContext: context } = useAppContext(); if (listenToStorageChanges !== appContext) {
if (listenToStorageChanges !== context) {
return; return;
} }
} }

View File

@ -1,4 +1,5 @@
// Firefox `browser.tabs.executeScript()` requires scripts return a primitive value // Firefox `browser.tabs.executeScript()` requires scripts return a primitive value
(() => { (() => {
Object.assign(self, { appContext: 'content script' });
import('./html-to-image'); import('./html-to-image');
})(); })();

4
src/global.d.ts vendored
View File

@ -9,6 +9,10 @@ declare module '*.vue' {
export default component; export default component;
} }
declare type AppContext = 'options' | 'sidepanel' | 'background' | 'content script';
declare const appContext: AppContext;
declare interface Chrome { declare interface Chrome {
sidePanel?: { sidePanel?: {
setPanelBehavior: (options: { openPanelOnActionClick: boolean }) => void; setPanelBehavior: (options: { openPanelOnActionClick: boolean }) => void;

View File

@ -1,5 +1,4 @@
import type { App } from 'vue'; import type { App } from 'vue';
import { useAppContext } from '~/composables/useAppContext';
import { router } from '~/router'; import { router } from '~/router';
/** /**
@ -7,11 +6,9 @@ import { router } from '~/router';
* @param app Vue app * @param app Vue app
*/ */
export function setupApp(app: App) { export function setupApp(app: App) {
const { appContext: context } = useAppContext();
// Inject a globally available `$app` object in template // Inject a globally available `$app` object in template
app.config.globalProperties.$app = { app.config.globalProperties.$app = {
context, context: appContext,
}; };
// Provide access to `app` in script setup with `const app = inject('app')` // Provide access to `app` in script setup with `const app = inject('app')`

View File

@ -2,6 +2,9 @@ import App from './App.vue';
import { setupApp } from '~/logic/common-setup'; import { setupApp } from '~/logic/common-setup';
import '../styles'; import '../styles';
// This is the options page of the extension.
Object.assign(self, { appContext: 'options' });
const app = createApp(App); const app = createApp(App);
setupApp(app); setupApp(app);
app.mount('#app'); app.mount('#app');

View File

@ -370,7 +370,7 @@ const handleClearData = async () => {
</div> </div>
</template> </template>
<style lang="scss" scoped> <style scoped lang="scss">
.result-table { .result-table {
width: 100%; width: 100%;
} }

View File

@ -5,10 +5,9 @@ import {
createMemoryHistory, createMemoryHistory,
RouteRecordRaw, RouteRecordRaw,
} from 'vue-router'; } from 'vue-router';
import { useAppContext } from '~/composables/useAppContext';
import { site } from '~/logic/storages/global'; import { site } from '~/logic/storages/global';
const routeObj: Record<AppContext, RouteRecordRaw[]> = { const routeObj: Record<'sidepanel' | 'options', RouteRecordRaw[]> = {
options: [ options: [
{ path: '/', redirect: `/${site.value}` }, { path: '/', redirect: `/${site.value}` },
{ path: '/amazon', component: () => import('~/options/views/AmazonResultTable.vue') }, { path: '/amazon', component: () => import('~/options/views/AmazonResultTable.vue') },
@ -23,12 +22,17 @@ const routeObj: Record<AppContext, RouteRecordRaw[]> = {
export const router: Plugin = { export const router: Plugin = {
install(app) { install(app) {
const { appContext: context } = useAppContext(); switch (appContext) {
const routes = routeObj[context]; case 'sidepanel':
const router = createRouter({ case 'options':
history: context === 'sidepanel' ? createMemoryHistory() : createWebHashHistory(), const routes = routeObj[appContext];
routes, const router = createRouter({
}); history: appContext === 'sidepanel' ? createMemoryHistory() : createWebHashHistory(),
app.use(router); routes,
});
app.use(router);
default:
break;
}
}, },
}; };

View File

@ -2,6 +2,9 @@ import App from './App.vue';
import { setupApp } from '~/logic/common-setup'; import { setupApp } from '~/logic/common-setup';
import '../styles'; import '../styles';
// This is the sidepanel page of the extension.
Object.assign(self, { appContext: 'sidepanel' });
const app = createApp(App); const app = createApp(App);
setupApp(app); setupApp(app);
app.mount('#app'); app.mount('#app');

View File

@ -111,7 +111,7 @@ const updateReviews = (params: { asin: string; reviews: AmazonReview[] }) => {
</div> </div>
</template> </template>
<style lang="scss" scoped> <style scoped lang="scss">
.review-page-entry { .review-page-entry {
width: 100%; width: 100%;
height: 100%; height: 100%;

View File

@ -128,7 +128,7 @@ const handleInterrupt = () => {
</div> </div>
</template> </template>
<style lang="scss" scoped> <style scoped lang="scss">
.search-page-entry { .search-page-entry {
width: 100%; width: 100%;
display: flex; display: flex;

View File

@ -81,7 +81,7 @@ const handleInterrupt = () => {
</div> </div>
</template> </template>
<style lang="scss" scoped> <style scoped lang="scss">
.homedepot-sidepanel { .homedepot-sidepanel {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -1,7 +1,6 @@
import { createEndpointRuntime } from './internal/endpoint-runtime'; import { createEndpointRuntime } from './internal/endpoint-runtime';
import { createStreamWirings } from './internal/stream'; import { createStreamWirings } from './internal/stream';
import { createPersistentPort } from './internal/persistent-port'; import { createPersistentPort } from './internal/persistent-port';
import browser from 'webextension-polyfill';
// Chrome API types for sidepanel // Chrome API types for sidepanel
declare global { declare global {
@ -23,7 +22,8 @@ declare global {
getOptions: (options: { tabId?: number }) => Promise<{ path?: string }>; getOptions: (options: { tabId?: number }) => Promise<{ path?: string }>;
}; };
} }
var chrome: Chrome | undefined;
var chrome: Chrome;
} }
const port = createPersistentPort('sidepanel'); const port = createPersistentPort('sidepanel');
@ -31,71 +31,6 @@ const endpointRuntime = createEndpointRuntime('sidepanel', (message) => port.pos
port.onMessage(endpointRuntime.handleMessage); port.onMessage(endpointRuntime.handleMessage);
/**
* Set up Chrome's sidepanel API for Manifest V3 extensions
*
* This function initializes the Chrome sidepanel API and configures its behavior.
* Use this in your sidepanel entry point to ensure the sidepanel works correctly.
*
* Example usage in your sidepanel script:
*
* ```ts
* import { setupSidepanel, sendMessage } from 'webext-bridge/sidepanel'
*
* // Initialize the sidepanel
* setupSidepanel({ defaultPath: 'sidepanel.html' })
*
* // Send a message to background
* sendMessage('get-data', { key: 'value' }, 'background')
* .then(response => console.log(response))
*
* // Listen for messages from other contexts
* onMessage('update-sidebar', (message) => {
* console.log(message.data)
* // Update sidebar UI
* })
* ```
*
* @param options Configuration options for the sidepanel
* @param options.defaultPath Default HTML path for the sidepanel
*/
export function setupSidepanel(options: { defaultPath?: string } = {}) {
if (typeof chrome !== 'undefined' && chrome.sidePanel) {
// Chrome specific sidepanel API
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
if (options.defaultPath) {
chrome.sidePanel.setOptions({ path: options.defaultPath });
}
}
}
/**
*
* @param callback
* @returns
*/
export function onSidepanelShown(callback: () => void): () => void {
if (typeof chrome !== 'undefined' && chrome.sidePanel && chrome.sidePanel.onShown) {
chrome.sidePanel.onShown.addListener(callback);
return () => chrome.sidePanel.onShown.removeListener(callback);
}
return () => {};
}
/**
*
* @param callback
* @returns
*/
export function onSidepanelHidden(callback: () => void): () => void {
if (typeof chrome !== 'undefined' && chrome.sidePanel && chrome.sidePanel.onHidden) {
chrome.sidePanel.onHidden.addListener(callback);
return () => chrome.sidePanel.onHidden.removeListener(callback);
}
return () => {};
}
export function isSidepanelSupported(): boolean { export function isSidepanelSupported(): boolean {
return !!chrome.sidePanel; return !!chrome.sidePanel;
} }