Commit 62c4fef2 authored by Domi's avatar Domi Committed by baotlake

feat: pip copilot window

parents
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
{
"tabWidth": 2,
"useTabs": false
}
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}
# picture-in-picture
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension
1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Type-Check, Compile and Minify for Production
```sh
npm run build
```
/// <reference types="vite/client" />
interface DocumentPictureInPicture {
window: Window | null;
requestWindow(option?: { width?: number; height?: number }): Promise<Window>;
}
export declare global {
interface documentPictureInPicture extends DocumentPictureInPicture {}
interface Window {
documentPictureInPicture: DocumentPictureInPicture;
trustedTypes: any
}
}
export {};
import * as esbuild from "esbuild";
const isWatch = process.argv.includes("--watch");
const ctx = await esbuild.context({
entryPoints: {
main: "./src/content/main.ts",
bg: "./src/bg/index.ts",
},
bundle: true,
format: "iife",
outdir: "./dist",
alias: {
"@": "./src/",
},
});
if (isWatch) {
await ctx.watch();
} else {
await ctx.rebuild();
ctx.dispose();
}
<!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>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/pages/guide.ts"></script>
</body>
</html>
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",
action: {
default_icon: {
16: "logo.png",
24: "logo.png",
32: "logo.png",
},
default_title: "pip",
default_popup: "popup.html",
},
default_locale: "en",
icons: {
16: "logo.png",
32: "logo.png",
48: "logo.png",
128: "logo.png",
},
author: "support@ziziyi.com",
background: {
service_worker: "bg.js",
type: "module",
},
content_scripts: [
{
matches: ["<all_urls>"],
js: ["main.js"],
run_at: "document_start",
world: "MAIN",
},
{
matches: ["<all_urls>"],
js: ["content.js"],
run_at: "document_start",
},
],
options_page: "guide.html",
permissions: ["tabs", "scripting", "activeTab"],
host_permissions: ["<all_urls>"],
minimum_chrome_version: "111",
web_accessible_resources: [
{
resources: ["index.css"],
matches: ["<all_urls>"],
},
],
};
export default manifest;
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "picture-in-picture",
"version": "0.0.0",
"private": true,
"scripts": {
"dev:page": "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",
"build": "run-p type-check build:js build:content \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"build:content": "vite build -c vite.content.config.ts",
"build:js": "node esbuild.mjs",
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
"zip:win": "7z a anything-copilot.zip .\\dist\\*",
"zip": ""
},
"dependencies": {
"vue": "^3.3.4"
},
"devDependencies": {
"@tsconfig/node18": "^18.2.2",
"@types/chrome": "^0.0.251",
"@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",
"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"
}
}
<!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>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/pages/popup.ts"></script>
</body>
</html>
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
{
"A": {
"message": "A"
}
}
{
"A": {
"message": "A"
}
}
<svg width="1080" height="1080" viewBox="0 0 1080 1080" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M515.09 725.824L472.006 824.503C455.444 862.434 402.954 862.434 386.393 824.503L343.308 725.824C304.966 638.006 235.953 568.104 149.868 529.892L31.2779 477.251C-6.42601 460.515 -6.42594 405.665 31.2779 388.929L146.164 337.932C234.463 298.737 304.714 226.244 342.401 135.431L386.044 30.2693C402.239 -8.75637 456.159 -8.75646 472.355 30.2692L515.998 135.432C553.685 226.244 623.935 298.737 712.234 337.932L827.121 388.929C864.825 405.665 864.825 460.515 827.121 477.251L708.53 529.892C622.446 568.104 553.433 638.006 515.09 725.824Z" fill="url(#paint0_radial_2525_777)"/>
<path d="M915.485 1036.98L903.367 1064.75C894.499 1085.08 866.349 1085.08 857.481 1064.75L845.364 1036.98C823.765 987.465 784.862 948.042 736.318 926.475L698.987 909.889C678.802 900.921 678.802 871.578 698.987 862.61L734.231 846.951C784.023 824.829 823.623 783.947 844.851 732.75L857.294 702.741C865.966 681.826 894.882 681.826 903.554 702.741L915.997 732.75C937.225 783.947 976.826 824.829 1026.62 846.951L1061.86 862.61C1082.05 871.578 1082.05 900.921 1061.86 909.889L1024.53 926.475C975.987 948.042 937.083 987.465 915.485 1036.98Z" fill="url(#paint1_radial_2525_777)"/>
<defs>
<radialGradient id="paint0_radial_2525_777" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(670.447 474.006) rotate(78.858) scale(665.5 665.824)">
<stop stop-color="#1BA1E3"/>
<stop offset="0.0001" stop-color="#1BA1E3"/>
<stop offset="0.300221" stop-color="#5489D6"/>
<stop offset="0.545524" stop-color="#9B72CB"/>
<stop offset="0.825372" stop-color="#D96570"/>
<stop offset="1" stop-color="#F49C46"/>
</radialGradient>
<radialGradient id="paint1_radial_2525_777" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(670.447 474.006) rotate(78.858) scale(665.5 665.824)">
<stop stop-color="#1BA1E3"/>
<stop offset="0.0001" stop-color="#1BA1E3"/>
<stop offset="0.300221" stop-color="#5489D6"/>
<stop offset="0.545524" stop-color="#9B72CB"/>
<stop offset="0.825372" stop-color="#D96570"/>
<stop offset="1" stop-color="#F49C46"/>
</radialGradient>
</defs>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 2406 2406">
<path d="M1 578.4C1 259.5 259.5 1 578.4 1h1249.1c319 0 577.5 258.5 577.5 577.4V2406H578.4C259.5 2406 1 2147.5 1 1828.6V578.4z" fill="#74aa9c"/>
<path id="a" d="M1107.3 299.1c-197.999 0-373.9 127.3-435.2 315.3L650 743.5v427.9c0 21.4 11 40.4 29.4 51.4l344.5 198.515V833.3h.1v-27.9L1372.7 604c33.715-19.52 70.44-32.857 108.47-39.828L1447.6 450.3C1361 353.5 1237.1 298.5 1107.3 299.1zm0 117.5-.6.6c79.699 0 156.3 27.5 217.6 78.4-2.5 1.2-7.4 4.3-11 6.1L952.8 709.3c-18.4 10.4-29.4 30-29.4 51.4V1248l-155.1-89.4V755.8c-.1-187.099 151.601-338.9 339-339.2z" fill="#fff"/>
<use xlink:href="#a" transform="rotate(60 1203 1203)"/>
<use xlink:href="#a" transform="rotate(120 1203 1203)"/>
<use xlink:href="#a" transform="rotate(180 1203 1203)"/>
<use xlink:href="#a" transform="rotate(240 1203 1203)"/>
<use xlink:href="#a" transform="rotate(300 1203 1203)"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 512 512"><rect fill="#CC9B7A" width="512" height="512" rx="104.187" ry="105.042"/><path fill="#1F1F1E" fill-rule="nonzero" d="M318.663 149.787h-43.368l78.952 212.423 43.368.004-78.952-212.427zm-125.326 0l-78.952 212.427h44.255l15.932-44.608 82.846-.004 16.107 44.612h44.255l-79.126-212.427h-45.317zm-4.251 128.341l26.91-74.701 27.083 74.701h-53.993z"/></svg>
\ No newline at end of file
<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"/>
</svg>
<svg viewBox="0 0 291.72499821636245 291.1" xmlns="http://www.w3.org/2000/svg" width="2500" height="2495"><path d="M219.51 291.1H71.58C32.05 291.1 0 259.05 0 219.51V71.58C0 32.05 32.05 0 71.58 0h147.93c39.53 0 71.58 32.05 71.58 71.58v147.93c.01 39.54-32.04 71.59-71.58 71.59z"/><g fill="#25f4ee"><path d="M120.96 123.89v-8.8a64.83 64.83 0 0 0-9.23-.79c-29.93-.06-56.42 19.33-65.41 47.87s1.62 59.62 26.18 76.71c-25.77-27.58-24.3-70.83 3.28-96.6a68.425 68.425 0 0 1 45.18-18.39z"/><path d="M122.62 223.53c16.73-.02 30.48-13.2 31.22-29.92V44.44h27.25a50.7 50.7 0 0 1-.79-9.44h-37.27v149.02c-.62 16.8-14.41 30.11-31.22 30.14-5.02-.04-9.97-1.28-14.42-3.6a31.276 31.276 0 0 0 25.23 12.97zM231.98 95.05v-8.29c-10.03 0-19.84-2.96-28.19-8.51a51.63 51.63 0 0 0 28.19 16.8z"/></g><path d="M203.8 78.26a51.301 51.301 0 0 1-12.76-33.89h-9.95a51.564 51.564 0 0 0 22.71 33.89zM111.73 151.58c-17.28.09-31.22 14.17-31.13 31.45a31.293 31.293 0 0 0 16.71 27.53c-10.11-13.96-6.99-33.48 6.97-43.6a31.191 31.191 0 0 1 18.34-5.93c3.13.04 6.24.53 9.23 1.45v-37.93c-3.05-.46-6.14-.7-9.23-.72h-1.66v28.84c-3.01-.82-6.12-1.18-9.23-1.09z" fill="#fe2c55"/><path d="M231.98 95.05v28.84a88.442 88.442 0 0 1-51.69-16.8v75.77c-.08 37.81-30.75 68.42-68.56 68.42a67.816 67.816 0 0 1-39.22-12.4c25.73 27.67 69.02 29.25 96.7 3.52a68.397 68.397 0 0 0 21.83-50.09v-75.56a88.646 88.646 0 0 0 51.76 16.58V96.21c-3.64-.02-7.26-.4-10.82-1.16z" fill="#fe2c55"/><path d="M180.29 182.87V107.1a88.505 88.505 0 0 0 51.76 16.58V94.84a51.73 51.73 0 0 1-28.26-16.58 51.634 51.634 0 0 1-22.71-33.89h-27.25v149.24c-.71 17.27-15.27 30.69-32.54 29.99a31.278 31.278 0 0 1-24.06-12.9c-15.29-8.05-21.16-26.97-13.11-42.26a31.274 31.274 0 0 1 27.53-16.71c3.13.03 6.24.51 9.23 1.44V123.9c-37.74.64-67.82 32.19-67.18 69.93a68.353 68.353 0 0 0 18.73 45.86 67.834 67.834 0 0 0 39.29 11.61c37.82-.01 68.49-30.62 68.57-68.43z" fill="#fff"/></svg>
\ No newline at end of file
File added
/* color palette from <https://github.com/vuejs/theme> */
@tailwind base;
@tailwind components;
@tailwind utilities;
:root,
:host {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
/* semantic color variables for this project */
:root,
:host {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
@media (prefers-color-scheme: dark) {
:root,
:host {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
body,
#app {
color: var(--color-text);
background: var(--color-background);
transition: color 0.5s, background-color 0.5s;
line-height: 1.6;
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
@import './base.css';
#app {
margin: 0 auto;
font-weight: normal;
}
a,
.green {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
}
@media (hover: hover) {
a:hover {
background-color: hsla(160, 100%, 37%, 0.2);
}
}
@media (min-width: 1024px) {
body {
display: flex;
place-items: center;
}
#app {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0 2rem;
}
}
import { tabUpdated } from "@/utils/ext";
async function openPipBackground(url: string) {
const tab = await chrome.tabs.create({
url: url,
});
await tabUpdated({ tabId: tab.id!, status: "complete" });
chrome.tabs.sendMessage(tab.id!, {
type: "pip",
options: {
url: url,
mode: "write-html",
},
});
}
async function getContentCss(id: number, url: string) {
const res = await fetch(url);
const text = await res.text();
chrome.tabs.sendMessage(id, {
type: "content-css",
payload: {
url: url,
value: text,
},
});
}
async function pipLaunch(url: string) {
const tab = await chrome.tabs.create({ url });
await tabUpdated({ tabId: tab.id!, status: "complete" });
chrome.tabs.sendMessage(tab.id!, {
type: "pip-launch",
url: url,
});
}
function handleMessage(message: any, sender: chrome.runtime.MessageSender) {
console.log("bg message: ", message, sender);
switch (message?.type) {
case "bg-open-pip":
openPipBackground(message.url);
break;
case "get-content-css":
getContentCss(sender.tab?.id || 0, message.url);
break;
case "bg-pip-launch":
pipLaunch(message.url);
break;
}
}
chrome.runtime.onMessage.addListener(handleMessage);
<script setup lang="ts">
import { pipEventName } from "@/content/pip";
import { reactive, ref } from "vue";
const emit = defineEmits(["close"]);
const host = ref(location.host);
function handleClick() {
document.dispatchEvent(
new CustomEvent(pipEventName, {
detail: {
url: location.href,
mode: "write-html",
},
})
);
emit("close");
}
function handleClose(e: MouseEvent) {
e.stopPropagation();
emit("close");
}
</script>
<template>
<div
class="pip-launcher rounded-lg overflow-hidden shadow-2xl cursor-pointer"
@click="handleClick"
>
<div
:class="[
'h-8 w-full text-sm px-3 flex items-center',
'bg-[var(--color-background-mute)]',
]"
>
<div class="truncate opacity-40 select-none">
{{ host }}
</div>
<div
:class="[
'ml-auto w-6 h-6 rounded-full flex items-center justify-center text-lg leading-6',
'hover:bg-[var(--color-background-soft)]',
]"
@click="handleClose"
>
×
</div>
</div>
<div class="text-2xl font-bold text-center mt-24 opacity-60">
Anything Copilot
</div>
<div class="mt-28">
<div class="flex flex-col items-center">
<div
class="w-36 h-36 rounded-full animate-ping bg-[var(--color-background-soft)]"
></div>
<div class="font-bold text-base">Click Here</div>
</div>
</div>
</div>
</template>
<style scoped>
.pip-launcher {
position: fixed;
height: 80%;
max-height: 1200px;
top: 10%;
z-index: 9999;
background: var(--color-background);
border: 1.5px solid var(--color-background-mute);
width: 350px;
max-width: 90vw;
right: 10vw;
}
</style>
<script setup lang="ts">
defineProps<{
image?: string;
title: string;
}>();
</script>
<template>
<button class="primary-btn w-full flex items-center mt-2 rounded-lg p-2">
<span
class="w-6 h-6 inline-block mr-2 rounded"
:style="{
background: 'center / contain url(' + image + ')',
}"
>
</span>
<span class="text-base font-bold">{{ title }}</span>
</button>
</template>
<style scoped></style>
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/>
</svg>
</template>
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
<path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
/>
</svg>
</template>
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
<path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
/>
</svg>
</template>
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/>
</svg>
</template>
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--mdi"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
>
<path
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
fill="currentColor"
></path>
</svg>
</template>
<script setup lang="ts">
import { contentCss, pipLauncher } from "./store";
import PipLauncher from "@/components/PipLauncher.vue";
</script>
<template>
<PipLauncher
v-if="pipLauncher.visible"
@close="pipLauncher.visible = false"
/>
</template>
<style scoped></style>
import { pip, pipEventName } from "./pip";
import { waitMountApp } from "./ui";
import { contentCss, pipLauncher } from "./store";
function handleMessage(
message: any,
sender: chrome.runtime.MessageSender,
sendResponse: (res: any) => void
) {
console.log(message, sender);
switch (message?.type) {
case "pip":
document.dispatchEvent(
new CustomEvent(pipEventName, { detail: message.options })
);
case "content-css":
contentCss.value = message.payload?.value || "";
break;
case "pip-launch":
pipLauncher.visible = true;
break;
case "hi-content":
chrome.runtime.sendMessage({
type: "content-here",
});
sendResponse({ type: "content-here" });
break;
}
}
chrome.runtime.onMessage.addListener(handleMessage);
waitMountApp();
import { pip, pipEventName } from "./pip";
function handleEvent(event: any) {
console.log(event);
try {
pip(event.detail);
} catch (e) {
console.error(e);
}
}
document.addEventListener(pipEventName, handleEvent);
window.addEventListener("securitypolicyviolation", (e) => {
console.warn(e);
console.log(e.originalPolicy);
});
import type { PipOptions } from "@/types/pip";
import {
querySome,
copyStyleSheets,
getDomNonce,
replaceHtmlNonce,
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",
};
return fetch(input, {
...init,
headers: {
...headers,
...init?.headers,
},
});
}
export async function pip(options: PipOptions) {
let { mode, selector, url, isCopyStyle } = options;
url = url || location.href;
let element: Element | null = null;
if (selector) {
mode = "move-element";
element = querySome(selector);
}
const pipWindow = await window.documentPictureInPicture.requestWindow({
width: 350,
height: 800,
});
if (isCopyStyle) {
copyStyleSheets(pipWindow, document);
}
if (mode === "iframe") {
const iframe = document.createElement("iframe");
iframe.src = url;
iframe.id = "";
iframe.setAttribute("style", "width: 100%; height: 100%; border: none;");
pipWindow.document.body.append(iframe);
return;
}
if (mode === "move-element") {
if (element) {
pipWindow.document.body.append(element);
return;
} else {
throw Error("selector not found");
}
}
if (mode === "write-html") {
const res = await fetchDoc(url);
const html = await res.text();
writeHtml(pipWindow, html);
navGuard(pipWindow);
return;
}
}
export async function copilotNavigateTo(url: string) {
const pipWindow = window.documentPictureInPicture.window;
if (!pipWindow) {
throw Error("pipWindow not found");
}
const res = await fetchDoc(url);
const html = await res.text();
writeHtml(pipWindow, html);
navGuard(pipWindow);
history.replaceState(null, "", url);
}
type ReopenOptions = {
url: string;
width?: number;
height?: number;
};
/** Error: requires user activation */
export async function copilotReopen({ url, width, height }: ReopenOptions) {
const p = window.documentPictureInPicture.window;
let w = width;
let h = height;
if (p) {
w = w || p.innerWidth;
h = h || p.innerHeight;
p.close();
}
const pipWindow = await window.documentPictureInPicture.requestWindow({
width: w,
height: h,
});
const res = await fetchDoc(url);
const html = await res.text();
writeHtml(pipWindow, html);
navGuard(pipWindow);
}
function writeHtml(pipWindow: Window, html: string) {
const nonce = getDomNonce(document);
let escaped = replaceHtmlNonce(html, nonce);
if (window.trustedTypes) {
const escapeHTMLPolicy = window.trustedTypes.createPolicy("escapePolicy", {
createHTML: (string: string) => string,
});
escaped = escapeHTMLPolicy.createHTML(escaped);
}
pipWindow.document.open();
pipWindow.document.write(escaped);
pipWindow.document.close();
const base = document.createElement("base");
base.target = "_blank";
pipWindow.document.head.append(base);
removePrerenderRules(pipWindow.document);
}
function navGuard(pipWindow: Window) {
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
console.log("before unload: ", e);
e.preventDefault();
e.returnValue = true;
};
const handleClick = (e: MouseEvent) => {
const target = e.target as Element | null;
if (!target) return;
console.log("click ", e);
const anchor = target.closest<HTMLAnchorElement>("a, [href]");
if (!anchor) return;
const href = anchor.getAttribute("href");
if (!href) return;
if (href.slice(0, 1) == "#") {
e.preventDefault();
pipWindow.location.hash = href;
return;
}
console.log(">> href: ", href, e.defaultPrevented);
if (
href.startsWith(location.origin) ||
!href.startsWith(location.protocol)
) {
if (!e.defaultPrevented) {
e.preventDefault();
// copilotReopen({ url: new URL(anchor.href, location.origin).href });
copilotNavigateTo(new URL(anchor.href, location.origin).href);
}
return;
}
};
pipWindow.addEventListener("beforeunload", handleBeforeUnload);
pipWindow.addEventListener("click", handleClick);
}
import { reactive, ref } from "vue";
export const contentCss = ref("");
export const pipLauncher = reactive({
visible: false,
});
export const items = reactive([
{
url: "https://chat.openai.com/",
img: "/chatgpt.svg",
title: "ChatGPT - OpenAI",
},
{
url: "https://bard.google.com/",
img: "/bard.svg",
title: "Bard - Google AI",
},
{
url: "https://claude.ai/",
img: "/claude-ai.svg",
title: "Claude",
},
{
url: "https://tiktok.com/",
img: "/tiktok.svg",
title: "Tiktok",
},
]);
import { createApp } from "vue";
import App from "./App.vue";
import "@/assets/main.css";
export function mountApp() {
const outter = document.createElement("anything-copilot");
const shadowRoot = outter.attachShadow({ mode: "open" });
const appContainer = document.createElement("div");
appContainer.id = "app";
const link = document.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);
// chrome.runtime.sendMessage({
// type: "get-content-css",
// url: "/index.css",
// });
}
export function waitMountApp() {
if (document.readyState == "interactive") {
mountApp();
} else {
const hanldeStateChange = () => {
if (document.readyState == "interactive") {
document.removeEventListener("readystatechange", hanldeStateChange);
mountApp();
}
};
document.addEventListener("readystatechange", hanldeStateChange);
}
}
<script setup lang="ts"></script>
<template>
<main></main>
</template>
<style scoped></style>
<script setup lang="ts">
import { ref, onMounted, computed, reactive } from "vue";
import { emptyTab, checkContent } from "@/utils/ext";
import { items } from "@/content/store";
const activeTab = ref<chrome.tabs.Tab>(emptyTab);
const manifest = reactive(chrome.runtime.getManifest());
const avaiable = ref(false);
onMounted(() => {
chrome.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
console.log(tabs);
if (tabs[0]) activeTab.value = tabs[0];
checkContent(tabs[0].id!).then((value) => {
avaiable.value = value;
});
});
});
const host = computed({
get: () => {
if (!activeTab.value.url) return "";
const u = new URL(activeTab.value.url);
return u.host;
},
set: () => {},
});
async function handleWriteHtml() {
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
const tab = tabs[0];
if (tab) {
chrome.tabs.sendMessage(tab.id!, {
type: "pip",
options: {
url: tab.url,
mode: "write-html",
},
});
}
}
async function handleClickLaunch(url: string) {
console.log(activeTab.value);
chrome.runtime.sendMessage({
type: "bg-pip-launch",
url,
});
}
</script>
<template>
<main class="w-[300px] p-4 mx-auto">
<div>
<span class="font-bold opacity-50">Anything Copilot</span>
<span class="mx-2 text-sm opacity-50">{{ manifest.version }}</span>
</div>
<div class="text-sm truncate mt-6">
{{ host }}
<span class="text-rose-800" v-if="!avaiable">{{
"is protected by browser"
}}</span>
</div>
<button
:class="[
'primary-btn w-full flex items-center mt-2 rounded-lg p-2 px-3',
{
'cursor-not-allowed': !avaiable,
},
]"
@click="handleWriteHtml"
>
<span
class="w-5 h-5 inline-block mr-3 rounded"
:style="{
background: 'center / contain url(' + activeTab.favIconUrl + ')',
}"
>
</span>
<span class="text-base">Open in Copilot window</span>
</button>
<div class="text-sm mt-6">other</div>
<button
v-for="item of items"
class="primary-btn w-full flex items-center mt-3 rounded-lg p-2 px-3"
@click="handleClickLaunch(item.url)"
>
<span
class="w-5 h-5 inline-block mr-3 rounded"
:style="{
background: 'center / contain url(' + item.img + ')',
}"
>
</span>
<span class="text-base">{{ item.title }}</span>
</button>
<div class="mt-6"></div>
</main>
</template>
<style scoped>
.primary-btn {
background: var(--color-background-soft);
}
.primary-btn:hover {
background: var(--color-background-mute);
}
</style>
import "@/assets/main.css";
import { createApp } from "vue";
import Guide from "./Popup.vue";
createApp(Guide).mount("#app");
function injectContent() {
const mainScript = document.createElement("script");
mainScript.src = "/main.js";
const script = document.createElement("script");
script.src = "/content.js";
document.head.append(mainScript);
document.head.append(script);
}
injectContent();
import '@/assets/main.css'
import { createApp } from 'vue'
import Popup from './Popup.vue'
createApp(Popup).mount('#app')
export type PipOptions = {
url: string;
selector?: string[];
mode: "iframe" | "write-html" | "move-element";
isCopyStyle?: boolean;
};
type Selector = string | { xpath: string };
/** Custom query function that supports shadow DOM penetration with /deep/ combinator and XPath
* */
export function query(selector: Selector) {
if (typeof selector === "string") {
// Check for /deep/ combinator in the selector
if (selector.includes("/deep/")) {
return queryShadowDom(document.documentElement, selector.split("/deep/"));
} else {
return document.querySelector(selector);
}
} else if (selector.xpath) {
// Handle XPath selector
const result = document.evaluate(
selector.xpath,
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
);
return result.singleNodeValue as Element;
}
return null;
}
function queryShadowDom(el: Element, parts: string[]) {
return null;
}
export function querySome(selectors: Selector[]) {
for (let selector of selectors) {
const result = query(selector);
if (result) {
return result;
}
}
return null;
}
export function copyStyleSheets(pipWindow: Window, document: Document) {
[...document.styleSheets].forEach((styleSheet) => {
try {
const cssRules = [...styleSheet.cssRules]
.map((rule) => rule.cssText)
.join("");
const style = document.createElement("style");
style.textContent = cssRules;
pipWindow.document.head.appendChild(style);
} catch (e) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.type = styleSheet.type;
link.media = styleSheet.media as any;
link.href = styleSheet.href as string;
pipWindow.document.head.appendChild(link);
}
});
}
export function getDomNonce(doc: Document) {
const nonce = { style: "", script: "" };
const elements = doc.querySelectorAll<
HTMLScriptElement | HTMLStyleElement | HTMLLinkElement
>("[nonce");
const isLink = (el: HTMLElement): el is HTMLLinkElement =>
el.nodeName == "LINK";
for (let element of elements) {
const code = element.nonce;
if (!code) continue;
if (element.nodeName == "SCRIPT" && !nonce.script) {
nonce.script = code;
continue;
}
if (isLink(element) && element.as == "script" && !nonce.script) {
nonce.script = code;
continue;
}
if (element.nodeName == "STYLE" && !nonce.style) {
nonce.style = code;
continue;
}
if (
isLink(element) &&
element.rel?.search("stylesheet") > -1 &&
!nonce.style
) {
nonce.style = code;
continue;
}
if (isLink(element) && element.as == "style" && !nonce.style) {
nonce.style = code;
continue;
}
}
return nonce;
}
export function replaceHtmlNonce(
html: string,
nonce: { script: string; style: string }
) {
const replacer = (match: string, p1: string, p2: string) => {
const isScript =
p1 == "script" || (p1 == "link" && match.search("script") > -1);
const isStyle =
p1 == "style" || (p1 == "link" && match.search("style") > -1);
let r = match;
if (isScript) {
r = match.replace(p2, nonce.script);
}
if (isStyle) {
r = match.replace(p2, nonce.style);
}
return r;
};
let txt = html.replace(/<(script|style|link)\s[^>]*nonce="(.+?)"/g, replacer);
return txt;
}
export function removePrerenderRules(doc: Document) {
const rules = doc.querySelectorAll('script[type="speculationrules"]');
if (rules) {
rules.forEach((s) => s.remove());
}
}
type MessageSender = chrome.runtime.MessageSender;
type UpdatedOption = {
tabId: number;
status: string;
timeout?: number;
};
export async function tabUpdated({ tabId, status, timeout }: UpdatedOption) {
return new Promise<void>((r) => {
const handleUpdate = (id: number, info: chrome.tabs.TabChangeInfo) => {
console.log(id, info);
if (id === tabId && info.status === status) {
chrome.tabs.onUpdated.removeListener(handleUpdate);
r();
}
};
setTimeout(() => {
chrome.tabs.onUpdated.removeListener(handleUpdate);
r();
}, timeout || 30 * 1000);
chrome.tabs.onUpdated.addListener(handleUpdate);
});
}
export const emptyTab: chrome.tabs.Tab = {
active: false,
autoDiscardable: false,
discarded: false,
highlighted: false,
selected: false,
groupId: 0,
height: 0,
id: 0,
incognito: false,
index: 0,
pinned: false,
windowId: 0,
};
export async function checkContent(tabId: number) {
let alive = false;
try {
const resMsgPromise = new Promise<void>((r) => {
const handleMessage = (message: any, sender: MessageSender) => {
if (sender.tab?.id == tabId) {
alive = true;
r();
chrome.runtime.onMessage.removeListener(handleMessage);
}
};
setTimeout(() => r(), 3000);
chrome.runtime.onMessage.addListener(handleMessage);
});
const res = await chrome.tabs.sendMessage(tabId, { type: "hi-content" });
alive = !!res;
console.log("hi-content response: ", alive, res);
if (!alive) {
await resMsgPromise;
}
} catch (err) {
console.error(err);
}
console.log("checkContent alive: ", alive);
if (alive) {
return true;
}
const manifest = chrome.runtime.getManifest();
if (!manifest.content_scripts) {
return false;
}
try {
for (let item of manifest.content_scripts) {
if (item.js) {
const world =
"world" in item && item.world == "MAIN" ? "MAIN" : "ISOLATED";
await chrome.scripting.executeScript({
files: item.js,
target: { tabId: tabId },
world: world,
});
}
}
return true;
} catch (e) {
return false;
}
}
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{vue,js,ts,jsx,tsx}"],
theme: {
fontSize: {
xs: "12px",
sm: "14px",
base: "16px",
lg: "18px",
xl: "20px",
"2xl": "24px",
"3xl": "30px",
"4xl": "36px",
"5xl": "48px",
"6xl": "60px",
"7xl": "72px",
},
spacing: {
px: "1px",
0: "0",
0.5: "2px",
1: "4px",
1.5: "6px",
2: "8px",
2.5: "10px",
3: "12px",
3.5: "14px",
4: "16px",
5: "20px",
6: "24px",
7: "28px",
8: "32px",
9: "36px",
10: "40px",
11: "44px",
12: "48px",
14: "56px",
16: "64px",
20: "80px",
24: "96px",
28: "112px",
32: "128px",
36: "144px",
40: "160px",
44: "176px",
48: "192px",
52: "208px",
56: "224px",
60: "240px",
64: "256px",
72: "288px",
80: "320px",
96: "384px",
},
extend: {
lineHeight: {
3: "12px",
4: "16px",
5: "20px",
6: "24px",
7: "28px",
8: "32px",
9: "36px",
10: "40px",
},
},
},
plugins: [],
};
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"types": [
"@types/chrome"
]
}
}
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}
{
"extends": "@tsconfig/node18/tsconfig.json",
"include": [
"utils",
"manifest.ts",
"package.json",
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*"
],
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}
import { resolve } from "path";
import type { PluginOption } from "vite";
export default function makeManifest(
manifest: any,
config: {
isDev: boolean;
assetKeys?: string[];
}
): PluginOption {
return {
name: "manifest-plugin",
generateBundle(output, bundle) {
if (config.assetKeys) {
for (let key of config.assetKeys) {
const id = resolve(manifest[key]).replace(/\\/g, "/");
const chunk = Object.values(bundle).find(
(b) => b.type === "chunk" && b.facadeModuleId == id
);
if (chunk) {
manifest[key] = chunk.fileName;
}
}
}
const content = JSON.stringify(manifest, null, 2);
try {
this.emitFile({
type: "asset",
source: content,
fileName: "manifest.json",
});
} catch (e) {
console.error("manifest-plugin error: ", e);
console.error("Failed to emit asset file, possibly a naming conflict");
}
},
// buildStart() {
// const content = JSON.stringify(manifest, null, 2);
// try {
// this.emitFile({
// type: "asset",
// source: content,
// fileName: "manifest.json",
// });
// } catch (e) {
// console.error("manifest-plugin error: ", e);
// console.error("Failed to emit asset file, possibly a naming conflict");
// }
// },
};
}
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 })],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
build: {
emptyOutDir: false,
rollupOptions: {
input: {
popup: "popup.html",
guide: "guide.html",
},
},
},
});
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()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
build: {
copyPublicDir: false,
emptyOutDir: false,
rollupOptions: {
input: {
content: "./src/content/index.ts",
},
output: {
assetFileNames: "[name].[ext]",
entryFileNames: "[name].js",
},
},
},
});
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