From 5508e0abb41a456e7b295de045b4d82052092da4 Mon Sep 17 00:00:00 2001 From: GaoXiang233 <1679562189@qq.com> Date: Sat, 25 Apr 2026 13:17:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=A6=96=E6=AC=A1=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E6=88=91=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- entrypoints/content.tsx | 79 ++- entrypoints/options/App.tsx | 271 +++++++--- entrypoints/sidebar/App.tsx | 887 +++++++++++++++++++++++++++++++++ entrypoints/sidebar/index.html | 32 ++ entrypoints/sidebar/main.tsx | 9 + lib/storage.ts | 105 ++++ lib/translate.ts | 190 +++++++ wxt.config.ts | 19 +- 8 files changed, 1512 insertions(+), 80 deletions(-) create mode 100644 entrypoints/sidebar/App.tsx create mode 100644 entrypoints/sidebar/index.html create mode 100644 entrypoints/sidebar/main.tsx create mode 100644 lib/translate.ts diff --git a/entrypoints/content.tsx b/entrypoints/content.tsx index 7ea2034..05b45ba 100644 --- a/entrypoints/content.tsx +++ b/entrypoints/content.tsx @@ -6,7 +6,8 @@ import { createRoot } from "react-dom/client" import Turndown from "turndown" import { browser } from "wxt/browser" import { getRoot, Noti, showNotification } from "@/lib/showNotification" -import { getOptions } from "@/lib/storage" +import { getOptions, addToHistory } from "@/lib/storage" +import { translateText, translateMarkdown } from "@/lib/translate" import { defaultTagsToRemove } from "@/lib/tagsToRemove" import { convertSrtToText } from "@/lib/yt/convertSrtToText" import { getVideoInfo } from "@/lib/yt/getVideoInfo" @@ -14,7 +15,8 @@ import { getVideoSubtitle } from "@/lib/yt/getVideoSubtitle" const tiktoken = new Tiktoken(o200k_base) -// Utility to copy markdown to clipboard, respond to sender and optionally show toast/confetti +type ContentType = "webpage" | "subtitle" | "selection" + const copyAndNotify = async ({ markdown, wrapInTripleBackticks, @@ -22,6 +24,9 @@ const copyAndNotify = async ({ showConfetti, sendResponse, successMessagePrefix, + contentType, + title, + url, }: { markdown: string wrapInTripleBackticks: boolean @@ -29,17 +34,19 @@ const copyAndNotify = async ({ showConfetti: boolean sendResponse: (response: { success: boolean }) => void successMessagePrefix: string + contentType: ContentType + title?: string + url?: string }) => { - if (wrapInTripleBackticks) { - markdown = `\`\`\`md\n${markdown}\n\`\`\`` - } + const finalMarkdown = wrapInTripleBackticks + ? `\`\`\`md\n${markdown}\n\`\`\`` + : markdown try { - await navigator.clipboard.writeText(markdown) + await navigator.clipboard.writeText(finalMarkdown) } catch (error) { - // Fallback for when document is not focused (e.g., DevTools is open) const textarea = document.createElement("textarea") - textarea.value = markdown + textarea.value = finalMarkdown textarea.style.position = "fixed" textarea.style.opacity = "0" document.body.appendChild(textarea) @@ -48,16 +55,28 @@ const copyAndNotify = async ({ document.body.removeChild(textarea) } - sendResponse({ success: true }) + const tokens = tiktoken.encode(finalMarkdown) + const tokenCount = tokens.length - const tokens = tiktoken.encode(markdown) + try { + await addToHistory({ + content: finalMarkdown, + title: title || document.title || "Untitled", + url: url || window.location.href, + type: contentType, + tokenCount, + }) + } catch (error) { + console.error("Failed to add to history:", error) + } + + sendResponse({ success: true }) if (showSuccessToast) { - showNotification(`${successMessagePrefix} (${tokens.length} tokens)`) + showNotification(`${successMessagePrefix} (${tokenCount} tokens)`) } if (showConfetti) { - // Send a message to the background script to open the Raycast confetti URL browser.runtime.sendMessage({ type: "OPEN_CONFETTI" }) } } @@ -147,6 +166,9 @@ export default defineContentScript({ showConfetti, sendResponse, successMessagePrefix: "Copied as markdown", + contentType: "webpage", + title: document.title, + url: window.location.href, }) return true @@ -184,10 +206,43 @@ export default defineContentScript({ showConfetti, sendResponse, successMessagePrefix: "Subtitle copied to clipboard", + contentType: "subtitle", + title, + url: window.location.href, }) return true } + + if (msg.type === "TRANSLATE_TEXT") { + try { + const { text, targetLanguage } = msg.payload + const result = await translateText(text, targetLanguage) + sendResponse(result) + } catch (error) { + console.error("Translation error:", error) + sendResponse({ + success: false, + error: error instanceof Error ? error.message : "Translation failed", + }) + } + return true + } + + if (msg.type === "TRANSLATE_MARKDOWN") { + try { + const { markdown, targetLanguage } = msg.payload + const result = await translateMarkdown(markdown, targetLanguage) + sendResponse(result) + } catch (error) { + console.error("Markdown translation error:", error) + sendResponse({ + success: false, + error: error instanceof Error ? error.message : "Translation failed", + }) + } + return true + } }) }, }) diff --git a/entrypoints/options/App.tsx b/entrypoints/options/App.tsx index dfe4648..cd8a2a2 100644 --- a/entrypoints/options/App.tsx +++ b/entrypoints/options/App.tsx @@ -1,6 +1,13 @@ import { useEffect, useState } from "react" import { ToggleOption } from "@/components/ToggleOption" -import { getOptions, type OptionsState, saveOptions } from "@/lib/storage" +import { + getOptions, + type OptionsState, + saveOptions, + supportedLanguages, + clearHistory +} from "@/lib/storage" +import { showNotification } from "@/lib/showNotification" import packageJson from "../../package.json" export const App = () => { @@ -17,15 +24,15 @@ export const App = () => { const handleOptionChange = async ( key: keyof OptionsState, - value: boolean, + value: boolean | string | number, ) => { if (!options) return let newOptions = { ...options, [key]: value } - if (key === "useReadability" && value) { + if (key === "useReadability" && value === true) { newOptions = { ...newOptions, useDeffudle: false } - } else if (key === "useDeffudle" && value) { + } else if (key === "useDeffudle" && value === true) { newOptions = { ...newOptions, useReadability: false } } @@ -33,76 +40,208 @@ export const App = () => { await saveOptions(newOptions) } + const handleClearHistory = async () => { + try { + await clearHistory() + showNotification("History cleared successfully", "success") + } catch (error) { + showNotification("Failed to clear history", "error") + } + } + return (
+ Choose the default language for translations. +
++ Maximum number of items to keep in history. +
++ Remove all items from your copy history. +
+