Commit 7f03fb1b authored by Domi's avatar Domi

fix: popup minimize

parent 1ee1ea77
......@@ -7,10 +7,8 @@
<title>Anything Copilot DEV</title>
</head>
<body>
<div id="app"></div>
<div>
<div class="h-screen"></div>
<div class="h-screen"></div>
<div id="app">
<iframe class="w-screen h-screen" src="https://bing.com"></iframe>
</div>
<script type="module" src="./src/pages/dev.ts"></script>
</body>
......
{
"data": {
"configVersion": 20240330,
"configVersion": 20240406,
"chatDocSites": [
{
"host": "huggingface.co",
......@@ -140,7 +140,8 @@
"defaultUA": "",
"hostUA": {
"www.google.com": 1,
"bing.com": 1
"bing.com": 1,
"copilot.microsoft.com": 0
}
}
}
......
......@@ -4,8 +4,8 @@ import {
type ParseDocOptions,
ContentScriptId,
} from "@/types"
import { waitMessage, tabUpdated, getLocal } from "@/utils/ext"
import { offscreen } from "./offscreen"
import { waitMessage, tabUpdated, getLocal, getPipWindow } from "@/utils/ext"
import { setupOffscreenDocument, offscreenHtmlPath } from "./offscreen"
import {
registerContentSidebar,
unregisterContentSidebar,
......@@ -14,6 +14,7 @@ import {
import config from "@/assets/config.json"
import { allFrameScript, contentMainScript } from "@/manifest"
import { getIsEdge } from "@/utils/ext"
import { contentInvoke } from "@/utils/invoke"
type Config = typeof config
......@@ -69,33 +70,6 @@ async function pipLaunch(url: string) {
})
}
type QueryOptions = {
windowId?: number
width?: number
height?: number
}
async function getPipWindow(
id: number,
{ windowId, width, height }: QueryOptions
) {
if (windowId) {
const win = await chrome.windows.get(windowId)
chrome.tabs.sendMessage(id, {
type: MessageType.pipWinInfo,
window: win,
})
return win
}
const windows = await chrome.windows.getAll({})
const win = windows.find((w) => w.width === width && w.height === height)
chrome.tabs.sendMessage(id, {
type: MessageType.pipWinInfo,
window: win,
})
return win
}
type UpdatePipWinOption = {
windowId: number
windowInfo: Partial<chrome.windows.UpdateInfo>
......@@ -105,42 +79,38 @@ async function updateWindow({ windowId, windowInfo }: UpdatePipWinOption) {
await chrome.windows.update(windowId, windowInfo)
}
let currentSender: chrome.runtime.MessageSender | null = null
contentInvoke
.register(ServiceFunc.setupOffscreen, () =>
setupOffscreenDocument(offscreenHtmlPath)
)
.register(ServiceFunc.getAllCommands, () => chrome.commands.getAll())
.register(ServiceFunc.createTab, (p: chrome.tabs.CreateProperties) =>
chrome.tabs.create(p)
)
.register(ServiceFunc.getPipWindow, getPipWindow)
.register(ServiceFunc.getMyTab, () =>
chrome.tabs.get(currentSender!.tab!.id!)
)
async function handleInvokeRequest(
message: any,
sender: chrome.runtime.MessageSender
) {
const { key, func, args } = message
let result = null
let error = null
try {
switch (func) {
case ServiceFunc.parseDoc:
case ServiceFunc.calcTokens:
case ServiceFunc.tokenSlice:
result = await offscreen.invoke({
func,
args,
})
break
}
} catch (err) {
console.error("invoke error: ", err)
error = err
}
console.log("invoke response: ", result, error)
currentSender = sender
let result = await contentInvoke.handleReqMsg(message)
if (!sender.tab?.id) {
console.error("sender tab id is undefined", sender)
}
chrome.tabs.sendMessage(sender.tab?.id!, {
type: MessageType.invokeResponse,
key,
success: !error,
payload: !error ? result : error,
})
if (result) {
chrome.tabs.sendMessage(sender.tab?.id!, {
type: MessageType.invokeResponse,
...result,
})
}
}
function handleMessage(message: any, sender: chrome.runtime.MessageSender) {
......@@ -152,19 +122,15 @@ function handleMessage(message: any, sender: chrome.runtime.MessageSender) {
case MessageType.bgPipLaunch:
pipLaunch(message.url)
break
case MessageType.getPipWinInfo:
getPipWindow(sender.tab?.id!, message.options)
break
case MessageType.updateWindow:
updateWindow(message.options)
break
case MessageType.removeWindow:
chrome.windows.remove(message.options.windowId)
break
case MessageType.setupOffscreenDocument:
return offscreen.setup()
case MessageType.fromOffscreen:
return offscreen.handleResMsg(message)
case MessageType.forwardToTab:
chrome.tabs.sendMessage(message.tabId, message.message)
break
case MessageType.invokeRequest:
handleInvokeRequest(message, sender)
break
......@@ -181,12 +147,17 @@ function handleMessage(message: any, sender: chrome.runtime.MessageSender) {
}
async function handleToggleMinimize() {
const { pipWindowId } = await chrome.storage.local.get({ pipWindowId: null })
if (!pipWindowId) return
const windowInfo = await chrome.windows.get(pipWindowId)
const { pipWindow } = await chrome.storage.local.get({ pipWindow: null })
if (!pipWindow) return
const windowInfo = await chrome.windows.get(pipWindow.windowId)
if (!windowInfo) return
await chrome.windows.update(pipWindowId, {
state: windowInfo.state == "minimized" ? "normal" : "minimized",
const tabId = pipWindow.tabId
contentInvoke.invoke({
tabId,
func: ServiceFunc.toggleMinimize,
args: [],
})
}
......
import { MessageType, ServiceFunc, type ParseDocOptions } from "@/types"
import { Invoke } from "@/utils/invoke"
let creating: Promise<void> | null // A global promise to avoid concurrency issues
export async function setupOffscreenDocument(path: string) {
......@@ -37,39 +34,3 @@ export async function setupOffscreenDocument(path: string) {
}
export const offscreenHtmlPath = "/offscreen.html"
class Offscreen extends Invoke {
public readonly path: string
constructor(path: string) {
super("offscreen")
this.path = path
}
public async send(req: any): Promise<{ key: string; response: any }> {
const key = this.key
await this.setup()
console.log("offscreen send: ", key, req)
const response = await chrome.runtime.sendMessage({
type: MessageType.toOffscreen,
key,
...req,
})
return { key, response }
}
public handleResMsg(message: any): void {
const { type, key, payload, success } = message
if (type === MessageType.fromOffscreen) {
this.setReturnValue(key, success, payload)
}
}
public setup() {
return setupOffscreenDocument(this.path)
}
}
export const offscreen = new Offscreen(offscreenHtmlPath)
......@@ -3,56 +3,123 @@ import { ref, computed, watch, onMounted, onUnmounted } from "vue"
import IconMinimize from "@/components/icons/IconMinimize.vue"
import IconSplitRight from "@/components/icons/IconSplitscreenRight.vue"
import IconClose from "@/components/icons/IconClose.vue"
import { ContentEventType, MessageType } from "@/types"
import { ContentEventType, MessageType, ServiceFunc } from "@/types"
import { pipWindow } from "@/store/content"
import { throttle } from "lodash-es"
import IconRefresh from "./icons/IconRefresh.vue"
import { dispatchContentEvent } from "@/content/event"
import { useI18n } from "@/utils/i18n"
import IconHide from "./icons/IconHide.vue"
import { pip } from "@/content/pip"
import { getPageIcon } from "@/utils/dom"
import { contentInvoke } from "@/utils/invoke"
import IconKeyboard from "@/components/icons/IconKeyboard.vue"
import IconEdit from "@/components/icons/IconEdit.vue"
const logoUrl = chrome.runtime.getURL("/logo.svg")
const globeImg = chrome.runtime.getURL("img/globe.svg")
const isWindows = navigator.userAgentData?.platform == "Windows"
const modiferKey = isWindows ? "ctrl" : "⌘"
let timer = 0
let sheet: CSSStyleSheet | null = null
const open = ref(false)
const pinOpen = ref(false)
const timer = ref(0)
const wrapper = ref<HTMLDivElement>()
const { t } = useI18n()
const rect = ref({
w: 0,
h: 0,
l: 0,
t: 0,
r: 0,
b: 0,
})
const toggleMinimizeCommand = ref()
const normalRect = ref({ w: 420, h: 800, l: 0, t: 0, r: 0, b: 0 })
const isWindows = navigator.userAgentData?.platform == "Windows"
const modiferKey = isWindows ? "ctrl" : "⌘"
const isMinimized = computed(() => {
const { w, h } = rect.value
return w > 0 && w < 300 && h > 0 && h < 100
})
const pipRect = computed(() => {
const winInfo = pipWindow.windowsWindow
const { width } = screen
watch(open, (value, oldValue, onCleanup) => {
updateWindowInfo()
if (value) {
window.addEventListener("click", handleClickAway)
onCleanup(() => {
window.removeEventListener("click", handleClickAway)
})
}
})
return {
width: winInfo?.width || 0,
height: winInfo?.height || 0,
left: winInfo?.left || 0,
right: width - (winInfo?.left || 0) - (winInfo?.width || 0),
watch(isMinimized, (value) => {
const doc = pipWindow.window?.document
const CSSStyleSheet = doc!.defaultView!.CSSStyleSheet
if (!doc) {
return
}
if (!sheet) {
sheet = new CSSStyleSheet()
const style = `html { overflow: hidden; }`
sheet.replace(style)
doc.adoptedStyleSheets = [...doc.adoptedStyleSheets, sheet]
}
sheet.disabled = !value
contentInvoke.getAllCommands().then((commands) => {
const command = commands.find((c) => c.name == "toggleMinimize")
if (command) {
toggleMinimizeCommand.value = command
}
})
chrome.storage.local.set({})
})
const updateWindowInfo = throttle(() => {
const id = pipWindow.windowsWindow?.id
console.log("updateWindowInfo", id)
if (id) {
chrome.runtime.sendMessage({
type: MessageType.getPipWinInfo,
options: {
windowId: id,
},
})
onMounted(() => {
pipWindow.window?.addEventListener("keydown", handleKeydown)
pipWindow.window?.addEventListener("resize", updateWindowInfo)
updateWindowInfo()
contentInvoke.register(ServiceFunc.toggleMinimize, () => {
console.log("invoke toggleMinimize")
toggleMinimize()
})
})
onUnmounted(() => {
pipWindow.window?.removeEventListener("keydown", handleKeydown)
pipWindow.window?.removeEventListener("resize", updateWindowInfo)
})
function updateWindowInfo() {
const win = pipWindow.window
if (!win) {
return
}
}, 3000)
const clickMenu = (e: MouseEvent) => {
const { outerWidth: w, outerHeight: h, screenX: l, screenY: t } = win
const s = win.screen
const sw = s.width
const sh = s.height
const r = s.width - l - w
const b = s.height - t - h
if (w > 300 && h > 500) {
normalRect.value = { w, h, l, t, r, b }
}
rect.value = { w, h, l, t, r, b }
}
function clickMenu(e: MouseEvent) {
open.value = true
if (e.ctrlKey || e.metaKey) {
pinOpen.value = true
}
}
const handleClickAway = (e: MouseEvent) => {
function handleClickAway(e: MouseEvent) {
const target = e.composedPath()[0] as Element
if (wrapper.value?.contains(target)) {
return
......@@ -60,47 +127,65 @@ const handleClickAway = (e: MouseEvent) => {
open.value = false
}
watch(open, (value, oldValue, onCleanup) => {
value && updateWindowInfo()
window.addEventListener("click", handleClickAway)
onCleanup(() => {
window.removeEventListener("click", handleClickAway)
})
})
const menuPointerEnter = () => {
clearTimeout(timer.value)
timer.value = window.setTimeout(() => (open.value = true), 200)
clearTimeout(timer)
timer = window.setTimeout(() => (open.value = true), 200)
}
const menuPointerLeave = () => {
clearTimeout(timer.value)
timer.value = window.setTimeout(() => (open.value = false), 200)
clearTimeout(timer)
timer = window.setTimeout(() => (open.value = false), 200)
}
const btnPointerEnter = () => {
clearTimeout(timer.value)
clearTimeout(timer)
}
const btnPointerLeave = () => {
if (pinOpen.value) {
return
}
timer.value = window.setTimeout(() => (open.value = false), 350)
timer = window.setTimeout(() => (open.value = false), 350)
}
const minimize = () => {
const toggleMinimize = () => {
open.value = false
const windowId = pipWindow.windowsWindow?.id
if (!windowId) {
console.log("windowId is not set", pipWindow.windowsWindow)
return
}
updateWindowInfo()
let left, top, width, height
const s = pipWindow.window?.screen || screen
if (!isMinimized.value) {
// minimize
let { r, l, t, b } = rect.value
width = 255
height = 94
left = r > l ? Math.max(l, -85) : Math.min(s.width - r - 255, s.width - 170)
top = Math.min(s.height - b - 94, s.height - 94)
} else {
// restore
const { r, l, t, b } = rect.value
const { w, h } = normalRect.value
left = r > l ? Math.max(l, 0) : Math.min(s.width - w - r, s.width - w)
top = Math.max(b > t ? t : s.height - h - b, 0)
width = w
height = h
}
chrome.runtime.sendMessage({
type: MessageType.updateWindow,
options: {
windowId: windowId,
windowInfo: {
state: "minimized",
left,
top,
width,
height,
},
},
})
......@@ -115,9 +200,7 @@ const moveAside = (value?: number) => {
}
const left =
pipRect.value.right > pipRect.value.left
? screen.width - 60 - pipRect.value.width
: 60
rect.value.r > rect.value.l ? screen.width - 60 - rect.value.w : 60
chrome.runtime.sendMessage({
type: MessageType.updateWindow,
......@@ -153,11 +236,11 @@ const handleKeydown = (e: KeyboardEvent) => {
}
if (e.code == "KeyM") {
return minimize()
return toggleMinimize()
}
if (e.code == "ArrowRight") {
return moveAside(screen.width - 60 - pipRect.value.width)
return moveAside(screen.width - 60 - rect.value.w)
}
if (e.code == "ArrowLeft") {
......@@ -166,30 +249,42 @@ const handleKeydown = (e: KeyboardEvent) => {
}
}
onMounted(() => {
pipWindow.window?.addEventListener("keydown", handleKeydown)
})
onUnmounted(() => {
pipWindow.window?.removeEventListener("keydown", handleKeydown)
})
const handleEditCommand = (e: MouseEvent) => {
e.preventDefault()
e.stopPropagation()
const desc = chrome.i18n.getMessage("toggle_minimize_desc")
const text = encodeURIComponent(desc)
contentInvoke.createTab({
url: `chrome://extensions/shortcuts#:~:text=${text}`,
})
}
</script>
<template>
<div ref="wrapper">
<button
class="multitasking-menu flex gap-1 px-2 py-1.5 z-[9999]"
:class="[
'group fixed flex top-1 left-1/2 -translate-x-1/2 gap-1 px-2 py-1.5 z-[9999]',
]"
@click="clickMenu"
@pointerenter="menuPointerEnter"
@pointerleave="menuPointerLeave"
>
<div class="dot w-1.5 h-1.5 rounded-full"></div>
<div class="dot w-1.5 h-1.5 rounded-full"></div>
<div class="dot w-1.5 h-1.5 rounded-full"></div>
<div
class="absolute w-full h-full top-0 left-0 rounded-full group-hover:backdrop-blur-sm group-hover:backdrop-invert-[5%]"
></div>
<div
v-for="i in 3"
class="size-1.5 rounded-full backdrop-invert-[20%] group-hover:backdrop-invert-[50%]"
></div>
</button>
<div
v-if="open"
class="btn-group flex flex-col px-2 py-2 gap-2 fixed rounded-lg shadow-lg left-6 z-[9999]"
:class="[
'btn-group flex flex-col top-6 left-1/2 -translate-x-1/2 px-2 py-2 gap-2 ',
'fixed rounded-lg shadow-lg z-[9999] bg-background/65 text-foreground',
]"
@pointerenter="btnPointerEnter"
@pointerleave="btnPointerLeave"
>
......@@ -197,14 +292,14 @@ onUnmounted(() => {
<input
autofocus
placeholder="search"
class="search text-xs rounded h-6 leading-6 px-2"
class="bg-background-soft border text-xs rounded h-6 leading-6 px-2"
/>
</div>
<button
tabindex="1"
class="btn-item"
@click="minimize"
@click="toggleMinimize"
:aria-label="t('minimize')"
>
<IconHide class="shrink-0 scale-95" />
......@@ -219,17 +314,12 @@ onUnmounted(() => {
:aria-label="t('moveAside')"
>
<IconSplitRight
:class="[
'shrink-0 scale-95',
{ 'rotate-180': pipRect.right < pipRect.left },
]"
:class="['shrink-0 scale-95', { 'rotate-180': rect.r < rect.l }]"
/>
<span class="truncate">{{ t("moveAside") }}</span>
<span class="shortcut">
<span class="key">{{ modiferKey }}</span> +
<span class="key">{{
pipRect.right < pipRect.left ? "←" : "→"
}}</span>
<span class="key">{{ rect.r < rect.l ? "←" : "→" }}</span>
</span>
</button>
<button class="btn-item" @click="close" :aria-label="t('close')">
......@@ -247,47 +337,50 @@ onUnmounted(() => {
</span>
</button>
</div>
<div
v-if="isMinimized"
:class="[
'group z-[99999] fixed w-screen h-screen bottom-0 left-0 flex gap-3 items-center ',
'justify-center box-border px-6 bg-background/70 hover:bg-background/80 ',
'backdrop-blur max-h-20 cursor-pointer transition',
]"
@click="toggleMinimize"
>
<img :src="getPageIcon()" :data-fallback="globeImg" class="size-6" />
<div class="flex flex-col flex-1 w-0">
<div class="truncate text-sm font-bold">
{{ pipWindow.window?.document.title || "Anything Copilot" }}
</div>
<div class="flex gap-2 items-center">
<IconKeyboard class="size-4" />
<span
v-if="toggleMinimizeCommand?.shortcut"
class="text-xs font-bold"
>
{{ toggleMinimizeCommand?.shortcut }}
</span>
<button v-else class="text-xs" @click="handleEditCommand">
{{ t("setShortcutKeys") }}
</button>
<button
class="hidden group-hover:flex p-0.5 -m-0.5 rounded hover:bg-background-soft"
@click="handleEditCommand"
>
<IconEdit class="size-3.5" />
</button>
</div>
</div>
<img :src="logoUrl" class="absolute size-3 bottom-2 right-2" />
</div>
</div>
</template>
<style scoped>
.multitasking-menu {
top: 4px;
position: fixed;
left: 50%;
transform: translateX(-50%);
}
.multitasking-menu:hover::before {
backdrop-filter: blur(5px) invert(5%);
}
.multitasking-menu::before {
content: "";
position: absolute;
width: 100%;
height: 100%;
border-radius: 999px;
top: 0;
left: 0;
}
.multitasking-menu:hover dot {
backdrop-filter: invert(50%);
}
.dot {
backdrop-filter: invert(20%);
}
.search {
background: var(--color-background-soft);
border: 1px solid var(--color-border);
}
.btn-group {
backdrop-filter: blur(10px) invert(5%);
top: 24px;
border: 1px solid var(--color-border);
color: var(--color-text);
left: 50%;
transform: translateX(-50%);
background-color: rgba(var(--bg-rgb), 0.65);
animation: 200ms ease-out fadein;
max-width: 100%;
......@@ -328,4 +421,4 @@ onUnmounted(() => {
opacity: 100;
}
}
</style>
\ No newline at end of file
</style>
......@@ -145,12 +145,6 @@ async function loadFrame(url: string, ua?: string) {
// iframe.srcdoc = html
}
}
iframe.contentWindow?.postMessage(
{
type: FrameMessageType.webviewRun,
},
"*"
)
}
function handleFrameMessage(e: MessageEvent) {
......@@ -162,13 +156,10 @@ function handleFrameMessage(e: MessageEvent) {
switch (type) {
case FrameMessageType.pageInfo:
if (!pageInfo.url) {
pageInfo.url = e.data.url
pageInfo.title = e.data.title
pageInfo.icon = e.data.icon
emit("load", pageInfo)
}
pageInfo.url = e.data.url
pageInfo.title = e.data.title
pageInfo.icon = e.data.icon
emit("load", pageInfo)
break
case FrameMessageType.invokeResponse:
webviewInvoke.value?.handleResMsg(e.data)
......
......@@ -171,7 +171,7 @@ onUnmounted(() => {
</div>
<p class="text-center text-sm mt-2">{{ t("chatDocs.supportFormat") }}</p>
<div :class="['absolute text-xs flex items-center bottom-2 right-2']">
<img :src="logoUrl" class="w-3 h-3" />
<img :src="logoUrl" class="size-3" />
</div>
</div>
</div>
......
......@@ -126,6 +126,7 @@ watch(
if (maxInputType !== "token") return
if (!message) return
await contentInvoke.setupOffscreen()
const tokenLength = await contentInvoke.calcTokens(message)
const rate = (message.length / tokenLength) * 0.95
const exceedMaxInput = tokenLength > maxInput
......@@ -172,6 +173,7 @@ watch(
if (item.kind == "file" && typeof item.data != "string") {
const url = await convertBlobToBase64(item.data)
await contentInvoke.setupOffscreen()
const results = await contentInvoke.parseDoc({
key: item.key,
type: item.type,
......@@ -640,4 +642,5 @@ input:hover {
transform: translate(100%, 0);
}
}
</style>@/utils/const@/utils/invoke/service@/utils/invokeb
\ No newline at end of file
</style>
@/utils/const@/utils/invoke/service@/utils/invokeb
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 -960 960 960"
width="24"
fill="currentColor"
>
<path
d="M120-120v-170l528-527q12-11 26.5-17t30.5-6q16 0 31 6t26 18l55 56q12 11 17.5 26t5.5 30q0 16-5.5 30.5T817-647L290-120H120Zm584-528 56-56-56-56-56 56 56 56Z"
/>
</svg>
</template>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 -960 960 960"
width="24"
fill="currentColor"
>
<path
d="M160-200q-33 0-56.5-23.5T80-280v-400q0-33 23.5-56.5T160-760h640q33 0 56.5 23.5T880-680v400q0 33-23.5 56.5T800-200H160Zm160-120h320v-80H320v80ZM200-440h80v-80h-80v80Zm120 0h80v-80h-80v80Zm120 0h80v-80h-80v80Zm120 0h80v-80h-80v80Zm120 0h80v-80h-80v80ZM200-560h80v-80h-80v80Zm120 0h80v-80h-80v80Zm120 0h80v-80h-80v80Zm120 0h80v-80h-80v80Zm120 0h80v-80h-80v80Z"
/>
</svg>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from "vue"
import { pipWindow } from "@/store/content"
import { pipWindow } from "@/store/popup"
import IconHide from "@/components/icons/IconHide.vue"
import IconArrowCircleRight from "@/components/icons/IconArrowCircleRight.vue"
import IconClose from "@/components/icons/IconClose.vue"
import { MessageType } from "@/types"
import { MessageType, ServiceFunc } from "@/types"
import { computed } from "vue"
import { contentInvoke } from "@/utils/invoke"
import { handleImgError } from "@/utils/dom"
async function handleUpdatePip(state: "normal" | "minimized") {
await chrome.runtime.sendMessage({
type: MessageType.updateWindow,
options: {
windowId: pipWindow.id,
windowInfo: {
state,
},
},
const globeImg = chrome.runtime.getURL("img/globe.svg")
const isMinimized = computed(() => {
if (pipWindow.windowsWindow) {
const { width, height } = pipWindow.windowsWindow
return width! < 300 && height! < 100
}
return false
})
async function toggleMinimize() {
await contentInvoke.invoke({
tabId: pipWindow.tabId,
func: ServiceFunc.toggleMinimize,
args: [],
})
if (pipWindow.id) {
const win = await chrome.windows.get(pipWindow.id)
pipWindow.windowsWindow = win
}
}
async function closePip() {
pipWindow.windowsWindow = null
await chrome.runtime.sendMessage({
type: MessageType.updateWindow,
options: {
......@@ -40,24 +56,25 @@ async function closePip() {
<template>
<div class="justify-between border-2 border-solid border-background-mute">
<div
<img
:src="pipWindow.icon"
:data-fallback="globeImg"
class="size-7 rounded mr-auto"
:style="{
background: `#8881 center / contain url('${pipWindow.tab?.favIconUrl}')`,
}"
></div>
loading="lazy"
@error="handleImgError"
/>
<button
v-if="pipWindow.windowsWindow?.state === 'normal'"
v-if="!isMinimized"
class="bg-background-soft hover:bg-background-mute rounded-full size-8 p-1 flex items-center justify-center"
@click="handleUpdatePip('minimized')"
@click="toggleMinimize"
>
<IconHide class="size-5" />
</button>
<button
v-if="pipWindow.windowsWindow?.state === 'minimized'"
v-else
class="bg-background-soft hover:bg-background-mute rounded-full size-8 p-1 flex items-center justify-center"
@click="handleUpdatePip('normal')"
@click="toggleMinimize"
>
<IconArrowCircleRight class="size-5" />
</button>
......@@ -70,4 +87,4 @@ async function closePip() {
</div>
</template>
<style scoped></style>
\ No newline at end of file
<style scoped></style>
......@@ -4,6 +4,11 @@ import PipSplash from "@/components/PipSplash.vue"
import { pipLoading } from "@/store/content"
import LoadingBar from "@/components/LoadingBar.vue"
import ChatDocsAddon from "@/components/chatdocs/ChatDocsAddon.vue"
// const isDev =
// process.env.NODE_ENV === "development" &&
// location.host == chrome.runtime.id &&
// location.pathname == "/dev.html"
</script>
<template>
......@@ -26,4 +31,4 @@ import ChatDocsAddon from "@/components/chatdocs/ChatDocsAddon.vue"
<ChatDocsAddon />
</template>
<style scoped></style>
\ No newline at end of file
<style scoped></style>
......@@ -10,6 +10,7 @@ import {
ContentEventType,
FrameMessageType,
MessageType,
ServiceFunc,
WebviewFunc,
WindowName,
} from "@/types"
......@@ -45,12 +46,6 @@ function handleMessage(
})
sendResponse({ type: MessageType.contentHere })
break
case MessageType.pipWinInfo:
pipWindow.windowsWindow = message.window
chrome.storage.local.set({
pipWindowId: message.window.id,
})
break
case MessageType.invokeResponse:
contentInvoke.handleResMsg(message)
break
......@@ -62,6 +57,16 @@ function handleMessage(
sidebarAddon.hidden = false
sidebarAddon.url = message.url
break
case MessageType.invokeRequest:
contentInvoke.handleReqMsg(message).then((result) => {
if (result) {
chrome.runtime.sendMessage({
type: MessageType.invokeResponse,
...result,
})
}
})
break
}
}
......@@ -90,18 +95,33 @@ async function handlePipEvent(event: any) {
addContentEventListener(ContentEventType.pipLoaded, handlePipLoaded)
})
// may be 0 if not wait document is loaded
chrome.runtime.sendMessage({
type: MessageType.getPipWinInfo,
options: {
width: win.outerWidth,
height: win.outerHeight,
const window = await contentInvoke.invoke({
func: ServiceFunc.getPipWindow,
args: [
{
width: win.outerWidth,
height: win.outerHeight,
},
],
})
const tab = await contentInvoke.invoke({
func: ServiceFunc.getMyTab,
args: [],
})
pipWindow.windowsWindow = window
chrome.storage.local.set({
pipWindow: {
windowId: window.id,
tabId: tab.id,
icon: getPageIcon(),
},
})
win.addEventListener("pagehide", () => {
chrome.storage.local.set({
pipWindowId: 0,
pipWindow: null,
})
})
}
......@@ -146,10 +166,6 @@ function handleFrameMessage(e: MessageEvent) {
if (!e.data || typeof e.data !== "object") return
const type = e.data.type
switch (type) {
case FrameMessageType.webviewRun:
run()
postPageInfo()
return
case FrameMessageType.escapeLoad:
return dispatchContentEvent({
type: ContentEventType.escapeLoad,
......@@ -207,9 +223,13 @@ if (window.top !== window && window.name.startsWith(WindowName.webview)) {
},
chrome.runtime.getURL("")
)
run()
postPageInfo()
}
// dev
if (location.host == chrome.runtime.id && location.hash == "#copilot") {
pipWindow.window = window
mount(Copilot, window.document)
}
......@@ -44,5 +44,7 @@
"notSupported": "ይህ ገጽ ራስ-ሰር መላክን አይደግፍም. እባክዎ መልዕክቱን ይቅዱ እና እራስዎ ይላኩ."
},
"openSidebar": "የጎን አሞሌውን ይክፈቱ",
"searchPlaceholder": "ዩአርኤል ይፈልጉ ወይም ይተይቡ"
"searchPlaceholder": "ዩአርኤል ይፈልጉ ወይም ይተይቡ",
"setShortcutKeys": "አቋራጭ ቁልፎችን አዘጋጅ",
"mobileView": "የሞባይል እይታ"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "هذه الصفحة لا تدعم الإرسال التلقائي. يرجى نسخ الرسالة وإرسالها يدويًا."
},
"openSidebar": "افتح الشريط الجانبي",
"searchPlaceholder": "ابحث عن عنوان URL أو اكتبه"
"searchPlaceholder": "ابحث عن عنوان URL أو اكتبه",
"setShortcutKeys": "تعيين مفاتيح الاختصار",
"mobileView": "واجهه جوال"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Тази страница не поддържа автоматично изпращане. Моля, копирайте съобщението и го изпратете ръчно."
},
"openSidebar": "Отворете страничната лента",
"searchPlaceholder": "Потърсете или въведете URL"
"searchPlaceholder": "Потърсете или въведете URL",
"setShortcutKeys": "Задаване на клавишни комбинации",
"mobileView": "Мобилен изглед"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "এই পৃষ্ঠাটি স্বয়ংক্রিয় প্রেরণকে সমর্থন করে না। দয়া করে বার্তাটি অনুলিপি করুন এবং এটি ম্যানুয়ালি প্রেরণ করুন।"
},
"openSidebar": "সাইডবারটি খুলুন",
"searchPlaceholder": "একটি URL অনুসন্ধান করুন বা টাইপ করুন"
"searchPlaceholder": "একটি URL অনুসন্ধান করুন বা টাইপ করুন",
"setShortcutKeys": "শর্টকাট কী সেট করুন",
"mobileView": "মোবাইল দৃশ্য"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Aquesta pàgina no admet l'enviament automàtic. Copieu el missatge i envieu -lo manualment."
},
"openSidebar": "Obriu la barra lateral",
"searchPlaceholder": "Cerqueu o escriviu un URL"
"searchPlaceholder": "Cerqueu o escriviu un URL",
"setShortcutKeys": "Estableix tecles de drecera",
"mobileView": "Vista mòbil"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Tato stránka nepodporuje automatické odesílání. Zkopírujte zprávu a odešlete ji ručně."
},
"openSidebar": "Otevřete postranní panel",
"searchPlaceholder": "Vyhledejte nebo zadejte adresu URL"
"searchPlaceholder": "Vyhledejte nebo zadejte adresu URL",
"setShortcutKeys": "Nastavit klávesové zkratky",
"mobileView": "Mobilní pohled"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Denne side understøtter ikke automatisk afsendelse. Kopier meddelelsen og send den manuelt."
},
"openSidebar": "Åbn sidebjælken",
"searchPlaceholder": "Søg eller skriv en URL"
"searchPlaceholder": "Søg eller skriv en URL",
"setShortcutKeys": "Indstil genvejstaster",
"mobileView": "Mobil visning"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Diese Seite unterstützt das automatische Senden nicht. Bitte kopieren Sie die Nachricht und senden Sie sie manuell."
},
"openSidebar": "Öffnen Sie die Seitenleiste",
"searchPlaceholder": "Suchen oder geben Sie eine URL ein"
"searchPlaceholder": "Suchen oder geben Sie eine URL ein",
"setShortcutKeys": "Legen Sie Tastenkombinationen fest",
"mobileView": "Mobile Ansicht"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Αυτή η σελίδα δεν υποστηρίζει αυτόματη αποστολή. Αντιγράψτε το μήνυμα και στείλτε το χειροκίνητα."
},
"openSidebar": "Ανοίξτε την πλαϊνή μπάρα",
"searchPlaceholder": "Αναζητήστε ή πληκτρολογήστε μια διεύθυνση URL"
"searchPlaceholder": "Αναζητήστε ή πληκτρολογήστε μια διεύθυνση URL",
"setShortcutKeys": "Ορισμός πλήκτρων συντόμευσης",
"mobileView": "Προβολή κινητού"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "This page does not support automatic sending. Please copy the message and send it manually."
},
"openSidebar": "Open Sidebar",
"searchPlaceholder": "Search or type a URL"
"searchPlaceholder": "Search or type a URL",
"setShortcutKeys": "Set shortcut keys",
"mobileView": "Mobile View"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Esta página no admite el envío automático. Copie el mensaje y envíelo manualmente."
},
"openSidebar": "Abrir la barra lateral",
"searchPlaceholder": "Buscar o escribir una URL"
"searchPlaceholder": "Buscar o escribir una URL",
"setShortcutKeys": "Establecer teclas de acceso directo",
"mobileView": "Vista móvil"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Esta página no admite el envío automático. Copie el mensaje y envíelo manualmente."
},
"openSidebar": "Abrir la barra lateral",
"searchPlaceholder": "Buscar o escribir una URL"
"searchPlaceholder": "Buscar o escribir una URL",
"setShortcutKeys": "Establecer teclas de acceso directo",
"mobileView": "Vista móvil"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "See leht ei toeta automaatset saatmist. Kopeerige sõnum ja saatke see käsitsi."
},
"openSidebar": "Avage külgriba",
"searchPlaceholder": "Otsige või sisestage URL"
"searchPlaceholder": "Otsige või sisestage URL",
"setShortcutKeys": "Määrake kiirklahvid",
"mobileView": "Mobiilivaade"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "این صفحه از ارسال خودکار پشتیبانی نمی کند. لطفا پیام را کپی کرده و به صورت دستی ارسال کنید."
},
"openSidebar": "نوار کناری را باز کنید",
"searchPlaceholder": "یک URL را جستجو یا تایپ کنید"
"searchPlaceholder": "یک URL را جستجو یا تایپ کنید",
"setShortcutKeys": "تنظیم کلیدهای میانبر",
"mobileView": "نمای موبایل"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Tämä sivu ei tue automaattista lähettämistä. Kopioi viesti ja lähetä se manuaalisesti."
},
"openSidebar": "Avaa sivupalkki",
"searchPlaceholder": "Hae tai kirjoita URL-osoite"
"searchPlaceholder": "Hae tai kirjoita URL-osoite",
"setShortcutKeys": "Aseta pikanäppäimet",
"mobileView": "Mobiilinäkymä"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Ang pahinang ito ay hindi sumusuporta sa awtomatikong pagpapadala. Mangyaring kopyahin ang mensahe at manu -manong ipadala ito."
},
"openSidebar": "Buksan ang sidebar",
"searchPlaceholder": "Maghanap o mag-type ng URL"
"searchPlaceholder": "Maghanap o mag-type ng URL",
"setShortcutKeys": "Itakda ang mga shortcut key",
"mobileView": "Mobile view"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Cette page ne prend pas en charge l'envoi automatique. Veuillez copier le message et l'envoyer manuellement."
},
"openSidebar": "Ouvrir la barre latérale",
"searchPlaceholder": "Rechercher ou saisir une URL"
"searchPlaceholder": "Rechercher ou saisir une URL",
"setShortcutKeys": "Définir les touches de raccourci",
"mobileView": "Vue mobile"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "આ પૃષ્ઠ સ્વચાલિત મોકલવાનું સમર્થન કરતું નથી. કૃપા કરીને સંદેશની નકલ કરો અને તેને જાતે મોકલો."
},
"openSidebar": "સાઇડબાર ખોલો",
"searchPlaceholder": "URL શોધો અથવા લખો"
"searchPlaceholder": "URL શોધો અથવા લખો",
"setShortcutKeys": "શોર્ટકટ કી સેટ કરો",
"mobileView": "મોબાઇલ દૃશ્ય"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "דף זה אינו תומך בשליחה אוטומטית. אנא העתק את ההודעה ושלח אותה ידנית."
},
"openSidebar": "פתח את סרגל הצד",
"searchPlaceholder": "חפש או הקלד כתובת אתר"
"searchPlaceholder": "חפש או הקלד כתובת אתר",
"setShortcutKeys": "הגדר מקשי קיצור",
"mobileView": "נוף נייד"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "यह पृष्ठ स्वचालित भेजने का समर्थन नहीं करता है। कृपया संदेश कॉपी करें और इसे मैन्युअल रूप से भेजें।"
},
"openSidebar": "साइडबार खोलें",
"searchPlaceholder": "URL खोजें या टाइप करें"
"searchPlaceholder": "URL खोजें या टाइप करें",
"setShortcutKeys": "शॉर्टकट कुंजियाँ सेट करें",
"mobileView": "मोबाइल दृश्य"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Ova stranica ne podržava automatsko slanje. Kopirajte poruku i pošaljite je ručno."
},
"openSidebar": "Otvorite bočnu traku",
"searchPlaceholder": "Pretražite ili upišite URL"
"searchPlaceholder": "Pretražite ili upišite URL",
"setShortcutKeys": "Postavite tipke prečaca",
"mobileView": "Mobilni prikaz"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Ez az oldal nem támogatja az automatikus küldéseket. Kérjük, másolja az üzenetet, és küldje el manuálisan."
},
"openSidebar": "Nyissa ki az oldalsávot",
"searchPlaceholder": "Keressen vagy írjon be egy URL-t"
"searchPlaceholder": "Keressen vagy írjon be egy URL-t",
"setShortcutKeys": "Gyorsbillentyűk beállítása",
"mobileView": "Mobil nézet"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Halaman ini tidak mendukung pengiriman otomatis. Harap salin pesan dan kirimkan secara manual."
},
"openSidebar": "Buka bilah samping",
"searchPlaceholder": "Cari atau ketik URL"
"searchPlaceholder": "Cari atau ketik URL",
"setShortcutKeys": "Setel tombol pintas",
"mobileView": "Tampilan seluler"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Questa pagina non supporta l'invio automatico. Si prega di copiare il messaggio e inviarlo manualmente."
},
"openSidebar": "Apri la barra laterale",
"searchPlaceholder": "Cerca o digita un URL"
"searchPlaceholder": "Cerca o digita un URL",
"setShortcutKeys": "Imposta i tasti di scelta rapida",
"mobileView": "Visualizzazione mobile"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "このページは、自動送信をサポートしていません。メッセージをコピーして手動で送信してください。"
},
"openSidebar": "サイドバーを開きます",
"searchPlaceholder": "URLを検索または入力します"
"searchPlaceholder": "URLを検索または入力します",
"setShortcutKeys": "ショートカットキーを設定する",
"mobileView": "モバイルビュー"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "ಈ ಪುಟವು ಸ್ವಯಂಚಾಲಿತ ಕಳುಹಿಸುವಿಕೆಯನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ. ದಯವಿಟ್ಟು ಸಂದೇಶವನ್ನು ನಕಲಿಸಿ ಮತ್ತು ಅದನ್ನು ಕೈಯಾರೆ ಕಳುಹಿಸಿ."
},
"openSidebar": "ಸೈಡ್‌ಬಾರ್ ತೆರೆಯಿರಿ",
"searchPlaceholder": "URL ಅನ್ನು ಹುಡುಕಿ ಅಥವಾ ಟೈಪ್ ಮಾಡಿ"
"searchPlaceholder": "URL ಅನ್ನು ಹುಡುಕಿ ಅಥವಾ ಟೈಪ್ ಮಾಡಿ",
"setShortcutKeys": "ಶಾರ್ಟ್‌ಕಟ್ ಕೀಗಳನ್ನು ಹೊಂದಿಸಿ",
"mobileView": "ಮೊಬೈಲ್ ವೀಕ್ಷಣೆ"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "이 페이지는 자동 전송을 지원하지 않습니다. 메시지를 복사하여 수동으로 보내주십시오."
},
"openSidebar": "사이드 바를 엽니 다",
"searchPlaceholder": "URL을 검색하거나 입력하세요."
"searchPlaceholder": "URL을 검색하거나 입력하세요.",
"setShortcutKeys": "단축키 설정",
"mobileView": "모바일 보기"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Šis puslapis nepalaiko automatinio siuntimo. Nukopijuokite pranešimą ir atsiųskite jį rankiniu būdu."
},
"openSidebar": "Atidarykite šoninę juostą",
"searchPlaceholder": "Ieškokite arba įveskite URL"
"searchPlaceholder": "Ieškokite arba įveskite URL",
"setShortcutKeys": "Nustatykite sparčiuosius klavišus",
"mobileView": "Mobilusis vaizdas"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Šī lapa neatbalsta automātisku sūtīšanu. Lūdzu, nokopējiet ziņojumu un nosūtiet to manuāli."
},
"openSidebar": "Atveriet sānjoslu",
"searchPlaceholder": "Meklējiet vai ierakstiet URL"
"searchPlaceholder": "Meklējiet vai ierakstiet URL",
"setShortcutKeys": "Iestatiet īsinājumtaustiņus",
"mobileView": "Mobilais skats"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "ഈ പേജ് യാന്ത്രിക അയയ്ക്കുന്നതിനെ പിന്തുണയ്ക്കുന്നില്ല. സന്ദേശം പകർത്തി സ്വമേധയാ അയയ്ക്കുക."
},
"openSidebar": "സൈഡ്ബാർ തുറക്കുക",
"searchPlaceholder": "ഒരു URL തിരയുക അല്ലെങ്കിൽ ടൈപ്പ് ചെയ്യുക"
"searchPlaceholder": "ഒരു URL തിരയുക അല്ലെങ്കിൽ ടൈപ്പ് ചെയ്യുക",
"setShortcutKeys": "കുറുക്കുവഴി കീകൾ സജ്ജമാക്കുക",
"mobileView": "മൊബൈൽ കാഴ്ച"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "हे पृष्ठ स्वयंचलित पाठविण्यास समर्थन देत नाही. कृपया संदेश कॉपी करा आणि तो व्यक्तिचलितपणे पाठवा."
},
"openSidebar": "साइडबार उघडा",
"searchPlaceholder": "URL शोधा किंवा टाइप करा"
"searchPlaceholder": "URL शोधा किंवा टाइप करा",
"setShortcutKeys": "शॉर्टकट की सेट करा",
"mobileView": "मोबाइल दृश्य"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Halaman ini tidak menyokong penghantaran automatik. Sila salin mesej dan hantarkan secara manual."
},
"openSidebar": "Buka bar sisi",
"searchPlaceholder": "Cari atau taip URL"
"searchPlaceholder": "Cari atau taip URL",
"setShortcutKeys": "Tetapkan kekunci pintasan",
"mobileView": "Paparan mudah alih"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Deze pagina ondersteunt geen automatisch verzenden. Kopieer het bericht en stuur het handmatig."
},
"openSidebar": "Open de zijbalk",
"searchPlaceholder": "Zoek of typ een URL"
"searchPlaceholder": "Zoek of typ een URL",
"setShortcutKeys": "Sneltoetsen instellen",
"mobileView": "Mobiele weergave"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Denne siden støtter ikke automatisk sending. Kopier meldingen og send den manuelt."
},
"openSidebar": "Åpne sidefeltet",
"searchPlaceholder": "Søk eller skriv inn en URL"
"searchPlaceholder": "Søk eller skriv inn en URL",
"setShortcutKeys": "Angi snarveistaster",
"mobileView": "Mobil visning"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Ta strona nie obsługuje automatycznego wysyłania. Skopiuj wiadomość i wysyłaj ją ręcznie."
},
"openSidebar": "Otwórz pasek boczny",
"searchPlaceholder": "Wyszukaj lub wpisz adres URL"
"searchPlaceholder": "Wyszukaj lub wpisz adres URL",
"setShortcutKeys": "Ustaw klawisze skrótu",
"mobileView": "Widok mobilny"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Esta página não suporta o envio automático. Copie a mensagem e envie -a manualmente."
},
"openSidebar": "Abra a barra lateral",
"searchPlaceholder": "Pesquise ou digite um URL"
"searchPlaceholder": "Pesquise ou digite um URL",
"setShortcutKeys": "Definir teclas de atalho",
"mobileView": "Exibição móvel"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Esta página não suporta o envio automático. Copie a mensagem e envie -a manualmente."
},
"openSidebar": "Abra a barra lateral",
"searchPlaceholder": "Pesquise ou digite um URL"
"searchPlaceholder": "Pesquise ou digite um URL",
"setShortcutKeys": "Definir teclas de atalho",
"mobileView": "Exibição móvel"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Această pagină nu acceptă trimiterea automată. Vă rugăm să copiați mesajul și să -l trimiteți manual."
},
"openSidebar": "Deschideți bara laterală",
"searchPlaceholder": "Căutați sau introduceți o adresă URL"
"searchPlaceholder": "Căutați sau introduceți o adresă URL",
"setShortcutKeys": "Setați tastele de comandă rapidă",
"mobileView": "Vizualizare mobilă"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Эта страница не поддерживает автоматическую отправку. Пожалуйста, скопируйте сообщение и отправьте его вручную."
},
"openSidebar": "Откройте боковую панель",
"searchPlaceholder": "Найдите или введите URL-адрес"
"searchPlaceholder": "Найдите или введите URL-адрес",
"setShortcutKeys": "Установить сочетания клавиш",
"mobileView": "Мобильная версия"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Táto stránka nepodporuje automatické odosielanie. Skopírujte správu a pošlite ju manuálne."
},
"openSidebar": "Otvorte bočný panel",
"searchPlaceholder": "Vyhľadajte alebo zadajte adresu URL"
"searchPlaceholder": "Vyhľadajte alebo zadajte adresu URL",
"setShortcutKeys": "Nastaviť klávesové skratky",
"mobileView": "Mobilný pohľad"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Ta stran ne podpira samodejnega pošiljanja. Kopirajte sporočilo in ga pošljite ročno."
},
"openSidebar": "Odprite stransko vrstico",
"searchPlaceholder": "Iščite ali vnesite URL"
"searchPlaceholder": "Iščite ali vnesite URL",
"setShortcutKeys": "Nastavite tipke za bližnjice",
"mobileView": "Mobilni pogled"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Ова страница не подржава аутоматско слање. Копирајте поруку и пошаљите га ручно."
},
"openSidebar": "Отвори бочну траку",
"searchPlaceholder": "Претражите или унесите УРЛ"
"searchPlaceholder": "Претражите или унесите УРЛ",
"setShortcutKeys": "Подесите пречице",
"mobileView": "Мобилни поглед"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Denna sida stöder inte automatisk sändning. Kopiera meddelandet och skicka det manuellt."
},
"openSidebar": "Öppna sidofältet",
"searchPlaceholder": "Sök eller skriv en URL"
"searchPlaceholder": "Sök eller skriv en URL",
"setShortcutKeys": "Ställ in kortkommandon",
"mobileView": "Mobilvy"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Ukurasa huu hauungi mkono kutuma moja kwa moja. Tafadhali nakili ujumbe na utumie kwa mikono."
},
"openSidebar": "Fungua pembeni",
"searchPlaceholder": "Tafuta au charaza URL"
"searchPlaceholder": "Tafuta au charaza URL",
"setShortcutKeys": "Weka funguo za njia za mkato",
"mobileView": "Mwonekano wa rununu"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "இந்த பக்கம் தானியங்கி அனுப்புதலை ஆதரிக்காது. தயவுசெய்து செய்தியை நகலெடுத்து கைமுறையாக அனுப்புங்கள்."
},
"openSidebar": "பக்கப்பட்டியைத் திறக்கவும்",
"searchPlaceholder": "URL ஐத் தேடவும் அல்லது தட்டச்சு செய்யவும்"
"searchPlaceholder": "URL ஐத் தேடவும் அல்லது தட்டச்சு செய்யவும்",
"setShortcutKeys": "குறுக்குவழி விசைகளை அமைக்கவும்",
"mobileView": "மொபைல் பார்வை"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "ఈ పేజీ ఆటోమేటిక్ పంపడానికి మద్దతు ఇవ్వదు. దయచేసి సందేశాన్ని కాపీ చేసి మానవీయంగా పంపండి."
},
"openSidebar": "సైడ్‌బార్ తెరవండి",
"searchPlaceholder": "URLని శోధించండి లేదా టైప్ చేయండి"
"searchPlaceholder": "URLని శోధించండి లేదా టైప్ చేయండి",
"setShortcutKeys": "షార్ట్‌కట్ కీలను సెట్ చేయండి",
"mobileView": "మొబైల్ వీక్షణ"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "หน้านี้ไม่รองรับการส่งอัตโนมัติ กรุณาคัดลอกข้อความและส่งด้วยตนเอง"
},
"openSidebar": "เปิดแถบด้านข้าง",
"searchPlaceholder": "ค้นหาหรือพิมพ์ URL"
"searchPlaceholder": "ค้นหาหรือพิมพ์ URL",
"setShortcutKeys": "ตั้งค่าปุ่มลัด",
"mobileView": "มุมมองมือถือ"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Bu sayfa otomatik göndermeyi desteklemez. Lütfen mesajı kopyalayın ve manuel olarak gönderin."
},
"openSidebar": "Kenar çubuğunu aç",
"searchPlaceholder": "Bir URL arayın veya yazın"
"searchPlaceholder": "Bir URL arayın veya yazın",
"setShortcutKeys": "Kısayol tuşlarını ayarla",
"mobileView": "Mobil görünüm"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Ця сторінка не підтримує автоматичне надсилання. Будь ласка, скопіюйте повідомлення та надішліть його вручну."
},
"openSidebar": "Відкрийте бічну панель",
"searchPlaceholder": "Знайдіть або введіть URL-адресу"
"searchPlaceholder": "Знайдіть або введіть URL-адресу",
"setShortcutKeys": "Встановити комбінації клавіш",
"mobileView": "Мобільний вид"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "Trang này không hỗ trợ gửi tự động. Vui lòng sao chép tin nhắn và gửi thủ công."
},
"openSidebar": "Mở thanh bên",
"searchPlaceholder": "Tìm kiếm hoặc nhập URL"
"searchPlaceholder": "Tìm kiếm hoặc nhập URL",
"setShortcutKeys": "Đặt phím tắt",
"mobileView": "Chế độ xem trên thiết bị di động"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "此页面不支持自动发送,请复制消息发送"
},
"openSidebar": "打开侧边栏",
"searchPlaceholder": "搜索或输入 URL"
"searchPlaceholder": "搜索或输入 URL",
"setShortcutKeys": "设置快捷键",
"mobileView": "移动端视图"
}
\ No newline at end of file
......@@ -44,5 +44,7 @@
"notSupported": "此頁面不支持自動發送。請複制消息並手動發送。"
},
"openSidebar": "打開側邊欄",
"searchPlaceholder": "搜尋或輸入 URL"
"searchPlaceholder": "搜尋或輸入 URL",
"setShortcutKeys": "設定快速鍵",
"mobileView": "行動端視圖"
}
\ No newline at end of file
......@@ -24,7 +24,7 @@ export const defaultSidebarPath = "sidebar.html"
const manifest = {
// maximum of 45 characters
name: "__MSG_name__",
version: "1.2.8",
version: "1.2.9",
// edge 12 characters
// short_name: "__MSG_short_name__",
// no more than 132 characters
......
<script setup lang="ts">
import ContentSidebarAddon from "@/components/sidebar/ContentSidebarAddon.vue"
import Webview from "@/components/Webview.vue"
</script>
<template>
<!-- <ContentSidebarAddon /> -->
<!-- <div class="w-screen h-screen">
<Webview url="https://github.com" />
</div> -->
</template>
<style scoped></style>
......@@ -49,17 +49,24 @@ const host = computed({
watch(
() => pipWindow.id,
async (id) => {
console.log("pip window tabs: ", id)
if (id) {
const tabs = await chrome.tabs.query({ windowId: id })
if (tabs && tabs.length == 1) {
pipWindow.tab = tabs[0]
}
const win = await chrome.windows.get(id)
pipWindow.windowsWindow = win
return
} else {
pipWindow.windowsWindow = null
}
}
)
watch(
() => pipWindow.tabId,
async (tabId) => {
if (tabId) {
const tab = await chrome.tabs.get(tabId)
pipWindow.tab = tab
} else {
pipWindow.tab = null
}
pipWindow.tab = null
}
)
......@@ -73,16 +80,19 @@ onMounted(() => {
})
})
getLocal({ pipWindowId: null, popularSites: config.data.popularSites }).then(
({ pipWindowId: id, popularSites }) => {
if (id) {
pipWindow.id = id
}
if (Array.isArray(popularSites)) {
popularItems.splice(0, popularItems.length, ...popularSites)
}
getLocal({
pipWindow: null as null | { windowId: number; tabId: number; icon: string },
popularSites: config.data.popularSites,
}).then(({ pipWindow: pip, popularSites }) => {
if (pip) {
pipWindow.id = pip.windowId
pipWindow.tabId = pip.tabId
pipWindow.icon = pip.icon
}
)
if (Array.isArray(popularSites)) {
popularItems.splice(0, popularItems.length, ...popularSites)
}
})
chrome.storage.local.onChanged.addListener(handleLocalChange)
window.addEventListener("keydown", handleKeydown)
......@@ -98,7 +108,7 @@ onUnmounted(() => {
function handleKeydown(e: KeyboardEvent) {
if (e.code == "KeyP" && (e.ctrlKey || e.metaKey)) {
e.preventDefault()
handleWriteHtml()
handlePipPopup()
}
if (e.code == "Backslash" && (e.ctrlKey || e.metaKey)) {
e.preventDefault()
......@@ -125,12 +135,13 @@ function hanldeKeyup(e: KeyboardEvent) {
const handleLocalChange = (changes: {
[key: string]: chrome.storage.StorageChange
}) => {
if (changes.pipWindowId) {
pipWindow.id = changes.pipWindowId.newValue
if (changes.pipWindow) {
pipWindow.id = changes.pipWindow.newValue.windowId
pipWindow.tabId = changes.pipWindow.newValue.tabId
}
}
async function handleWriteHtml() {
async function handlePipPopup() {
const tabs = await chrome.tabs.query({ active: true, currentWindow: true })
const tab = tabs[0]
if (tab) {
......@@ -142,6 +153,8 @@ async function handleWriteHtml() {
},
})
}
window.close()
}
async function openSidebar(url = "") {
......@@ -270,11 +283,11 @@ function showChatDocs() {
]"
>
<button
v-if="!pipWindow.tab"
v-if="!(pipWindow.tab && pipWindow.windowsWindow)"
class="hover:bg-background-mute bg-background-soft disabled:bg-background-soft disabled:opacity-50 disabled:cursor-not-allowed"
:disabled="!avaiable"
:title="t('openInPip')"
@click="handleWriteHtml"
@click="handlePipPopup"
>
<IconAmpStories class="size-8 shrink-0" />
<div class="flex items-center justify-between flex-1 w-2/3 gap-2">
......
......@@ -19,6 +19,7 @@ import IconNavigateNext from "@/components/icons/IconNavigateNext.vue"
import IconRefresh from "@/components/icons/IconRefresh.vue"
import IconHome from "@/components/icons/IconHome.vue"
import IconPhone from "@/components/icons/IconPhone.vue"
import IconHide from "@/components/icons/IconHide.vue"
import { handleImgError } from "@/utils/dom"
import { FrameMessageType } from "@/types"
import { homeUrl } from "@/utils/const"
......@@ -52,7 +53,7 @@ const webviewsAttr = computed(() => {
})
})
const active = ref(-1)
const pagesInfo = reactive<{ [index: number]: PageInfo }>({})
const pagesInfo = reactive<(PageInfo | null)[]>([])
const hostUA = reactive<Record<string, number>>(config.data.embedView.hostUA)
const isPointerIn = ref(false)
......@@ -155,12 +156,8 @@ onUnmounted(() => {
async function handleMessage(message: any) {
switch (message.type) {
case MessageType.openInSidebar:
if (currentTab.tabId == 0) {
const current = await chrome.tabs.getCurrent()
currentTab.tabId = current?.id || -1
}
if (message.tabId == currentTab.tabId) {
const url = message.url
const url = message.url
if (message.tabId == currentTab.tabId && !isProtectedUrl(url)) {
go(url)
}
break
......@@ -231,7 +228,7 @@ function reload() {
function closeWebview(index: number) {
pages.splice(index, 1)
delete pagesInfo[index]
pagesInfo.splice(index, 1)
if (active.value > pages.length - 1) {
active.value = pages.length - 1
}
......@@ -332,8 +329,9 @@ function handlePointerLeave() {
<a
@click="(e) => (e.preventDefault(), (active = -1))"
:href="homeUrl"
title="Anything Copilot"
:class="[
'group rounded-lg relative box-border border',
'group rounded-full relative box-border border',
active === -1
? 'bg-primary/10 border-primary-500'
: 'border-transparent hover:bg-background-mute bg-background-soft',
......@@ -352,16 +350,17 @@ function handlePointerLeave() {
<a
v-for="(page, i) of pages"
@click="(e) => (e.preventDefault(), (active = i))"
:href="page.url"
:href="pagesInfo[i]?.url"
:title="pagesInfo[i]?.title"
:class="[
'group rounded-lg relative box-border border',
'group rounded-full relative box-border border',
active === i
? 'bg-primary/10 border-primary-500'
: 'border-transparent hover:bg-background-mute bg-background-soft',
]"
>
<img
class="size-5 scale-90 pointer-events-none"
class="size-4 pointer-events-none"
loading="lazy"
:src="pagesInfo[i]?.icon || globeImg"
:data-fallback="globeImg"
......@@ -401,6 +400,7 @@ function handlePointerLeave() {
</button> -->
<button
@click="reload()"
:title="t('refresh')"
class="group hover:bg-background-soft rounded-full"
>
<IconRefresh
......@@ -409,6 +409,7 @@ function handlePointerLeave() {
</button>
<button
@click="toggleMobileUA"
:title="t('mobileView')"
:class="[
'group hover:bg-background-soft rounded-full transition ease-in-out delay-200',
{ 'bg-background-soft text-primary-500 ': isMobileUA },
......@@ -420,15 +421,17 @@ function handlePointerLeave() {
<button
v-if="mode == 'content'"
@click="collapseSidebar()"
:title="t('minimize')"
class="group hover:bg-background-soft rounded-full"
>
<IconSplitscreenRight
<IconHide
class="size-5 scale-95 group-active:scale-90 transition-transform"
/>
</button>
<button
v-if="mode == 'content'"
@click="closeSidebar()"
:title="t('close')"
class="group hover:bg-background-soft rounded-full"
>
<IconClose class="size-5 group-active:scale-90 transition-transform" />
......
import "@/assets/main.css"
import { createApp } from "vue"
import "@/content/index"
import "@/content/main"
// import "@/pages/popup"
import Dev from "./Dev.vue"
......
......@@ -6,10 +6,10 @@ import TurndownService from "turndown"
import { MessageType, ServiceFunc, type ParseDocOptions } from "@/types"
import { Tiktoken } from "tiktoken/lite"
import cl100k_base from "tiktoken/encoders/cl100k_base.json"
import { contentInvoke } from "@/utils/invoke"
pdfjs.GlobalWorkerOptions.workerSrc = "/js/pdf.worker.js"
async function parsePdf(file: File) {
const buffer = await file.arrayBuffer()
const task = pdfjs.getDocument(buffer)
......@@ -102,34 +102,27 @@ async function handleMessage(
message: any,
sender: chrome.runtime.MessageSender
) {
if (message?.type === MessageType.toOffscreen) {
console.log("offscreen message: ", message, sender)
const { func, args } = message
let taskPromise: Promise<any> | null = null
switch (func) {
case ServiceFunc.parseDoc:
taskPromise = parseDoc(...(args as [any]))
break
case ServiceFunc.calcTokens:
taskPromise = calcTokens(...(args as [any]))
break
case ServiceFunc.tokenSlice:
taskPromise = tokenSlice(...(args as [any, any]))
break
}
if (taskPromise) {
const result = await taskPromise
console.log("offscreen message: ", message, sender)
if (message?.type === MessageType.invokeRequest) {
const result = await contentInvoke.handleReqMsg(message)
if (result) {
chrome.runtime.sendMessage({
type: MessageType.fromOffscreen,
key: message.key,
task: message.task,
payload: result,
type: MessageType.forwardToTab,
tabId: sender.tab?.id,
message: {
type: MessageType.invokeResponse,
...result,
},
})
}
}
}
contentInvoke
.register(ServiceFunc.parseDoc, parseDoc)
.register(ServiceFunc.calcTokens, calcTokens)
.register(ServiceFunc.tokenSlice, tokenSlice)
chrome.runtime.onMessage.addListener(handleMessage)
// for testing purposes
......
......@@ -3,8 +3,18 @@ import "@/assets/main.css"
import { createApp } from "vue"
import Popup from "./Popup.vue"
import { i18n } from "@/utils/i18n"
import { MessageType } from "@/types"
import { contentInvoke } from "@/utils/invoke"
const app = createApp(Popup)
app.use(i18n)
app.mount("#app")
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
switch (message.type) {
case MessageType.invokeResponse:
contentInvoke.handleResMsg(message)
break
}
})
......@@ -5,4 +5,6 @@ export const pipWindow = reactive({
window: null as Window | null,
windowsWindow: null as chrome.windows.Window | null,
tab: null as chrome.tabs.Tab | null,
tabId: 0,
icon: "" as string,
})
......@@ -6,13 +6,13 @@ export enum MessageType {
bgPipLaunch = "bg-pip-launch",
pipLaunch = "pip-launch",
contentMounted = "content-mount",
getPipWinInfo = "get-pip-win-info",
pipWinInfo = "pip-win-info",
// getPipWinInfo = "get-pip-win-info",
// pipWinInfo = "pip-win-info",
updateWindow = "update-window",
removeWindow = "remove-window",
setupOffscreenDocument = "setup-offscreen-document",
toOffscreen = "to-offscreen",
fromOffscreen = "from-offscreen",
forwardToTab = "forward-to-tab",
// toOffscreen = "to-offscreen",
// fromOffscreen = "from-offscreen",
invokeRequest = "invoke-request",
invokeResponse = "invoke-Response",
showChatDocs = "show-chat-docs",
......@@ -23,9 +23,15 @@ export enum MessageType {
}
export enum ServiceFunc {
setupOffscreen = "setup-offscreen",
parseDoc = "parse-doc",
calcTokens = "calc-tokens",
tokenSlice = "token-slice",
getAllCommands = "get-all-commands",
createTab = "create-tab",
toggleMinimize = "toggle-minimize",
getPipWindow = "get-pip-window",
getMyTab = "get-my-tab",
}
export type ParseDocOptions = {
......@@ -51,7 +57,6 @@ export enum ContentEventType {
export enum FrameMessageType {
frameReady = "anything-copilot_frame-ready",
webviewRun = "anything-copilot_webview-run",
escapeLoad = "anything-copilot_escape-load",
pageInfo = "anything-copilot_page-info",
collapseSidebar = "anything-copilot_collapse-sidebar",
......
......@@ -2,4 +2,3 @@ export const chatDocPrompt = `Here is some relevant reference information. Pleas
export const homeUrl = 'https://ziziyi.com/copilot'
export const feedbackUrl = 'https://tawk.to/anythingcopilot'
......@@ -327,3 +327,22 @@ export function isProtectedUrl(url: string) {
return true
}
export async function getPipWindow({
windowId,
width,
height,
}: {
windowId?: number
width?: number
height?: number
}) {
if (windowId) {
const win = await chrome.windows.get(windowId)
return win
}
const windows = await chrome.windows.getAll({})
const win = windows.find((w) => w.width === width && w.height === height)
return win
}
......@@ -6,9 +6,9 @@ import type ZhMessage from "@/locales/zh-CN.json"
type MessageSchema = typeof EnMessage & typeof ZhMessage
export function getLocale() {
if (__DEV__) {
return "zh-CN"
}
// if (__DEV__) {
// return "zh-CN"
// }
const language = chrome.i18n.getUILanguage()
let code = language
......
import { MessageType, ServiceFunc, type ParseDocOptions } from "@/types"
import Invoke from "./Invoke"
import Invoke, { type InvokeReq } from "./Invoke"
class ContentInvoke extends Invoke {
public async send(req: any) {
public async send(req: InvokeReq & { tabId?: number }) {
const key = this.key
const response = await chrome.runtime.sendMessage({
type: MessageType.invokeRequest,
key,
...req,
})
return { key, response }
if (req.tabId) {
chrome.tabs.sendMessage(req.tabId, {
type: MessageType.invokeRequest,
key,
...req,
})
} else {
chrome.runtime.sendMessage({
type: MessageType.invokeRequest,
key,
...req,
})
}
return { key }
}
public handleResMsg(message: any) {
if (message?.type === MessageType.invokeResponse) {
const { key, success, payload } = message
this.setReturnValue(key, success, payload)
const { key, success, value } = message
this.setReturnValue(key, success, value)
}
}
public setupOffscreen() {
return this.invoke({
func: ServiceFunc.setupOffscreen,
args: [],
})
}
public parseDoc(options: ParseDocOptions): Promise<string[]> {
return this.invoke({
func: ServiceFunc.parseDoc,
......@@ -40,6 +56,22 @@ class ContentInvoke extends Invoke {
args: [text, length],
})
}
public getAllCommands(): Promise<chrome.commands.Command[]> {
return this.invoke({
func: ServiceFunc.getAllCommands,
args: [],
})
}
public createTab(
properties: chrome.tabs.CreateProperties
): Promise<chrome.tabs.Tab> {
return this.invoke({
func: ServiceFunc.createTab,
args: [properties],
})
}
}
const contentInvoke = new ContentInvoke("content")
......
......@@ -3,29 +3,33 @@ type CallbackItem = {
reject: (e: any) => void
}
interface InvokeReq {
export interface InvokeReq {
func: string
args: any[]
timeout?: number
key?: string
[key: string]: any
}
interface InvokeRes {
export interface InvokeRes {
key: string
success: boolean
value: any
}
abstract class Invoke {
export abstract class Invoke {
private pendingCallback: Record<string, CallbackItem>
private count: number
private name: string
private uniqueId: string
private services: Map<string, Function>
constructor(name: string) {
this.name = name
this.uniqueId = `${Math.round(Math.random() * 1e6)}`
this.pendingCallback = {}
this.count = 0
this.services = new Map()
}
protected get key() {
......@@ -64,15 +68,48 @@ abstract class Invoke {
}
}
abstract send(req: InvokeReq): Promise<{ key: string; response: any }>
abstract handleResMsg(message: InvokeRes): void
abstract send(req: InvokeReq): Promise<{ key: string }>
public handleResMsg(message: InvokeRes) {
const { key, success, value } = message
this.setReturnValue(key, success, value)
}
public async handleReqMsg(message: InvokeReq) {
const { key, func, args, timeout } = message
let result = null
let error = null
try {
const service = this.services.get(func)
if (service) {
result = await service(...args)
} else {
error = `unknown service: ${func}`
console.warn(`unknown service: ${func}`)
return null
}
} catch (err) {
console.error("invoke error: ", err)
error = err
}
return { key, success: !error, value: !error ? result : error }
}
public async invoke<T = any>(req: InvokeReq): Promise<T> {
const { key } = await this.send(req)
req.timeout = req.timeout || 20000
return this.getReturnValue(key, req)
}
public register(func: string, service: Function) {
this.services.set(func, service)
return this
}
public unregister(func: string) {
this.services.delete(func)
return this
}
}
export default Invoke
export { Invoke }
......@@ -18,13 +18,6 @@ class WebviewInvoke extends Invoke {
win?.postMessage({ type: FrameMessageType.invokeRequest, key, ...req }, "*")
return { key, response: null }
}
public handleResMsg(message: any) {
if (message?.type === FrameMessageType.invokeResponse) {
const { key, success, payload } = message
this.setReturnValue(key, success, payload)
}
}
}
export { WebviewInvoke }
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