Add clipping/highlight planning docs and sync current extension updates
This commit is contained in:
115
docs/CLIPPING_HIGHLIGHT_PLAN.md
Normal file
115
docs/CLIPPING_HIGHLIGHT_PLAN.md
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# Page Clipping + Highlight Implementation Plan
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
- 사용자가 특정 페이지에서 선택한 텍스트를 클리핑(저장)한다.
|
||||||
|
- 저장된 클립은 원문 위치에 하이라이트로 다시 표시된다.
|
||||||
|
- 팝업에서 클립 목록을 보고 해당 위치로 다시 이동할 수 있다.
|
||||||
|
|
||||||
|
## Scope (v1)
|
||||||
|
1. 지원 대상
|
||||||
|
- 텍스트 기반 웹페이지 (`contenteditable` 제외, 일반 DOM 문서 우선)
|
||||||
|
- 탭 단위 클립 저장/조회
|
||||||
|
|
||||||
|
2. 포함 기능
|
||||||
|
- 선택 텍스트 캡처
|
||||||
|
- 하이라이트 주입/복원
|
||||||
|
- 클립 목록 조회/삭제
|
||||||
|
- 클릭 시 원문 위치로 스크롤
|
||||||
|
|
||||||
|
3. 제외 기능(후속)
|
||||||
|
- PDF/캔버스/이미지 OCR 하이라이트
|
||||||
|
- 협업 공유/서버 동기화
|
||||||
|
- 다중 색상 태깅, 폴더 분류
|
||||||
|
|
||||||
|
## Data Model (Draft)
|
||||||
|
```ts
|
||||||
|
type ClipItem = {
|
||||||
|
id: string
|
||||||
|
tabId?: number
|
||||||
|
pageUrl: string
|
||||||
|
pageTitle: string
|
||||||
|
quote: string
|
||||||
|
createdAt: string
|
||||||
|
color: 'yellow'
|
||||||
|
anchor: {
|
||||||
|
textStart: string
|
||||||
|
textEnd: string
|
||||||
|
exact: string
|
||||||
|
prefix?: string
|
||||||
|
suffix?: string
|
||||||
|
xpathStart?: string
|
||||||
|
xpathEnd?: string
|
||||||
|
startOffset?: number
|
||||||
|
endOffset?: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
1. Content Script
|
||||||
|
- 사용자 선택(`window.getSelection`)에서 `Range` 추출
|
||||||
|
- `anchor` 생성(텍스트 인용 + DOM 포지션)
|
||||||
|
- background에 `clip:create` 메시지 전송
|
||||||
|
- `clip:apply` 이벤트 수신 시 하이라이트 렌더링
|
||||||
|
|
||||||
|
2. Background (Service Worker)
|
||||||
|
- `clip:create`, `clip:list`, `clip:delete`, `clip:reveal` 메시지 처리
|
||||||
|
- `storage.local` 기반 영속화
|
||||||
|
- 탭 활성화/페이지 완료 시 `clip:sync` 트리거
|
||||||
|
|
||||||
|
3. Popup/History UI
|
||||||
|
- 현재 탭 URL 기준 클립 목록 요청
|
||||||
|
- 항목별 `위치로 이동`, `삭제` 버튼
|
||||||
|
- 상태 메시지(저장 성공/실패) 표시
|
||||||
|
|
||||||
|
## Step-by-Step
|
||||||
|
1. Step 1: Selection Capture + Overlay
|
||||||
|
- `src/lib/clipAnchor.ts` 생성
|
||||||
|
- `Range -> anchor` 변환 유틸 구현
|
||||||
|
- `src/content/index.ts`에 단축키/우클릭 기반 캡처 진입점 추가
|
||||||
|
- `span[data-gomdown-clip]` 하이라이트 렌더링/해제 로직 구현
|
||||||
|
|
||||||
|
2. Step 2: Store + Message Channel
|
||||||
|
- `src/lib/clipStore.ts` 생성 (`list/upsert/delete/byUrl`)
|
||||||
|
- background message router에 `clip:*` 타입 추가
|
||||||
|
- dedupe(`pageUrl + exact + createdAt window`) 정책 추가
|
||||||
|
|
||||||
|
3. Step 3: Popup UI
|
||||||
|
- `src/popup/main.tsx`에 "Clips" 섹션 추가
|
||||||
|
- 현재 탭 URL 클립 조회 + 목록 렌더링
|
||||||
|
- `Reveal/Delete` 액션과 오류 상태 처리
|
||||||
|
|
||||||
|
4. Step 4: Re-anchoring
|
||||||
|
- 페이지 로드 시 `clip:list` 후 순차 복원
|
||||||
|
- 우선순위:
|
||||||
|
- `exact + prefix/suffix` 텍스트 매칭
|
||||||
|
- 실패 시 `xpath + offset` 복구
|
||||||
|
- 둘 다 실패 시 `broken anchor`로 표시
|
||||||
|
|
||||||
|
5. Step 5: Export/Import + QA
|
||||||
|
- JSON export/import 메시지 추가
|
||||||
|
- 샘플 페이지(뉴스, 블로그, SPA) 수동 테스트
|
||||||
|
- 회귀 체크리스트 문서화
|
||||||
|
|
||||||
|
## QA Checklist
|
||||||
|
- 같은 페이지 새로고침 후 하이라이트가 유지된다.
|
||||||
|
- SPA 라우팅(YouTube/블로그) 후에도 복원 시도가 동작한다.
|
||||||
|
- 원문 DOM이 일부 바뀐 경우, 텍스트 매칭 fallback이 동작한다.
|
||||||
|
- 클립 삭제 시 화면/저장소에서 모두 제거된다.
|
||||||
|
- 확장 비활성화 상태에서 캡처가 차단된다.
|
||||||
|
|
||||||
|
## Risk & Mitigation
|
||||||
|
1. DOM 변형으로 앵커 붕괴
|
||||||
|
- 텍스트 인용 앵커 + XPath 이중 저장
|
||||||
|
|
||||||
|
2. 성능 저하(클립 다수)
|
||||||
|
- URL 단위 lazy apply, viewport 근처 우선 렌더
|
||||||
|
|
||||||
|
3. 사이트 충돌(CSS/스크립트)
|
||||||
|
- 고유 data-attribute와 최소 침습 스타일 사용
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
- 사용자는 텍스트 선택 후 1회 액션으로 클립 저장 가능
|
||||||
|
- 같은 URL 재방문 시 하이라이트 자동 복원
|
||||||
|
- 팝업에서 클립 조회/이동/삭제 가능
|
||||||
|
- 크롬 기준 수동 시나리오 10개 중 9개 이상 성공
|
||||||
@@ -9,3 +9,11 @@
|
|||||||
- [ ] Step 2: content script 보조 탐지
|
- [ ] Step 2: content script 보조 탐지
|
||||||
- [ ] Step 3: 노이즈 필터/품질 그룹핑
|
- [ ] Step 3: 노이즈 필터/품질 그룹핑
|
||||||
- [ ] Step 4: 고급 분석/진단 UI
|
- [ ] Step 4: 고급 분석/진단 UI
|
||||||
|
|
||||||
|
## Page Clipping + Highlight
|
||||||
|
- [x] 계획서 작성 (`docs/CLIPPING_HIGHLIGHT_PLAN.md`)
|
||||||
|
- [ ] Step 1: 텍스트 선택 캡처(`Selection` + `Range`)와 하이라이트 렌더러 구현
|
||||||
|
- [ ] Step 2: 저장소/메시지 채널 추가 (`clipStore`, background relay)
|
||||||
|
- [ ] Step 3: Popup/History UI에 클립 목록, 재이동(스크롤), 삭제 기능 추가
|
||||||
|
- [ ] Step 4: 페이지 재진입 시 하이라이트 복원(anchoring) 및 깨진 앵커 fallback 처리
|
||||||
|
- [ ] Step 5: 내보내기/가져오기(JSON)와 기본 회귀 테스트 시나리오 정리
|
||||||
|
|||||||
@@ -1,83 +1,73 @@
|
|||||||
{
|
{
|
||||||
"../../../../../@crx/manifest": {
|
"../../../../../../../@crx/manifest": {
|
||||||
"file": "assets/crx-manifest.js-mSo6-ym3.js",
|
"file": "assets/crx-manifest.js-CXu7hmTa.js",
|
||||||
"name": "crx-manifest.js",
|
"name": "crx-manifest.js",
|
||||||
"src": "../../../../../@crx/manifest",
|
"src": "../../../../../../../@crx/manifest",
|
||||||
"isEntry": true
|
"isEntry": true
|
||||||
},
|
},
|
||||||
"_browser-polyfill-CZ_dLIqp.js": {
|
"_client-BzjyOx7y.js": {
|
||||||
"file": "assets/browser-polyfill-CZ_dLIqp.js",
|
"file": "assets/client-BzjyOx7y.js",
|
||||||
"name": "browser-polyfill"
|
|
||||||
},
|
|
||||||
"_client-CBvt1tWS.js": {
|
|
||||||
"file": "assets/client-CBvt1tWS.js",
|
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"imports": [
|
"imports": [
|
||||||
"_browser-polyfill-CZ_dLIqp.js"
|
"_settings-CgBxHrrF.js"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"_downloadIntent-Dv31jC2S.js": {
|
"_downloadIntent-Dv31jC2S.js": {
|
||||||
"file": "assets/downloadIntent-Dv31jC2S.js",
|
"file": "assets/downloadIntent-Dv31jC2S.js",
|
||||||
"name": "downloadIntent"
|
"name": "downloadIntent"
|
||||||
},
|
},
|
||||||
"_index.ts-loader-Bju9eGS_.js": {
|
"_index.ts-loader-D_eQmgUa.js": {
|
||||||
"file": "assets/index.ts-loader-Bju9eGS_.js",
|
"file": "assets/index.ts-loader-D_eQmgUa.js",
|
||||||
"src": "_index.ts-loader-Bju9eGS_.js"
|
"src": "_index.ts-loader-D_eQmgUa.js"
|
||||||
},
|
},
|
||||||
"_settings-Bo6W9Drl.js": {
|
"_settings-CgBxHrrF.js": {
|
||||||
"file": "assets/settings-Bo6W9Drl.js",
|
"file": "assets/settings-CgBxHrrF.js",
|
||||||
"name": "settings",
|
"name": "settings"
|
||||||
"imports": [
|
|
||||||
"_browser-polyfill-CZ_dLIqp.js"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"src/background/index.ts": {
|
"src/background/index.ts": {
|
||||||
"file": "assets/index.ts-BljhweV3.js",
|
"file": "assets/index.ts-U8lbRRO-.js",
|
||||||
"name": "index.ts",
|
"name": "index.ts",
|
||||||
"src": "src/background/index.ts",
|
"src": "src/background/index.ts",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"_browser-polyfill-CZ_dLIqp.js",
|
"_settings-CgBxHrrF.js",
|
||||||
"_downloadIntent-Dv31jC2S.js",
|
"_downloadIntent-Dv31jC2S.js"
|
||||||
"_settings-Bo6W9Drl.js"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/config/index.html": {
|
"src/config/index.html": {
|
||||||
"file": "assets/index.html-B0Kfv8fq.js",
|
"file": "assets/index.html-B7fMyQPm.js",
|
||||||
"name": "index.html",
|
"name": "index.html",
|
||||||
"src": "src/config/index.html",
|
"src": "src/config/index.html",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"_client-CBvt1tWS.js",
|
"_client-BzjyOx7y.js",
|
||||||
"_settings-Bo6W9Drl.js",
|
"_settings-CgBxHrrF.js"
|
||||||
"_browser-polyfill-CZ_dLIqp.js"
|
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"assets/index-B2D5FcJM.css"
|
"assets/index-B2D5FcJM.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/content/index.ts": {
|
"src/content/index.ts": {
|
||||||
"file": "assets/index.ts-C6ePCen1.js",
|
"file": "assets/index.ts-w1ilzv93.js",
|
||||||
"name": "index.ts",
|
"name": "index.ts",
|
||||||
"src": "src/content/index.ts",
|
"src": "src/content/index.ts",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"_browser-polyfill-CZ_dLIqp.js",
|
"_settings-CgBxHrrF.js",
|
||||||
"_downloadIntent-Dv31jC2S.js"
|
"_downloadIntent-Dv31jC2S.js"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/popup/index.html": {
|
"src/popup/index.html": {
|
||||||
"file": "assets/index.html-BLzIyLM-.js",
|
"file": "assets/index.html-92_ZB8wX.js",
|
||||||
"name": "index.html",
|
"name": "index.html",
|
||||||
"src": "src/popup/index.html",
|
"src": "src/popup/index.html",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"_client-CBvt1tWS.js",
|
"_client-BzjyOx7y.js",
|
||||||
"_browser-polyfill-CZ_dLIqp.js",
|
"_settings-CgBxHrrF.js"
|
||||||
"_settings-Bo6W9Drl.js"
|
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"assets/index-D6aWDpYY.css"
|
"assets/index-CJaGAyoX.css"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because one or more lines are too long
1
packages/chrome/assets/index-CJaGAyoX.css
Normal file
1
packages/chrome/assets/index-CJaGAyoX.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
:root{color-scheme:dark;font-family:ui-sans-serif,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}body{margin:0;background:#151821;color:#e7ebf7;width:360px}.container{padding:14px;display:grid;gap:10px}.top-row{display:flex;align-items:center;justify-content:space-between;gap:8px}h1{margin:0;font-size:14px}.power-toggle{height:28px;min-width:70px;border-radius:999px;padding:0 12px;display:inline-flex;align-items:center;justify-content:center;gap:6px;font-size:11px;font-weight:800;letter-spacing:.04em}.power-toggle.on{background:#204f37;border-color:#2b7a52;color:#c7ffe3}.power-toggle.off{background:#4a2631;border-color:#75404d;color:#ffd6df}.power-dot{width:7px;height:7px;border-radius:50%;background:currentColor;opacity:.9}.field{display:grid;gap:6px}label{font-size:12px;color:#aeb6cc}input[type=text],input[type=number]{height:32px;border:1px solid #3e4658;border-radius:6px;padding:0 10px;background:#202532;color:#e7ebf7}.toggle{display:flex;align-items:center;justify-content:space-between;font-size:12px;color:#cdd5ea}button{height:34px;border:1px solid #5562f0;border-radius:8px;background:#5562f0;color:#fff;font-weight:600;cursor:pointer}.media-panel{margin-top:6px;border:1px solid #384255;border-radius:8px;padding:8px;background:#1b202c;display:grid;gap:8px}.media-head{display:flex;align-items:center;justify-content:space-between;font-size:12px}.media-list{display:grid;gap:6px;max-height:150px;overflow:auto}.media-item{display:flex;align-items:center;justify-content:space-between;gap:8px}.media-meta{min-width:0;display:grid;gap:2px}.kind{font-size:11px;color:#8fc0ff}.url{font-size:11px;color:#c6d1e8;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:250px}.mini{height:26px;min-width:44px;padding:0 8px;border-radius:6px;font-size:11px}.mini.ghost{background:#2a3140;border-color:#47546c;color:#d4def2}.empty{font-size:11px;color:#96a3bc}.status{font-size:12px;color:#8fe0a6;min-height:14px}
|
||||||
@@ -1 +0,0 @@
|
|||||||
:root{color-scheme:dark;font-family:ui-sans-serif,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}body{margin:0;background:#151821;color:#e7ebf7;width:360px}.container{padding:14px;display:grid;gap:10px}h1{margin:0;font-size:14px}.field{display:grid;gap:6px}label{font-size:12px;color:#aeb6cc}input[type=text],input[type=number]{height:32px;border:1px solid #3e4658;border-radius:6px;padding:0 10px;background:#202532;color:#e7ebf7}.toggle{display:flex;align-items:center;justify-content:space-between;font-size:12px;color:#cdd5ea}button{height:34px;border:1px solid #5562f0;border-radius:8px;background:#5562f0;color:#fff;font-weight:600;cursor:pointer}.media-panel{margin-top:6px;border:1px solid #384255;border-radius:8px;padding:8px;background:#1b202c;display:grid;gap:8px}.media-head{display:flex;align-items:center;justify-content:space-between;font-size:12px}.media-list{display:grid;gap:6px;max-height:150px;overflow:auto}.media-item{display:flex;align-items:center;justify-content:space-between;gap:8px}.media-meta{min-width:0;display:grid;gap:2px}.kind{font-size:11px;color:#8fc0ff}.url{font-size:11px;color:#c6d1e8;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:250px}.mini{height:26px;min-width:44px;padding:0 8px;border-radius:6px;font-size:11px}.mini.ghost{background:#2a3140;border-color:#47546c;color:#d4def2}.empty{font-size:11px;color:#96a3bc}.status{font-size:12px;color:#8fe0a6;min-height:14px}
|
|
||||||
1
packages/chrome/assets/index.html-92_ZB8wX.js
Normal file
1
packages/chrome/assets/index.html-92_ZB8wX.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import{c as N,j as t,R as r}from"./client-BzjyOx7y.js";import{g as k,b as i,s as m}from"./settings-CgBxHrrF.js";function S(){const[s,d]=r.useState(null),[x,n]=r.useState(""),[l,u]=r.useState([]);r.useEffect(()=>{k().then(d)},[]),r.useEffect(()=>{let e=null;const a=async()=>{const o=await i.runtime.sendMessage({type:"media:list"});o?.ok&&Array.isArray(o.items)&&u(o.items.slice(0,10))};return a(),e=window.setInterval(()=>{a()},2e3),()=>{e!==null&&window.clearInterval(e)}},[]);const c=(e,a)=>{d(o=>o&&{...o,[e]:a})},p=async e=>{if(!s)return;const a={...s,extensionStatus:e};d(a),await m({extensionStatus:e}),n(e?"Extension ON":"Extension OFF"),window.setTimeout(()=>n(""),1200)},g=async()=>{s&&(await m(s),n("Saved"),window.setTimeout(()=>n(""),1200))},h=async()=>{const e=i.runtime.getURL("src/config/index.html");await i.tabs.create({url:e})},j=async()=>{const e=i.runtime.getURL("src/history/index.html");await i.tabs.create({url:e})},v=async()=>{const e=await i.runtime.sendMessage({type:"page:enqueue-ytdlp"});n(e?.ok?"Active tab sent to gdown (yt-dlp)":`Send failed: ${e?.error||"unknown error"}`),window.setTimeout(()=>n(""),1800)},w=async e=>{const a=await i.runtime.sendMessage({type:"media:enqueue",url:e.url,referer:e.referer||"",kind:e.kind,suggestedOut:e.suggestedOut||"",cookie:e.cookie||"",userAgent:e.userAgent||""});n(a?.ok?"Media sent to gdown":`Send failed: ${a?.error||"unknown error"}`),window.setTimeout(()=>n(""),1600)},y=async()=>{await i.runtime.sendMessage({type:"media:clear"}),u([]),n("Captured media cleared"),window.setTimeout(()=>n(""),1200)};return s?t.jsxs("div",{className:"container",children:[t.jsxs("div",{className:"top-row",children:[t.jsx("h1",{children:"Gomdown Helper"}),t.jsxs("button",{className:`power-toggle ${s.extensionStatus?"on":"off"}`,onClick:()=>{p(!s.extensionStatus)},children:[t.jsx("span",{className:"power-dot"}),s.extensionStatus?"ON":"OFF"]})]}),t.jsxs("div",{className:"field",children:[t.jsx("label",{children:"RPC Secret"}),t.jsx("input",{type:"text",value:s.motrixAPIkey,onChange:e=>c("motrixAPIkey",e.target.value),placeholder:"aria2 rpc secret"})]}),t.jsxs("div",{className:"field",children:[t.jsx("label",{children:"RPC Port"}),t.jsx("input",{type:"number",value:s.motrixPort,onChange:e=>c("motrixPort",Number(e.target.value||16800))})]}),t.jsxs("label",{className:"toggle",children:["Use Native Host",t.jsx("input",{type:"checkbox",checked:s.useNativeHost,onChange:e=>c("useNativeHost",e.target.checked)})]}),t.jsxs("label",{className:"toggle",children:["Activate gdown App",t.jsx("input",{type:"checkbox",checked:s.activateAppOnDownload,onChange:e=>c("activateAppOnDownload",e.target.checked)})]}),t.jsx("button",{onClick:g,children:"Save"}),t.jsx("button",{onClick:h,children:"Settings"}),t.jsx("button",{onClick:j,children:"History"}),t.jsx("button",{onClick:()=>{v()},children:"Send Active Tab (yt-dlp)"}),t.jsxs("div",{className:"media-panel",children:[t.jsxs("div",{className:"media-head",children:[t.jsx("strong",{children:"Captured Media"}),t.jsx("button",{className:"mini ghost",onClick:y,children:"Clear"})]}),l.length===0?t.jsx("div",{className:"empty",children:"No media captured yet"}):t.jsx("div",{className:"media-list",children:l.map(e=>t.jsxs("div",{className:"media-item",children:[t.jsxs("div",{className:"media-meta",children:[t.jsx("span",{className:"kind",children:e.kind.toUpperCase()}),e.pageTitle?t.jsx("span",{className:"url",children:e.pageTitle}):null,t.jsx("span",{className:"url",children:e.url})]}),t.jsx("button",{className:"mini",onClick:()=>{w(e)},children:"Add"})]},e.id))})]}),t.jsx("div",{className:"status",children:x})]}):t.jsx("div",{className:"container",children:"Loading..."})}N.createRoot(document.getElementById("root")).render(t.jsx(r.StrictMode,{children:t.jsx(S,{})}));
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
import{c as p,j as e,R as a}from"./client-CBvt1tWS.js";import{g as j,s as m}from"./settings-Bo6W9Drl.js";import"./browser-polyfill-CZ_dLIqp.js";function u(){const[t,i]=a.useState(null),[l,r]=a.useState(""),[h,d]=a.useState("");a.useEffect(()=>{j().then(s=>{i(s),r((s.blacklist||[]).join(`
|
import{c as j,j as e,R as a}from"./client-BzjyOx7y.js";import{g as p,s as m}from"./settings-CgBxHrrF.js";function u(){const[t,i]=a.useState(null),[l,r]=a.useState(""),[h,d]=a.useState("");a.useEffect(()=>{p().then(s=>{i(s),r((s.blacklist||[]).join(`
|
||||||
`))})},[]);const n=(s,c)=>{i(o=>o&&{...o,[s]:c})},x=async()=>{if(!t)return;const s={...t,minFileSize:Number(t.minFileSize||0),motrixPort:Number(t.motrixPort||16800),blacklist:l.split(`
|
`))})},[]);const n=(s,c)=>{i(o=>o&&{...o,[s]:c})},x=async()=>{if(!t)return;const s={...t,minFileSize:Number(t.minFileSize||0),motrixPort:Number(t.motrixPort||16800),blacklist:l.split(`
|
||||||
`).map(c=>c.trim()).filter(Boolean)};await m(s),i(s),d("Saved"),window.setTimeout(()=>d(""),1500)};return t?e.jsxs("div",{className:"wrap",children:[e.jsx("h1",{children:"Gomdown Helper Settings"}),e.jsxs("div",{className:"grid",children:[e.jsxs("section",{className:"card",children:[e.jsx("label",{children:"RPC Secret"}),e.jsx("input",{value:t.motrixAPIkey,onChange:s=>n("motrixAPIkey",s.target.value)}),e.jsx("label",{children:"RPC Port"}),e.jsx("input",{type:"number",value:t.motrixPort,onChange:s=>n("motrixPort",Number(s.target.value||16800))}),e.jsx("label",{children:"Min file size (MB)"}),e.jsx("input",{type:"number",value:t.minFileSize,onChange:s=>n("minFileSize",Number(s.target.value||0))}),e.jsx("label",{children:"Blacklist (one per line)"}),e.jsx("textarea",{rows:8,value:l,onChange:s=>r(s.target.value)})]}),e.jsxs("section",{className:"card",children:[e.jsxs("div",{className:"row",children:[e.jsx("span",{children:"Extension enabled"}),e.jsx("input",{type:"checkbox",checked:t.extensionStatus,onChange:s=>n("extensionStatus",s.target.checked)})]}),e.jsxs("div",{className:"row",children:[e.jsx("span",{children:"Enable notifications"}),e.jsx("input",{type:"checkbox",checked:t.enableNotifications,onChange:s=>n("enableNotifications",s.target.checked)})]}),e.jsxs("div",{className:"row",children:[e.jsx("span",{children:"Use native host"}),e.jsx("input",{type:"checkbox",checked:t.useNativeHost,onChange:s=>n("useNativeHost",s.target.checked)})]}),e.jsxs("div",{className:"row",children:[e.jsx("span",{children:"Activate app on download"}),e.jsx("input",{type:"checkbox",checked:t.activateAppOnDownload,onChange:s=>n("activateAppOnDownload",s.target.checked)})]}),e.jsxs("div",{className:"row",children:[e.jsx("span",{children:"Hide browser download shelf"}),e.jsx("input",{type:"checkbox",checked:t.hideChromeBar,onChange:s=>n("hideChromeBar",s.target.checked)})]}),e.jsxs("div",{className:"row",children:[e.jsx("span",{children:"Show context menu option"}),e.jsx("input",{type:"checkbox",checked:t.showContextOption,onChange:s=>n("showContextOption",s.target.checked)})]}),e.jsxs("div",{className:"row",children:[e.jsx("span",{children:"Download fallback"}),e.jsx("input",{type:"checkbox",checked:t.downloadFallback,onChange:s=>n("downloadFallback",s.target.checked)})]}),e.jsxs("div",{className:"row",children:[e.jsx("span",{children:"Dark mode"}),e.jsx("input",{type:"checkbox",checked:t.darkMode,onChange:s=>n("darkMode",s.target.checked)})]}),e.jsxs("div",{className:"row",children:[e.jsx("span",{children:"Show only aria downloads"}),e.jsx("input",{type:"checkbox",checked:t.showOnlyAria,onChange:s=>n("showOnlyAria",s.target.checked)})]})]})]}),e.jsxs("div",{className:"actions",children:[e.jsx("button",{onClick:x,children:"Save"}),e.jsx("button",{className:"ghost",onClick:()=>window.close(),children:"Close"})]}),e.jsx("div",{children:h})]}):e.jsx("div",{className:"wrap",children:"Loading..."})}p.createRoot(document.getElementById("root")).render(e.jsx(a.StrictMode,{children:e.jsx(u,{})}));
|
`).map(c=>c.trim()).filter(Boolean)};await m(s),i(s),d("Saved"),window.setTimeout(()=>d(""),1500)};return t?e.jsxs("div",{className:"wrap",children:[e.jsx("h1",{children:"Gomdown Helper Settings"}),e.jsxs("div",{className:"grid",children:[e.jsxs("section",{className:"card",children:[e.jsx("label",{children:"RPC Secret"}),e.jsx("input",{value:t.motrixAPIkey,onChange:s=>n("motrixAPIkey",s.target.value)}),e.jsx("label",{children:"RPC Port"}),e.jsx("input",{type:"number",value:t.motrixPort,onChange:s=>n("motrixPort",Number(s.target.value||16800))}),e.jsx("label",{children:"Min file size (MB)"}),e.jsx("input",{type:"number",value:t.minFileSize,onChange:s=>n("minFileSize",Number(s.target.value||0))}),e.jsx("label",{children:"Blacklist (one per line)"}),e.jsx("textarea",{rows:8,value:l,onChange:s=>r(s.target.value)})]}),e.jsxs("section",{className:"card",children:[e.jsxs("div",{className:"row",children:[e.jsx("span",{children:"Extension enabled"}),e.jsx("input",{type:"checkbox",checked:t.extensionStatus,onChange:s=>n("extensionStatus",s.target.checked)})]}),e.jsxs("div",{className:"row",children:[e.jsx("span",{children:"Enable notifications"}),e.jsx("input",{type:"checkbox",checked:t.enableNotifications,onChange:s=>n("enableNotifications",s.target.checked)})]}),e.jsxs("div",{className:"row",children:[e.jsx("span",{children:"Use native host"}),e.jsx("input",{type:"checkbox",checked:t.useNativeHost,onChange:s=>n("useNativeHost",s.target.checked)})]}),e.jsxs("div",{className:"row",children:[e.jsx("span",{children:"Activate app on download"}),e.jsx("input",{type:"checkbox",checked:t.activateAppOnDownload,onChange:s=>n("activateAppOnDownload",s.target.checked)})]}),e.jsxs("div",{className:"row",children:[e.jsx("span",{children:"Hide browser download shelf"}),e.jsx("input",{type:"checkbox",checked:t.hideChromeBar,onChange:s=>n("hideChromeBar",s.target.checked)})]}),e.jsxs("div",{className:"row",children:[e.jsx("span",{children:"Show context menu option"}),e.jsx("input",{type:"checkbox",checked:t.showContextOption,onChange:s=>n("showContextOption",s.target.checked)})]}),e.jsxs("div",{className:"row",children:[e.jsx("span",{children:"Download fallback"}),e.jsx("input",{type:"checkbox",checked:t.downloadFallback,onChange:s=>n("downloadFallback",s.target.checked)})]}),e.jsxs("div",{className:"row",children:[e.jsx("span",{children:"Dark mode"}),e.jsx("input",{type:"checkbox",checked:t.darkMode,onChange:s=>n("darkMode",s.target.checked)})]}),e.jsxs("div",{className:"row",children:[e.jsx("span",{children:"Show only aria downloads"}),e.jsx("input",{type:"checkbox",checked:t.showOnlyAria,onChange:s=>n("showOnlyAria",s.target.checked)})]})]})]}),e.jsxs("div",{className:"actions",children:[e.jsx("button",{onClick:x,children:"Save"}),e.jsx("button",{className:"ghost",onClick:()=>window.close(),children:"Close"})]}),e.jsx("div",{children:h})]}):e.jsx("div",{className:"wrap",children:"Loading..."})}j.createRoot(document.getElementById("root")).render(e.jsx(a.StrictMode,{children:e.jsx(u,{})}));
|
||||||
@@ -1 +0,0 @@
|
|||||||
import{c as k,j as t,R as r}from"./client-CBvt1tWS.js";import{b as i}from"./browser-polyfill-CZ_dLIqp.js";import{g as N,s as m}from"./settings-Bo6W9Drl.js";function b(){const[s,d]=r.useState(null),[x,n]=r.useState(""),[l,u]=r.useState([]);r.useEffect(()=>{N().then(d)},[]),r.useEffect(()=>{let e=null;const a=async()=>{const o=await i.runtime.sendMessage({type:"media:list"});o?.ok&&Array.isArray(o.items)&&u(o.items.slice(0,10))};return a(),e=window.setInterval(()=>{a()},2e3),()=>{e!==null&&window.clearInterval(e)}},[]);const c=(e,a)=>{d(o=>o&&{...o,[e]:a})},h=async e=>{if(!s)return;const a={...s,extensionStatus:e};d(a),await m({extensionStatus:e}),n(e?"Extension ON":"Extension OFF"),window.setTimeout(()=>n(""),1200)},g=async()=>{s&&(await m(s),n("Saved"),window.setTimeout(()=>n(""),1200))},p=async()=>{const e=i.runtime.getURL("src/config/index.html");await i.tabs.create({url:e})},j=async()=>{const e=i.runtime.getURL("src/history/index.html");await i.tabs.create({url:e})},v=async()=>{const e=await i.runtime.sendMessage({type:"page:enqueue-ytdlp"});n(e?.ok?"Active tab sent to gdown (yt-dlp)":`Send failed: ${e?.error||"unknown error"}`),window.setTimeout(()=>n(""),1800)},w=async e=>{const a=await i.runtime.sendMessage({type:"media:enqueue",url:e.url,referer:e.referer||"",kind:e.kind,suggestedOut:e.suggestedOut||"",cookie:e.cookie||"",userAgent:e.userAgent||""});n(a?.ok?"Media sent to gdown":`Send failed: ${a?.error||"unknown error"}`),window.setTimeout(()=>n(""),1600)},y=async()=>{await i.runtime.sendMessage({type:"media:clear"}),u([]),n("Captured media cleared"),window.setTimeout(()=>n(""),1200)};return s?t.jsxs("div",{className:"container",children:[t.jsx("h1",{children:"Gomdown Helper"}),t.jsxs("div",{className:"field",children:[t.jsx("label",{children:"RPC Secret"}),t.jsx("input",{type:"text",value:s.motrixAPIkey,onChange:e=>c("motrixAPIkey",e.target.value),placeholder:"aria2 rpc secret"})]}),t.jsxs("div",{className:"field",children:[t.jsx("label",{children:"RPC Port"}),t.jsx("input",{type:"number",value:s.motrixPort,onChange:e=>c("motrixPort",Number(e.target.value||16800))})]}),t.jsxs("label",{className:"toggle",children:["Extension Enabled",t.jsx("input",{type:"checkbox",checked:s.extensionStatus,onChange:e=>{h(e.target.checked)}})]}),t.jsxs("label",{className:"toggle",children:["Use Native Host",t.jsx("input",{type:"checkbox",checked:s.useNativeHost,onChange:e=>c("useNativeHost",e.target.checked)})]}),t.jsxs("label",{className:"toggle",children:["Activate gdown App",t.jsx("input",{type:"checkbox",checked:s.activateAppOnDownload,onChange:e=>c("activateAppOnDownload",e.target.checked)})]}),t.jsx("button",{onClick:g,children:"Save"}),t.jsx("button",{onClick:p,children:"Settings"}),t.jsx("button",{onClick:j,children:"History"}),t.jsx("button",{onClick:()=>{v()},children:"Send Active Tab (yt-dlp)"}),t.jsxs("div",{className:"media-panel",children:[t.jsxs("div",{className:"media-head",children:[t.jsx("strong",{children:"Captured Media"}),t.jsx("button",{className:"mini ghost",onClick:y,children:"Clear"})]}),l.length===0?t.jsx("div",{className:"empty",children:"No media captured yet"}):t.jsx("div",{className:"media-list",children:l.map(e=>t.jsxs("div",{className:"media-item",children:[t.jsxs("div",{className:"media-meta",children:[t.jsx("span",{className:"kind",children:e.kind.toUpperCase()}),e.pageTitle?t.jsx("span",{className:"url",children:e.pageTitle}):null,t.jsx("span",{className:"url",children:e.url})]}),t.jsx("button",{className:"mini",onClick:()=>{w(e)},children:"Add"})]},e.id))})]}),t.jsx("div",{className:"status",children:x})]}):t.jsx("div",{className:"container",children:"Loading..."})}k.createRoot(document.getElementById("root")).render(t.jsx(r.StrictMode,{children:t.jsx(b,{})}));
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
packages/chrome/assets/index.ts-U8lbRRO-.js
Normal file
1
packages/chrome/assets/index.ts-U8lbRRO-.js
Normal file
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@
|
|||||||
(async () => {
|
(async () => {
|
||||||
const { onExecute } = await import(
|
const { onExecute } = await import(
|
||||||
/* @vite-ignore */
|
/* @vite-ignore */
|
||||||
chrome.runtime.getURL("assets/index.ts-C6ePCen1.js")
|
chrome.runtime.getURL("assets/index.ts-w1ilzv93.js")
|
||||||
);
|
);
|
||||||
onExecute?.({ perf: { injectTime, loadTime: performance.now() - injectTime } });
|
onExecute?.({ perf: { injectTime, loadTime: performance.now() - injectTime } });
|
||||||
})().catch(console.error);
|
})().catch(console.error);
|
||||||
1
packages/chrome/assets/index.ts-w1ilzv93.js
Normal file
1
packages/chrome/assets/index.ts-w1ilzv93.js
Normal file
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
import{b as e}from"./browser-polyfill-CZ_dLIqp.js";const t={extensionStatus:!0,useNativeHost:!0,activateAppOnDownload:!0,enableNotifications:!0,hideChromeBar:!0,showContextOption:!0,downloadFallback:!1,darkMode:!1,showOnlyAria:!1,minFileSize:0,blacklist:[],motrixPort:16800,motrixAPIkey:""};async function n(){const o=await e.storage.sync.get(Object.keys(t));return{extensionStatus:!!(o.extensionStatus??t.extensionStatus),useNativeHost:!!(o.useNativeHost??t.useNativeHost),activateAppOnDownload:!!(o.activateAppOnDownload??t.activateAppOnDownload),enableNotifications:!!(o.enableNotifications??t.enableNotifications),hideChromeBar:!!(o.hideChromeBar??t.hideChromeBar),showContextOption:!!(o.showContextOption??t.showContextOption),downloadFallback:!!(o.downloadFallback??t.downloadFallback),darkMode:!!(o.darkMode??t.darkMode),showOnlyAria:!!(o.showOnlyAria??t.showOnlyAria),minFileSize:Number(o.minFileSize??t.minFileSize),blacklist:Array.isArray(o.blacklist)?o.blacklist.map(a=>String(a)):t.blacklist,motrixPort:Number(o.motrixPort??t.motrixPort),motrixAPIkey:String(o.motrixAPIkey??t.motrixAPIkey)}}async function s(o){await e.storage.sync.set(o)}export{n as g,s};
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -28,7 +28,7 @@
|
|||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
"js": [
|
"js": [
|
||||||
"assets/index.ts-loader-Bju9eGS_.js"
|
"assets/index.ts-loader-D_eQmgUa.js"
|
||||||
],
|
],
|
||||||
"matches": [
|
"matches": [
|
||||||
"<all_urls>"
|
"<all_urls>"
|
||||||
@@ -57,9 +57,9 @@
|
|||||||
],
|
],
|
||||||
"resources": [
|
"resources": [
|
||||||
"images/*",
|
"images/*",
|
||||||
"assets/browser-polyfill-CZ_dLIqp.js",
|
"assets/settings-CgBxHrrF.js",
|
||||||
"assets/downloadIntent-Dv31jC2S.js",
|
"assets/downloadIntent-Dv31jC2S.js",
|
||||||
"assets/index.ts-C6ePCen1.js"
|
"assets/index.ts-w1ilzv93.js"
|
||||||
],
|
],
|
||||||
"use_dynamic_url": false
|
"use_dynamic_url": false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
import './assets/index.ts-BljhweV3.js';
|
import './assets/index.ts-U8lbRRO-.js';
|
||||||
|
|||||||
@@ -4,10 +4,9 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Gomdown Helper Settings</title>
|
<title>Gomdown Helper Settings</title>
|
||||||
<script type="module" crossorigin src="/assets/index.html-B0Kfv8fq.js"></script>
|
<script type="module" crossorigin src="/assets/index.html-B7fMyQPm.js"></script>
|
||||||
<link rel="modulepreload" crossorigin href="/assets/browser-polyfill-CZ_dLIqp.js">
|
<link rel="modulepreload" crossorigin href="/assets/settings-CgBxHrrF.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/client-CBvt1tWS.js">
|
<link rel="modulepreload" crossorigin href="/assets/client-BzjyOx7y.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/settings-Bo6W9Drl.js">
|
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-B2D5FcJM.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-B2D5FcJM.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -4,11 +4,10 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Gomdown Helper</title>
|
<title>Gomdown Helper</title>
|
||||||
<script type="module" crossorigin src="/assets/index.html-BLzIyLM-.js"></script>
|
<script type="module" crossorigin src="/assets/index.html-92_ZB8wX.js"></script>
|
||||||
<link rel="modulepreload" crossorigin href="/assets/browser-polyfill-CZ_dLIqp.js">
|
<link rel="modulepreload" crossorigin href="/assets/settings-CgBxHrrF.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/client-CBvt1tWS.js">
|
<link rel="modulepreload" crossorigin href="/assets/client-BzjyOx7y.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/settings-Bo6W9Drl.js">
|
<link rel="stylesheet" crossorigin href="/assets/index-CJaGAyoX.css">
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-D6aWDpYY.css">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
Binary file not shown.
@@ -1,8 +1,10 @@
|
|||||||
import browser from 'webextension-polyfill'
|
import browser from 'webextension-polyfill'
|
||||||
import { isLikelyDownloadUrl, normalizeUrl } from '../lib/downloadIntent'
|
import { isLikelyDownloadUrl, normalizeUrl } from '../lib/downloadIntent'
|
||||||
|
import { getSettings } from '../lib/settings'
|
||||||
|
|
||||||
const CAPTURE_TTL_MS = 8000
|
const CAPTURE_TTL_MS = 8000
|
||||||
const captureInFlight = new Map<string, number>()
|
const captureInFlight = new Map<string, number>()
|
||||||
|
let extensionEnabled = false
|
||||||
|
|
||||||
function pruneCaptureInFlight(): void {
|
function pruneCaptureInFlight(): void {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
@@ -12,6 +14,7 @@ function pruneCaptureInFlight(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function sendCapture(url: string, referer: string): Promise<boolean> {
|
async function sendCapture(url: string, referer: string): Promise<boolean> {
|
||||||
|
if (!extensionEnabled) return false
|
||||||
const normalized = normalizeUrl(url, window.location.href)
|
const normalized = normalizeUrl(url, window.location.href)
|
||||||
if (!normalized) return false
|
if (!normalized) return false
|
||||||
|
|
||||||
@@ -46,6 +49,7 @@ function shouldIgnoreHotkey(event: MouseEvent | KeyboardEvent): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function interceptAnchorEvent(event: MouseEvent): Promise<void> {
|
async function interceptAnchorEvent(event: MouseEvent): Promise<void> {
|
||||||
|
if (!extensionEnabled) return
|
||||||
if (event.defaultPrevented) return
|
if (event.defaultPrevented) return
|
||||||
if (shouldIgnoreHotkey(event)) return
|
if (shouldIgnoreHotkey(event)) return
|
||||||
|
|
||||||
@@ -61,6 +65,7 @@ async function interceptAnchorEvent(event: MouseEvent): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function interceptMouseLike(event: MouseEvent): void {
|
function interceptMouseLike(event: MouseEvent): void {
|
||||||
|
if (!extensionEnabled) return
|
||||||
const anchor = findAnchor(event.target)
|
const anchor = findAnchor(event.target)
|
||||||
if (!anchor) return
|
if (!anchor) return
|
||||||
const href = anchor.href || ''
|
const href = anchor.href || ''
|
||||||
@@ -89,6 +94,7 @@ document.addEventListener('click', (event: MouseEvent) => {
|
|||||||
}, true)
|
}, true)
|
||||||
|
|
||||||
document.addEventListener('keydown', (event: KeyboardEvent) => {
|
document.addEventListener('keydown', (event: KeyboardEvent) => {
|
||||||
|
if (!extensionEnabled) return
|
||||||
if (event.key !== 'Enter') return
|
if (event.key !== 'Enter') return
|
||||||
if (event.defaultPrevented) return
|
if (event.defaultPrevented) return
|
||||||
if (shouldIgnoreHotkey(event)) return
|
if (shouldIgnoreHotkey(event)) return
|
||||||
@@ -114,7 +120,7 @@ function installProgrammaticInterceptors(): void {
|
|||||||
const originalOpen = window.open.bind(window)
|
const originalOpen = window.open.bind(window)
|
||||||
window.open = function gomdownInterceptOpen(url?: string | URL, target?: string, features?: string): Window | null {
|
window.open = function gomdownInterceptOpen(url?: string | URL, target?: string, features?: string): Window | null {
|
||||||
const raw = String(url || '').trim()
|
const raw = String(url || '').trim()
|
||||||
if (raw && isLikelyDownloadUrl(raw, window.location.href)) {
|
if (extensionEnabled && raw && isLikelyDownloadUrl(raw, window.location.href)) {
|
||||||
void sendCapture(raw, window.location.href)
|
void sendCapture(raw, window.location.href)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -128,7 +134,7 @@ function installProgrammaticInterceptors(): void {
|
|||||||
const originalAnchorClick = HTMLAnchorElement.prototype.click
|
const originalAnchorClick = HTMLAnchorElement.prototype.click
|
||||||
HTMLAnchorElement.prototype.click = function gomdownInterceptAnchorClick(): void {
|
HTMLAnchorElement.prototype.click = function gomdownInterceptAnchorClick(): void {
|
||||||
const href = this.href || this.getAttribute('href') || ''
|
const href = this.href || this.getAttribute('href') || ''
|
||||||
if (href && isLikelyDownloadUrl(href, window.location.href)) {
|
if (extensionEnabled && href && isLikelyDownloadUrl(href, window.location.href)) {
|
||||||
void sendCapture(href, document.referrer || window.location.href)
|
void sendCapture(href, document.referrer || window.location.href)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -196,6 +202,10 @@ function removeYoutubeOverlay(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ensureYoutubeOverlay(): void {
|
function ensureYoutubeOverlay(): void {
|
||||||
|
if (!extensionEnabled) {
|
||||||
|
removeYoutubeOverlay()
|
||||||
|
return
|
||||||
|
}
|
||||||
if (window.top !== window.self) return
|
if (window.top !== window.self) return
|
||||||
if (!isYoutubeWatchPage(window.location.href)) {
|
if (!isYoutubeWatchPage(window.location.href)) {
|
||||||
removeYoutubeOverlay()
|
removeYoutubeOverlay()
|
||||||
@@ -283,6 +293,15 @@ watchYoutubeRouteChanges()
|
|||||||
let mediaToastRoot: HTMLDivElement | null = null
|
let mediaToastRoot: HTMLDivElement | null = null
|
||||||
let mediaToastTimer: number | null = null
|
let mediaToastTimer: number | null = null
|
||||||
|
|
||||||
|
function hideMediaCapturedToast(): void {
|
||||||
|
if (!mediaToastRoot) return
|
||||||
|
mediaToastRoot.style.display = 'none'
|
||||||
|
if (mediaToastTimer !== null) {
|
||||||
|
window.clearTimeout(mediaToastTimer)
|
||||||
|
mediaToastTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function ensureMediaToastRoot(): HTMLDivElement {
|
function ensureMediaToastRoot(): HTMLDivElement {
|
||||||
if (mediaToastRoot) return mediaToastRoot
|
if (mediaToastRoot) return mediaToastRoot
|
||||||
const root = document.createElement('div')
|
const root = document.createElement('div')
|
||||||
@@ -308,6 +327,7 @@ function ensureMediaToastRoot(): HTMLDivElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showMediaCapturedToast(payload: { kind?: string; url?: string; suggestedOut?: string }): void {
|
function showMediaCapturedToast(payload: { kind?: string; url?: string; suggestedOut?: string }): void {
|
||||||
|
if (!extensionEnabled) return
|
||||||
const root = ensureMediaToastRoot()
|
const root = ensureMediaToastRoot()
|
||||||
const kind = String(payload?.kind || 'media').toUpperCase()
|
const kind = String(payload?.kind || 'media').toUpperCase()
|
||||||
const out = String(payload?.suggestedOut || '').trim()
|
const out = String(payload?.suggestedOut || '').trim()
|
||||||
@@ -332,3 +352,33 @@ browser.runtime.onMessage.addListener((message: any) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
async function syncExtensionEnabled(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const settings = await getSettings()
|
||||||
|
extensionEnabled = Boolean(settings.extensionStatus)
|
||||||
|
} catch {
|
||||||
|
extensionEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!extensionEnabled) {
|
||||||
|
removeYoutubeOverlay()
|
||||||
|
hideMediaCapturedToast()
|
||||||
|
} else {
|
||||||
|
ensureYoutubeOverlay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void syncExtensionEnabled()
|
||||||
|
|
||||||
|
browser.storage.onChanged.addListener((changes, areaName) => {
|
||||||
|
if (areaName !== 'sync') return
|
||||||
|
if (!changes.extensionStatus) return
|
||||||
|
extensionEnabled = Boolean(changes.extensionStatus.newValue)
|
||||||
|
if (!extensionEnabled) {
|
||||||
|
removeYoutubeOverlay()
|
||||||
|
hideMediaCapturedToast()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ensureYoutubeOverlay()
|
||||||
|
})
|
||||||
|
|||||||
@@ -111,7 +111,18 @@ function App(): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="container">
|
||||||
|
<div className="top-row">
|
||||||
<h1>Gomdown Helper</h1>
|
<h1>Gomdown Helper</h1>
|
||||||
|
<button
|
||||||
|
className={`power-toggle ${settings.extensionStatus ? 'on' : 'off'}`}
|
||||||
|
onClick={() => {
|
||||||
|
void onToggleExtension(!settings.extensionStatus)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="power-dot" />
|
||||||
|
{settings.extensionStatus ? 'ON' : 'OFF'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<label>RPC Secret</label>
|
<label>RPC Secret</label>
|
||||||
@@ -132,17 +143,6 @@ function App(): JSX.Element {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label className="toggle">
|
|
||||||
Extension Enabled
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={settings.extensionStatus}
|
|
||||||
onChange={(e) => {
|
|
||||||
void onToggleExtension(e.target.checked)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label className="toggle">
|
<label className="toggle">
|
||||||
Use Native Host
|
Use Native Host
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -16,11 +16,52 @@ body {
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.top-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.power-toggle {
|
||||||
|
height: 28px;
|
||||||
|
min-width: 70px;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 0 12px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.power-toggle.on {
|
||||||
|
background: #204f37;
|
||||||
|
border-color: #2b7a52;
|
||||||
|
color: #c7ffe3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.power-toggle.off {
|
||||||
|
background: #4a2631;
|
||||||
|
border-color: #75404d;
|
||||||
|
color: #ffd6df;
|
||||||
|
}
|
||||||
|
|
||||||
|
.power-dot {
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: currentColor;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
.field {
|
.field {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
|
|||||||
Reference in New Issue
Block a user