Commit 307cb86f authored by baotlake's avatar baotlake Committed by Domi

feat: context menu

parent 0cc896d9
...@@ -20,6 +20,14 @@ export declare global { ...@@ -20,6 +20,14 @@ export declare global {
platform: string platform: string
} }
} }
interface PromiseConstructor {
withResolvers: () => {
promise: Promise<any>
resolve: (value: any) => void
reject: (reason: any) => void
}
}
} }
export {} export {}
This diff is collapsed.
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
}, },
"dependencies": { "dependencies": {
"@firebase/remote-config": "^0.4.5", "@firebase/remote-config": "^0.4.5",
"@types/chrome": "^0.0.267",
"@types/turndown": "^5.0.4", "@types/turndown": "^5.0.4",
"@vueuse/core": "^10.7.1", "@vueuse/core": "^10.7.1",
"@vueuse/gesture": "^2.0.0-beta.1", "@vueuse/gesture": "^2.0.0-beta.1",
...@@ -36,7 +37,6 @@ ...@@ -36,7 +37,6 @@
"@crxjs/vite-plugin": "^2.0.0-beta.23", "@crxjs/vite-plugin": "^2.0.0-beta.23",
"@intlify/unplugin-vue-i18n": "^1.5.0", "@intlify/unplugin-vue-i18n": "^1.5.0",
"@tsconfig/node18": "^18.2.2", "@tsconfig/node18": "^18.2.2",
"@types/chrome": "^0.0.261",
"@types/lodash-es": "^4.17.11", "@types/lodash-es": "^4.17.11",
"@types/node": "^18.18.5", "@types/node": "^18.18.5",
"@vitejs/plugin-vue": "^4.4.0", "@vitejs/plugin-vue": "^4.4.0",
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
"@vue/tsconfig": "^0.4.0", "@vue/tsconfig": "^0.4.0",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"gulp": "^4.0.2", "gulp": "^5.0.0",
"gulp-zip": "^6.0.0", "gulp-zip": "^6.0.0",
"npm-run-all2": "^6.1.1", "npm-run-all2": "^6.1.1",
"postcss": "^8.4.31", "postcss": "^8.4.31",
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
"sass": "^1.71.1", "sass": "^1.71.1",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"typescript": "~5.2.0", "typescript": "~5.2.0",
"vite": "^4.4.11", "vite": "^4.5.3",
"vitest": "^1.1.0", "vitest": "^1.1.0",
"vue-tsc": "^1.8.19" "vue-tsc": "^1.8.19"
} }
......
...@@ -61,6 +61,8 @@ ...@@ -61,6 +61,8 @@
--fg-hsl: var(--fg-hue) var(--fg-s) var(--fg-l); --fg-hsl: var(--fg-hue) var(--fg-s) var(--fg-l);
--section-gap: 160px; --section-gap: 160px;
color-scheme: light;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
...@@ -88,6 +90,8 @@ ...@@ -88,6 +90,8 @@
--fg-hue: 200; --fg-hue: 200;
--fg-s: 0%; --fg-s: 0%;
--fg-l: 100%; --fg-l: 100%;
color-scheme: dark;
} }
input, input,
...@@ -105,13 +109,26 @@ ...@@ -105,13 +109,26 @@
} }
body, body,
#app { #app,
:root {
color: var(--color-text); color: var(--color-text);
background: var(--color-background); background: var(--color-background);
transition: color 0.5s, background-color 0.5s; /* transition:
color 0.5s,
background-color 0.5s; */
line-height: 1.6; line-height: 1.6;
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, font-family:
Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Inter,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
Oxygen,
Ubuntu,
Cantarell,
"Fira Sans",
"Droid Sans",
"Helvetica Neue",
sans-serif; sans-serif;
font-size: 15px; font-size: 15px;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
......
...@@ -12,8 +12,8 @@ import { ...@@ -12,8 +12,8 @@ import {
} from "./offscreen" } from "./offscreen"
import { import {
registerContentSidebar, registerContentSidebar,
unregisterContentSidebar,
handleContentMounted, handleContentMounted,
getContentSidebarItem,
} from "./sidebar" } from "./sidebar"
import config from "@/assets/config.json" import config from "@/assets/config.json"
import { allFrameScript, contentMainScript } from "@/manifest" import { allFrameScript, contentMainScript } from "@/manifest"
...@@ -39,13 +39,29 @@ chrome.commands.onCommand.addListener(handleCommand) ...@@ -39,13 +39,29 @@ chrome.commands.onCommand.addListener(handleCommand)
chrome.runtime.onStartup.addListener(() => { chrome.runtime.onStartup.addListener(() => {
updateConfig() updateConfig()
}) })
chrome.runtime.onInstalled.addListener(handleOnInstalled)
chrome.contextMenus.onClicked.addListener(handleContextMenusClicked)
if (isEdge) { if (isEdge && chrome.sidePanel) {
chrome.sidePanel.setOptions({ chrome.sidePanel.setOptions({
enabled: false, enabled: false,
}) })
} }
async function handleOnInstalled(details: chrome.runtime.InstalledDetails) {
chrome.contextMenus.create({
contexts: ["action"],
id: "toggle-sidebar",
title: "Toggle Sidebar",
})
chrome.contextMenus.create({
contexts: ["link"],
id: "open-in-sidebar",
title: "Open Link in Sidebar",
})
}
async function openPipBackground(url: string) { async function openPipBackground(url: string) {
const tab = await chrome.tabs.create({ const tab = await chrome.tabs.create({
url: url, url: url,
...@@ -101,16 +117,16 @@ async function handleInvokeRequest( ...@@ -101,16 +117,16 @@ async function handleInvokeRequest(
sender: chrome.runtime.MessageSender sender: chrome.runtime.MessageSender
) { ) {
currentSender = sender currentSender = sender
let result = await messageInvoke.handleReqMsg(message) let res = await messageInvoke.handleReqMsg(message)
if (!sender.tab?.id) { if (!sender.tab?.id) {
console.error("sender tab id is undefined", sender) console.error("sender tab id is undefined", sender)
} }
if (result) { if (res) {
chrome.tabs.sendMessage(sender.tab?.id!, { chrome.tabs.sendMessage(sender.tab?.id!, {
type: MessageType.invokeResponse, type: MessageType.invokeResponse,
...result, ...res,
}) })
} }
} }
...@@ -134,24 +150,22 @@ function handleMessage(message: any, sender: chrome.runtime.MessageSender) { ...@@ -134,24 +150,22 @@ function handleMessage(message: any, sender: chrome.runtime.MessageSender) {
chrome.tabs.sendMessage(message.tabId, message.message) chrome.tabs.sendMessage(message.tabId, message.message)
break break
case MessageType.invokeRequest: case MessageType.invokeRequest:
handleInvokeRequest(message, sender) currentSender = sender
messageInvoke.handleReqMsg(message, sender)
break break
case MessageType.invokeResponse: case MessageType.invokeResponse:
messageInvoke.handleResMsg(message) messageInvoke.handleResMsg(message)
break break
case MessageType.contentMounted: case MessageType.contentMounted:
handleContentMounted(sender.tab!.id!) handleContentMounted(sender)
break break
case MessageType.registerContentSidebar: case MessageType.registerContentSidebar:
registerContentSidebar({ tabId: sender.tab!.id!, url: message.url }) registerContentSidebar(sender.tab!.id!, message.info)
break
case MessageType.unregisterContentSidebar:
unregisterContentSidebar(sender.tab!.id!)
break break
} }
} }
async function handleToggleMinimize() { async function toggleMinimize() {
const { pipWindow } = await chrome.storage.local.get({ pipWindow: null }) const { pipWindow } = await chrome.storage.local.get({ pipWindow: null })
if (!pipWindow) return if (!pipWindow) return
const windowInfo = await chrome.windows.get(pipWindow.windowId) const windowInfo = await chrome.windows.get(pipWindow.windowId)
...@@ -160,17 +174,65 @@ async function handleToggleMinimize() { ...@@ -160,17 +174,65 @@ async function handleToggleMinimize() {
const tabId = pipWindow.tabId const tabId = pipWindow.tabId
messageInvoke.invoke({ messageInvoke.invoke({
tabId,
func: ServiceFunc.toggleMinimize, func: ServiceFunc.toggleMinimize,
args: [], args: [],
tabId,
})
}
async function toggleSidebar(tab?: chrome.tabs.Tab) {
if (tab?.windowId) {
chrome.sidePanel.open({
windowId: tab?.windowId,
})
}
messageInvoke.invoke({
func: ServiceFunc.toggleSidebar,
args: [],
}) })
} }
function handleCommand(command: string) { async function openInSidebar(url: string, tab?: chrome.tabs.Tab) {
let contentMode = isEdge || !chrome.sidePanel
const contentSidebar = getContentSidebarItem(tab?.id!)
if (contentSidebar && contentSidebar.visible) {
contentMode = true
}
if (contentMode) {
await messageInvoke.invoke({
func: ServiceFunc.toggleContentSidebar,
args: [{ visible: true, collapse: false }],
tabId: tab?.id,
})
} else {
await chrome.sidePanel.open({
windowId: tab?.windowId!,
})
}
await messageInvoke.invoke({
key: ServiceFunc.waitSidebar,
func: ServiceFunc.waitSidebar,
args: [],
timeout: 300,
})
await messageInvoke.invoke({
func: ServiceFunc.openInSidebar,
args: [{ urls: [url] }],
})
}
function handleCommand(command: string, tab?: chrome.tabs.Tab) {
console.log("command: ", command) console.log("command: ", command)
switch (command) { switch (command) {
case "toggleMinimize": case "toggleMinimize":
handleToggleMinimize() toggleMinimize()
break
case "toggleSidebar":
toggleSidebar(tab)
break break
} }
} }
...@@ -234,3 +296,18 @@ async function updateConfig() { ...@@ -234,3 +296,18 @@ async function updateConfig() {
loadCandidates, loadCandidates,
}) })
} }
async function handleContextMenusClicked(
info: chrome.contextMenus.OnClickData,
tab?: chrome.tabs.Tab
) {
console.log(info)
switch (info.menuItemId) {
case "toggle-sidebar":
toggleSidebar(tab)
break
case "open-in-sidebar":
openInSidebar(info.linkUrl!, tab)
break
}
}
import { MessageType } from "@/types" import { MessageType } from "@/types"
import { messageInvoke } from "@/utils/invoke"
import { ServiceFunc } from "@/types"
import { openSidebar } from "@/utils/ext"
type ContentSidebar = { type ContentSidebarItem = {
visible: boolean
tabId: number tabId: number
url?: string urls: string[]
} }
const contentSidebarMap = new Map<number, ContentSidebar>() const contentSidebarMap = new Map<number, ContentSidebarItem>()
export function registerContentSidebar({ tabId, url }: ContentSidebar) { export function registerContentSidebar(
contentSidebarMap.set(tabId, { tabId, url }) tabId: number,
info: Partial<ContentSidebarItem>
) {
const item = contentSidebarMap.get(tabId) || { visible: false, urls: [] }
contentSidebarMap.set(tabId, {
...item,
...info,
tabId,
})
} }
export function unregisterContentSidebar(tabId: number) { export async function handleContentMounted(
contentSidebarMap.delete(tabId) sender: chrome.runtime.MessageSender
} ) {
if (sender.frameId !== 0) return
if (!sender.tab) return
const tabId = sender.tab.id!
export async function handleContentMounted(tabId: number) { const item = contentSidebarMap.get(tabId)
const sidebar = contentSidebarMap.get(tabId) console.log("handleContentMounted", item)
if (sidebar) { if (item && item.visible) {
await chrome.storage.session.set({ openSidebar({
sidebarInitUrl: { content: sidebar.url }, urls: item.urls,
}) tab: sender.tab,
chrome.tabs.sendMessage(tabId, { sidePanel: false,
type: MessageType.openContentSidebar,
url: sidebar.url,
}) })
} }
} }
export function getContentSidebarItem(tabId: number) {
return contentSidebarMap.get(tabId)
}
...@@ -109,6 +109,9 @@ onMounted(() => { ...@@ -109,6 +109,9 @@ onMounted(() => {
window.addEventListener("message", handleMessage) window.addEventListener("message", handleMessage)
chrome.runtime.sendMessage({ chrome.runtime.sendMessage({
type: MessageType.registerContentSidebar, type: MessageType.registerContentSidebar,
info: {
visible: true,
},
}) })
}) })
...@@ -118,8 +121,12 @@ onUnmounted(() => { ...@@ -118,8 +121,12 @@ onUnmounted(() => {
window.removeEventListener("keyup", handleKeyUp) window.removeEventListener("keyup", handleKeyUp)
window.removeEventListener("message", handleMessage) window.removeEventListener("message", handleMessage)
chrome.runtime.sendMessage({ chrome.runtime.sendMessage({
type: MessageType.unregisterContentSidebar, type: MessageType.registerContentSidebar,
info: {
visible: false,
},
}) })
console.log("content sidebar unmounted")
}) })
</script> </script>
......
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue" import { ref, onMounted } from "vue"
import ContentSidebar from "./ContentSidebar.vue" import ContentSidebar from "./ContentSidebar.vue"
import { sidebarAddon } from "@/store/content" import { sidebarAddon } from "@/store/content"
import { autoPointerCapture } from "@/utils/dom" import { autoPointerCapture } from "@/utils/dom"
import { messageInvoke } from "@/utils/invoke"
import { ServiceFunc } from "@/types"
const logoUrl = chrome.runtime.getURL("/logo.svg") const logoUrl = chrome.runtime.getURL("/logo.svg")
const top = ref(72) const top = ref(72)
...@@ -13,18 +15,37 @@ function handlePointerMove(e: PointerEvent) { ...@@ -13,18 +15,37 @@ function handlePointerMove(e: PointerEvent) {
top.value = Math.max(12, Math.min(window.innerHeight - 60, value)) top.value = Math.max(12, Math.min(window.innerHeight - 60, value))
} }
} }
onMounted(() => {
messageInvoke.register(
ServiceFunc.toggleContentSidebar,
async ({ visible, collapse }: { visible: boolean; collapse?: boolean }) => {
sidebarAddon.visible = visible ?? !sidebarAddon.visible
sidebarAddon.collapse = collapse ?? sidebarAddon.collapse
// how to
awai messageInvoke.invoke({
key: ServiceFunc.waitSidebar,
func: ServiceFunc.waitSidebar,
args: [],
timeout: 300,
})
}
)
})
</script> </script>
<template> <template>
<ContentSidebar <ContentSidebar
v-if="sidebarAddon.visible" v-if="sidebarAddon.visible"
:hidden="sidebarAddon.hidden" :hidden="sidebarAddon.collapse"
@close="sidebarAddon.visible = false" @close="sidebarAddon.visible = false"
@hide="sidebarAddon.hidden = true" @hide="sidebarAddon.collapse = true"
/> />
<div <div
v-if="sidebarAddon.hidden" v-if="sidebarAddon.collapse"
:class="[ :class="[
'fixed top-16 right-0 p-1.5 bg-background/65 backdrop-blur-md cursor-pointer', 'fixed top-16 right-0 p-1.5 bg-background/65 backdrop-blur-md cursor-pointer',
'rounded-s shadow-lg z-[999999] hover:bg-background-mute/65', 'rounded-s shadow-lg z-[999999] hover:bg-background-mute/65',
...@@ -32,10 +53,10 @@ function handlePointerMove(e: PointerEvent) { ...@@ -32,10 +53,10 @@ function handlePointerMove(e: PointerEvent) {
:style="{ top: top + 'px' }" :style="{ top: top + 'px' }"
@pointerdown="autoPointerCapture" @pointerdown="autoPointerCapture"
@pointermove="handlePointerMove" @pointermove="handlePointerMove"
@click="sidebarAddon.hidden = false" @click="sidebarAddon.collapse = false"
> >
<img :src="logoUrl" class="size-4 pointer-events-none select-none" /> <img :src="logoUrl" class="size-4 pointer-events-none select-none" />
</div> </div>
</template> </template>
<style scoped></style> <style scoped></style>
\ No newline at end of file
...@@ -81,7 +81,7 @@ const handleDrop = (e: DragEvent) => { ...@@ -81,7 +81,7 @@ const handleDrop = (e: DragEvent) => {
<template> <template>
<div <div
:class="[ :class="[
'flex gap-1 items-center justify-between h-9 px-1 z-10 shadow-sm', 'flex gap-1 items-center justify-between h-9 px-1 z-10 shadow-sm shrink-0',
'*:size-7 *:flex *:items-center *:justify-center ', '*:size-7 *:flex *:items-center *:justify-center ',
]" ]"
@dragenter="handleDragEnter" @dragenter="handleDragEnter"
......
...@@ -29,7 +29,7 @@ const emit = defineEmits({ ...@@ -29,7 +29,7 @@ const emit = defineEmits({
<Search @go="(url) => emit('go', url)" /> <Search @go="(url) => emit('go', url)" />
</div> </div>
<div class="flex flex-wrap gap-x-4 gap-y-4 justify-center"> <div class="flex flex-wrap gap-x-3 gap-y-4 justify-center">
<SiteButton <SiteButton
v-for="item of recentItems.slice(0, 12)" v-for="item of recentItems.slice(0, 12)"
:key="item.url" :key="item.url"
...@@ -44,7 +44,7 @@ const emit = defineEmits({ ...@@ -44,7 +44,7 @@ const emit = defineEmits({
<!-- <div class="text-center my-3">Popular</div> --> <!-- <div class="text-center my-3">Popular</div> -->
<div class="w-full my-6 border-b border-background-soft h-0"></div> <div class="w-full my-6 border-b border-background-soft h-0"></div>
<div class="flex flex-wrap gap-x-4 gap-y-4 justify-center"> <div class="flex flex-wrap gap-x-3 gap-y-4 justify-center">
<SiteButton <SiteButton
v-for="item of popularItems" v-for="item of popularItems"
:icon="item.icon" :icon="item.icon"
......
...@@ -12,7 +12,9 @@ const { t } = useI18n() ...@@ -12,7 +12,9 @@ const { t } = useI18n()
const topFrame = window.parent == window const topFrame = window.parent == window
onMounted(() => { onMounted(() => {
chrome.runtime?.sendMessage({ type: MessageType.contentMounted }) if (topFrame) {
chrome.runtime?.sendMessage({ type: MessageType.contentMounted })
}
}) })
</script> </script>
...@@ -24,4 +26,4 @@ onMounted(() => { ...@@ -24,4 +26,4 @@ onMounted(() => {
<ChatDocsAddon /> <ChatDocsAddon />
<ContentSidebarAddon v-if="topFrame" /> <ContentSidebarAddon v-if="topFrame" />
</template> </template>
\ No newline at end of file
...@@ -46,16 +46,15 @@ function handleMessage( ...@@ -46,16 +46,15 @@ function handleMessage(
}) })
sendResponse({ type: MessageType.contentHere }) sendResponse({ type: MessageType.contentHere })
break break
case MessageType.invokeResponse:
messageInvoke.handleResMsg(message)
break
case MessageType.showChatDocs: case MessageType.showChatDocs:
chatDocsPanel.visible = true chatDocsPanel.visible = true
break break
case MessageType.openContentSidebar: // case MessageType.openContentSidebar:
sidebarAddon.visible = true // sidebarAddon.visible = true
sidebarAddon.hidden = false // sidebarAddon.collapse = false
sidebarAddon.url = message.url // break
case MessageType.invokeResponse:
messageInvoke.handleResMsg(message)
break break
case MessageType.invokeRequest: case MessageType.invokeRequest:
messageInvoke.handleReqMsg(message).then((result) => { messageInvoke.handleReqMsg(message).then((result) => {
...@@ -172,12 +171,12 @@ function handleFrameMessage(e: MessageEvent) { ...@@ -172,12 +171,12 @@ function handleFrameMessage(e: MessageEvent) {
detail: { url: e.data.url }, detail: { url: e.data.url },
}) })
case FrameMessageType.invokeRequest: case FrameMessageType.invokeRequest:
handleInvokeRequest(e.data, e.source as Window) handleWebviewInvokeRequest(e.data, e.source as Window)
break break
} }
} }
async function handleInvokeRequest(message: any, source: Window) { async function handleWebviewInvokeRequest(message: any, source: Window) {
const { key, func, args } = message const { key, func, args } = message
let result = null let result = null
let error = null let error = null
......
...@@ -77,6 +77,7 @@ const manifest = { ...@@ -77,6 +77,7 @@ const manifest = {
"sidePanel", "sidePanel",
"declarativeNetRequestWithHostAccess", "declarativeNetRequestWithHostAccess",
"declarativeNetRequestFeedback", "declarativeNetRequestFeedback",
"contextMenus",
// "cookies", // "cookies",
], ],
optional_permissions: [], optional_permissions: [],
...@@ -90,6 +91,12 @@ const manifest = { ...@@ -90,6 +91,12 @@ const manifest = {
description: "__MSG_toggle_minimize_desc__", description: "__MSG_toggle_minimize_desc__",
global: true, global: true,
}, },
toggleSidebar: {
suggested_key: {
default: "Ctrl+0",
},
description: "Toggle Sidebar",
},
}, },
web_accessible_resources: [ web_accessible_resources: [
{ {
...@@ -100,9 +107,9 @@ const manifest = { ...@@ -100,9 +107,9 @@ const manifest = {
], ],
content_security_policy: { content_security_policy: {
extension_pages: __DEV__ extension_pages: __DEV__
? `script-src 'self' http://localhost:3000 'wasm-unsafe-eval';` ? `script-src 'self' http://localhost:5173 'wasm-unsafe-eval';`
: `script-src 'self' 'wasm-unsafe-eval'`, : `script-src 'self' 'wasm-unsafe-eval'`,
}, },
} satisfies Manifest as unknown as chrome.runtime.Manifest } satisfies Manifest as any
export default manifest export default manifest
...@@ -20,7 +20,7 @@ import IconArrowCircleRight from "@/components/icons/IconArrowCircleRight.vue" ...@@ -20,7 +20,7 @@ import IconArrowCircleRight from "@/components/icons/IconArrowCircleRight.vue"
import IconSplitscreenRight from "@/components/icons/IconSplitscreenRight.vue" import IconSplitscreenRight from "@/components/icons/IconSplitscreenRight.vue"
import IconLogo from "@/components/icons/IconLogo.vue" import IconLogo from "@/components/icons/IconLogo.vue"
import SiteButton from "@/components/SiteButton.vue" import SiteButton from "@/components/SiteButton.vue"
import { getIsEdge } from "@/utils/ext" import { getIsEdge, openSidebar } from "@/utils/ext"
import { handleImgError } from "@/utils/dom" import { handleImgError } from "@/utils/dom"
import { homeUrl, feedbackUrl } from "@/utils/const" import { homeUrl, feedbackUrl } from "@/utils/const"
...@@ -113,7 +113,9 @@ function handleKeydown(e: KeyboardEvent) { ...@@ -113,7 +113,9 @@ function handleKeydown(e: KeyboardEvent) {
} }
if (e.code == "Backslash" && (e.ctrlKey || e.metaKey)) { if (e.code == "Backslash" && (e.ctrlKey || e.metaKey)) {
e.preventDefault() e.preventDefault()
openSidebar() openSidebar({
urls: [],
}).then(() => close())
} }
if (e.code == "ControlLeft" || e.code == "ControlRight") { if (e.code == "ControlLeft" || e.code == "ControlRight") {
...@@ -158,30 +160,7 @@ async function handlePipPopup() { ...@@ -158,30 +160,7 @@ async function handlePipPopup() {
window.close() window.close()
} }
async function openSidebar(url = "") { function close() {
chrome.runtime.sendMessage({
type: MessageType.openInSidebar,
tabId: -1,
url,
})
const win = await chrome.windows.getCurrent()
await chrome.storage.session.set({
sidebarInitUrl: { sidepanel: url },
})
await chrome.sidePanel.open({ windowId: win.id! })
window.close()
}
async function openContentSidebar(url = "") {
await chrome.storage.session.set({
sidebarInitUrl: { content: url },
})
await chrome.tabs.sendMessage(activeTab.value.id!, {
type: MessageType.openContentSidebar,
tabId: activeTab.value.id,
url,
})
window.close() window.close()
} }
...@@ -308,17 +287,18 @@ function showChatDocs() { ...@@ -308,17 +287,18 @@ function showChatDocs() {
:disabled="isEdge && !avaiable" :disabled="isEdge && !avaiable"
:title="t('openInSidebar')" :title="t('openInSidebar')"
@click=" @click="
() => { (e) => {
if (isEdge || keyboard.ctrl) { openSidebar({
return openContentSidebar() urls: [activeTab.url!],
} sidePanel: !(isEdge || e.ctrlKey || e.metaKey),
openSidebar(activeTab.url) tab: activeTab,
}).then(() => close())
} }
" "
> >
<IconSplitscreenRight class="size-8 shrink-0 scale-95" /> <IconSplitscreenRight class="size-8 shrink-0 scale-95" />
<div class="flex items-center justify-between flex-1 w-2/3 gap-2"> <div class="flex items-center justify-between flex-1 w-2/3 gap-2">
<span class="text-sm font-bold leading-4 truncate"> <span class="text-base font-bold leading-4 truncate">
{{ isEdge ? t("openSidebar") : t("openInSidebar") }} {{ isEdge ? t("openSidebar") : t("openInSidebar") }}
</span> </span>
<span class="text-xs">CTRL + \</span> <span class="text-xs">CTRL + \</span>
...@@ -372,10 +352,10 @@ function showChatDocs() { ...@@ -372,10 +352,10 @@ function showChatDocs() {
return handleClickLaunch(item.url) return handleClickLaunch(item.url)
} }
if (isEdge || keyboard.ctrl) { openSidebar({
return openContentSidebar(item.url) urls: [item.url],
} sidePanel: !(isEdge || keyboard.ctrl),
openSidebar(item.url) }).then(() => close())
} }
" "
/> />
......
...@@ -10,7 +10,8 @@ import { ...@@ -10,7 +10,8 @@ import {
import config from "@/assets/config.json" import config from "@/assets/config.json"
import Webview, { type PageInfo } from "@/components/Webview.vue" import Webview, { type PageInfo } from "@/components/Webview.vue"
import { useI18n } from "@/utils/i18n" import { useI18n } from "@/utils/i18n"
import { MessageType } from "@/types" import { messageInvoke } from "@/utils/invoke"
import { MessageType, ServiceFunc } from "@/types"
import SidebarHome from "@/components/sidebar/SidebarHome.vue" import SidebarHome from "@/components/sidebar/SidebarHome.vue"
import Navbar from "@/components/sidebar/Navbar.vue" import Navbar from "@/components/sidebar/Navbar.vue"
import { FrameMessageType } from "@/types" import { FrameMessageType } from "@/types"
...@@ -121,38 +122,34 @@ onMounted(() => { ...@@ -121,38 +122,34 @@ onMounted(() => {
} }
) )
getSession({
sidebarInitUrl: {} as Record<string, string>,
}).then(({ sidebarInitUrl }) => {
console.log("[sidebar]", sidebarInitUrl, mode.value)
const url = sidebarInitUrl?.[mode.value]
if (url && !isProtectedUrl(url)) {
go(url)
}
})
chrome.tabs.getCurrent().then((t) => { chrome.tabs.getCurrent().then((t) => {
currentTab.tabId = t?.id || -1 currentTab.tabId = t?.id || -1
}) })
chrome.runtime.onMessage.addListener(handleMessage) const mountedAt = Date.now()
}) messageInvoke
.register(ServiceFunc.waitSidebar, () => {})
.register(ServiceFunc.toggleSidebar, () => {
if (Date.now() - mountedAt > 200) {
window.close()
}
})
.register(ServiceFunc.openInSidebar, ({ urls }: { urls: string[] }) => {
urls.forEach((url) => {
if (!isProtectedUrl(url)) {
go(url)
}
})
})
onUnmounted(() => { chrome.runtime.sendMessage({
chrome.runtime.onMessage.removeListener(handleMessage) type: MessageType.invokeResponse,
key: ServiceFunc.waitSidebar,
success: true,
value: null,
})
}) })
async function handleMessage(message: any) {
switch (message.type) {
case MessageType.openInSidebar:
const url = message.url
if (message.tabId == currentTab.tabId && !isProtectedUrl(url)) {
go(url)
}
break
}
}
function handlePageLoad(i: number) { function handlePageLoad(i: number) {
pagesInfo[i] = { ...pagesInfo[i]!, loading: true } pagesInfo[i] = { ...pagesInfo[i]!, loading: true }
} }
...@@ -163,7 +160,9 @@ async function handlePageLoaded(i: number, pageInfo: PageInfo) { ...@@ -163,7 +160,9 @@ async function handlePageLoaded(i: number, pageInfo: PageInfo) {
if (mode.value === "content") { if (mode.value === "content") {
chrome.runtime.sendMessage({ chrome.runtime.sendMessage({
type: MessageType.registerContentSidebar, type: MessageType.registerContentSidebar,
pages: pages, info: {
urls: pages.map((p) => p.url),
},
}) })
} }
...@@ -328,7 +327,12 @@ function handlePointerLeave() { ...@@ -328,7 +327,12 @@ function handlePointerLeave() {
@drop="go" @drop="go"
/> />
<div :class="['w-full h-full', { hidden: active != -1 }]"> <div
:class="[
'scrollbar w-full h-full flex-1 overflow-auto ',
{ hidden: active != -1 },
]"
>
<SidebarHome <SidebarHome
:recentItems="recentItems" :recentItems="recentItems"
:popularItems="popularItems" :popularItems="popularItems"
......
...@@ -16,5 +16,8 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { ...@@ -16,5 +16,8 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
case MessageType.invokeResponse: case MessageType.invokeResponse:
messageInvoke.handleResMsg(message) messageInvoke.handleResMsg(message)
break break
case MessageType.invokeRequest:
messageInvoke.handleReqMsg(message, sender)
break
} }
}) })
...@@ -2,9 +2,23 @@ import "@/assets/main.css" ...@@ -2,9 +2,23 @@ import "@/assets/main.css"
import { createApp } from "vue" import { createApp } from "vue"
import { i18n } from "@/utils/i18n" import { i18n } from "@/utils/i18n"
import { messageInvoke } from "@/utils/invoke"
import Sidebar from "./Sidebar.vue" import Sidebar from "./Sidebar.vue"
import { MessageType } from "@/types"
const app = createApp(Sidebar) const app = createApp(Sidebar)
app.use(i18n) app.use(i18n)
app.mount("#app") app.mount("#app")
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
console.log("[sidebar] ", message.type, message, sender)
switch (message.type) {
case MessageType.invokeResponse:
messageInvoke.handleResMsg(message)
break
case MessageType.invokeRequest:
messageInvoke.handleReqMsg(message, sender)
break
}
})
...@@ -23,8 +23,7 @@ export const docsAddon = reactive({ ...@@ -23,8 +23,7 @@ export const docsAddon = reactive({
export const sidebarAddon = reactive({ export const sidebarAddon = reactive({
visible: false, visible: false,
hidden: false, collapse: false,
url: "",
}) })
type DocInputItem = { type DocInputItem = {
......
declare namespace chrome.declarativeNetRequest { declare namespace chrome.declarativeNetRequest {
export function getSessionRules(filter: { export function getSessionRules(filter: {
ruleIds: number[] ruleIds: number[]
}): Promise<Rule[]> }): Promise<chrome.declarativeNetRequest.Rule[]>
} }
declare interface ManifestPatch { declare interface ManifestPatch {
......
...@@ -6,20 +6,14 @@ export enum MessageType { ...@@ -6,20 +6,14 @@ export enum MessageType {
bgPipLaunch = "bg-pip-launch", bgPipLaunch = "bg-pip-launch",
pipLaunch = "pip-launch", pipLaunch = "pip-launch",
contentMounted = "content-mount", contentMounted = "content-mount",
// getPipWinInfo = "get-pip-win-info",
// pipWinInfo = "pip-win-info",
updateWindow = "update-window", updateWindow = "update-window",
removeWindow = "remove-window", removeWindow = "remove-window",
forwardToTab = "forward-to-tab", forwardToTab = "forward-to-tab",
// toOffscreen = "to-offscreen",
// fromOffscreen = "from-offscreen",
invokeRequest = "invoke-request", invokeRequest = "invoke-request",
invokeResponse = "invoke-Response", invokeResponse = "invoke-Response",
showChatDocs = "show-chat-docs", showChatDocs = "show-chat-docs",
openInSidebar = "open-in-sidebar", openInSidebar = "open-in-sidebar",
registerContentSidebar = "register-content-sidebar", registerContentSidebar = "register-content-sidebar",
unregisterContentSidebar = "unregister-content-sidebar",
openContentSidebar = "open-content-sidebar",
} }
export enum ServiceFunc { export enum ServiceFunc {
...@@ -33,6 +27,10 @@ export enum ServiceFunc { ...@@ -33,6 +27,10 @@ export enum ServiceFunc {
getPipWindow = "get-pip-window", getPipWindow = "get-pip-window",
getMyTab = "get-my-tab", getMyTab = "get-my-tab",
waitOffscreen = "wait-offscreen", waitOffscreen = "wait-offscreen",
toggleSidebar = "toggle-sidebar",
toggleContentSidebar = "toggle-content-sidebar",
waitSidebar = "wait-sidebar",
openInSidebar = "open-in-sidebar",
} }
export type ParseDocOptions = { export type ParseDocOptions = {
......
import { contentMainScript } from "@/manifest" import { contentMainScript } from "@/manifest"
import { MessageType } from "@/types" import { MessageType, ServiceFunc } from "@/types"
import { messageInvoke } from "./invoke"
type MessageSender = chrome.runtime.MessageSender type MessageSender = chrome.runtime.MessageSender
...@@ -346,3 +347,60 @@ export async function getPipWindow({ ...@@ -346,3 +347,60 @@ export async function getPipWindow({
const win = windows.find((w) => w.width === width && w.height === height) const win = windows.find((w) => w.width === width && w.height === height)
return win return win
} }
export async function openSidebar({
urls,
tab,
sidePanel,
}: {
urls: string[]
tab?: chrome.tabs.Tab
sidePanel?: boolean
}) {
sidePanel = sidePanel ?? !!chrome.sidePanel
if (sidePanel) {
let windowId = tab?.windowId
if (!windowId) {
const win = await chrome.windows.getCurrent()
windowId = win.id
}
await chrome.sidePanel.open({
windowId: windowId!,
})
await messageInvoke.invoke({
key: ServiceFunc.waitSidebar,
func: ServiceFunc.waitSidebar,
args: [],
timeout: 300,
})
messageInvoke.invoke({
func: ServiceFunc.openInSidebar,
args: [{ urls }],
})
return
}
let tabId = tab?.id
if (!tabId) {
const tabs = await chrome.tabs.query({
active: true,
currentWindow: true,
})
tabId = tabs[0].id
}
await messageInvoke.invoke({
func: ServiceFunc.toggleContentSidebar,
args: [{ visible: true, collapse: false }],
tabId: tabId,
})
messageInvoke.invoke({
func: ServiceFunc.openInSidebar,
args: [{ urls }],
tabId: tabId,
})
}
type CallbackItem = {
resolve: (v: any) => void
reject: (e: any) => void
}
export interface InvokeReq { export interface InvokeReq {
func: string func: string
args: any[] args: any[]
...@@ -17,8 +12,10 @@ export interface InvokeRes { ...@@ -17,8 +12,10 @@ export interface InvokeRes {
value: any value: any
} }
type PromiseObject = ReturnType<PromiseConstructor["withResolvers"]>
export abstract class Invoke { export abstract class Invoke {
private pendingCallback: Record<string, CallbackItem> private pendingCallback: Record<string, PromiseObject>
private count: number private count: number
private name: string private name: string
private uniqueId: string private uniqueId: string
...@@ -39,28 +36,29 @@ export abstract class Invoke { ...@@ -39,28 +36,29 @@ export abstract class Invoke {
protected getReturnValue(key: string, req: InvokeReq) { protected getReturnValue(key: string, req: InvokeReq) {
let timer: any let timer: any
const { func, timeout } = req const { func, timeout } = req
const promise = new Promise<any>((resolve, reject) => {
this.pendingCallback[key] = { resolve, reject }
if (timeout) {
timer = setTimeout(
() => reject(`"${this.name}" invoke timeout: ${func} key: ${key}`),
timeout
)
}
})
promise.finally(() => { const p = this.pendingCallback[key] || Promise.withResolvers()
this.pendingCallback[key] = p
if (timeout) {
timer = setTimeout(
() => p.reject(`"${this.name}" invoke timeout: ${func} key: ${key}`),
timeout
)
}
p.promise.finally(() => {
delete this.pendingCallback[key] delete this.pendingCallback[key]
timer && clearTimeout(timer) timer && clearTimeout(timer)
}) })
return promise return p.promise
} }
protected setReturnValue(key: string, success: boolean, value: any) { protected setReturnValue(key: string, success: boolean, value: any) {
const callback = this.pendingCallback[key] const p = this.pendingCallback[key]
if (callback) { if (p) {
const fn = success != false ? callback.resolve : callback.reject const fn = success != false ? p.resolve : p.reject
fn(value) fn(value)
} else { } else {
console.error(`unknown invoke callback message: ${key}`) console.error(`unknown invoke callback message: ${key}`)
...@@ -69,12 +67,21 @@ export abstract class Invoke { ...@@ -69,12 +67,21 @@ export abstract class Invoke {
} }
abstract send(req: InvokeReq): Promise<{ key: string }> abstract send(req: InvokeReq): Promise<{ key: string }>
public sendRes?(res: InvokeRes, sender?: any): void
public handleResMsg(message: InvokeRes) { public handleResMsg(message: InvokeRes) {
const { key, success, value } = message const { key, success, value } = message
if (!key || typeof success !== "boolean") {
console.error(`invalid invoke response: ${key}`, message)
}
this.setReturnValue(key, success, value) this.setReturnValue(key, success, value)
} }
public async handleReqMsg(message: InvokeReq) { public async handleReqMsg(
message: InvokeReq & { key: string },
sender?: any
) {
const { key, func, args, timeout } = message const { key, func, args, timeout } = message
let result = null let result = null
let error = null let error = null
...@@ -92,7 +99,9 @@ export abstract class Invoke { ...@@ -92,7 +99,9 @@ export abstract class Invoke {
error = err error = err
} }
return { key, success: !error, value: !error ? result : error } const res = { key, success: !error, value: !error ? result : error }
this.sendRes?.(res, sender)
return res
} }
public async invoke<T = any>(req: InvokeReq): Promise<T> { public async invoke<T = any>(req: InvokeReq): Promise<T> {
......
import { MessageType, ServiceFunc, type ParseDocOptions } from "@/types" import { MessageType, ServiceFunc, type ParseDocOptions } from "@/types"
import Invoke, { type InvokeReq } from "./Invoke" import Invoke, { type InvokeReq, type InvokeRes } from "./Invoke"
class MessageInvoke extends Invoke { class MessageInvoke extends Invoke {
public async send(req: InvokeReq & { tabId?: number }) { public async send(req: InvokeReq & { tabId?: number }) {
...@@ -22,10 +22,21 @@ class MessageInvoke extends Invoke { ...@@ -22,10 +22,21 @@ class MessageInvoke extends Invoke {
return { key } return { key }
} }
public handleResMsg(message: any) { public sendRes(res: InvokeRes, sender?: chrome.runtime.MessageSender) {
if (message?.type === MessageType.invokeResponse) { if (!sender) {
const { key, success, value } = message return
this.setReturnValue(key, success, value) }
if (sender.tab?.id) {
chrome.tabs.sendMessage(sender.tab.id, {
type: MessageType.invokeResponse,
...res,
})
} else {
chrome.runtime.sendMessage({
type: MessageType.invokeResponse,
...res,
})
} }
} }
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
"include": [ "include": [
"utils", "utils",
"./src/manifest.ts", "./src/manifest.ts",
"./src/types/**/*.ts",
"package.json", "package.json",
"vite.config.*", "vite.config.*",
"vitest.config.*", "vitest.config.*",
...@@ -14,6 +15,9 @@ ...@@ -14,6 +15,9 @@
"composite": true, "composite": true,
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Bundler", "moduleResolution": "Bundler",
"types": ["node"] "types": [
"node",
"@types/chrome"
]
} }
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment