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');
}
Object.assign(self, { appContext: 'background' });
// remove or turn this off if you don't use side panel
const USE_SIDE_PANEL = true;

View File

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

View File

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

View File

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

View File

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

View File

@ -155,7 +155,7 @@ const handleExport = () => {
</div>
</template>
<style lang="scss" scoped>
<style scoped lang="scss">
.review-list {
display: flex;
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 { Ref } from 'vue-demi';
import type { Storage } from 'webextension-polyfill';
import { useAppContext } from './useAppContext';
export type WebExtensionStorageOptions<T> = UseStorageAsyncOptions<T>;
@ -121,8 +120,7 @@ export function useWebExtensionStorage<T>(
return;
}
if (typeof listenToStorageChanges === 'string') {
const { appContext: context } = useAppContext();
if (listenToStorageChanges !== context) {
if (listenToStorageChanges !== appContext) {
return;
}
}

View File

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

4
src/global.d.ts vendored
View File

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

View File

@ -1,5 +1,4 @@
import type { App } from 'vue';
import { useAppContext } from '~/composables/useAppContext';
import { router } from '~/router';
/**
@ -7,11 +6,9 @@ import { router } from '~/router';
* @param app Vue app
*/
export function setupApp(app: App) {
const { appContext: context } = useAppContext();
// Inject a globally available `$app` object in template
app.config.globalProperties.$app = {
context,
context: appContext,
};
// 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 '../styles';
// This is the options page of the extension.
Object.assign(self, { appContext: 'options' });
const app = createApp(App);
setupApp(app);
app.mount('#app');

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,6 @@
import { createEndpointRuntime } from './internal/endpoint-runtime';
import { createStreamWirings } from './internal/stream';
import { createPersistentPort } from './internal/persistent-port';
import browser from 'webextension-polyfill';
// Chrome API types for sidepanel
declare global {
@ -23,7 +22,8 @@ declare global {
getOptions: (options: { tabId?: number }) => Promise<{ path?: string }>;
};
}
var chrome: Chrome | undefined;
var chrome: Chrome;
}
const port = createPersistentPort('sidepanel');
@ -31,71 +31,6 @@ const endpointRuntime = createEndpointRuntime('sidepanel', (message) => port.pos
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 {
return !!chrome.sidePanel;
}