mirror of
https://github.com/chenasraf/copy-tab-url.git
synced 2026-05-17 17:38:15 +00:00
feat: poc working
This commit is contained in:
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@@ -0,0 +1,9 @@
|
||||
[*]
|
||||
tab_width = 2
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
1
.prettierignore
Normal file
1
.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
gen/
|
||||
15
.prettierrc
Normal file
15
.prettierrc
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.md",
|
||||
"options": {
|
||||
"printWidth": 100,
|
||||
"proseWrap": "always"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
18
eslint.config.mjs
Normal file
18
eslint.config.mjs
Normal file
@@ -0,0 +1,18 @@
|
||||
import eslint from '@eslint/js'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default [
|
||||
...tseslint.config(eslint.configs.recommended, ...tseslint.configs.recommended),
|
||||
{
|
||||
rules: {
|
||||
'no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: ['node_modules/', 'build/', 'dist/', 'gen/'],
|
||||
},
|
||||
]
|
||||
@@ -54,6 +54,7 @@
|
||||
"prettier": "^3.3.3",
|
||||
"rimraf": "^6.0.1",
|
||||
"typescript": "^5.6.2",
|
||||
"typescript-eslint": "^8.8.0",
|
||||
"unplugin-auto-import": "^0.18.3",
|
||||
"vite": "^5.4.8",
|
||||
"web-ext": "^8.3.0",
|
||||
|
||||
23
pnpm-lock.yaml
generated
23
pnpm-lock.yaml
generated
@@ -87,6 +87,9 @@ importers:
|
||||
typescript:
|
||||
specifier: ^5.6.2
|
||||
version: 5.6.2
|
||||
typescript-eslint:
|
||||
specifier: ^8.8.0
|
||||
version: 8.8.0(eslint@9.11.1)(typescript@5.6.2)
|
||||
unplugin-auto-import:
|
||||
specifier: ^0.18.3
|
||||
version: 0.18.3(rollup@4.24.0)
|
||||
@@ -2978,6 +2981,15 @@ packages:
|
||||
typedarray@0.0.6:
|
||||
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
|
||||
|
||||
typescript-eslint@8.8.0:
|
||||
resolution: {integrity: sha512-BjIT/VwJ8+0rVO01ZQ2ZVnjE1svFBiRczcpr1t1Yxt7sT25VSbPfrJtDsQ8uQTy2pilX5nI9gwxhUyLULNentw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
typescript@5.6.2:
|
||||
resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==}
|
||||
engines: {node: '>=14.17'}
|
||||
@@ -6323,6 +6335,17 @@ snapshots:
|
||||
|
||||
typedarray@0.0.6: {}
|
||||
|
||||
typescript-eslint@8.8.0(eslint@9.11.1)(typescript@5.6.2):
|
||||
dependencies:
|
||||
'@typescript-eslint/eslint-plugin': 8.8.0(@typescript-eslint/parser@8.8.0(eslint@9.11.1)(typescript@5.6.2))(eslint@9.11.1)(typescript@5.6.2)
|
||||
'@typescript-eslint/parser': 8.8.0(eslint@9.11.1)(typescript@5.6.2)
|
||||
'@typescript-eslint/utils': 8.8.0(eslint@9.11.1)(typescript@5.6.2)
|
||||
optionalDependencies:
|
||||
typescript: 5.6.2
|
||||
transitivePeerDependencies:
|
||||
- eslint
|
||||
- supports-color
|
||||
|
||||
typescript@5.6.2: {}
|
||||
|
||||
ufo@1.5.4: {}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
// var process
|
||||
import { resolve } from 'path'
|
||||
import { bgCyan, black } from 'kolorist'
|
||||
import react from '@vitejs/plugin-react'
|
||||
const __dirname = import.meta.dirname
|
||||
console.debug('process', global)
|
||||
// process = global.process || {}
|
||||
const env = import.meta.env || {}
|
||||
// const env = process.env || import.meta.env || {}
|
||||
|
||||
export const fastRefresh = true
|
||||
|
||||
export const port = parseInt(process.env.PORT || '') || 3303
|
||||
export const port = parseInt(env.PORT || '') || 3303
|
||||
export const r = (...args: string[]) => resolve(__dirname, '..', ...args)
|
||||
export const isDev = process.env.NODE_ENV !== 'production'
|
||||
export const isDev = env.NODE_ENV !== 'production'
|
||||
export const preambleCode = react.preambleCode.replace('__BASE__', '/')
|
||||
|
||||
export function log(name: string, message: string) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(black(bgCyan(` ${name} `)), message)
|
||||
}
|
||||
|
||||
@@ -2,17 +2,21 @@ import { isFirefox, isForbiddenUrl } from '@/env'
|
||||
|
||||
// Firefox fetch files from cache instead of reloading changes from disk,
|
||||
// hmr will not work as Chromium based browser
|
||||
browser.webNavigation.onCommitted.addListener(({ tabId, frameId, url }) => {
|
||||
browser.webNavigation.onCommitted.addListener(async ({ tabId, frameId, url }) => {
|
||||
// Filter out non main window events.
|
||||
if (frameId !== 0) return
|
||||
|
||||
if (isForbiddenUrl(url)) return
|
||||
|
||||
console.debug('Injecting', `${isFirefox ? '' : './'}dist/contentScripts/index.global.js`)
|
||||
|
||||
// inject the latest scripts
|
||||
browser.tabs
|
||||
.executeScript(tabId, {
|
||||
try {
|
||||
await browser.tabs.executeScript(tabId, {
|
||||
file: `${isFirefox ? '' : '.'}/dist/contentScripts/index.global.js`,
|
||||
runAt: 'document_end',
|
||||
})
|
||||
.catch(error => console.error(error))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { sendMessage, onMessage } from 'webext-bridge'
|
||||
import type { Tabs } from 'webextension-polyfill'
|
||||
import { sendMessage } from 'webext-bridge/background'
|
||||
import { Tabs } from 'webextension-polyfill'
|
||||
|
||||
// only on dev mode
|
||||
if (import.meta.hot) {
|
||||
@@ -10,47 +10,37 @@ if (import.meta.hot) {
|
||||
}
|
||||
|
||||
browser.runtime.onInstalled.addListener((): void => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Extension installed')
|
||||
})
|
||||
|
||||
let previousTabId = 0
|
||||
console.log('Hello from background script')
|
||||
|
||||
// communication example: send previous tab title from background page
|
||||
// see shim.d.ts for type declaration
|
||||
browser.tabs.onActivated.addListener(async ({ tabId }) => {
|
||||
if (!previousTabId) {
|
||||
previousTabId = tabId
|
||||
return
|
||||
}
|
||||
browser.commands.onCommand.addListener(async (cmd) => {
|
||||
const tab = await browser.tabs
|
||||
.query({ active: true, currentWindow: true })
|
||||
.then((tabs) => tabs[0])
|
||||
console.debug('command received', cmd, tab)
|
||||
const resp = getResponse(cmd, tab)
|
||||
if (!resp || !tab) return
|
||||
|
||||
let tab: Tabs.Tab
|
||||
|
||||
try {
|
||||
tab = await browser.tabs.get(previousTabId)
|
||||
previousTabId = tabId
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('previous tab', tab)
|
||||
sendMessage(
|
||||
'tab-prev',
|
||||
{ title: tab.title },
|
||||
{ context: 'content-script', tabId },
|
||||
)
|
||||
console.debug('sending message', resp, 'to tab', tab.id)
|
||||
return sendMessage(resp.cmd, resp.payload!, {
|
||||
context: 'content-script',
|
||||
tabId: tab.id!,
|
||||
})
|
||||
})
|
||||
|
||||
onMessage('get-current-tab', async () => {
|
||||
try {
|
||||
const tab = await browser.tabs.get(previousTabId)
|
||||
return {
|
||||
title: tab?.title,
|
||||
}
|
||||
} catch {
|
||||
return {
|
||||
title: undefined,
|
||||
}
|
||||
function getResponse(msg: string, tab: Tabs.Tab) {
|
||||
switch (msg) {
|
||||
case 'copy-tab-url':
|
||||
// copy tab url to clipboard
|
||||
return { cmd: 'copy-text', payload: tab?.url }
|
||||
case 'copy-tab-markdown':
|
||||
// copy tab url and title in markdown format
|
||||
return {
|
||||
cmd: 'copy-text',
|
||||
payload: `[${tab?.title}](${tab?.url})`,
|
||||
}
|
||||
}
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,30 +1,35 @@
|
||||
import App from './App'
|
||||
import * as ReactDOM from 'react-dom/client'
|
||||
// import App from './App'
|
||||
// import * as ReactDOM from 'react-dom/client'
|
||||
import { onMessage } from 'webext-bridge/content-script'
|
||||
|
||||
// Firefox `browser.tabs.executeScript()` requires scripts return a primitive value
|
||||
;(() => {
|
||||
console.info('[vite-react-webext] Hello world from content script')
|
||||
// Firefox `browser.tabs.executeScript()` requires scripts return a primitive value
|
||||
; (() => {
|
||||
console.info('[vite-react-webext] Hello world from content script')
|
||||
|
||||
// communication example: send previous tab title from background page
|
||||
onMessage('tab-prev', ({ data }) => {
|
||||
console.log(`[vite-react-webext] Navigate from page "${data.title}"`)
|
||||
})
|
||||
onMessage('copy-text', (payload) => {
|
||||
console.debug('copy-text', payload)
|
||||
setTimeout(async () => {
|
||||
console.debug('Copying text', payload.data)
|
||||
navigator.clipboard.writeText(payload.data as string)
|
||||
}, 10)
|
||||
// document.execCommand('copy', false, payload.data as string)
|
||||
})
|
||||
|
||||
// mount component to context window
|
||||
const container = document.createElement('div')
|
||||
const root = document.createElement('div')
|
||||
const styleEl = document.createElement('link')
|
||||
const shadowDOM =
|
||||
container.attachShadow?.({ mode: __DEV__ ? 'open' : 'closed' }) || container
|
||||
styleEl.setAttribute('rel', 'stylesheet')
|
||||
styleEl.setAttribute(
|
||||
'href',
|
||||
browser.runtime.getURL('dist/contentScripts/style.css'),
|
||||
)
|
||||
shadowDOM.appendChild(styleEl)
|
||||
shadowDOM.appendChild(root)
|
||||
document.body.appendChild(container)
|
||||
const reactRoot = ReactDOM.createRoot(root)
|
||||
reactRoot.render(<App />)
|
||||
})()
|
||||
// mount component to context window
|
||||
try {
|
||||
// const container = document.createElement('div')
|
||||
// const root = document.createElement('div')
|
||||
// const styleEl = document.createElement('link')
|
||||
// const shadowDOM = container.attachShadow?.({ mode: __DEV__ ? 'open' : 'closed' }) || container
|
||||
// styleEl.setAttribute('rel', 'stylesheet')
|
||||
// styleEl.setAttribute('href', browser.runtime.getURL('dist/contentScripts/style.css'))
|
||||
// shadowDOM.appendChild(styleEl)
|
||||
// shadowDOM.appendChild(root)
|
||||
// document.body.appendChild(container)
|
||||
// const reactRoot = ReactDOM.createRoot(root)
|
||||
// reactRoot.render(<App />)
|
||||
console.info('[vite-react-webext] Component mounted')
|
||||
} catch (error) {
|
||||
console.error('Error mounting component', error)
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -32,7 +32,15 @@ export async function getManifest() {
|
||||
48: './assets/icon-512.png',
|
||||
128: './assets/icon-512.png',
|
||||
},
|
||||
permissions: ['tabs', 'storage', 'activeTab', 'http://*/', 'https://*/'],
|
||||
permissions: [
|
||||
// 'host',
|
||||
'tabs',
|
||||
'storage',
|
||||
'activeTab',
|
||||
'clipboardWrite',
|
||||
'http://*/',
|
||||
'https://*/',
|
||||
],
|
||||
content_scripts: [
|
||||
{
|
||||
matches: ['http://*/*', 'https://*/*'],
|
||||
@@ -40,6 +48,22 @@ export async function getManifest() {
|
||||
},
|
||||
],
|
||||
web_accessible_resources: ['dist/contentScripts/style.css'],
|
||||
commands: {
|
||||
'copy-tab-url': {
|
||||
suggested_key: {
|
||||
default: 'Ctrl+Shift+U',
|
||||
mac: 'Command+Shift+U',
|
||||
},
|
||||
description: 'Copy the current tab URL',
|
||||
},
|
||||
'copy-tab-markdown': {
|
||||
suggested_key: {
|
||||
default: 'Ctrl+Shift+M',
|
||||
mac: 'Command+Shift+M',
|
||||
},
|
||||
description: 'Copy the current tab URL and title in Markdown format',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if (isDev) {
|
||||
@@ -49,13 +73,10 @@ export async function getManifest() {
|
||||
delete manifest.content_scripts
|
||||
manifest.permissions?.push('webNavigation')
|
||||
|
||||
const preambleCodeHash = crypto
|
||||
.createHash('sha256')
|
||||
.update(preambleCode)
|
||||
.digest('base64')
|
||||
const preambleCodeHash = crypto.createHash('sha256').update(preambleCode).digest('base64')
|
||||
|
||||
// this is required on dev for Vite script to load
|
||||
manifest.content_security_policy = `script-src \'self\' 'sha256-${preambleCodeHash}' http://localhost:${port}; object-src \'self\'`
|
||||
manifest.content_security_policy = `script-src 'self' 'sha256-${preambleCodeHash}' http://localhost:${port}; object-src 'self'`
|
||||
}
|
||||
|
||||
return manifest
|
||||
|
||||
Reference in New Issue
Block a user