Commit df56514b authored by Domi's avatar Domi

feat: loading & shortcut

parent b3c31913
/// <reference types="vite/client" />
interface DocumentPictureInPicture {
interface DocumentPictureInPicture extends EventTarget {
window: Window | null;
requestWindow(option?: { width?: number; height?: number }): Promise<Window>;
}
export declare global {
interface documentPictureInPicture extends DocumentPictureInPicture {}
interface Window {
documentPictureInPicture: DocumentPictureInPicture;
trustedTypes: any
trustedTypes: any;
}
interface Navigator {
userAgentData: {
platform: string;
};
}
}
export {};
const contentCss = "index.css";
const manifest = {
manifest_version: 3,
name: "Anything Copilot",
description:
"Anything Copilot opens any page, including AI tools like ChatGPT, GPTs, Bard, and Claude, in pop-ups for multitasking.",
version: "1.0.0",
// maximum of 45 characters
name: "__MSG_name__",
// edge 12 characters
// short_name: "__MSG_short_name__",
// no more than 132 characters
description: "__MSG_description__",
version: "1.1.1",
action: {
default_icon: {
16: "logo.png",
24: "logo.png",
32: "logo.png",
},
default_title: "pip",
default_title: "__MSG_short_name__",
default_popup: "popup.html",
},
default_locale: "en",
......@@ -39,15 +44,30 @@ const manifest = {
},
],
options_page: "guide.html",
permissions: ["tabs", "scripting", "activeTab"],
permissions: ["tabs", "scripting", "activeTab", "storage"],
host_permissions: ["<all_urls>"],
minimum_chrome_version: "111",
commands: {
toggleMinimize: {
suggested_key: {
default: "Ctrl+Shift+1",
},
description: "__MSG_toggle_minimize_desc__",
global: true,
},
},
web_accessible_resources: [
{
resources: ["index.css"],
resources: [contentCss],
matches: ["<all_urls>"],
},
],
content_security_policy:
process.env.NODE_ENV == "development"
? {
extension_pages: `script-src 'self' http://localhost:3000;`,
}
: undefined,
};
export default manifest;
......@@ -8,22 +8,25 @@
"name": "picture-in-picture",
"version": "0.0.0",
"dependencies": {
"vue": "^3.3.4"
"lodash-es": "^4.17.21",
"vue": "^3.3.4",
"vue-i18n": "^9.7.0"
},
"devDependencies": {
"@tsconfig/node18": "^18.2.2",
"@types/chrome": "^0.0.251",
"@types/lodash-es": "^4.17.11",
"@types/node": "^18.18.5",
"@vitejs/plugin-vue": "^4.4.0",
"@vitejs/plugin-vue-jsx": "^3.0.2",
"@vue/tsconfig": "^0.4.0",
"autoprefixer": "^10.4.16",
"cross-env": "^7.0.3",
"npm-run-all2": "^6.1.1",
"postcss": "^8.4.31",
"tailwindcss": "^3.3.5",
"typescript": "~5.2.0",
"vite": "^4.4.11",
"vite-plugin-svgr": "^4.1.0",
"vue-tsc": "^1.8.19"
}
},
......@@ -814,6 +817,47 @@
"node": ">=12"
}
},
"node_modules/@intlify/core-base": {
"version": "9.7.0",
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.7.0.tgz",
"integrity": "sha512-1tBnfnCI23jXqGW15cagCjn2GgD487VST1dMG8P5LRzrSfx+kUzqFyTrjMNIwgq1tVaF4HnDpFMUuyrzTLKphw==",
"dependencies": {
"@intlify/message-compiler": "9.7.0",
"@intlify/shared": "9.7.0"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/message-compiler": {
"version": "9.7.0",
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.7.0.tgz",
"integrity": "sha512-/YdZCio2L2tCM5bZ2eMHbSEIQNPh1QqvZIOLI/yCVKXLscis7O0SsR2nmuU/DfCJ3iSeI8juw82C2wLvfsAeww==",
"dependencies": {
"@intlify/shared": "9.7.0",
"source-map-js": "^1.0.2"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/shared": {
"version": "9.7.0",
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.7.0.tgz",
"integrity": "sha512-PUkEuk//YKu4CHS5ah3mNa3XL/+TZj6rAY/6yYN+GCNFd2u+uWUkeuwE4Q6t8dydRWlErOePHHS0KyNoof/oBw==",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.3",
"dev": true,
......@@ -891,241 +935,6 @@
"node": ">= 8"
}
},
"node_modules/@rollup/pluginutils": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.5.tgz",
"integrity": "sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==",
"dev": true,
"dependencies": {
"@types/estree": "^1.0.0",
"estree-walker": "^2.0.2",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@svgr/babel-plugin-add-jsx-attribute": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz",
"integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==",
"dev": true,
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@svgr/babel-plugin-remove-jsx-attribute": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz",
"integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==",
"dev": true,
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz",
"integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==",
"dev": true,
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz",
"integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==",
"dev": true,
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@svgr/babel-plugin-svg-dynamic-title": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz",
"integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==",
"dev": true,
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@svgr/babel-plugin-svg-em-dimensions": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz",
"integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==",
"dev": true,
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@svgr/babel-plugin-transform-react-native-svg": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz",
"integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==",
"dev": true,
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@svgr/babel-plugin-transform-svg-component": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz",
"integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@svgr/babel-preset": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz",
"integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==",
"dev": true,
"dependencies": {
"@svgr/babel-plugin-add-jsx-attribute": "8.0.0",
"@svgr/babel-plugin-remove-jsx-attribute": "8.0.0",
"@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0",
"@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0",
"@svgr/babel-plugin-svg-dynamic-title": "8.0.0",
"@svgr/babel-plugin-svg-em-dimensions": "8.0.0",
"@svgr/babel-plugin-transform-react-native-svg": "8.1.0",
"@svgr/babel-plugin-transform-svg-component": "8.0.0"
},
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@svgr/core": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz",
"integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==",
"dev": true,
"dependencies": {
"@babel/core": "^7.21.3",
"@svgr/babel-preset": "8.1.0",
"camelcase": "^6.2.0",
"cosmiconfig": "^8.1.3",
"snake-case": "^3.0.4"
},
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
}
},
"node_modules/@svgr/hast-util-to-babel-ast": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz",
"integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==",
"dev": true,
"dependencies": {
"@babel/types": "^7.21.3",
"entities": "^4.4.0"
},
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
}
},
"node_modules/@svgr/plugin-jsx": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz",
"integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==",
"dev": true,
"dependencies": {
"@babel/core": "^7.21.3",
"@svgr/babel-preset": "8.1.0",
"@svgr/hast-util-to-babel-ast": "8.0.0",
"svg-parser": "^2.0.4"
},
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
},
"peerDependencies": {
"@svgr/core": "*"
}
},
"node_modules/@tsconfig/node18": {
"version": "18.2.2",
"dev": true,
......@@ -1141,12 +950,6 @@
"@types/har-format": "*"
}
},
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
},
"node_modules/@types/filesystem": {
"version": "0.0.35",
"resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.35.tgz",
......@@ -1168,6 +971,21 @@
"integrity": "sha512-RpQH4rXLuvTXKR0zqHq3go0RVXYv/YVqv4TnPH95VbwUxZdQlK1EtcMvQvMpDngHbt13Csh9Z4qT9AbkiQH5BA==",
"dev": true
},
"node_modules/@types/lodash": {
"version": "4.14.201",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.201.tgz",
"integrity": "sha512-y9euML0cim1JrykNxADLfaG0FgD1g/yTHwUs/Jg9ZIU7WKj2/4IW9Lbb1WZbvck78W/lfGXFfe+u2EGfIJXdLQ==",
"dev": true
},
"node_modules/@types/lodash-es": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.11.tgz",
"integrity": "sha512-eCw8FYAWHt2DDl77s+AMLLzPn310LKohruumpucZI4oOFJkIgnlaJcy23OKMJxx4r9PeTF13Gv6w+jqjWQaYUg==",
"dev": true,
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/node": {
"version": "18.18.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.9.tgz",
......@@ -1338,6 +1156,11 @@
"@vue/shared": "3.3.8"
}
},
"node_modules/@vue/devtools-api": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.1.tgz",
"integrity": "sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA=="
},
"node_modules/@vue/language-core": {
"version": "1.8.22",
"dev": true,
......@@ -1474,12 +1297,6 @@
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"dev": true
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
},
"node_modules/autoprefixer": {
"version": "10.4.16",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz",
......@@ -1582,15 +1399,6 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/camelcase": {
"version": "6.3.0",
"dev": true,
......@@ -1720,60 +1528,22 @@
"dev": true,
"license": "MIT"
},
"node_modules/cosmiconfig": {
"version": "8.3.6",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
"integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
"node_modules/cross-env": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
"dev": true,
"dependencies": {
"import-fresh": "^3.3.0",
"js-yaml": "^4.1.0",
"parse-json": "^5.2.0",
"path-type": "^4.0.0"
},
"engines": {
"node": ">=14"
"cross-spawn": "^7.0.1"
},
"funding": {
"url": "https://github.com/sponsors/d-fischer"
},
"peerDependencies": {
"typescript": ">=4.9.5"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/cosmiconfig/node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"dev": true
},
"node_modules/cosmiconfig/node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"dev": true
},
"node_modules/cosmiconfig/node_modules/parse-json": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.0.0",
"error-ex": "^1.3.1",
"json-parse-even-better-errors": "^2.3.0",
"lines-and-columns": "^1.1.6"
"bin": {
"cross-env": "src/bin/cross-env.js",
"cross-env-shell": "src/bin/cross-env-shell.js"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
"node": ">=10.14",
"npm": ">=6",
"yarn": ">=1"
}
},
"node_modules/cross-spawn": {
......@@ -1838,33 +1608,11 @@
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true
},
"node_modules/dot-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
"integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==",
"dev": true,
"dependencies": {
"no-case": "^3.0.4",
"tslib": "^2.0.3"
}
},
"node_modules/electron-to-chromium": {
"version": "1.4.579",
"dev": true,
"license": "ISC"
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/error-ex": {
"version": "1.3.2",
"dev": true,
......@@ -2162,22 +1910,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
"dev": true,
"dependencies": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
......@@ -2271,18 +2003,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/jsesc": {
"version": "2.5.2",
"dev": true,
......@@ -2330,14 +2050,10 @@
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
}
},
"node_modules/lower-case": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
"integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
"dev": true,
"dependencies": {
"tslib": "^2.0.3"
}
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"node_modules/lru-cache": {
"version": "5.1.1",
......@@ -2437,16 +2153,6 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/no-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
"integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
"dev": true,
"dependencies": {
"lower-case": "^2.0.2",
"tslib": "^2.0.3"
}
},
"node_modules/node-releases": {
"version": "2.0.13",
"dev": true,
......@@ -2576,18 +2282,6 @@
"wrappy": "1"
}
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
"dependencies": {
"callsites": "^3.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/parse-json": {
"version": "7.1.1",
"dev": true,
......@@ -2645,15 +2339,6 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/picocolors": {
"version": "1.0.0",
"license": "ISC"
......@@ -2904,15 +2589,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
......@@ -2996,16 +2672,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/snake-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz",
"integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==",
"dev": true,
"dependencies": {
"dot-case": "^3.0.4",
"tslib": "^2.0.3"
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"license": "BSD-3-Clause",
......@@ -3092,12 +2758,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/svg-parser": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
"integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==",
"dev": true
},
"node_modules/svg-tags": {
"version": "1.0.0",
"dev": true
......@@ -3186,12 +2846,6 @@
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true
},
"node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
"dev": true
},
"node_modules/type-fest": {
"version": "4.7.1",
"dev": true,
......@@ -3318,20 +2972,6 @@
}
}
},
"node_modules/vite-plugin-svgr": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.1.0.tgz",
"integrity": "sha512-v7Qic+FWmCChgQNGSI4V8X63OEYsdUoLt66iqIcHozq9bfK/Dwmr0V+LBy1NE8CE98Y8HouEBJ+pto4AMfN5xw==",
"dev": true,
"dependencies": {
"@rollup/pluginutils": "^5.0.4",
"@svgr/core": "^8.1.0",
"@svgr/plugin-jsx": "^8.1.0"
},
"peerDependencies": {
"vite": "^2.6.0 || 3 || 4"
}
},
"node_modules/vue": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.3.8.tgz",
......@@ -3352,6 +2992,25 @@
}
}
},
"node_modules/vue-i18n": {
"version": "9.7.0",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.7.0.tgz",
"integrity": "sha512-8Z8kSz9U2juzuAf+6mjW1HTd5pIlYuFJZkC+HvYOglFdpzwc2rTUGjxKwN8xGdtGur1MFnyJ44TSr+TksJtY8A==",
"dependencies": {
"@intlify/core-base": "9.7.0",
"@intlify/shared": "9.7.0",
"@vue/devtools-api": "^6.5.0"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/vue-template-compiler": {
"version": "2.7.15",
"dev": true,
......
......@@ -3,7 +3,7 @@
"version": "0.0.0",
"private": true,
"scripts": {
"dev:page": "vite build --watch",
"dev:page": "cross-env NODE_ENV=development vite build --watch",
"dev:content": "vite build --watch -c vite.content.config.ts",
"dev:js": "node esbuild.mjs --watch",
"dev": "run-p dev:page dev:content dev:js",
......@@ -17,22 +17,25 @@
"zip": ""
},
"dependencies": {
"vue": "^3.3.4"
"lodash-es": "^4.17.21",
"vue": "^3.3.4",
"vue-i18n": "^9.7.0"
},
"devDependencies": {
"@tsconfig/node18": "^18.2.2",
"@types/chrome": "^0.0.251",
"@types/lodash-es": "^4.17.11",
"@types/node": "^18.18.5",
"@vitejs/plugin-vue": "^4.4.0",
"@vitejs/plugin-vue-jsx": "^3.0.2",
"@vue/tsconfig": "^0.4.0",
"autoprefixer": "^10.4.16",
"cross-env": "^7.0.3",
"npm-run-all2": "^6.1.1",
"postcss": "^8.4.31",
"tailwindcss": "^3.3.5",
"typescript": "~5.2.0",
"vite": "^4.4.11",
"vite-plugin-svgr": "^4.1.0",
"vue-tsc": "^1.8.19"
}
}
{
"A": {
"message": "A"
"name": {
"message": "Anything Copilot - Any web page as copilot"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Use the official ChatGPT website or any other webpage for free as your AI copilot, including GPTs, GPT-4, or any new features."
},
"toggle_minimize_desc": {
"message": "Toggle show/hide Copilot window"
}
}
{
"A": {
"message": "A"
"name": {
"message": "Anything Copilot - 无限应用、AI助手"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "免费将 ChatGPT 官方网页或其他任何页面变为您的 AI 助手,GPTs、GPT4 任何新功能都能立即体验。也能与开源大模型、文心一言对话,用 Google Translate 翻译甚至抖音刷短视频"
},
"toggle_minimize_desc": {
"message": "切换显示/隐藏Copilot窗口"
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
<base href="http://localhost:3000">
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/content/index.ts"></script>
<script type="module" src="/@vite/client"></script>
</body>
</html>
public/logo.png

1.54 KB | W: | H:

public/logo.png

1.6 KB | W: | H:

public/logo.png
public/logo.png
public/logo.png
public/logo.png
  • 2-up
  • Swipe
  • Onion skin
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="83" y="28" width="200" height="50" rx="8" transform="rotate(90 83 28)" fill="#3BABFD"/>
<rect x="153" y="28" width="200" height="50" rx="8" transform="rotate(90 153 28)" fill="#0087E8"/>
<rect x="223" y="28" width="200" height="50" rx="8" transform="rotate(90 223 28)" fill="#005592"/>
<rect x="75" y="12" width="232" height="58" rx="8" transform="rotate(90 75 12)" fill="#3BABFD"/>
<rect x="157" y="12" width="232" height="59" rx="8" transform="rotate(90 157 12)" fill="#0087E8"/>
<rect x="238" y="12" width="232" height="58" rx="8" transform="rotate(90 238 12)" fill="#005592"/>
</svg>
......@@ -24,9 +24,9 @@ a,
place-items: center;
}
#app {
/* #app {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0 2rem;
}
} */
}
import { tabUpdated } from "@/utils/ext";
import { MessageType } from "@/types";
import { waitMessage, tabUpdated } from "@/utils/ext";
async function openPipBackground(url: string) {
const tab = await chrome.tabs.create({
......@@ -31,26 +32,103 @@ async function getContentCss(id: number, url: string) {
async function pipLaunch(url: string) {
const tab = await chrome.tabs.create({ url });
await tabUpdated({ tabId: tab.id!, status: "complete" });
await waitMessage({
tabId: tab.id!,
type: MessageType.contentMount,
});
chrome.tabs.sendMessage(tab.id!, {
type: "pip-launch",
type: MessageType.pipLaunch,
url: url,
});
}
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 MinimizeOptions = {
windowId: number;
};
async function minimizePip({ windowId }: MinimizeOptions) {
await chrome.windows.update(windowId, { state: "minimized" });
}
type UpdatePipWinOption = {
windowId: number;
windowInfo: Partial<chrome.windows.UpdateInfo>;
};
async function updatePipWin({ windowId, windowInfo }: UpdatePipWinOption) {
await chrome.windows.update(windowId, windowInfo);
}
function handleMessage(message: any, sender: chrome.runtime.MessageSender) {
console.log("bg message: ", message, sender);
switch (message?.type) {
case "bg-open-pip":
case MessageType.bgOpenPip:
openPipBackground(message.url);
break;
case "get-content-css":
getContentCss(sender.tab?.id || 0, message.url);
break;
case "bg-pip-launch":
case MessageType.bgPipLaunch:
pipLaunch(message.url);
break;
case MessageType.getPipWinInfo:
getPipWindow(sender.tab?.id!, message.options);
break;
case MessageType.minimizePipWin:
minimizePip(message.options);
console.log(message, sender);
break;
case MessageType.updatePipWin:
updatePipWin(message.options);
break;
}
}
chrome.runtime.onMessage.addListener(handleMessage);
async function handleToggleMinimize() {
const { pipWindowId } = await chrome.storage.local.get({ pipWindowId: null });
if (!pipWindowId) return;
const windowInfo = await chrome.windows.get(pipWindowId);
if (!windowInfo) return;
await chrome.windows.update(pipWindowId, {
state: windowInfo.state == "minimized" ? "normal" : "minimized",
});
}
function handleCommand(command: string) {
console.log("command: ", command);
switch (command) {
case "toggleMinimize":
handleToggleMinimize();
break;
}
}
chrome.commands.onCommand.addListener(handleCommand);
<template>
<div class="progress h-1 w-full bg-blue-500/30 overflow-hidden">
<div class="run h-full w-1/5 bg-blue-500"></div>
</div>
</template>
<style scoped>
.run {
animation: run 1.5s ease-in-out infinite;
}
@keyframes run {
from {
transform: translateX(-100%);
}
to {
transform: translate(500%);
}
}
</style>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from "vue";
import IconMinimize from "@/components/icons/IconMinimize.vue";
import IconSplitRight from "@/components/icons/IconSplitscreenRight.vue";
import IconClose from "@/components/icons/IconClose.vue";
import { MessageType } from "@/types";
import { pipWindowInfo, pipWindowRef } from "@/content/store";
import { throttle } from "lodash-es";
import IconRefresh from "./icons/IconRefresh.vue";
import { PipEventName } from "@/types/pip";
import { onUnmounted } from "vue";
const open = ref(false);
const timer = ref(0);
const btns = ref<HTMLDivElement>();
const pipRight = computed(() => {
const winInfo = pipWindowInfo.value;
const { width } = screen;
if (!winInfo) return 0;
if (!winInfo.width || !winInfo.left) return 0;
return width - winInfo.left - winInfo.width;
});
const pipLeft = computed(() => {
const winInfo = pipWindowInfo.value;
return winInfo?.left || 0;
});
const updateWindowInfo = throttle(() => {
const id = pipWindowInfo.value?.id;
console.log("updateWindowInfo", id);
if (id) {
chrome.runtime.sendMessage({
type: MessageType.getPipWinInfo,
options: {
windowId: id,
},
});
}
}, 3000);
const handleClick = (e: MouseEvent) => {
const target = e.composedPath()[0] as Element;
if (btns.value?.contains(target)) {
return;
}
open.value = false;
};
watch(open, (value, oldValue, onCleanup) => {
value && updateWindowInfo();
window.addEventListener("click", handleClick);
onCleanup(() => {
window.removeEventListener("click", handleClick);
});
});
const menuPointerEnter = () => {
clearTimeout(timer.value);
timer.value = setTimeout(() => (open.value = true), 200);
};
const menuPointerLeave = () => {
clearTimeout(timer.value);
timer.value = setTimeout(() => (open.value = false), 200);
};
const btnPointerEnter = () => {
clearTimeout(timer.value);
};
const btnPointerLeave = () => {
timer.value = setTimeout(() => (open.value = false), 350);
};
const minimize = () => {
const windowId = pipWindowInfo.value?.id;
if (!windowId) {
console.log("windowId is not set", pipWindowInfo.value);
return;
}
chrome.runtime.sendMessage({
type: MessageType.updatePipWin,
options: {
windowId: windowId,
windowInfo: {
state: "minimized",
},
},
});
};
const slideOver = () => {
const windowId = pipWindowInfo.value?.id;
if (!windowId) {
console.log("windowId is not set", pipWindowInfo.value);
return;
}
console.log("slide over: ", pipRight, pipLeft);
const left = Math.min(Math.max(pipRight.value, 60), screen.width - 60);
chrome.runtime.sendMessage({
type: MessageType.updatePipWin,
options: {
windowId,
windowInfo: {
left,
},
},
});
};
const close = () => {
const win = window.documentPictureInPicture.window;
win?.close();
};
const refresh = () => {
const pipWindow = pipWindowRef.value;
if (pipWindow) {
document.dispatchEvent(
new CustomEvent(PipEventName.loadDoc, {
detail: { url: pipWindow.location.href },
})
);
}
// const win = window.documentPictureInPicture.window;
// win?.close();
// document.dispatchEvent(
// new CustomEvent(PipEventName.pip, {
// detail: {
// url: location.href,
// mode: "write-html",
// },
// })
// );
};
const throttledRefresh = throttle(refresh, 3000);
const handleKeydown = (e: KeyboardEvent) => {
const isWindows = navigator.userAgentData.platform == "Windows";
if (e.code == "KeyR" && (isWindows ? e.ctrlKey : e.metaKey)) {
throttledRefresh();
}
// if (e.code == "KeyM" && e.metaKey) {
// e.preventDefault();
// e.stopPropagation()
// }
};
onMounted(() => {
pipWindowRef.value?.addEventListener("keydown", handleKeydown);
});
onUnmounted(() => {
pipWindowRef.value?.removeEventListener("keydown", handleKeydown);
});
</script>
<template>
<button
class="multitasking-menu flex gap-1 px-2 py-1.5 z-[9999]"
@click="open = true"
@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>
</button>
<div
v-if="open"
ref="btns"
class="btn-group flex px-2 py-1 gap-2 fixed rounded-full left-6 z-[9999]"
@pointerenter="btnPointerEnter"
@pointerleave="btnPointerLeave"
@click="open = false"
>
<button
class="btn w-7 h-7 flex items-center justify-center rounded"
@click="minimize"
aria-label="minimize"
>
<IconMinimize />
</button>
<button
class="btn w-7 h-7 flex items-center justify-center rounded"
@click="slideOver"
aria-label="slide over"
>
<IconSplitRight :class="{ 'rotate-180': pipRight < pipLeft }" />
</button>
<button
class="btn w-7 h-7 flex items-center justify-center rounded"
@click="close"
aria-label="close"
>
<IconClose />
</button>
<button
class="btn w-7 h-7 flex items-center justify-center rounded"
@click="refresh"
aria-label="refresh"
>
<IconRefresh />
</button>
<!-- <button @click="">forward</button>
<button @click="">backward</button> -->
</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%);
}
.btn-group {
backdrop-filter: blur(5px) invert(5%);
top: 12px;
border: 1px solid var(--color-border);
color: white;
left: 50%;
transform: translateX(-50%);
background-color: rgba(60, 60, 60, 0.5);
animation: 200ms ease-out fadein;
}
.btn-group .btn {
}
.btn-group .btn:hover {
background: rgba(30, 30, 30, 0.2);
}
@keyframes fadein {
from {
transform: translateX(-50%) translateY(-50%) scale(50%);
opacity: 0.2;
}
to {
transform: translateX(-50%) translateY(0) scale(100%);
opacity: 100;
}
}
</style>
<script setup lang="ts">
import { pipEventName } from "@/content/pip";
import { PipEventName } from "@/types/pip";
import { reactive, ref } from "vue";
import IconClose from "./icons/IconClose.vue";
const emit = defineEmits(["close"]);
......@@ -8,7 +9,7 @@ const host = ref(location.host);
function handleClick() {
document.dispatchEvent(
new CustomEvent(pipEventName, {
new CustomEvent(PipEventName.pip, {
detail: {
url: location.href,
mode: "write-html",
......@@ -45,7 +46,7 @@ function handleClose(e: MouseEvent) {
]"
@click="handleClose"
>
×
<IconClose class="w-4 h-4" />
</div>
</div>
<div class="text-2xl font-bold text-center mt-24 opacity-60">
......
<script setup lang="ts">
import LoadingBar from "./LoadingBar.vue";
</script>
<template>
<div>
<div class="text-2xl font-bold text-center mt-24 opacity-60">
Anything Copilot
</div>
<div class="px-5 mt-12">
<LoadingBar />
</div>
</div>
</template>
<style scoped></style>
<script setup lang="ts"></script>
<template>
<div></div>
</template>
<style scoped></style>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 -960 960 960"
width="24"
fill="currentColor"
>
<path
d="m480-320 160-160-160-160-56 56 64 64H320v80h168l-64 64 56 56Zm0 240q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"
/>
</svg>
</template>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 -960 960 960"
width="24"
fill="currentColor"
>
<path
d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"
/>
</svg>
</template>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 -960 960 960"
width="24"
fill="currentColor"
>
<path
d="m177-120-57-57 184-183H200v-80h240v240h-80v-104L177-120Zm343-400v-240h80v104l183-184 57 57-184 183h104v80H520Z"
/>
</svg>
</template>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 -960 960 960"
width="24"
fill="currentColor"
>
<path d="M240-120v-80h480v80H240Z" />
</svg>
</template>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 -960 960 960"
width="24"
fill="currentColor"
>
<path
d="M480-160q-134 0-227-93t-93-227q0-134 93-227t227-93q69 0 132 28.5T720-690v-110h80v280H520v-80h168q-32-56-87.5-88T480-720q-100 0-170 70t-70 170q0 100 70 170t170 70q77 0 139-44t87-116h84q-28 106-114 173t-196 67Z"
/>
</svg>
</template>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 -960 960 960"
width="24"
fill="currentColor"
>
<path
d="M600-120q-33 0-56.5-23.5T520-200v-560q0-33 23.5-56.5T600-840h160q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H600Zm-400 0q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h160q33 0 56.5 23.5T440-760v560q0 33-23.5 56.5T360-120H200Zm0-640v560h160v-560H200Zm160 560H200h160Z"
/>
</svg>
</template>
<script setup lang="ts">
import { contentCss, pipLauncher } from "./store";
import { pipLauncher } from "./store";
import PipLauncher from "@/components/PipLauncher.vue";
</script>
......
<script setup lang="ts">
import Multitasking from "@/components/Multitasking.vue";
import PipSplash from "@/components/PipSplash.vue";
import { pipLoading } from "./store";
import LoadingBar from "@/components/LoadingBar.vue";
</script>
<template>
<Multitasking />
<div
v-if="pipLoading.splashScreen && pipLoading.isLoading"
class="fixed top-0 left-0 w-full h-full"
>
<PipSplash />
</div>
<div
v-if="!pipLoading.splashScreen && pipLoading.isLoading"
class="fixed w-full top-0 left-0 z-[9999]"
>
<LoadingBar />
</div>
</template>
<style scoped></style>
import { pip, pipEventName } from "./pip";
import { waitMountApp } from "./ui";
import { contentCss, pipLauncher } from "./store";
import { mount, waitMountApp } from "./ui";
import {
contentCss,
pipLauncher,
pipLoading,
pipWindowInfo,
pipWindowRef,
} from "./store";
import { MessageType } from "@/types";
import Copilot from "./Copilot.vue";
import { waitMessage } from "@/utils/ext";
import { PipEventName } from "@/types/pip";
function handleMessage(
message: any,
......@@ -9,25 +18,88 @@ function handleMessage(
) {
console.log(message, sender);
switch (message?.type) {
case "pip":
case MessageType.pip:
document.dispatchEvent(
new CustomEvent(pipEventName, { detail: message.options })
new CustomEvent(PipEventName.pip, { detail: message.options })
);
break;
case "content-css":
contentCss.value = message.payload?.value || "";
break;
case "pip-launch":
case MessageType.pipLaunch:
pipLauncher.visible = true;
break;
case "hi-content":
case MessageType.hiContent:
chrome.runtime.sendMessage({
type: "content-here",
type: MessageType.contentHere,
});
sendResponse({ type: MessageType.contentHere });
break;
case MessageType.pipWinInfo:
pipWindowInfo.value = message.window;
chrome.storage.local.set({
pipWindowId: message.window.id,
});
sendResponse({ type: "content-here" });
break;
}
}
chrome.runtime.onMessage.addListener(handleMessage);
async function handlePipEvent(event: any) {
const pipWindow = await new Promise<Window | null>((r) => {
const docPip = window.documentPictureInPicture;
const handleEnter = () => {
r(docPip.window);
docPip?.removeEventListener("enter", handleEnter);
};
docPip?.addEventListener("enter", handleEnter);
});
console.log("content pip event: ", event);
if (pipWindow) {
pipWindowRef.value = pipWindow;
mount(Copilot, pipWindow.document);
await new Promise<void>((r) => {
document.addEventListener(PipEventName.loaded, (e) => {
console.log("load", e);
r();
});
});
// may be 0 if not wait document is loaded
chrome.runtime.sendMessage({
type: MessageType.getPipWinInfo,
options: {
width: pipWindow.outerWidth,
height: pipWindow.outerHeight,
},
});
pipWindow.addEventListener("pagehide", () => {
chrome.storage.local.set({
pipWindowId: undefined,
});
});
}
}
async function handlePipLoadedEvent(e: Event) {
console.log("e: ", e);
pipLoading.splashScreen = false;
pipLoading.isLoading = false;
const pipWindow = window.documentPictureInPicture.window;
if (pipWindow) {
mount(Copilot, pipWindow.document);
}
}
async function handlePopLoadDocEvent(e: CustomEvent | Event) {
pipLoading.isLoading = true;
}
chrome.runtime.onMessage.addListener(handleMessage);
document.addEventListener(PipEventName.pip, handlePipEvent);
document.addEventListener(PipEventName.loaded, handlePipLoadedEvent);
document.addEventListener(PipEventName.loadDoc, handlePopLoadDocEvent);
waitMountApp();
import { pip, pipEventName } from "./pip";
import { PipEventName } from "@/types/pip";
import { copilotNavigateTo, pip } from "./pip";
function handleEvent(event: any) {
function handlePipEvent(event: CustomEvent | Event) {
console.log(event);
if ("detail" in event) {
try {
pip(event.detail);
} catch (e) {
console.error(e);
}
}
}
function handleLoadDocEvent(event: CustomEvent | Event) {
if ("detail" in event) {
copilotNavigateTo(event.detail.url);
}
}
document.addEventListener(pipEventName, handleEvent);
document.addEventListener(PipEventName.pip, handlePipEvent);
document.addEventListener(PipEventName.loadDoc, handleLoadDocEvent);
window.addEventListener("securitypolicyviolation", (e) => {
console.warn(e);
......
import type { PipOptions } from "@/types/pip";
import { PipEventName, type PipOptions } from "@/types/pip";
import {
querySome,
copyStyleSheets,
......@@ -7,12 +7,12 @@ import {
removePrerenderRules,
} from "@/utils/dom";
export const pipEventName = "anything-copilot-pip";
function fetchDoc(input: URL | RequestInfo, init?: RequestInit) {
const headers = {
Accept:
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
// "User-Agent":
// "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Mobile Safari/537.36",
};
return fetch(input, {
...init,
......@@ -23,6 +23,8 @@ function fetchDoc(input: URL | RequestInfo, init?: RequestInit) {
});
}
let initWindow = null as any;
export async function pip(options: PipOptions) {
let { mode, selector, url, isCopyStyle } = options;
url = url || location.href;
......@@ -33,10 +35,14 @@ export async function pip(options: PipOptions) {
}
const pipWindow = await window.documentPictureInPicture.requestWindow({
width: 350,
width: 420,
height: 800,
});
initWindow = { ...pipWindow };
// test(pipWindow.document, pipWindow);
if (isCopyStyle) {
copyStyleSheets(pipWindow, document);
}
......@@ -76,9 +82,9 @@ export async function copilotNavigateTo(url: string) {
const res = await fetchDoc(url);
const html = await res.text();
// resetWindow(pipWindow);
writeHtml(pipWindow, html);
navGuard(pipWindow);
history.replaceState(null, "", url);
pipWindow.history.replaceState(pipWindow.history.state, "", url);
}
type ReopenOptions = {
......@@ -121,6 +127,7 @@ function writeHtml(pipWindow: Window, html: string) {
pipWindow.document.open();
pipWindow.document.write(escaped);
pipWindow.document.close();
document.dispatchEvent(new CustomEvent(PipEventName.loaded));
const base = document.createElement("base");
base.target = "_blank";
......@@ -173,3 +180,53 @@ function navGuard(pipWindow: Window) {
pipWindow.addEventListener("beforeunload", handleBeforeUnload);
pipWindow.addEventListener("click", handleClick);
}
function resetWindow(win: Window) {
// const iframe = document.createElement("iframe");
// iframe.style.display = "none";
// win.document.body.appendChild(iframe);
// const iframeWindow = iframe.contentWindow;
// if (iframeWindow) {
// for (let k of Object.keys(win)) {
// if (!(k in iframeWindow)) {
// delete win[k as any];
// }
// }
// }
Object.keys(win).forEach((k) => {
if (k == "location") return;
try {
win[k as any] = initWindow[k];
} catch (e) {
console.error(e);
}
});
Object.keys(win.document).forEach((k) => {
if (k == "location") return;
try {
win.document[k as "body"] = initWindow.document[k];
} catch (e) {
console.error(e);
}
});
}
function test(doc: Document, win: Window) {
doc.addEventListener("DOMContentLoaded", (e) => {
console.warn("DOMContentLoaded", e);
});
win.addEventListener("beforeunload", (e) => {
console.warn("beforeunload", e);
});
win.addEventListener("load", (e) => {
console.warn("load", e);
});
win.addEventListener("unload", (e) => {
console.warn("unload", e);
});
}
......@@ -28,3 +28,11 @@ export const items = reactive([
title: "Tiktok",
},
]);
export const pipWindowRef = ref<Window | null>(null);
export const pipWindowInfo = ref<chrome.windows.Window | null>(null);
export const pipLoading = reactive({
isLoading: true,
splashScreen: true,
});
import { createApp } from "vue";
import { createApp, type Component } from "vue";
import { createI18n } from "vue-i18n";
import App from "./App.vue";
import { MessageType } from "@/types";
import "@/assets/main.css";
export function mountApp() {
const outter = document.createElement("anything-copilot");
const shadowRoot = outter.attachShadow({ mode: "open" });
const appContainer = document.createElement("div");
const isSelf = chrome.runtime.id === location.host;
export function mount(App: Component, doc = document) {
const outter = doc.createElement("anything-copilot");
const root = isSelf ? outter : outter.attachShadow({ mode: "open" });
const appContainer = doc.createElement("div");
appContainer.id = "app";
const link = document.createElement("link");
const link = doc.createElement("link");
link.rel = "stylesheet";
link.href = chrome.runtime.getURL("/index.css");
shadowRoot.append(link);
shadowRoot.append(appContainer);
document.documentElement.append(outter);
createApp(App).mount(appContainer);
root.append(link);
root.append(appContainer);
doc.documentElement.append(outter);
const app = createApp(App);
const i18n = createI18n({});
app.use(i18n);
app.mount(appContainer);
}
// chrome.runtime.sendMessage({
// type: "get-content-css",
// url: "/index.css",
// });
export function mountApp(doc = document) {
mount(App, doc);
chrome.runtime.sendMessage({ type: MessageType.contentMount });
}
export function waitMountApp() {
......
<script setup lang="ts">
import { ref, onMounted, computed, reactive } from "vue";
import { ref, onMounted, onUnmounted, computed, reactive, watch } from "vue";
import { emptyTab, checkContent } from "@/utils/ext";
import { items } from "@/content/store";
import IconHide from "@/components/icons/IconHide.vue";
import IconArrowCircleRight from "@/components/icons/IconArrowCircleRight.vue";
import IconClose from "@/components/icons/IconClose.vue";
const activeTab = ref<chrome.tabs.Tab>(emptyTab);
const manifest = reactive(chrome.runtime.getManifest());
const avaiable = ref(false);
const pipWindowId = ref(0);
const pipWindowInfo = reactive({
windowId: 0,
isOpen: false,
favIconUrl: "",
title: "",
});
const handleLocalChange = (changes: {
[key: string]: chrome.storage.StorageChange;
}) => {
if (changes.pipWindowId) {
pipWindowId.value = changes.pipWindowId.newValue;
}
};
watch(pipWindowId, async (id) => {
if (id) {
const tabs = await chrome.tabs.query({ windowId: id });
console.log("pip window tabs: ", tabs);
if (tabs && tabs.length == 1) {
pipWindowInfo.isOpen = true;
pipWindowInfo.favIconUrl = tabs[0].favIconUrl || "";
pipWindowInfo.title = tabs[0].title || "";
}
return;
}
pipWindowInfo.isOpen = false;
});
onMounted(() => {
chrome.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
console.log(tabs);
......@@ -16,6 +52,20 @@ onMounted(() => {
avaiable.value = value;
});
});
chrome.storage.local
.get({ pipWindowId: null })
.then(({ pipWindowId: id }) => {
if (id) {
pipWindowId.value = id;
}
});
chrome.storage.local.onChanged.addListener(handleLocalChange);
});
onUnmounted(() => {
chrome.storage.local.onChanged.removeListener(handleLocalChange);
});
const host = computed({
......@@ -48,6 +98,17 @@ async function handleClickLaunch(url: string) {
url,
});
}
async function handleUpdatePip(state: "normal" | "minimized") {
chrome.windows.update(pipWindowId.value, {
state,
});
}
async function closePip() {
await chrome.windows.update(pipWindowId.value, { state: "normal" });
chrome.windows.remove(pipWindowId.value);
}
</script>
<template>
......@@ -57,6 +118,39 @@ async function handleClickLaunch(url: string) {
<span class="mx-2 text-sm opacity-50">{{ manifest.version }}</span>
</div>
<div v-if="pipWindowInfo.isOpen">
<div class="text-sm flex items-center truncate mt-6">
<span
class="w-4 h-4 inline-block mr-2 rounded"
:style="{
background:
'center / contain url(' + pipWindowInfo.favIconUrl + ')',
}"
></span>
<span>{{ pipWindowInfo.title }}</span>
</div>
<div class="flex gap-2">
<button
class="primary-btn flex items-center mt-2 rounded-lg p-2 px-3"
@click="handleUpdatePip('minimized')"
>
<IconHide />
</button>
<button
class="primary-btn flex items-center mt-2 rounded-lg p-2 px-3"
@click="handleUpdatePip('normal')"
>
<IconArrowCircleRight />
</button>
<button
class="primary-btn flex items-center mt-2 rounded-lg p-2 px-3"
@click="closePip"
>
<IconClose />
</button>
</div>
</div>
<div class="text-sm truncate mt-6">
{{ host }}
<span class="text-rose-800" v-if="!avaiable">{{
......
import '@/assets/main.css'
import "@/assets/main.css";
import { createApp } from 'vue'
import Popup from './Popup.vue'
import { createApp } from "vue";
import { createI18n } from "vue-i18n";
import Popup from "./Popup.vue";
createApp(Popup).mount('#app')
const i18n = createI18n({});
const app = createApp(Popup);
app.use(i18n);
app.mount("#app");
export enum MessageType {
pip = "pip",
hiContent = "hi-content",
contentHere = "content-here",
bgOpenPip = "bg-open-pip",
bgPipLaunch = "bg-pip-launch",
pipLaunch = "pip-launch",
contentMount = "content-mount",
minimizePipWin = "minimize-pip-win",
getPipWinInfo = "get-pip-win-info",
pipWinInfo = "pip-win-info",
updatePipWin = "update-pip-win",
}
......@@ -4,3 +4,9 @@ export type PipOptions = {
mode: "iframe" | "write-html" | "move-element";
isCopyStyle?: boolean;
};
export enum PipEventName {
pip = "anything-copilot-pip",
loaded = "anything-copilot-loaded",
loadDoc = "anything-copilot-load-doc",
}
import { MessageType } from "@/types";
type MessageSender = chrome.runtime.MessageSender;
type UpdatedOption = {
......@@ -23,6 +25,37 @@ export async function tabUpdated({ tabId, status, timeout }: UpdatedOption) {
});
}
type WaitMessageOption = {
type: string;
tabId?: number;
timeout?: number;
};
export async function waitMessage<T = unknown>({
type,
tabId,
timeout,
}: WaitMessageOption): Promise<T> {
return new Promise<T>((r, reject) => {
let timer = 0;
const handleMessage = (message: any, sender: MessageSender) => {
const sameId = typeof tabId == "number" ? tabId == sender.tab?.id : true;
if (sameId && message.type == type) {
chrome.runtime.onMessage.removeListener(handleMessage);
r(message);
clearTimeout(timer);
}
};
chrome.runtime.onMessage.addListener(handleMessage);
if (timeout) {
timer = setTimeout(() => {
chrome.runtime.onMessage.removeListener(handleMessage);
reject();
}, timeout);
}
});
}
export const emptyTab: chrome.tabs.Tab = {
active: false,
autoDiscardable: false,
......@@ -54,7 +87,9 @@ export async function checkContent(tabId: number) {
chrome.runtime.onMessage.addListener(handleMessage);
});
const res = await chrome.tabs.sendMessage(tabId, { type: "hi-content" });
const res = await chrome.tabs.sendMessage(tabId, {
type: MessageType.hiContent,
});
alive = !!res;
console.log("hi-content response: ", alive, res);
......
......@@ -3,13 +3,12 @@ import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
import svgr from "vite-plugin-svgr";
import manifest from "./manifest";
import makeManifest from "./utils/manifest-plugin";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), vueJsx(), svgr(), makeManifest(manifest, { isDev: false })],
plugins: [vue(), vueJsx(), makeManifest(manifest, { isDev: false })],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
......@@ -17,11 +16,18 @@ export default defineConfig({
},
build: {
emptyOutDir: false,
assetsDir: "assets",
outDir: "dist",
rollupOptions: {
input: {
popup: "popup.html",
guide: "guide.html",
},
output: {
assetFileNames: "[name].[ext]",
chunkFileNames: "[name]-chunk.js",
entryFileNames: "[name].js",
},
},
},
});
......@@ -2,10 +2,12 @@ import { defineConfig } from "vite";
import { fileURLToPath, URL } from "node:url";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
import svgr from "vite-plugin-svgr";
export default defineConfig({
plugins: [vue(), vueJsx(), svgr()],
server: {
port: 3000,
},
plugins: [vue(), vueJsx()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
......
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