├── .github └── workflows │ └── submit.yml ├── .gitignore ├── .prettierrc.mjs ├── COPYING ├── README.md ├── README_EN.md ├── assets ├── icon.png └── logo.png ├── background ├── index.ts └── messages │ ├── download.ts │ ├── icon.ts │ ├── ping.ts │ ├── screenshot.ts │ ├── sidepanel.ts │ └── tab.ts ├── component ├── contents │ └── validateContent.tsx ├── items │ ├── cssCode.tsx │ ├── downloadHtml.tsx │ ├── downloadImages.tsx │ ├── downloadMarkdown.tsx │ ├── downloadPdf.tsx │ ├── editMarkdown.tsx │ ├── options.tsx │ ├── parseMarkdown.tsx │ ├── setAiType.tsx │ └── showTag.tsx ├── options │ ├── 360doc.tsx │ ├── 51cto.tsx │ ├── baidu.tsx │ ├── cnblogs.tsx │ ├── config.tsx │ ├── content.tsx │ ├── csdn.tsx │ ├── custom.tsx │ ├── jb51.tsx │ ├── jianshu.tsx │ ├── juejin.tsx │ ├── lougu.tsx │ ├── medium.tsx │ ├── mp.tsx │ ├── oschina.tsx │ ├── paywallbuster.tsx │ ├── php.tsx │ ├── pianshen.tsx │ ├── segmentfault.tsx │ ├── weixin.tsx │ └── zhihu.tsx ├── tagBtn │ └── style.ts └── ui │ ├── QRCodeModal.tsx │ ├── customDomSelector.tsx │ ├── modal.tsx │ ├── tags.tsx │ ├── toolBox.tsx │ ├── tooltip.tsx │ └── validModal.tsx ├── contents ├── 360doc.tsx ├── 51cto.tsx ├── baidu.tsx ├── chatgpt.tsx ├── cnblogs.tsx ├── copycode.tsx ├── csdn.tsx ├── custom.tsx ├── deepseek.tsx ├── jb51.tsx ├── jianshu.tsx ├── juejin.tsx ├── kimi.tsx ├── luogu.tsx ├── md.tsx ├── medium.tsx ├── mp.tsx ├── oschina.tsx ├── paywallbuster.tsx ├── php.tsx ├── pianshen.tsx ├── segmentfault.tsx ├── weixin.tsx └── zhihu.tsx ├── index.css ├── locales ├── en │ └── messages.json └── zh_CN │ └── messages.json ├── options ├── index.html ├── index.module.scss └── index.tsx ├── package.json ├── popup ├── index.html └── index.tsx ├── public ├── 1.jpg ├── 1.md ├── 128x128.png ├── 1723096379951.jpg ├── 2.md ├── 2.png ├── 3.jpg ├── 4.jpg ├── 5.jpg ├── 6.jpg ├── 7.jpg ├── 8.png ├── app.md ├── app.txt ├── config.jpg ├── en │ ├── 1400x560.png │ ├── 440x280.png │ └── en-config.jpg ├── logo.html ├── logo.js ├── logo │ └── logo.svg ├── privacy-policy.html ├── slogan.html ├── webstore │ ├── 0f8137ce6861f2a387095aef446fe60.png │ ├── 1723513894421.jpg │ ├── 1724640034462.jpg │ ├── 1724640161752.jpg │ └── img.png ├── wx │ ├── download.jpg │ ├── gzh.jpg │ ├── u_wx.jpg │ ├── wx_logo.jpg │ └── xcx.jpg └── zh │ ├── 1400x560.png │ ├── 440x280.png │ └── config.jpg ├── server ├── .babelrc ├── WechatSender.js ├── package.json ├── server.js └── server.test.js ├── sidepanel ├── index.module.scss └── index.tsx ├── tabs ├── feed.html ├── feed.module.scss ├── feed.tsx ├── history.html ├── history.module.scss └── history.tsx ├── theme.tsx ├── tools.ts ├── tsconfig.json └── utils ├── coze.ts ├── cssCodeHook.tsx ├── downloadAllImg.ts ├── drawImages.ts ├── editMarkdownHook.ts ├── encryption.ts ├── makerQRPost.ts ├── parseMarkdownHook.ts ├── print.ts ├── totp.ts └── turndown.ts /.github/workflows/submit.yml: -------------------------------------------------------------------------------- 1 | name: "Submit to Web Store" 2 | on: 3 | workflow_dispatch: 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - name: Cache pnpm modules 11 | uses: actions/cache@v3 12 | with: 13 | path: ~/.pnpm-store 14 | key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} 15 | restore-keys: | 16 | ${{ runner.os }}- 17 | - uses: pnpm/action-setup@v2.2.4 18 | with: 19 | version: latest 20 | run_install: true 21 | - name: Use Node.js 16.x 22 | uses: actions/setup-node@v3.4.1 23 | with: 24 | node-version: 16.x 25 | cache: "pnpm" 26 | - name: Build the extension 27 | run: pnpm build 28 | - name: Package the extension into a zip artifact 29 | run: pnpm package 30 | - name: Browser Platform Publish 31 | uses: PlasmoHQ/bpp@v3 32 | with: 33 | keys: ${{ secrets.SUBMIT_KEYS }} 34 | artifact: build/chrome-mv3-prod.zip 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | /node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | # misc 13 | .DS_Store 14 | *.pem 15 | 16 | # debug 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | .pnpm-debug.log* 21 | 22 | pnpm-lock.yaml 23 | 24 | # local env files 25 | .env*.local 26 | 27 | out/ 28 | build/ 29 | dist/ 30 | 31 | # plasmo 32 | .plasmo 33 | 34 | # typescript 35 | .tsbuildinfo 36 | 37 | 38 | .idea 39 | .vscode 40 | 41 | .env 42 | 43 | server 44 | -------------------------------------------------------------------------------- /.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('prettier').Options} 3 | */ 4 | export default { 5 | printWidth: 80, 6 | tabWidth: 2, 7 | useTabs: false, 8 | semi: false, 9 | singleQuote: false, 10 | trailingComma: "none", 11 | bracketSpacing: true, 12 | bracketSameLine: true, 13 | plugins: ["@ianvs/prettier-plugin-sort-imports"], 14 | importOrder: [ 15 | "", // Node.js built-in modules 16 | "", // Imports not matched by other special words or groups. 17 | "", // Empty line 18 | "^@plasmo/(.*)$", 19 | "", 20 | "^@plasmohq/(.*)$", 21 | "", 22 | "^~(.*)$", 23 | "", 24 | "^[./]" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # codebox-一键复制代码/下载文章 2 | 3 | [中文](README.md) | [English](README_EN.md) 4 | 5 | ## 网站 6 | 1. 官网 7 | https://www.code-box.fun/ 8 | 2. 文档 9 | https://code-box.fun/docs 10 | 11 | ## 介绍 12 | 13 | 本浏览器插件可以用于CSDN/知乎/脚本之家/博客园/博客园/51CTO博客/php中文网/掘金/微信等网站,一键下载文章成html或markdown文件;实现无需登录一键复制代码;支持选中代码;或者代码右上角按钮的一键复制;解除关注博主即可阅读全文提示;去除登录弹窗;去除跳转APP弹窗. 14 | 15 | ## 安装 16 | 17 | ### 安装方式一:插件商店(推荐) 18 | 19 | 直接下载安装: 20 | 1. 进入应用商店 21 | - [Chrome应用商店](https://chrome.google.com/webstore/category/extensions?hl=zh-CN) 22 | - [Edge应用商店](https://microsoftedge.microsoft.com/addons/Microsoft-Edge-Extensions-Home?hl=zh-CN) 23 | - [360浏览器应用商店](https://ext.se.360.cn/#/extension-detail?id=acnnhjllgegbndgknlliobjlekgilbdf) 24 | - [Firefox应用商店](https://addons.mozilla.org/zh-CN/firefox/) 25 | 26 | 2. 搜索:`codebox` 27 | 28 | ![0f8137ce6861f2a387095aef446fe60](https://raw.githubusercontent.com/027xiguapi/code-box/main/public/webstore/0f8137ce6861f2a387095aef446fe60.png) 29 | ![img](https://raw.githubusercontent.com/027xiguapi/code-box/main/public/webstore/img.png) 30 | ![1724640161752](https://raw.githubusercontent.com/027xiguapi/code-box/main/public/webstore/1724640161752.jpg) 31 | 32 | ### 安装方式二:直接安装 33 | 34 | 1. 安装地址: 35 | - [前往 Chrome 扩展商店](https://chrome.google.com/webstore/detail/acnnhjllgegbndgknlliobjlekgilbdf) 36 | - [前往 360浏览器 扩展商店](https://ext.se.360.cn/#/extension-detail?id=acnnhjllgegbndgknlliobjlekgilbdf) 37 | - [前往 火狐 扩展商店](https://addons.mozilla.org/zh-CN/firefox/addon/code-box/) 38 | - [前往 Edge 扩展商店](https://microsoftedge.microsoft.com/addons/detail/code-box/cfpdbfmncaampihkmejogihjkenkonbn) 39 | 40 | ### 安装方式三:公众号下载 41 | 1. 关注公众号 42 | 43 | ![img](https://github.com/027xiguapi/code-box/blob/main/public/wx/gzh.jpg) 44 | 45 | 2. 点击 `软件下载` 46 | 47 | ![img](https://raw.githubusercontent.com/027xiguapi/code-box/main/public/wx/download.jpg) 48 | 49 | ### 安装方式三:源码安装 50 | 51 | 1. clone源码 52 | ```sh 53 | git clone https://github.com/027xiguapi/code-box.git 54 | ``` 55 | 2. 安装和打包 56 | ```sh 57 | pnpm install 58 | pnpm dev 59 | pnpm build 60 | ``` 61 | 3. 谷歌浏览器,从右上角菜单->更多工具->扩展程序可以进入插件管理页面,也可以直接在地址栏输入 chrome://extensions 访问 62 | 4. 勾选开发者模式,即可以文件夹的形式直接加载插件 63 | 64 | 65 | 66 | 67 | ## 功能 68 | 69 | ![img-1](https://raw.githubusercontent.com/027xiguapi/code-box/main/public/config.jpg) 70 | 71 | ### 自定义 72 | 73 | - 插入自定义样式(css代码) 74 | - 下载所有图片 75 | - 自定义选择下载html 76 | - 自定义选择下载markdown 77 | - 自定义选择下载pdf 78 | 79 | 80 | ### 微信 81 | - 插入自定义样式(css代码) 82 | - 一键下载文章html 83 | - 一键下载文章markdown 84 | - 一键下载文章pdf 85 | - 一键下载所有图片 86 | 87 | ### 掘金 88 | - 插入自定义样式(css代码) 89 | - 一键下载文章html 90 | - 一键下载文章markdown 91 | - 一键下载文章pdf 92 | 93 | ### CSDN 94 | 95 | - 一键下载文章html、pdf或markdown文件 96 | - 打开任意一个`CSDN`博客即可开始复制代码 97 | - 未登录`CSDN`状态下,支持选中代码 98 | - 未登录`CSDN`状态下,代码右上角按钮一键复制 99 | - 未登录`CSDN`状态下,不会再出现强制登录弹窗 100 | - 未关注博主状态下,不再提示关注博主即可阅读全文,且完整展示文章 101 | - 自动展开代码块 102 | - 移动端屏蔽跳转APP 103 | - 非vip用户,不再提示vip观看,且完整展示文章 104 | - 插入自定义样式(css代码) 105 | 106 | ### 知乎 107 | 108 | - 一键下载文章html、pdf或markdown文件 109 | - 一键复制代码 110 | - 未登录`知乎`状态下,不再提示展开阅读全文,且完整展示文章 111 | - 未登录`知乎`状态下,不会再出现强制登录弹窗 112 | - 插入自定义样式(css代码) 113 | 114 | ### 百度 115 | 116 | - 一键下载对话框html、pdf或markdown文件 117 | - 关闭AI对话框 118 | 119 | ### 简书 120 | 121 | - 一键下载文章html、pdf或markdown文件 122 | - 移动端,一键复制代码 123 | - 不再提示展开阅读全文,且完整展示文章 124 | - 不会再出现强制登录弹窗 125 | - 插入自定义样式(css代码) 126 | 127 | ### 脚本之家 128 | 129 | - 一键下载文章html、pdf或markdown文件 130 | - 打开任意一个`脚本之家`博客即可开始复制代码 131 | - 未登录`脚本之家`状态下,支持选中代码 132 | - 屏蔽广告 133 | - 移动端未登录`脚本之家`状态下,代码右上角按钮一键复制 134 | - 插入自定义样式(css代码) 135 | 136 | ### 博客园 137 | 138 | - 一键下载文章html、pdf或markdown文件 139 | - 一键复制代码 140 | - 插入自定义样式(css代码) 141 | 142 | ### 51CTO博客 143 | 144 | - 一键下载文章html、pdf或markdown文件 145 | - 未登录`51CTO博客`状态下,支持选中代码 146 | - 未登录`51CTO博客`状态下,代码右上角按钮一键复制 147 | - 未登录`51CTO博客`状态下,不会再出现强制登录弹窗 148 | - 移动端未登录`51CTO博客`状态下,代码右上角按钮一键复制 149 | - 插入自定义样式(css代码) 150 | 151 | ### php中文网 152 | 153 | - 一键下载文章html、pdf或markdown文件 154 | - 未登录`php中文网`状态下,支持选中代码 155 | - 未登录`php中文网`状态下,代码右上角按钮一键复制 156 | - 未登录`php中文网`状态下,不会再出现强制登录弹窗 157 | - 未登录`php中文网`状态下,移动端代码右上角按钮一键复制 158 | - 插入自定义样式(css代码) 159 | 160 | ![img](https://raw.githubusercontent.com/027xiguapi/code-box/main/public/8.png) 161 | ![img](https://raw.githubusercontent.com/027xiguapi/code-box/main/public/1.jpg) 162 | ![img](https://raw.githubusercontent.com/027xiguapi/code-box/main/public/2.png) 163 | ![img](https://raw.githubusercontent.com/027xiguapi/code-box/main/public/3.jpg) 164 | ![img](https://raw.githubusercontent.com/027xiguapi/code-box/main/public/4.jpg) 165 | ![img](https://raw.githubusercontent.com/027xiguapi/code-box/main/public/5.jpg) 166 | ![img](https://raw.githubusercontent.com/027xiguapi/code-box/main/public/6.jpg) 167 | ![img](https://raw.githubusercontent.com/027xiguapi/code-box/main/public/7.jpg) 168 | ![img](https://raw.githubusercontent.com/027xiguapi/code-box/main/public/1723096379951.jpg) 169 | 170 | 171 | ## 参考 172 | 173 | 1. [copy-csdn](https://github.com/openHacking/copy-csdn) 174 | 2. [plasmo](https://github.com/PlasmoHQ/plasmo) 175 | 3. [Chrome 应用商店一站式支持](https://support.google.com/chrome_webstore/contact/one_stop_support) 176 | 4. [开箱即用的web打印和下载](https://juejin.cn/post/7412672713376497727) 177 | 5. [导出微信公众号文章为PDF](https://greasyfork.org/en/scripts/510683-%E5%AF%BC%E5%87%BA%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7%E6%96%87%E7%AB%A0%E4%B8%BApdf/code) 178 | 6. [微信公众号推文图片一键下载](https://greasyfork.org/zh-CN/scripts/40583-%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7%E6%8E%A8%E6%96%87%E5%9B%BE%E7%89%87%E4%B8%80%E9%94%AE%E4%B8%8B%E8%BD%BD/code) 179 | 180 | ## 隐私政策 181 | 182 | [网页](https://027xiguapi.github.io/code-box/privacy-policy.html) 183 | 184 | ## 联系 185 | 186 | ![img](https://github.com/027xiguapi/code-box/blob/main/public/wx/gzh.jpg) 187 | 188 | ![u_wx](https://github.com/027xiguapi/code-box/blob/main/public/wx/u_wx.jpg) 189 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/assets/icon.png -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/assets/logo.png -------------------------------------------------------------------------------- /background/index.ts: -------------------------------------------------------------------------------- 1 | import "@plasmohq/messaging/background" 2 | 3 | export {} 4 | 5 | // 初始化设置 6 | chrome.runtime.onInstalled.addListener(function (object) { 7 | if (object.reason === chrome.runtime.OnInstalledReason.INSTALL) { 8 | chrome.tabs.create({ url: chrome.runtime.getURL("options.html") }) 9 | } 10 | }) 11 | 12 | // 检查更新 13 | chrome.runtime.requestUpdateCheck(function (status, details) { 14 | if (status === "update_available") { 15 | console.log("有更新可用!版本" + details.version) 16 | // chrome.action.setBadgeBackgroundColor({ color: "#fc5430" }) 17 | // chrome.action.setBadgeText({ 18 | // text: "升级" 19 | // }) 20 | // chrome.runtime.reload() 21 | } else if (status === "no_update") { 22 | console.log("没有可用更新。") 23 | } else if (status === "throttled") { 24 | console.log("更新检查被限流。") 25 | } 26 | }) 27 | -------------------------------------------------------------------------------- /background/messages/download.ts: -------------------------------------------------------------------------------- 1 | import JSZip from "jszip" 2 | 3 | import type { PlasmoMessaging } from "@plasmohq/messaging" 4 | 5 | const handler: PlasmoMessaging.MessageHandler = async (req, res) => { 6 | const { imageUrls, title, action, onProgress } = req.body 7 | if (action === "downloadAllImages") { 8 | const zip = new JSZip() 9 | 10 | const fetchImage = (url) => { 11 | return fetch(url) 12 | .then((response) => response.blob()) 13 | .then((blob) => { 14 | const fileName = url.split("/").pop().split("?")[0] 15 | return { blob, fileName } 16 | }) 17 | } 18 | 19 | Promise.all(imageUrls.map(fetchImage)) 20 | .then((results) => { 21 | results.forEach(({ blob, fileName }, index) => { 22 | onProgress && onProgress(index + 1, imageUrls.length) 23 | zip.file(fileName || `image-${index}.jpg`, blob) 24 | }) 25 | 26 | return zip.generateAsync({ type: "blob" }) 27 | }) 28 | .then((zipBlob) => { 29 | const reader = new FileReader() 30 | reader.onload = function (event) { 31 | const dataUrl = event.target.result as string 32 | 33 | chrome.downloads.download( 34 | { 35 | url: dataUrl, 36 | filename: `${title}.zip` 37 | }, 38 | () => { 39 | res.send({ code: 200, msg: "success" }) 40 | } 41 | ) 42 | } 43 | reader.readAsDataURL(zipBlob) 44 | }) 45 | .catch((err) => { 46 | console.error(err) 47 | res.send({ code: 0, msg: err }) 48 | }) 49 | } 50 | } 51 | 52 | export default handler 53 | -------------------------------------------------------------------------------- /background/messages/icon.ts: -------------------------------------------------------------------------------- 1 | import activeUrl from "raw:~/assets/icon.png" 2 | import defaultUrl from "raw:~/assets/logo.png" 3 | 4 | import type { PlasmoMessaging } from "@plasmohq/messaging" 5 | 6 | const handler: PlasmoMessaging.MessageHandler = async (req, res) => { 7 | const [tab] = await chrome.tabs.query({ currentWindow: true, active: true }) 8 | const { active } = req.body 9 | 10 | if (active) { 11 | chrome.action.setIcon({ tabId: tab.id, path: activeUrl }, () => {}) 12 | } else { 13 | chrome.action.setIcon({ tabId: tab.id, path: defaultUrl }, () => {}) 14 | } 15 | } 16 | 17 | export default handler 18 | -------------------------------------------------------------------------------- /background/messages/ping.ts: -------------------------------------------------------------------------------- 1 | import type { PlasmoMessaging } from "@plasmohq/messaging" 2 | 3 | const handler: PlasmoMessaging.MessageHandler = async (req, res) => { 4 | // const message = await querySomeApi(req.body.id) 5 | // 6 | // res.send({ 7 | // message 8 | // }) 9 | } 10 | 11 | export default handler -------------------------------------------------------------------------------- /background/messages/screenshot.ts: -------------------------------------------------------------------------------- 1 | import type { PlasmoMessaging } from "@plasmohq/messaging" 2 | 3 | const handler: PlasmoMessaging.MessageHandler = async (req, res) => { 4 | const tabs = await chrome.tabs.query({ currentWindow: true, active: true }) 5 | const tab = tabs[0] 6 | chrome.tabs.captureVisibleTab( 7 | tab.windowId, 8 | { 9 | format: "png" 10 | }, 11 | (dataUrl) => { 12 | res.send({ 13 | dataUrl: dataUrl 14 | }) 15 | } 16 | ) 17 | } 18 | 19 | export default handler 20 | -------------------------------------------------------------------------------- /background/messages/sidepanel.ts: -------------------------------------------------------------------------------- 1 | import { type PlasmoMessaging } from "@plasmohq/messaging" 2 | 3 | const handler: PlasmoMessaging.MessageHandler = async (req, res) => { 4 | try { 5 | chrome.tabs.query({ active: true, currentWindow: true }, ([tab]) => { 6 | const { active } = req.body 7 | 8 | if (active) { 9 | chrome.sidePanel.open({ tabId: tab.id }) 10 | } 11 | }) 12 | } catch (error) { 13 | console.error("Error toggling side panel:", error) 14 | } 15 | } 16 | 17 | export default handler 18 | -------------------------------------------------------------------------------- /background/messages/tab.ts: -------------------------------------------------------------------------------- 1 | import type { PlasmoMessaging } from "@plasmohq/messaging" 2 | 3 | const handler: PlasmoMessaging.MessageHandler = async (req, res) => { 4 | const { url } = req.body 5 | 6 | chrome.tabs.create({ url: `/tabs/${url}` }) 7 | } 8 | 9 | export default handler 10 | -------------------------------------------------------------------------------- /component/contents/validateContent.tsx: -------------------------------------------------------------------------------- 1 | import { CheckOutlined, CloseOutlined } from "@ant-design/icons" 2 | import dayjs from "dayjs" 3 | import qrcodeUrl from "raw:~/public/wx/gzh.jpg" 4 | import React, { useRef, useState } from "react" 5 | 6 | import { sendToBackground } from "@plasmohq/messaging" 7 | import { useStorage } from "@plasmohq/storage/dist/hook" 8 | 9 | const validateContent = { 10 | blueButton: { 11 | backgroundColor: "#1677FF", 12 | border: "1px solid #1677FF", 13 | color: "#fff", 14 | borderRadius: "4px", 15 | padding: "7px 15px", 16 | fontSize: "14px" 17 | }, 18 | redButton: { 19 | backgroundColor: "#d9363e", 20 | border: "1px solid #d9363e", 21 | color: "#fff", 22 | borderRadius: "4px", 23 | padding: "7px 15px", 24 | fontSize: "14px" 25 | } 26 | } 27 | 28 | export default function ValidateContent(props) { 29 | const [validTime, setValidTime] = useStorage("app-validTime", "1730390400") 30 | const [activationCode, setActivationCode] = useState("") 31 | const [isValid, setIsValid] = useState(true) 32 | 33 | const handleChange = (event) => { 34 | const code = event.target.value 35 | setActivationCode(code) 36 | } 37 | 38 | function handleSubmit() { 39 | if (Number(validTime) > dayjs().unix()) { 40 | props.handleOk() 41 | } else if (process.env.PLASMO_PUBLIC_CODEBOX_SECRET3 == activationCode) { 42 | let time = dayjs().add(7, "day").unix() 43 | setValidTime(String(time)) 44 | setIsValid(true) 45 | props.handleOk() 46 | } else { 47 | setIsValid(false) 48 | } 49 | } 50 | 51 | function handleCancel() { 52 | props.handleCancel() 53 | } 54 | 55 | function help() { 56 | sendToBackground({ 57 | name: "tab", 58 | body: { 59 | url: "feed.html" 60 | } 61 | }) 62 | } 63 | 64 | const typeMap = { 65 | downloadHtml: "下载HTML", 66 | downloadMarkdown: "下载markdown", 67 | editMarkdown: "编辑markdown", 68 | parseMarkdown: "解析markdown", 69 | downloadPdf: "下载PDF" 70 | } 71 | 72 | return ( 73 |
85 | {Number(validTime) > dayjs().unix() ? ( 86 |

87 | 已激活!确认{typeMap[props.type]}? 88 |

89 | ) : ( 90 | <> 91 |
92 | 此功能需要关注公众号 93 |

94 | 【codebox代码助手】获取验证码( 95 | 96 | 帮助 97 | 98 | ) 99 |

100 |
101 |
102 | 微信公众号 103 |
104 |
111 | 116 | 130 |
131 | {!isValid ? ( 132 |
133 | 验证码错误 134 |
135 | ) : ( 136 | <> 137 | )} 138 | 139 | )} 140 |
146 | 152 | 155 |
156 |
157 | ) 158 | } 159 | -------------------------------------------------------------------------------- /component/items/cssCode.tsx: -------------------------------------------------------------------------------- 1 | import { Input } from "antd" 2 | 3 | import { useStorage } from "@plasmohq/storage/dist/hook" 4 | 5 | import { i18n } from "~tools" 6 | 7 | export default function CssCode(props) { 8 | let { name } = props 9 | 10 | const [runCss, setRunCss] = useStorage(`${name}-runCss`, (v) => 11 | v === undefined ? false : v 12 | ) 13 | const [cssCode, setCssCode, { setRenderValue, setStoreValue, remove }] = 14 | useStorage(`${name}-cssCode`) 15 | 16 | return ( 17 | <> 18 |
19 | {i18n("customCssCode")} 20 | setRunCss(e.target.checked)} 27 | /> 28 | 29 |
30 |
31 | setRenderValue(e.target.value)} 35 | onBlur={(e) => setStoreValue()}> 36 |
37 | 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /component/items/downloadHtml.tsx: -------------------------------------------------------------------------------- 1 | import { DownloadOutlined, StarTwoTone } from "@ant-design/icons" 2 | 3 | import { sendToContentScript } from "@plasmohq/messaging" 4 | 5 | import { i18n } from "~tools" 6 | 7 | export default function DownloadHtml(props) { 8 | let { name } = props 9 | 10 | async function downloadHtml() { 11 | sendToContentScript({ 12 | name: `${name}-downloadHtml` 13 | }) 14 | } 15 | 16 | return ( 17 |
18 | 19 | 20 | {i18n("downloadHtml")} 21 | 22 | 23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /component/items/downloadImages.tsx: -------------------------------------------------------------------------------- 1 | import { DownloadOutlined, StarTwoTone } from "@ant-design/icons" 2 | import { useState } from "react" 3 | 4 | import { sendToContentScript } from "@plasmohq/messaging" 5 | 6 | import { i18n } from "~tools" 7 | 8 | export default function DownloadImages({ name }) { 9 | const [isDownloading, setIsDownloading] = useState(false) 10 | const [progress, setProgress] = useState(0) 11 | 12 | const handleDownload = async () => { 13 | if (isDownloading) return 14 | 15 | try { 16 | setIsDownloading(true) 17 | setProgress(0) 18 | 19 | const res = await sendToContentScript({ 20 | name: `${name}-downloadImages`, 21 | body: { 22 | onProgress: (current, total) => { 23 | const percentage = Math.round((current / total) * 100) 24 | setProgress(percentage) 25 | } 26 | } 27 | }) 28 | } catch (error) { 29 | console.error("Failed to download images:", error) 30 | } finally { 31 | setIsDownloading(false) 32 | setProgress(0) 33 | } 34 | } 35 | 36 | return ( 37 |
44 | 45 | 46 | {isDownloading 47 | ? `${i18n("downloading")} (${progress}%)` 48 | : i18n("downloadAllImg")} 49 | 50 | 56 |
57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /component/items/downloadMarkdown.tsx: -------------------------------------------------------------------------------- 1 | import { DownloadOutlined, StarTwoTone } from "@ant-design/icons" 2 | 3 | import { sendToContentScript } from "@plasmohq/messaging" 4 | 5 | import { i18n } from "~tools" 6 | 7 | export default function EditMarkdown(props) { 8 | let { name } = props 9 | 10 | async function downloadMarkdown() { 11 | sendToContentScript({ 12 | name: `${name}-downloadMarkdown` 13 | }) 14 | } 15 | 16 | return ( 17 |
18 | 19 | 20 | {i18n("downloadMarkdown")} 21 | 22 | 23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /component/items/downloadPdf.tsx: -------------------------------------------------------------------------------- 1 | import { DownloadOutlined, StarTwoTone } from "@ant-design/icons" 2 | 3 | import { sendToContentScript } from "@plasmohq/messaging" 4 | 5 | import { i18n } from "~tools" 6 | 7 | export default function DownloadPdf(props) { 8 | let { name } = props 9 | 10 | async function downloadPdf() { 11 | sendToContentScript({ 12 | name: `${name}-downloadPdf` 13 | }) 14 | } 15 | 16 | return ( 17 |
18 | 19 | 20 | {i18n("downloadPdf")} 21 | 22 | 23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /component/items/editMarkdown.tsx: -------------------------------------------------------------------------------- 1 | import { EditOutlined, StarTwoTone } from "@ant-design/icons" 2 | 3 | import { sendToContentScript } from "@plasmohq/messaging" 4 | 5 | import { i18n } from "~tools" 6 | 7 | export default function EditMarkdown(props) { 8 | let { name } = props 9 | 10 | async function editMarkdown() { 11 | sendToContentScript({ 12 | name: `${name}-editMarkdown` 13 | }) 14 | } 15 | 16 | return ( 17 |
18 | 19 | 20 | {i18n("editMarkdown")} 21 | 22 | 23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /component/items/options.tsx: -------------------------------------------------------------------------------- 1 | import CssCode from "~component/items/cssCode" 2 | import DownloadHtml from "~component/items/downloadHtml" 3 | import DownloadMarkdown from "~component/items/downloadMarkdown" 4 | import DownloadPdf from "~component/items/downloadPdf" 5 | import EditMarkdown from "~component/items/editMarkdown" 6 | import ShowTag from "~component/items/showTag" 7 | import { i18n } from "~tools" 8 | 9 | export default function Options(props) { 10 | let { name } = props 11 | 12 | return ( 13 |
14 | {i18n(name + "Config")} 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /component/items/parseMarkdown.tsx: -------------------------------------------------------------------------------- 1 | import { EditOutlined, StarTwoTone } from "@ant-design/icons" 2 | 3 | import { sendToContentScript } from "@plasmohq/messaging" 4 | 5 | import { i18n } from "~tools" 6 | 7 | export default function ParseMarkdown(props) { 8 | let { name } = props 9 | 10 | async function parseMarkdown() { 11 | sendToContentScript({ 12 | name: `${name}-parseMarkdown` 13 | }) 14 | } 15 | 16 | return ( 17 |
18 | 19 | 20 | {i18n("parseMarkdown")} 21 | 22 | 23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /component/items/setAiType.tsx: -------------------------------------------------------------------------------- 1 | import { EditOutlined, StarTwoTone } from "@ant-design/icons" 2 | 3 | import { sendToContentScript } from "@plasmohq/messaging" 4 | import { useStorage } from "@plasmohq/storage/dist/hook" 5 | 6 | import { i18n } from "~tools" 7 | 8 | export default function SetAiType(props) { 9 | const [aiType, setAiType] = useStorage("app-aiType", (v) => 10 | v === undefined ? "chatgpt" : v 11 | ) 12 | 13 | return ( 14 |
15 | 16 | 17 | 选择AI 18 | 19 | 28 |
29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /component/items/showTag.tsx: -------------------------------------------------------------------------------- 1 | import { StarTwoTone, TagOutlined } from "@ant-design/icons" 2 | 3 | import { useStorage } from "@plasmohq/storage/dist/hook" 4 | 5 | import { i18n } from "~tools" 6 | 7 | export default function ShowTag(props) { 8 | let { name } = props 9 | 10 | const [showTag, setShowTag] = useStorage(`${name}-showTag`, (v) => 11 | v === undefined ? true : v 12 | ) 13 | 14 | return ( 15 |
16 | 17 | 18 | {i18n("showTag")} 19 | 20 | setShowTag(e.target.checked)} 27 | /> 28 | 29 |
30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /component/options/360doc.tsx: -------------------------------------------------------------------------------- 1 | import Options from "~component/items/options" 2 | 3 | export default function Doc360() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /component/options/51cto.tsx: -------------------------------------------------------------------------------- 1 | import { useImperativeHandle } from "react" 2 | 3 | import { useStorage } from "@plasmohq/storage/hook" 4 | 5 | import CssCode from "~component/items/cssCode" 6 | import DownloadHtml from "~component/items/downloadHtml" 7 | import DownloadMarkdown from "~component/items/downloadMarkdown" 8 | import DownloadPdf from "~component/items/downloadPdf" 9 | import EditMarkdown from "~component/items/editMarkdown" 10 | import ShowTag from "~component/items/showTag" 11 | import { i18n } from "~tools" 12 | 13 | export default function Cto51({ forwardRef }) { 14 | const [copyCode, setCopyCode] = useStorage("51cto-copyCode", (v) => 15 | v === undefined ? true : v 16 | ) 17 | const [closeLoginModal, setCloseLoginModal] = useStorage( 18 | "51cto-closeLoginModal", 19 | (v) => (v === undefined ? true : v) 20 | ) 21 | 22 | function handleReset() { 23 | setCopyCode(true) 24 | setCloseLoginModal(true) 25 | } 26 | 27 | useImperativeHandle(forwardRef, () => ({ 28 | handleReset 29 | })) 30 | 31 | return ( 32 |
33 | {i18n("51ctoConfig")} 34 |
35 | {i18n("51ctoCopyCode")} 36 | setCopyCode(e.target.checked)} 43 | /> 44 | 45 |
46 |
47 | {i18n("51ctoCloseLoginModal")} 48 | setCloseLoginModal(e.target.checked)} 55 | /> 56 | 59 |
60 | 61 | 62 | 63 | 64 | 65 | 66 |
67 | ) 68 | } 69 | -------------------------------------------------------------------------------- /component/options/baidu.tsx: -------------------------------------------------------------------------------- 1 | import { useImperativeHandle } from "react" 2 | 3 | import { useStorage } from "@plasmohq/storage/hook" 4 | 5 | import CssCode from "~component/items/cssCode" 6 | import DownloadHtml from "~component/items/downloadHtml" 7 | import DownloadMarkdown from "~component/items/downloadMarkdown" 8 | import DownloadPdf from "~component/items/downloadPdf" 9 | import EditMarkdown from "~component/items/editMarkdown" 10 | import ShowTag from "~component/items/showTag" 11 | import { i18n } from "~tools" 12 | 13 | export default function Baidu({ forwardRef }) { 14 | const [closeAIBox, setCloseAIBox] = useStorage("baidu-closeAIBox", (v) => 15 | v === undefined ? false : v 16 | ) 17 | 18 | function handleReset() { 19 | setCloseAIBox(false) 20 | } 21 | 22 | useImperativeHandle(forwardRef, () => ({ 23 | handleReset 24 | })) 25 | 26 | return ( 27 |
28 | {i18n("baiduConfig")} 29 |
30 | {i18n("baiduCloseAIBox")} 31 | setCloseAIBox(e.target.checked)} 38 | /> 39 | 40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 |
48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /component/options/cnblogs.tsx: -------------------------------------------------------------------------------- 1 | import { useImperativeHandle } from "react" 2 | 3 | import { useStorage } from "@plasmohq/storage/hook" 4 | 5 | import CssCode from "~component/items/cssCode" 6 | import DownloadHtml from "~component/items/downloadHtml" 7 | import DownloadMarkdown from "~component/items/downloadMarkdown" 8 | import DownloadPdf from "~component/items/downloadPdf" 9 | import EditMarkdown from "~component/items/editMarkdown" 10 | import ShowTag from "~component/items/showTag" 11 | import { i18n } from "~tools" 12 | 13 | export default function Cnblogs({ forwardRef }) { 14 | const [copyCode, setCopyCode] = useStorage("cnblogs-copyCode", (v) => 15 | v === undefined ? true : v 16 | ) 17 | 18 | function handleReset() { 19 | setCopyCode(true) 20 | } 21 | 22 | useImperativeHandle(forwardRef, () => ({ 23 | handleReset 24 | })) 25 | 26 | return ( 27 |
28 | {i18n("cnblogsConfig")} 29 |
30 | {i18n("cnblogsCopyCode")} 31 | setCopyCode(e.target.checked)} 38 | /> 39 | 40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /component/options/config.tsx: -------------------------------------------------------------------------------- 1 | import { BookOutlined, DownloadOutlined, StarTwoTone } from "@ant-design/icons" 2 | import { useImperativeHandle } from "react" 3 | 4 | import { sendToBackground, sendToContentScript } from "@plasmohq/messaging" 5 | import { useStorage } from "@plasmohq/storage/hook" 6 | 7 | import DownloadImages from "~component/items/downloadImages" 8 | import SetAiType from "~component/items/setAiType" 9 | import { i18n } from "~tools" 10 | 11 | export default function Config({ forwardRef }) { 12 | const [copyCode, setCopyCode] = useStorage("config-copyCode", (v) => 13 | v === undefined ? true : v 14 | ) 15 | const [closeLog, setCloseLog] = useStorage("config-closeLog", (v) => 16 | v === undefined ? true : v 17 | ) 18 | const [allShowTag, setAllShowTag] = useStorage("config-allShowTag", (v) => 19 | v === undefined ? true : v 20 | ) 21 | 22 | function downloadFullImg() { 23 | sendToContentScript({ 24 | name: "app-full-page-screenshot" 25 | }) 26 | } 27 | 28 | function getSummary() { 29 | sendToContentScript({ 30 | name: "app-get-summary" 31 | }) 32 | sendToBackground({ 33 | name: "sidepanel", 34 | body: { 35 | active: true 36 | } 37 | }) 38 | } 39 | 40 | function handleReset() { 41 | setCopyCode(true) 42 | setCloseLog(true) 43 | } 44 | 45 | useImperativeHandle(forwardRef, () => ({ 46 | handleReset 47 | })) 48 | 49 | return ( 50 |
51 | {i18n("AppConfig")} 52 | 53 |
54 | {i18n("copyCode")} 55 | setCopyCode(e.target.checked)} 62 | /> 63 | 64 |
65 |
66 | {i18n("allShowTag")} 67 | setAllShowTag(e.target.checked)} 74 | /> 75 | 76 |
77 |
78 | {i18n("configCloseLog")} 79 | setCloseLog(e.target.checked)} 86 | /> 87 | 88 |
89 | 90 |
91 | 92 | 93 | {i18n("fullPageScreenshot")} 94 | 95 | 96 |
97 |
98 | 99 | 100 | 总结文章 101 | 102 | 103 |
104 |
105 | ) 106 | } 107 | -------------------------------------------------------------------------------- /component/options/content.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react" 2 | 3 | import Cto51 from "~component/options/51cto" 4 | import Doc360 from "~component/options/360doc" 5 | import Baidu from "~component/options/baidu" 6 | import Cnblogs from "~component/options/cnblogs" 7 | import Config from "~component/options/config" 8 | import Csdn from "~component/options/csdn" 9 | import Custom from "~component/options/custom" 10 | import Jb51 from "~component/options/jb51" 11 | import Jianshu from "~component/options/jianshu" 12 | import Juejin from "~component/options/juejin" 13 | import Lougu from "~component/options/lougu" 14 | import Medium from "~component/options/medium" 15 | import Mp from "~component/options/mp" 16 | import Oschina from "~component/options/oschina" 17 | import Paywallbuster from "~component/options/paywallbuster" 18 | import Php from "~component/options/php" 19 | import Pianshen from "~component/options/pianshen" 20 | import Segmentfault from "~component/options/segmentfault" 21 | import Weixin from "~component/options/weixin" 22 | import Zhihu from "~component/options/zhihu" 23 | 24 | let csdnIsShow = false 25 | let zhihuIsShow = false 26 | let baiduIsShow = false 27 | let jianshuIsShow = false 28 | let jb51IsShow = false 29 | let cnblogsIsShow = false 30 | let ctoIsShow = false 31 | let juejinIsShow = false 32 | let phpIsShow = false 33 | let oschinaIsShow = false 34 | let segmentfaultIsShow = false 35 | let mediumIsShow = false 36 | let paywallbusterIsShow = false 37 | let doc360IsShow = false 38 | let mpIsShow = false 39 | let weixinIsShow = false 40 | let pianshenIsShow = false 41 | let louguIsShow = false 42 | 43 | chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { 44 | const currentTab = tabs[0] 45 | if (currentTab) { 46 | const url = new URL(currentTab.url) 47 | const { hostname, pathname } = url 48 | csdnIsShow = hostname.includes("csdn") 49 | zhihuIsShow = hostname.includes("zhihu") 50 | baiduIsShow = hostname.includes("baidu") 51 | jianshuIsShow = hostname.includes("jianshu") 52 | jb51IsShow = hostname.includes("jb51") 53 | cnblogsIsShow = hostname.includes("cnblogs") 54 | ctoIsShow = hostname.includes("51cto") 55 | juejinIsShow = hostname.includes("juejin") 56 | phpIsShow = hostname.includes("php") 57 | oschinaIsShow = hostname.includes("oschina") 58 | segmentfaultIsShow = hostname.includes("segmentfault") 59 | mediumIsShow = hostname.includes("medium") 60 | paywallbusterIsShow = hostname.includes("paywallbuster") 61 | doc360IsShow = hostname.includes("360doc") 62 | pianshenIsShow = hostname.includes("pianshen") 63 | louguIsShow = hostname.includes("lougu") 64 | 65 | if (hostname.includes("weixin")) { 66 | if (pathname.includes("cgi-bin")) { 67 | mpIsShow = true 68 | } else { 69 | weixinIsShow = true 70 | } 71 | } 72 | } 73 | }) 74 | 75 | export default function Content() { 76 | const csdnRef = useRef() 77 | const zhihuRef = useRef() 78 | const baiduRef = useRef() 79 | const juejinRef = useRef() 80 | const oschinaRef = useRef() 81 | const jianshuRef = useRef() 82 | const jb51Ref = useRef() 83 | const cnblogsRef = useRef() 84 | const cto51Ref = useRef() 85 | const phpRef = useRef() 86 | const segmentfaultRef = useRef() 87 | const weixinRef = useRef() 88 | const customRef = useRef() 89 | const appRef = useRef() 90 | 91 | return ( 92 |
93 | {csdnIsShow ? : <>} 94 | {zhihuIsShow ? : <>} 95 | {baiduIsShow ? : <>} 96 | {jianshuIsShow ? : <>} 97 | {jb51IsShow ? : <>} 98 | {cnblogsIsShow ? : <>} 99 | {ctoIsShow ? : <>} 100 | {juejinIsShow ? : <>} 101 | {phpIsShow ? : <>} 102 | {oschinaIsShow ? : <>} 103 | {segmentfaultIsShow ? : <>} 104 | {weixinIsShow ? : <>} 105 | {mediumIsShow ? : <>} 106 | {mpIsShow ? : <>} 107 | {paywallbusterIsShow ? : <>} 108 | {doc360IsShow ? : <>} 109 | {pianshenIsShow ? : <>} 110 | {louguIsShow ? : <>} 111 | 112 | 113 |
114 | ) 115 | } 116 | -------------------------------------------------------------------------------- /component/options/csdn.tsx: -------------------------------------------------------------------------------- 1 | import { useImperativeHandle } from "react" 2 | 3 | import { useStorage } from "@plasmohq/storage/hook" 4 | 5 | import CssCode from "~component/items/cssCode" 6 | import DownloadHtml from "~component/items/downloadHtml" 7 | import DownloadMarkdown from "~component/items/downloadMarkdown" 8 | import DownloadPdf from "~component/items/downloadPdf" 9 | import EditMarkdown from "~component/items/editMarkdown" 10 | import ShowTag from "~component/items/showTag" 11 | import { i18n } from "~tools" 12 | 13 | export default function Csdn({ forwardRef }) { 14 | const [closeAds, setCloseAds] = useStorage("csdn-closeAds", (v) => 15 | v === undefined ? true : v 16 | ) 17 | const [copyCode, setCopyCode] = useStorage("csdn-copyCode", (v) => 18 | v === undefined ? true : v 19 | ) 20 | const [closeFollow, setCloseFollow] = useStorage("csdn-closeFollow", (v) => 21 | v === undefined ? true : v 22 | ) 23 | const [closeVip, setCloseVip] = useStorage("csdn-closeVip", (v) => 24 | v === undefined ? true : v 25 | ) 26 | const [autoOpenCode, setAutoOpenCode] = useStorage( 27 | "csdn-autoOpenCode", 28 | (v) => (v === undefined ? true : v) 29 | ) 30 | const [closeLoginModal, setCloseLoginModal] = useStorage( 31 | "csdn-closeLoginModal", 32 | (v) => (v === undefined ? true : v) 33 | ) 34 | 35 | const [closeRedirectModal, setCloseRedirectModal] = useStorage( 36 | "csdn-closeRedirectModal", 37 | (v) => (v === undefined ? true : v) 38 | ) 39 | 40 | function handleReset() { 41 | setCloseAds(true) 42 | setCopyCode(true) 43 | setCloseFollow(true) 44 | setCloseVip(true) 45 | setAutoOpenCode(true) 46 | setCloseLoginModal(true) 47 | setCloseRedirectModal(true) 48 | } 49 | 50 | useImperativeHandle(forwardRef, () => ({ 51 | handleReset 52 | })) 53 | 54 | return ( 55 |
56 | {i18n("csdnConfig")} 57 |
58 | {i18n("csdnCloseAds")} 59 | setCloseAds(e.target.checked)} 66 | /> 67 | 68 |
69 |
70 | {i18n("csdnCopyCode")} 71 | setCopyCode(e.target.checked)} 78 | /> 79 | 80 |
81 |
82 | {i18n("csdnCloseFollow")} 83 | setCloseFollow(e.target.checked)} 90 | /> 91 | 92 |
93 |
94 | {i18n("csdnCloseVip")} 95 | setCloseVip(e.target.checked)} 102 | /> 103 | 104 |
105 |
106 | {i18n("csdnAutoOpenCode")} 107 | setAutoOpenCode(e.target.checked)} 114 | /> 115 | 116 |
117 |
118 | {i18n("csdnCloseLoginModal")} 119 | setCloseLoginModal(e.target.checked)} 126 | /> 127 | 130 |
131 |
132 | {i18n("csdnCloseRedirectModal")} 133 | setCloseRedirectModal(e.target.checked)} 140 | /> 141 | 144 |
145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 |
153 | ) 154 | } 155 | -------------------------------------------------------------------------------- /component/options/custom.tsx: -------------------------------------------------------------------------------- 1 | import { DownloadOutlined, StarTwoTone } from "@ant-design/icons" 2 | 3 | import { sendToContentScript } from "@plasmohq/messaging" 4 | 5 | import CssCode from "~component/items/cssCode" 6 | import DownloadHtml from "~component/items/downloadHtml" 7 | import DownloadMarkdown from "~component/items/downloadMarkdown" 8 | import DownloadPdf from "~component/items/downloadPdf" 9 | import EditMarkdown from "~component/items/editMarkdown" 10 | import ParseMarkdown from "~component/items/parseMarkdown" 11 | import { i18n } from "~tools" 12 | 13 | export default function Custom() { 14 | function makerQRPost() { 15 | sendToContentScript({ 16 | name: "app-makerQRPost" 17 | }) 18 | } 19 | function getArticle() { 20 | sendToContentScript({ 21 | name: "app-getArticle" 22 | }) 23 | } 24 | return ( 25 |
26 | {i18n("customConfig")} 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 35 | 36 | {i18n("makerQRPost")} 37 | 38 | 39 |
40 |
41 | 42 | 43 | {i18n("getArticle")} 44 | 45 | 46 |
47 |
48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /component/options/jb51.tsx: -------------------------------------------------------------------------------- 1 | import { useImperativeHandle } from "react" 2 | 3 | import { useStorage } from "@plasmohq/storage/hook" 4 | 5 | import CssCode from "~component/items/cssCode" 6 | import DownloadHtml from "~component/items/downloadHtml" 7 | import DownloadMarkdown from "~component/items/downloadMarkdown" 8 | import DownloadPdf from "~component/items/downloadPdf" 9 | import EditMarkdown from "~component/items/editMarkdown" 10 | import ShowTag from "~component/items/showTag" 11 | import { i18n } from "~tools" 12 | 13 | export default function Jb51({ forwardRef }) { 14 | const [closeAds, setCloseAds] = useStorage("jb51-closeAds", (v) => 15 | v === undefined ? true : v 16 | ) 17 | const [copyCode, setCopyCode] = useStorage("jb51-copyCode", (v) => 18 | v === undefined ? true : v 19 | ) 20 | 21 | function handleReset() { 22 | setCopyCode(true) 23 | setCloseAds(true) 24 | } 25 | 26 | useImperativeHandle(forwardRef, () => ({ 27 | handleReset 28 | })) 29 | 30 | return ( 31 |
32 | {i18n("jb51Config")} 33 |
34 | {i18n("jb51CloseAds")} 35 | setCloseAds(e.target.checked)} 42 | /> 43 | 44 |
45 |
46 | {i18n("jb51CopyCode")} 47 | setCopyCode(e.target.checked)} 54 | /> 55 | 56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 |
64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /component/options/jianshu.tsx: -------------------------------------------------------------------------------- 1 | import { useImperativeHandle } from "react" 2 | 3 | import { useStorage } from "@plasmohq/storage/hook" 4 | 5 | import CssCode from "~component/items/cssCode" 6 | import DownloadHtml from "~component/items/downloadHtml" 7 | import DownloadMarkdown from "~component/items/downloadMarkdown" 8 | import DownloadPdf from "~component/items/downloadPdf" 9 | import EditMarkdown from "~component/items/editMarkdown" 10 | import ShowTag from "~component/items/showTag" 11 | import { i18n } from "~tools" 12 | 13 | export default function Jianshu({ forwardRef }) { 14 | const [copyCode, setCopyCode] = useStorage("jianshu-copyCode", (v) => 15 | v === undefined ? true : v 16 | ) 17 | const [closeLoginModal, setCloseLoginModal] = useStorage( 18 | "jianshu-closeLoginModal", 19 | (v) => (v === undefined ? true : v) 20 | ) 21 | const [autoOpenCode, setAutoOpenCode] = useStorage( 22 | "jianshu-autoOpenCode", 23 | (v) => (v === undefined ? true : v) 24 | ) 25 | 26 | function handleReset() { 27 | setCopyCode(true) 28 | setCloseLoginModal(true) 29 | setAutoOpenCode(true) 30 | } 31 | 32 | useImperativeHandle(forwardRef, () => ({ 33 | handleReset 34 | })) 35 | 36 | return ( 37 |
38 | {i18n("jianshuConfig")} 39 |
40 | {i18n("jianshuCopyCode")} 41 | setCopyCode(e.target.checked)} 48 | /> 49 | 50 |
51 |
52 | {i18n("jianshuCloseLoginModal")} 53 | setCloseLoginModal(e.target.checked)} 60 | /> 61 | 64 |
65 |
66 | {i18n("jianshuAutoOpenCode")} 67 | setAutoOpenCode(e.target.checked)} 74 | /> 75 | 78 |
79 | 80 | 81 | 82 | 83 | 84 | 85 |
86 | ) 87 | } 88 | -------------------------------------------------------------------------------- /component/options/juejin.tsx: -------------------------------------------------------------------------------- 1 | import Options from "~component/items/options" 2 | 3 | export default function Juejin() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /component/options/lougu.tsx: -------------------------------------------------------------------------------- 1 | import Options from "~component/items/options" 2 | 3 | export default function Lougu() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /component/options/medium.tsx: -------------------------------------------------------------------------------- 1 | import Options from "~component/items/options" 2 | 3 | export default function Medium() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /component/options/mp.tsx: -------------------------------------------------------------------------------- 1 | import { BookOutlined, StarTwoTone } from "@ant-design/icons" 2 | 3 | import { sendToContentScript } from "@plasmohq/messaging" 4 | 5 | import { i18n } from "~tools" 6 | 7 | export default function Weixin() { 8 | async function getThumbMedia() { 9 | sendToContentScript({ 10 | name: `mp-getMediaResources` 11 | }) 12 | } 13 | 14 | return ( 15 |
16 | {i18n("weixinConfig")} 17 |
18 | 19 | 20 | {i18n("getMediaResources")} 21 | 22 | 23 |
24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /component/options/oschina.tsx: -------------------------------------------------------------------------------- 1 | import Options from "~component/items/options" 2 | 3 | export default function Oschina() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /component/options/paywallbuster.tsx: -------------------------------------------------------------------------------- 1 | import Options from "~component/items/options" 2 | 3 | export default function Paywallbuster() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /component/options/php.tsx: -------------------------------------------------------------------------------- 1 | import { useImperativeHandle } from "react" 2 | 3 | import { useStorage } from "@plasmohq/storage/hook" 4 | 5 | import CssCode from "~component/items/cssCode" 6 | import DownloadHtml from "~component/items/downloadHtml" 7 | import DownloadMarkdown from "~component/items/downloadMarkdown" 8 | import DownloadPdf from "~component/items/downloadPdf" 9 | import EditMarkdown from "~component/items/editMarkdown" 10 | import ShowTag from "~component/items/showTag" 11 | import { i18n } from "~tools" 12 | 13 | export default function Php({ forwardRef }) { 14 | const [copyCode, setCopyCode] = useStorage("php-copyCode", (v) => 15 | v === undefined ? true : v 16 | ) 17 | const [closeLoginModal, setCloseLoginModal] = useStorage( 18 | "php-closeLoginModal", 19 | (v) => (v === undefined ? true : v) 20 | ) 21 | 22 | function handleReset() { 23 | setCopyCode(true) 24 | setCloseLoginModal(true) 25 | } 26 | 27 | useImperativeHandle(forwardRef, () => ({ 28 | handleReset 29 | })) 30 | 31 | return ( 32 |
33 | {i18n("phpConfig")} 34 |
35 | {i18n("phpCopyCode")} 36 | setCopyCode(e.target.checked)} 43 | /> 44 | 45 |
46 |
47 | {i18n("phpCloseLoginModal")} 48 | setCloseLoginModal(e.target.checked)} 55 | /> 56 | 57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 |
65 | ) 66 | } 67 | -------------------------------------------------------------------------------- /component/options/pianshen.tsx: -------------------------------------------------------------------------------- 1 | import Options from "~component/items/options" 2 | 3 | export default function Pianshen() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /component/options/segmentfault.tsx: -------------------------------------------------------------------------------- 1 | import Options from "~component/items/options" 2 | 3 | export default function Segmentfault() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /component/options/weixin.tsx: -------------------------------------------------------------------------------- 1 | import { DownloadOutlined, StarTwoTone } from "@ant-design/icons" 2 | 3 | import { sendToContentScript } from "@plasmohq/messaging" 4 | 5 | import CssCode from "~component/items/cssCode" 6 | import DownloadHtml from "~component/items/downloadHtml" 7 | import DownloadImages from "~component/items/downloadImages" 8 | import DownloadMarkdown from "~component/items/downloadMarkdown" 9 | import DownloadPdf from "~component/items/downloadPdf" 10 | import EditMarkdown from "~component/items/editMarkdown" 11 | import ShowTag from "~component/items/showTag" 12 | import { i18n } from "~tools" 13 | 14 | export default function Weixin() { 15 | async function getThumbMedia() { 16 | sendToContentScript({ 17 | name: `weixin-getThumbMedia` 18 | }) 19 | } 20 | 21 | return ( 22 |
23 | {i18n("weixinConfig")} 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | 33 | 34 | {i18n("getThumbMedia")} 35 | 36 | 37 |
38 |
39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /component/options/zhihu.tsx: -------------------------------------------------------------------------------- 1 | import React, { useImperativeHandle } from "react" 2 | 3 | import { useStorage } from "@plasmohq/storage/hook" 4 | 5 | import CssCode from "~component/items/cssCode" 6 | import DownloadHtml from "~component/items/downloadHtml" 7 | import DownloadMarkdown from "~component/items/downloadMarkdown" 8 | import DownloadPdf from "~component/items/downloadPdf" 9 | import EditMarkdown from "~component/items/editMarkdown" 10 | import ShowTag from "~component/items/showTag" 11 | import { i18n } from "~tools" 12 | 13 | export default function Zhihu({ forwardRef }) { 14 | const [copyCode, setCopyCode] = useStorage("zhihu-copyCode", (v) => 15 | v === undefined ? true : v 16 | ) 17 | const [closeLoginModal, setCloseLoginModal] = useStorage( 18 | "zhihu-closeLoginModal", 19 | (v) => (v === undefined ? true : v) 20 | ) 21 | const [autoOpenCode, setAutoOpenCode] = useStorage( 22 | "zhihu-autoOpenCode", 23 | (v) => (v === undefined ? true : v) 24 | ) 25 | 26 | function handleReset() { 27 | setCopyCode(true) 28 | setCloseLoginModal(true) 29 | setAutoOpenCode(true) 30 | } 31 | 32 | useImperativeHandle(forwardRef, () => ({ 33 | handleReset 34 | })) 35 | 36 | return ( 37 |
38 | {i18n("zhihuConfig")} 39 |
40 | {i18n("zhihuCopyCode")} 41 | setCopyCode(e.target.checked)} 48 | /> 49 | 50 |
51 |
52 | {i18n("zhihuCloseLoginModal")} 53 | setCloseLoginModal(e.target.checked)} 60 | /> 61 | 64 |
65 |
66 | {i18n("zhihuAutoOpenCode")} 67 | setAutoOpenCode(e.target.checked)} 74 | /> 75 | 76 |
77 | 78 | 79 | 80 | 81 | 82 | 83 |
84 | ) 85 | } 86 | -------------------------------------------------------------------------------- /component/tagBtn/style.ts: -------------------------------------------------------------------------------- 1 | export default function TagBtnStyle() { 2 | const style = document.createElement("style") 3 | style.textContent = ` 4 | .codebox-tagBtn .btn:hover { 5 | color: #fff; 6 | background: #1e80ff; 7 | border-radius: 3px; 8 | } 9 | ` 10 | return style 11 | } 12 | -------------------------------------------------------------------------------- /component/ui/QRCodeModal.tsx: -------------------------------------------------------------------------------- 1 | import imageSrc from "raw:~/public/wx/xcx.jpg" 2 | import React, { useEffect, useState } from "react" 3 | 4 | import { TOTP } from "~utils/totp" 5 | 6 | interface QRCodeModalProps { 7 | onConfirm: () => void 8 | onClose: () => void 9 | } 10 | 11 | const styles = { 12 | overlay: { 13 | position: "fixed" as const, 14 | top: 0, 15 | left: 0, 16 | right: 0, 17 | bottom: 0, 18 | backgroundColor: "rgba(0, 0, 0, 0.5)", 19 | display: "flex", 20 | justifyContent: "center", 21 | alignItems: "center", 22 | zIndex: 1000 23 | }, 24 | content: { 25 | backgroundColor: "white", 26 | padding: "2rem", 27 | borderRadius: "8px", 28 | position: "relative" as const, 29 | maxWidth: "90%", 30 | boxShadow: "0 2px 10px rgba(0, 0, 0, 0.1)" 31 | }, 32 | closeButton: { 33 | position: "absolute" as const, 34 | top: "10px", 35 | right: "10px", 36 | background: "none", 37 | border: "none", 38 | fontSize: "18px", 39 | cursor: "pointer", 40 | padding: "0.5rem" 41 | }, 42 | qrcodeBox: { 43 | width: "100%", 44 | textAlign: "center" as const 45 | }, 46 | qrcodeImage: { 47 | maxWidth: "260px", 48 | width: "100%", 49 | height: "auto", 50 | margin: "0px auto" 51 | }, 52 | scanTip: { 53 | textAlign: "center" as const, 54 | color: "#666" 55 | }, 56 | captchaSection: { 57 | marginTop: "1.5rem", 58 | borderTop: "1px solid #eee", 59 | paddingTop: "1.5rem" 60 | }, 61 | captchaRow: { 62 | display: "flex", 63 | justifyContent: "space-between", 64 | alignItems: "center", 65 | marginBottom: "1rem" 66 | }, 67 | captchaText: { 68 | fontSize: "16px", 69 | letterSpacing: "2px", 70 | color: "#333" 71 | }, 72 | inputGroup: { 73 | display: "flex", 74 | gap: "0.5rem" 75 | }, 76 | input: { 77 | flex: 1, 78 | padding: "0.8rem", 79 | border: "1px solid #ddd", 80 | borderRadius: "4px", 81 | fontSize: "14px", 82 | outline: "none", 83 | "&:focus": { 84 | borderColor: "#007bff" 85 | } 86 | }, 87 | verifyButton: { 88 | padding: "0.8rem 1.5rem", 89 | backgroundColor: "#007bff", 90 | color: "white", 91 | border: "none", 92 | borderRadius: "4px", 93 | cursor: "pointer", 94 | "&:hover": { 95 | backgroundColor: "#0056b3" 96 | } 97 | }, 98 | errorText: { 99 | color: "#ff4444", 100 | fontSize: "14px", 101 | marginTop: "6px" 102 | } 103 | } 104 | 105 | export default function QRCodeModal({ onClose, onConfirm }: QRCodeModalProps) { 106 | const [inputCode, setInputCode] = useState("") 107 | const [isValid, setIsValid] = useState(null) 108 | 109 | const handleVerify = () => { 110 | const formattedInput = inputCode.replace(/\s/g, "") 111 | const secretKey = process.env.PLASMO_PUBLIC_CODEBOX_SECRET4 112 | 113 | if ( 114 | formattedInput.length == 6 && 115 | TOTP.verifyTOTP(secretKey, formattedInput) 116 | ) { 117 | setIsValid(true) 118 | onConfirm() 119 | } else if (process.env.PLASMO_PUBLIC_CODEBOX_SECRET3 == formattedInput) { 120 | setIsValid(true) 121 | onConfirm() 122 | } else { 123 | setIsValid(false) 124 | } 125 | } 126 | 127 | // 在原有return中添加: 128 | return ( 129 |
130 |
131 | 134 | 135 | {imageSrc && ( 136 |
137 | WeChat QR Code 142 |
扫描二维码获取验证码
143 |
144 | )} 145 | 146 | {/* 新增验证码区域 */} 147 |
148 |
149 | { 153 | // 只允许数字和空格 154 | const val = e.target.value.replace(/[^\d\s]/g, "") 155 | setInputCode(val) 156 | setIsValid(null) // 清除验证状态 157 | }} 158 | placeholder="请输入验证码" 159 | style={{ 160 | ...styles.input, 161 | borderColor: isValid === false ? "#ff4444" : "#ddd" 162 | }} 163 | maxLength={11} // 6数字+5空格 164 | /> 165 | 168 |
169 | {isValid === false && ( 170 |
验证码错误,请重新输入
171 | )} 172 |
173 |
174 |
175 | ) 176 | } 177 | -------------------------------------------------------------------------------- /component/ui/modal.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import ValidateContent from "~component/contents/validateContent" 4 | 5 | const Modal = ({ isOpen, onClose, onConfirm, message }) => { 6 | if (!isOpen) return null 7 | 8 | const modalOverlayStyle = { 9 | position: "fixed", 10 | top: 0, 11 | left: 0, 12 | right: 0, 13 | bottom: 0, 14 | background: "rgba(0, 0, 0, 0.5)", 15 | display: "flex", 16 | justifyContent: "center", 17 | alignItems: "center", 18 | zIndex: 1000 19 | } 20 | 21 | const modalStyle = { 22 | background: "white", 23 | padding: "20px", 24 | borderRadius: "8px", 25 | boxShadow: "0 4px 8px rgba(0, 0, 0, 0.1)", 26 | textAlign: "center" 27 | } 28 | 29 | const modalButtonsStyle = { 30 | marginTop: "20px" 31 | } 32 | 33 | const buttonStyle = { 34 | borderRadius: "4px", 35 | padding: "2px 7px", 36 | margin: "0 10px", 37 | fontSize: "16px", 38 | cursor: "pointer" 39 | } 40 | 41 | const cancelBtnStyle = { 42 | ...buttonStyle, 43 | backgroundColor: "#d9363e", 44 | border: "1px solid #d9363e", 45 | color: "#fff" 46 | } 47 | 48 | const confirmBtnStyle = { 49 | ...buttonStyle, 50 | backgroundColor: "#1677FF", 51 | border: "1px solid #1677FF", 52 | color: "#fff" 53 | } 54 | 55 | return ( 56 |
57 |
58 | 63 |
64 |
65 | ) 66 | } 67 | 68 | export default Modal 69 | -------------------------------------------------------------------------------- /component/ui/tags.tsx: -------------------------------------------------------------------------------- 1 | import { CloseOutlined } from "@ant-design/icons" 2 | import React, { useState } from "react" 3 | 4 | import { i18n } from "~tools" 5 | 6 | const tagsStyles = { 7 | tags: { 8 | height: "28px", 9 | display: "flex", 10 | cursor: "pointer", 11 | alignItems: "center", 12 | color: "#1e80ff", 13 | width: "160px", 14 | backgroundColor: "rgb(255, 255, 255, .3)", 15 | borderRadius: "5px", 16 | justifyContent: "space-between", 17 | padding: "0 8px", 18 | marginTop: "-20px", 19 | fontSize: "14px" 20 | } 21 | } 22 | 23 | export default function Tags(props: any) { 24 | const [isShowTag, setIsShowTag] = useState(true) 25 | const handleEdit = () => { 26 | props.onEdit() 27 | } 28 | 29 | const handleDownload = () => { 30 | props.onDownload() 31 | } 32 | 33 | const handleParse = () => { 34 | props.onParse() 35 | } 36 | 37 | const handlePrint = () => { 38 | props.onPrint() 39 | } 40 | 41 | const handleClose = () => { 42 | setIsShowTag(false) 43 | } 44 | 45 | return isShowTag ? ( 46 |
47 |
48 | {i18n("edit")} 49 |
50 |
51 | {i18n("download")} 52 |
53 |
54 | {i18n("print")} 55 |
56 |
57 | {i18n("parse")} 58 |
59 |
60 | 61 |
62 |
63 | ) : ( 64 | <> 65 | ) 66 | } 67 | -------------------------------------------------------------------------------- /component/ui/toolBox.tsx: -------------------------------------------------------------------------------- 1 | import qrcodeUrl from "raw:~/public/wx/gzh.jpg" 2 | import React, { useState } from "react" 3 | 4 | import { i18n } from "~tools" 5 | import { editQRCodeImg } from "~utils/makerQRPost" 6 | 7 | const boxStyles = { 8 | box: { 9 | position: "fixed" as const, 10 | border: "1px solid #D9DADC", 11 | left: "25px", 12 | top: "85px", 13 | width: "140px", 14 | padding: "16px", 15 | cursor: "pointer" 16 | }, 17 | close: { 18 | position: "absolute" as const, 19 | top: "-5px", 20 | right: "0px", 21 | background: "none", 22 | border: "none", 23 | fontSize: "20px", 24 | cursor: "pointer", 25 | padding: "10px" 26 | }, 27 | img: { 28 | width: "100%" 29 | }, 30 | item: { 31 | color: "#000000", 32 | fontSize: "16px", 33 | marginBottom: "3px" 34 | } 35 | } 36 | 37 | export default function ToolBox(props: any) { 38 | const [isShowTag, setIsShowTag] = useState(true) 39 | const handleGetDescription = () => { 40 | props.onGetDescription() 41 | } 42 | 43 | const handleEditMarkdown = () => { 44 | props.onEditMarkdown() 45 | } 46 | 47 | const handleDownloadMarkdown = () => { 48 | props.onDownloadMarkdown() 49 | } 50 | 51 | const handleParseMarkdown = () => { 52 | props.onParseMarkdown() 53 | } 54 | 55 | const handlePrint = () => { 56 | props.onPrint() 57 | } 58 | 59 | const getArticle = () => { 60 | const href = encodeURIComponent(location.href) 61 | window.open(`https://paywallbuster.com/articles/?article=${href}`) 62 | } 63 | 64 | const handleClose = () => { 65 | setIsShowTag(false) 66 | } 67 | 68 | return isShowTag ? ( 69 |
73 | 76 | qrcodeUrl 77 | 80 | 83 | 86 | 89 | 92 | 95 | 98 | 99 | 帮助 100 | 101 |
102 | ) : ( 103 | <> 104 | ) 105 | } 106 | -------------------------------------------------------------------------------- /component/ui/tooltip.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | CheckOutlined, 3 | CloseOutlined, 4 | DownSquareOutlined, 5 | LeftSquareOutlined, 6 | RightSquareOutlined, 7 | UpSquareOutlined 8 | } from "@ant-design/icons" 9 | import React from "react" 10 | 11 | const tooltipStyles = { 12 | tooltip: { 13 | position: "absolute" as const, 14 | zIndex: "2147483641", 15 | backgroundColor: "#fff", 16 | border: "1px solid #eee", 17 | borderRadius: "5px", 18 | padding: "8px", 19 | boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)", 20 | width: "450px", 21 | display: "flex", 22 | justifyContent: "space-between" 23 | }, 24 | button: { 25 | backgroundColor: "#fff", 26 | border: "1px solid #eee", 27 | color: "#000", 28 | borderRadius: "4px", 29 | padding: "2px 7px", 30 | fontSize: "14px", 31 | cursor: "pointer" 32 | }, 33 | blueButton: { 34 | backgroundColor: "#1677FF", 35 | border: "1px solid #1677FF", 36 | color: "#fff", 37 | borderRadius: "4px", 38 | padding: "2px 7px", 39 | fontSize: "14px" 40 | }, 41 | redButton: { 42 | backgroundColor: "#d9363e", 43 | border: "1px solid #d9363e", 44 | color: "#fff", 45 | borderRadius: "4px", 46 | padding: "2px 7px", 47 | fontSize: "14px" 48 | } 49 | } 50 | 51 | export default function Tooltip(props: any) { 52 | const handleConfirm = () => { 53 | props.onConfirm() 54 | } 55 | 56 | const navigateElement = (direction: "parent" | "child" | "prev" | "next") => { 57 | props.onNavigate(direction) 58 | } 59 | 60 | const handleCancel = () => { 61 | props.onCancel() 62 | } 63 | 64 | const setTooltipStyle = () => { 65 | return { ...tooltipStyles.tooltip, left: props.left, top: props.top } 66 | } 67 | 68 | return ( 69 |
70 | 73 | 78 | 83 | 88 | 93 | 96 |
97 | ) 98 | } 99 | -------------------------------------------------------------------------------- /contents/360doc.tsx: -------------------------------------------------------------------------------- 1 | import type { 2 | PlasmoCSConfig, 3 | PlasmoCSUIProps, 4 | PlasmoGetShadowHostId 5 | } from "plasmo" 6 | import React, { type FC } from "react" 7 | 8 | import { useMessage } from "@plasmohq/messaging/hook" 9 | import { useStorage } from "@plasmohq/storage/dist/hook" 10 | 11 | import ToolBox from "~component/ui/toolBox" 12 | import { saveHtml, saveMarkdown } from "~tools" 13 | import useCssCodeHook from "~utils/cssCodeHook" 14 | import { useEditMarkdown } from "~utils/editMarkdownHook" 15 | import { useParseMarkdown } from "~utils/parseMarkdownHook" 16 | import { Print } from "~utils/print" 17 | import Turndown from "~utils/turndown" 18 | 19 | export const config: PlasmoCSConfig = { 20 | matches: ["https://*.360doc.com/content/*", "http://*.360doc.com/content/*"] 21 | } 22 | 23 | const turndownService = Turndown() 24 | const articleTitle = document 25 | .querySelector("head title") 26 | .innerText.trim() 27 | 28 | export const getShadowHostId: PlasmoGetShadowHostId = () => "codebox-360doc" 29 | 30 | const PlasmoOverlay: FC = () => { 31 | const [parseContent, setParseContent] = useParseMarkdown() 32 | const [allShowTag, setAllShowTag] = useStorage("config-allShowTag", true) 33 | const [showTag, setShowTag] = useStorage("360doc-showTag", true) 34 | const [cssCode, runCss] = useCssCodeHook("360doc") 35 | const [content, setContent] = useEditMarkdown() 36 | 37 | useMessage(async (req, res) => { 38 | if (req.name == "360doc-isShow") { 39 | res.send({ isShow: true }) 40 | } 41 | if (req.name == "360doc-editMarkdown") { 42 | editMarkdown() 43 | } 44 | if (req.name == "360doc-downloadMarkdown") { 45 | downloadMarkdown() 46 | } 47 | if (req.name == "360doc-downloadHtml") { 48 | downloadHtml() 49 | } 50 | if (req.name == "360doc-downloadPdf") { 51 | downloadPdf() 52 | } 53 | }) 54 | 55 | function getDescription() { 56 | const summary = document.querySelector( 57 | 'meta[name="description"]' 58 | ).content 59 | summary && prompt("文章摘要:", summary) 60 | } 61 | 62 | function downloadPdf() { 63 | const article = document.querySelector("#bgchange") 64 | if (article) { 65 | Print.print(article, { title: articleTitle }) 66 | .then(() => console.log("Printing complete")) 67 | .catch((error) => console.error("Printing failed:", error)) 68 | } 69 | } 70 | 71 | function editMarkdown() { 72 | const dom = document.querySelector("#bgchange") 73 | setContent(dom, articleTitle) 74 | } 75 | 76 | function downloadMarkdown() { 77 | const html = document.querySelector("#bgchange") 78 | const markdown = turndownService.turndown(html) 79 | saveMarkdown(markdown, articleTitle) 80 | } 81 | 82 | function downloadHtml() { 83 | const dom = document.querySelector("#bgchange") 84 | saveHtml(dom, articleTitle) 85 | } 86 | 87 | function parseMarkdown() { 88 | const dom = document.querySelector("#bgchange") 89 | setParseContent(dom) 90 | } 91 | 92 | return showTag && allShowTag ? ( 93 | 100 | ) : ( 101 | <> 102 | ) 103 | } 104 | 105 | export default PlasmoOverlay 106 | -------------------------------------------------------------------------------- /contents/baidu.tsx: -------------------------------------------------------------------------------- 1 | import type { 2 | PlasmoCSConfig, 3 | PlasmoCSUIProps, 4 | PlasmoGetShadowHostId 5 | } from "plasmo" 6 | import { useEffect, useState, type FC } from "react" 7 | 8 | import { useMessage } from "@plasmohq/messaging/hook" 9 | import { useStorage } from "@plasmohq/storage/hook" 10 | 11 | import ToolBox from "~component/ui/toolBox" 12 | import { addCss, saveHtml, saveMarkdown } from "~tools" 13 | import useCssCodeHook from "~utils/cssCodeHook" 14 | import { useEditMarkdown } from "~utils/editMarkdownHook" 15 | import { useParseMarkdown } from "~utils/parseMarkdownHook" 16 | import { Print } from "~utils/print" 17 | import Turndown from "~utils/turndown" 18 | 19 | export const config: PlasmoCSConfig = { 20 | matches: ["https://baijiahao.baidu.com/*", "https://www.baidu.com/*"] 21 | } 22 | 23 | const turndownService = Turndown() 24 | const articleTitle = document 25 | .querySelector("head title") 26 | .innerText.trim() 27 | 28 | export const getShadowHostId: PlasmoGetShadowHostId = () => "codebox-baidu" 29 | const isBaijiahao = location.hostname.includes("baijiahao") 30 | const PlasmoOverlay: FC = () => { 31 | const [parseContent, setParseContent] = useParseMarkdown() 32 | const [allShowTag, setAllShowTag] = useStorage("config-allShowTag", true) 33 | const [showTag, setShowTag] = useStorage("baidu-showTag", true) 34 | const [cssCode, runCss] = useCssCodeHook("baidu") 35 | const [closeAIBox] = useStorage("baidu-closeAIBox") 36 | const [closeLog] = useStorage("config-closeLog", true) 37 | const [content, setContent] = useEditMarkdown() 38 | 39 | useEffect(() => { 40 | closeLog || console.log("baidu", { closeAIBox }) 41 | closeAIBox && closeAIBoxFunc() 42 | }, [closeAIBox]) 43 | 44 | useMessage(async (req, res) => { 45 | if (req.name == "baidu-isShow") { 46 | res.send({ isShow: true }) 47 | } 48 | if (req.name == "baidu-editMarkdown") { 49 | editMarkdown() 50 | } 51 | if (req.name == "baidu-downloadMarkdown") { 52 | downloadMarkdown() 53 | } 54 | if (req.name == "baidu-downloadHtml") { 55 | downloadHtml() 56 | } 57 | }) 58 | 59 | /* 删除百度AI对话框 */ 60 | function closeAIBoxFunc() { 61 | addCss(`.wd-ai-index-pc{ 62 | display:none !important; 63 | }`) 64 | } 65 | 66 | function getDescription() { 67 | const summary = document.querySelector( 68 | 'meta[name="description"]' 69 | ).content 70 | summary && prompt("文章摘要:", summary) 71 | } 72 | 73 | function downloadPdf() { 74 | const article = document.querySelector( 75 | isBaijiahao ? "#ssr-content .EaCvy" : ".wd-ai-index-pc" 76 | ) 77 | if (article) { 78 | Print.print(article, { title: articleTitle }) 79 | .then(() => console.log("Printing complete")) 80 | .catch((error) => console.error("Printing failed:", error)) 81 | } 82 | } 83 | 84 | function editMarkdown() { 85 | const dom = document.querySelector( 86 | isBaijiahao ? "#ssr-content .EaCvy" : ".wd-ai-index-pc" 87 | ) 88 | setContent(dom, articleTitle) 89 | } 90 | 91 | function downloadMarkdown() { 92 | const html = document.querySelector( 93 | isBaijiahao ? "#ssr-content .EaCvy" : ".wd-ai-index-pc" 94 | ) 95 | const markdown = turndownService.turndown(html) 96 | saveMarkdown(markdown, articleTitle) 97 | } 98 | 99 | function downloadHtml() { 100 | const dom = document.querySelector( 101 | isBaijiahao ? "#ssr-content .EaCvy" : ".wd-ai-index-pc" 102 | ) 103 | saveHtml(dom, articleTitle) 104 | } 105 | 106 | function parseMarkdown() { 107 | const dom = document.querySelector( 108 | isBaijiahao ? "#ssr-content .EaCvy" : ".wd-ai-index-pc" 109 | ) 110 | setParseContent(dom) 111 | } 112 | 113 | return showTag && allShowTag && isBaijiahao ? ( 114 | 121 | ) : ( 122 | <> 123 | ) 124 | } 125 | 126 | export default PlasmoOverlay 127 | -------------------------------------------------------------------------------- /contents/chatgpt.tsx: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs" 2 | import type { PlasmoCSConfig } from "plasmo" 3 | import React, { useEffect, useState } from "react" 4 | 5 | import { Storage } from "@plasmohq/storage" 6 | import { useStorage } from "@plasmohq/storage/hook" 7 | 8 | import QRCodeModal from "~component/ui/QRCodeModal" 9 | 10 | export const config: PlasmoCSConfig = { 11 | matches: ["https://chatgpt.com/*", "https://chatgpt.com/c/*"] 12 | } 13 | 14 | export default function Chatgpt() { 15 | const [validTime, setValidTime] = useStorage("ai-validTime", "1737872293") 16 | const [isModalOpen, setIsModalOpen] = useState(false) 17 | const [content, setContent] = useStorage({ 18 | key: "ai-content", 19 | instance: new Storage({ 20 | area: "local" 21 | }) 22 | }) 23 | 24 | useEffect(() => { 25 | let timer = null 26 | let index = 0 27 | if (content) { 28 | if (Number(validTime) < dayjs().unix()) { 29 | timer = setInterval(() => { 30 | const editorElement = document.querySelector( 31 | "._prosemirror-parent_cy42l_1 p" 32 | ) 33 | index++ 34 | if (editorElement && index < 30) { 35 | setIsModalOpen(true) 36 | clearTimeout(timer) 37 | } else if (index >= 30) { 38 | clearTimeout(timer) 39 | } 40 | }, 1000) 41 | } else { 42 | handleSetContent() 43 | } 44 | } 45 | return () => { 46 | clearTimeout(timer) 47 | } 48 | }, [content]) 49 | 50 | const handleCancelModal = () => { 51 | setContent("") 52 | setIsModalOpen(false) 53 | } 54 | 55 | function handleConfirmModal() { 56 | let time = dayjs().add(1, "day").unix() 57 | 58 | setValidTime(String(time)) 59 | handleSetContent() 60 | setTimeout(() => { 61 | setIsModalOpen(false) 62 | }, 500) 63 | } 64 | 65 | function handleSetContent() { 66 | const editorElement = document.querySelector( 67 | "._prosemirror-parent_cy42l_1 p" 68 | ) as HTMLElement 69 | 70 | if (editorElement) { 71 | editorElement.innerText = content 72 | 73 | let index = 0 74 | const timer = setInterval(() => { 75 | const buttonElement = document.querySelector( 76 | "button[data-testid=send-button]" 77 | ) as HTMLElement 78 | index++ 79 | if (buttonElement && index < 30) { 80 | buttonElement.click() 81 | setContent("") 82 | clearTimeout(timer) 83 | } else if (index >= 30) { 84 | clearTimeout(timer) 85 | } 86 | }, 1000) 87 | } 88 | } 89 | 90 | return ( 91 | <> 92 | {isModalOpen && content && ( 93 | 97 | )} 98 | 99 | ) 100 | } 101 | -------------------------------------------------------------------------------- /contents/copycode.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "antd" 2 | import type { 3 | PlasmoCSConfig, 4 | PlasmoCSUIProps, 5 | PlasmoGetInlineAnchor, 6 | PlasmoGetInlineAnchorList, 7 | PlasmoGetOverlayAnchorList, 8 | PlasmoGetShadowHostId, 9 | PlasmoGetStyle 10 | } from "plasmo" 11 | import { useEffect, useState } from "react" 12 | import type { FC } from "react" 13 | 14 | import { useStorage } from "@plasmohq/storage/dist/hook" 15 | 16 | import { i18n } from "~tools" 17 | 18 | export const config: PlasmoCSConfig = { 19 | matches: [ 20 | "https://*.npmjs.com/*", 21 | "https://*.medium.com/*", 22 | "https://day.js.org/*", 23 | "https://stackoverflow.com/*", 24 | "https://dev.to/*", 25 | "https://greasyfork.org/*" 26 | ] 27 | } 28 | 29 | export const getStyle: PlasmoGetStyle = () => { 30 | const style = document.createElement("style") 31 | style.textContent = ` 32 | .codebox-copyCodeHeader { 33 | height: 0; 34 | display: flex; 35 | justify-content: space-between; 36 | width: 100%; 37 | background: transparent; 38 | } 39 | .codebox-copyCodeBtn { 40 | margin-right: 3px; 41 | border: 0; 42 | cursor: pointer; 43 | height: 28px; 44 | }` 45 | return style 46 | } 47 | 48 | const HOST_ID = "codebox-copycode" 49 | export const getShadowHostId: PlasmoGetShadowHostId = () => HOST_ID 50 | 51 | // export const getOverlayAnchorList: PlasmoGetOverlayAnchorList = async () => 52 | // document.querySelectorAll("pre") 53 | 54 | export const getOverlayAnchorList: PlasmoGetOverlayAnchorList = async () => { 55 | const preList = document.querySelectorAll("pre") 56 | 57 | const anchors = [] as any 58 | Array.from(preList).map((pre) => { 59 | const classList = pre.classList 60 | if (pre.textContent && !classList.contains("CodeMirror-line")) 61 | anchors.push(pre) 62 | }) 63 | 64 | return anchors 65 | } 66 | 67 | const PlasmoOverlay: FC = ({ anchor }) => { 68 | const [copyCode] = useStorage("config-copyCode", true) 69 | const [isCopy, setIsCopy] = useState(false) 70 | 71 | const element = anchor.element 72 | const style = window.getComputedStyle(element) 73 | let width = style.getPropertyValue("width") 74 | 75 | if (location.host.includes("greasyfork")) { 76 | const codeContainer = element.closest(".code-container") 77 | width = window.getComputedStyle(codeContainer).getPropertyValue("width") 78 | } 79 | 80 | const onCopy = async () => { 81 | try { 82 | const target = anchor.element as HTMLElement 83 | const preBlock = target.closest("pre") 84 | const codeBlock = target.querySelector("code") 85 | let textContent = "" 86 | 87 | if (codeBlock) { 88 | textContent = codeBlock.innerText 89 | } else { 90 | textContent = preBlock && preBlock.innerText 91 | } 92 | 93 | navigator.clipboard.writeText(textContent) 94 | 95 | setIsCopy(true) 96 | setTimeout(() => { 97 | setIsCopy(false) 98 | }, 1000) 99 | } catch (error) { 100 | console.log(error) 101 | } 102 | } 103 | 104 | return ( 105 | <> 106 | {copyCode ? ( 107 |
108 | 109 | 116 |
117 | ) : ( 118 | <> 119 | )} 120 | 121 | ) 122 | } 123 | 124 | export default PlasmoOverlay 125 | -------------------------------------------------------------------------------- /contents/custom.tsx: -------------------------------------------------------------------------------- 1 | import type { 2 | PlasmoCSConfig, 3 | PlasmoGetShadowHostId, 4 | PlasmoGetStyle 5 | } from "plasmo" 6 | import { useEffect, useRef, useState } from "react" 7 | 8 | import { sendToBackground } from "@plasmohq/messaging" 9 | import { useMessage } from "@plasmohq/messaging/hook" 10 | import { useStorage } from "@plasmohq/storage/hook" 11 | 12 | import CustomDomSelector from "~component/ui/customDomSelector" 13 | import { setIcon } from "~tools" 14 | import { getSummary } from "~utils/coze" 15 | import DrawImages from "~utils/drawImages" 16 | import makerQRPost from "~utils/makerQRPost" 17 | 18 | const HOST_ID = "codebox-csui" 19 | 20 | export const getShadowHostId: PlasmoGetShadowHostId = () => HOST_ID 21 | 22 | const articleTitle = document 23 | .querySelector("head title") 24 | ?.innerText.trim() 25 | 26 | export default function CustomOverlay() { 27 | const [summary, setSummary] = useStorage("app-summary", "") 28 | 29 | useEffect(() => { 30 | setIcon(true) 31 | }, []) 32 | 33 | useMessage(async (req: any, res: any) => { 34 | if (req.name == "app-downloadImages") { 35 | await downloadImages(req.body?.onProgress) 36 | } 37 | if (req.name == "app-get-summary") { 38 | setSummary("") 39 | const res = await getSummary(location.href) 40 | if (res.code == 0) { 41 | const result = JSON.parse(res.data) 42 | setSummary(result) 43 | } 44 | } 45 | if (req.name == "app-makerQRPost") { 46 | makerQRPost() 47 | } 48 | if (req.name == "app-getArticle") { 49 | const href = encodeURIComponent(location.href) 50 | window.open(`https://paywallbuster.com/articles/?article=${href}`) 51 | } 52 | if (req.name == "app-full-page-screenshot") { 53 | if (confirm("确认截图?")) { 54 | const { scrollHeight, clientHeight } = document.documentElement 55 | const devicePixelRatio = window.devicePixelRatio || 1 56 | 57 | let capturedHeight = 0 58 | let capturedImages = [] 59 | 60 | const captureAndScroll = async () => { 61 | const scrollAmount = clientHeight * devicePixelRatio 62 | const res = await sendToBackground({ name: "screenshot" }) 63 | const dataUrl = res.dataUrl 64 | 65 | capturedHeight += scrollAmount 66 | if (capturedHeight < scrollHeight * devicePixelRatio) { 67 | capturedImages.push(dataUrl) 68 | window.scrollTo(0, capturedHeight) 69 | setTimeout(captureAndScroll, 2000) // Adjust the delay as needed 70 | } else { 71 | DrawImages(capturedImages, articleTitle) 72 | } 73 | } 74 | 75 | captureAndScroll() 76 | } 77 | } 78 | }) 79 | 80 | async function downloadImages( 81 | onProgress?: (current: number, total: number) => void 82 | ) { 83 | const imageUrls = Array.from(document.images).map((img) => img.src) 84 | try { 85 | const res = await sendToBackground({ 86 | name: "download", 87 | body: { 88 | action: "downloadAllImages", 89 | imageUrls: imageUrls, 90 | title: articleTitle, 91 | onProgress: onProgress 92 | } 93 | }) 94 | if (res.code == 0) { 95 | alert("下载失败") 96 | } 97 | } catch (error) { 98 | console.error(`Failed to download images:`, error) 99 | } 100 | } 101 | 102 | return 103 | } 104 | -------------------------------------------------------------------------------- /contents/deepseek.tsx: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs" 2 | import type { PlasmoCSConfig } from "plasmo" 3 | import React, { useEffect, useState } from "react" 4 | 5 | import { Storage } from "@plasmohq/storage" 6 | import { useStorage } from "@plasmohq/storage/hook" 7 | 8 | import QRCodeModal from "~component/ui/QRCodeModal" 9 | 10 | export const config: PlasmoCSConfig = { 11 | matches: ["https://chat.deepseek.com/*"] 12 | } 13 | 14 | export default function Deepseek() { 15 | const [validTime, setValidTime] = useStorage("ai-validTime", "1737872293") 16 | const [isModalOpen, setIsModalOpen] = useState(false) 17 | const [content, setContent] = useStorage({ 18 | key: "ai-content", 19 | instance: new Storage({ 20 | area: "local" 21 | }) 22 | }) 23 | 24 | useEffect(() => { 25 | let timer = null 26 | let index = 0 27 | if (content) { 28 | if (Number(validTime) < dayjs().unix()) { 29 | timer = setInterval(() => { 30 | const editorElement = document.querySelector( 31 | "#chat-input" 32 | ) as HTMLElement 33 | index++ 34 | if (editorElement && index < 30) { 35 | setIsModalOpen(true) 36 | clearTimeout(timer) 37 | } else if (index >= 30) { 38 | clearTimeout(timer) 39 | } 40 | }, 1000) 41 | } else { 42 | handleSetContent() 43 | } 44 | } 45 | return () => { 46 | clearTimeout(timer) 47 | } 48 | }, [content, validTime]) 49 | 50 | const handleCancelModal = () => { 51 | setContent("") 52 | setIsModalOpen(false) 53 | } 54 | 55 | function handleConfirmModal() { 56 | let time = dayjs().add(1, "day").unix() 57 | 58 | setValidTime(String(time)) 59 | handleSetContent() 60 | setTimeout(() => { 61 | setIsModalOpen(false) 62 | }, 500) 63 | } 64 | 65 | function handleSetContent() { 66 | const editorElement = document.querySelector( 67 | "#chat-input" 68 | ) as HTMLInputElement 69 | 70 | const buttonElement = document.querySelector(".f6d670") as HTMLElement 71 | 72 | if (editorElement) { 73 | editorElement.value = content 74 | editorElement.dispatchEvent(new Event("input", { bubbles: true })) 75 | editorElement.dispatchEvent(new Event("change", { bubbles: true })) 76 | editorElement.focus() 77 | editorElement.setSelectionRange( 78 | editorElement.value.length, 79 | editorElement.value.length 80 | ) 81 | document.execCommand("InsertText", false, content) 82 | 83 | setContent("") 84 | 85 | setTimeout(() => { 86 | buttonElement.click() 87 | }, 1000) 88 | } 89 | } 90 | 91 | return ( 92 | <> 93 | {isModalOpen && content && ( 94 | 98 | )} 99 | 100 | ) 101 | } 102 | -------------------------------------------------------------------------------- /contents/juejin.tsx: -------------------------------------------------------------------------------- 1 | import type { 2 | PlasmoCSConfig, 3 | PlasmoCSUIProps, 4 | PlasmoGetOverlayAnchor, 5 | PlasmoGetShadowHostId, 6 | PlasmoGetStyle 7 | } from "plasmo" 8 | import React, { useEffect, useRef, useState, type FC } from "react" 9 | 10 | import { useMessage } from "@plasmohq/messaging/hook" 11 | import { useStorage } from "@plasmohq/storage/dist/hook" 12 | 13 | import ToolBox from "~component/ui/toolBox" 14 | import { i18n, saveHtml, saveMarkdown } from "~tools" 15 | import useCssCodeHook from "~utils/cssCodeHook" 16 | import { useEditMarkdown } from "~utils/editMarkdownHook" 17 | import { useParseMarkdown } from "~utils/parseMarkdownHook" 18 | import { Print } from "~utils/print" 19 | import Turndown from "~utils/turndown" 20 | 21 | export const config: PlasmoCSConfig = { 22 | matches: ["https://*.juejin.cn/post/*"] 23 | } 24 | 25 | const turndownService = Turndown({ 26 | addRules: { 27 | fencedCodeBlock: { 28 | filter: function (node, options) { 29 | return ( 30 | options.codeBlockStyle === "fenced" && 31 | node.nodeName === "PRE" && 32 | node.querySelector("code") 33 | ) 34 | }, 35 | 36 | replacement: function (content, node, options) { 37 | const className = node.querySelector("code").getAttribute("class") || "" 38 | const language = (className.match(/lang-(\S+)/) || 39 | className.match(/language-(\S+)/) || [null, ""])[1] 40 | 41 | return ( 42 | "\n\n" + 43 | options.fence + 44 | language + 45 | "\n" + 46 | node.querySelector("code").textContent + 47 | "\n" + 48 | options.fence + 49 | "\n\n" 50 | ) 51 | } 52 | } 53 | } 54 | }) 55 | const articleTitle = document 56 | .querySelector("head title")! 57 | .innerText.trim() 58 | 59 | const HOST_ID = "codebox-juejin" 60 | export const getShadowHostId: PlasmoGetShadowHostId = () => HOST_ID 61 | 62 | export const getOverlayAnchor: PlasmoGetOverlayAnchor = async () => 63 | document.querySelector("#juejin") 64 | 65 | const PlasmoOverlay: FC = ({ anchor }) => { 66 | const [parseContent, setParseContent] = useParseMarkdown() 67 | const [allShowTag, setAllShowTag] = useStorage("config-allShowTag", true) 68 | const [showTag, setShowTag] = useStorage("juejin-showTag", true) 69 | const [cssCode, runCss] = useCssCodeHook("juejin") 70 | const [content, setContent] = useEditMarkdown() 71 | 72 | useMessage(async (req, res) => { 73 | if (req.name == "juejin-isShow") { 74 | res.send({ isShow: true }) 75 | } 76 | if (req.name == "juejin-editMarkdown") { 77 | editMarkdown() 78 | } 79 | if (req.name == "juejin-downloadMarkdown") { 80 | downloadMarkdown() 81 | } 82 | if (req.name == "juejin-downloadHtml") { 83 | downloadHtml() 84 | } 85 | if (req.name == "juejin-downloadPdf") { 86 | downloadPdf() 87 | } 88 | }) 89 | 90 | function getDescription() { 91 | const summary = document.querySelector( 92 | 'meta[name="description"]' 93 | ).content 94 | summary && prompt("文章摘要:", summary) 95 | } 96 | 97 | function downloadPdf() { 98 | const article = document.querySelector("article.article") 99 | if (article) { 100 | Print.print(article, { title: articleTitle }) 101 | .then(() => console.log("Printing complete")) 102 | .catch((error) => console.error("Printing failed:", error)) 103 | } 104 | } 105 | 106 | function editMarkdown() { 107 | const dom = document.querySelector("article.article") 108 | setContent(dom, articleTitle) 109 | } 110 | 111 | function downloadMarkdown() { 112 | const html = document.querySelector("article.article") 113 | const markdown = turndownService.turndown(html) 114 | saveMarkdown(markdown, articleTitle) 115 | } 116 | 117 | function downloadHtml() { 118 | const dom = document.querySelector("article.article") 119 | saveHtml(dom, articleTitle) 120 | } 121 | 122 | function parseMarkdown() { 123 | const dom = document.querySelector("article.article") 124 | setParseContent(dom) 125 | } 126 | 127 | return showTag && allShowTag ? ( 128 | 135 | ) : ( 136 | <> 137 | ) 138 | } 139 | 140 | export default PlasmoOverlay 141 | -------------------------------------------------------------------------------- /contents/kimi.tsx: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs" 2 | import type { PlasmoCSConfig } from "plasmo" 3 | import React, { useEffect, useState } from "react" 4 | 5 | import { Storage } from "@plasmohq/storage" 6 | import { useStorage } from "@plasmohq/storage/hook" 7 | 8 | import QRCodeModal from "~component/ui/QRCodeModal" 9 | 10 | export const config: PlasmoCSConfig = { 11 | matches: ["https://kimi.moonshot.cn/*", "https://kimi.moonshot.cn/chat/*"] 12 | } 13 | 14 | export default function Kimi() { 15 | const [validTime, setValidTime] = useStorage("ai-validTime", "1737872293") 16 | const [isModalOpen, setIsModalOpen] = useState(false) 17 | const [content, setContent] = useStorage({ 18 | key: "ai-content", 19 | instance: new Storage({ 20 | area: "local" 21 | }) 22 | }) 23 | 24 | useEffect(() => { 25 | let timer = null 26 | let index = 0 27 | if (content) { 28 | if (Number(validTime) < dayjs().unix()) { 29 | timer = setInterval(() => { 30 | const editorElement = document.querySelector( 31 | "div[data-lexical-editor=true]" 32 | ) 33 | index++ 34 | if (editorElement && index < 30) { 35 | setIsModalOpen(true) 36 | clearTimeout(timer) 37 | } else if (index >= 30) { 38 | clearTimeout(timer) 39 | } 40 | }, 1000) 41 | } else { 42 | handleSetContent() 43 | } 44 | } 45 | return () => { 46 | clearTimeout(timer) 47 | } 48 | }, [content, validTime]) 49 | 50 | const handleCancelModal = () => { 51 | setContent("") 52 | setIsModalOpen(false) 53 | } 54 | 55 | function handleConfirmModal() { 56 | let time = dayjs().add(1, "day").unix() 57 | 58 | setValidTime(String(time)) 59 | handleSetContent() 60 | setTimeout(() => { 61 | setIsModalOpen(false) 62 | }, 500) 63 | } 64 | 65 | function handleSetContent() { 66 | const editorElement = document.querySelector( 67 | "div[data-lexical-editor=true]" 68 | ) as HTMLElement 69 | 70 | const buttonElement = document.querySelector( 71 | ".chat-editor-action .send-button" 72 | ) as HTMLElement 73 | 74 | if (editorElement) { 75 | editorElement.focus() 76 | 77 | const event = new InputEvent("input", { 78 | bubbles: true, 79 | cancelable: true, 80 | data: content, 81 | inputType: "insertText" 82 | }) 83 | 84 | editorElement.dispatchEvent(event) 85 | setContent("") 86 | 87 | setTimeout(() => { 88 | buttonElement.click() 89 | }, 1000) 90 | } 91 | } 92 | 93 | return ( 94 | <> 95 | {isModalOpen && content && ( 96 | 100 | )} 101 | 102 | ) 103 | } 104 | -------------------------------------------------------------------------------- /contents/luogu.tsx: -------------------------------------------------------------------------------- 1 | import type { 2 | PlasmoCSConfig, 3 | PlasmoCSUIProps, 4 | PlasmoGetShadowHostId 5 | } from "plasmo" 6 | import React, { type FC } from "react" 7 | 8 | import { useMessage } from "@plasmohq/messaging/hook" 9 | import { useStorage } from "@plasmohq/storage/dist/hook" 10 | 11 | import ToolBox from "~component/ui/toolBox" 12 | import { saveHtml, saveMarkdown } from "~tools" 13 | import useCssCodeHook from "~utils/cssCodeHook" 14 | import { useEditMarkdown } from "~utils/editMarkdownHook" 15 | import { useParseMarkdown } from "~utils/parseMarkdownHook" 16 | import { Print } from "~utils/print" 17 | import Turndown from "~utils/turndown" 18 | 19 | export const config: PlasmoCSConfig = { 20 | matches: ["https://*.luogu.com.cn/article/*"] 21 | } 22 | 23 | const turndownService = Turndown() 24 | const articleTitle = document 25 | .querySelector("head title") 26 | .innerText.trim() 27 | 28 | export const getShadowHostId: PlasmoGetShadowHostId = () => "codebox-luogu" 29 | 30 | const PlasmoOverlay: FC = () => { 31 | const [parseContent, setParseContent] = useParseMarkdown() 32 | const [allShowTag, setAllShowTag] = useStorage("config-allShowTag", true) 33 | const [showTag, setShowTag] = useStorage("luogu-showTag", true) 34 | const [cssCode, runCss] = useCssCodeHook("luogu") 35 | const [content, setContent] = useEditMarkdown() 36 | 37 | useMessage(async (req, res) => { 38 | if (req.name == "luogu-isShow") { 39 | res.send({ isShow: true }) 40 | } 41 | if (req.name == "luogu-editMarkdown") { 42 | editMarkdown() 43 | } 44 | if (req.name == "luogu-downloadMarkdown") { 45 | downloadMarkdown() 46 | } 47 | if (req.name == "luogu-downloadHtml") { 48 | downloadHtml() 49 | } 50 | if (req.name == "luogu-downloadPdf") { 51 | downloadPdf() 52 | } 53 | }) 54 | 55 | function getDescription() { 56 | const summary = document.querySelector( 57 | 'meta[name="description"]' 58 | ).content 59 | summary && prompt("文章摘要:", summary) 60 | } 61 | 62 | function downloadPdf() { 63 | const article = document.querySelector(".article-content") 64 | if (article) { 65 | Print.print(article, { title: articleTitle }) 66 | .then(() => console.log("Printing complete")) 67 | .catch((error) => console.error("Printing failed:", error)) 68 | } 69 | } 70 | 71 | function editMarkdown() { 72 | const dom = document.querySelector(".article-content") 73 | setContent(dom, articleTitle) 74 | } 75 | 76 | function downloadMarkdown() { 77 | const html = document.querySelector(".article-content") 78 | const markdown = turndownService.turndown(html) 79 | saveMarkdown(markdown, articleTitle) 80 | } 81 | 82 | function downloadHtml() { 83 | const dom = document.querySelector(".article-content") 84 | saveHtml(dom, articleTitle) 85 | } 86 | 87 | function parseMarkdown() { 88 | const dom = document.querySelector(".article-content") 89 | setParseContent(dom) 90 | } 91 | 92 | return showTag && allShowTag ? ( 93 | 103 | ) : ( 104 | <> 105 | ) 106 | } 107 | 108 | export default PlasmoOverlay 109 | -------------------------------------------------------------------------------- /contents/md.tsx: -------------------------------------------------------------------------------- 1 | import type { PlasmoCSConfig } from "plasmo" 2 | import { useEffect } from "react" 3 | 4 | import { Storage } from "@plasmohq/storage" 5 | import { useStorage } from "@plasmohq/storage/dist/hook" 6 | 7 | export const config: PlasmoCSConfig = { 8 | matches: ["https://md.randbox.top/*", "https://md.code-box.fun/*"] 9 | } 10 | 11 | export default function Markdown() { 12 | const [post, setPost] = useStorage({ 13 | key: "md-post", 14 | instance: new Storage({ 15 | area: "local" 16 | }) 17 | }) 18 | 19 | useEffect(() => { 20 | if (post) { 21 | const posts = JSON.parse(window.localStorage.getItem("MD__posts")) || [] 22 | const _post = JSON.parse(post) 23 | 24 | if (posts[0] && _post.content != posts[0].content) { 25 | posts.unshift({ 26 | content: _post.content, 27 | title: _post.title || "文章1" 28 | }) 29 | window.localStorage.setItem("MD__posts", JSON.stringify(posts)) 30 | setPost("") 31 | location.reload() 32 | } 33 | } 34 | }, [post]) 35 | 36 | return
37 | } 38 | -------------------------------------------------------------------------------- /contents/mp.tsx: -------------------------------------------------------------------------------- 1 | import type { 2 | PlasmoCSConfig, 3 | PlasmoCSUIProps, 4 | PlasmoGetOverlayAnchorList, 5 | PlasmoGetShadowHostId 6 | } from "plasmo" 7 | import { type FC } from "react" 8 | 9 | import { i18n } from "~tools" 10 | 11 | export const config: PlasmoCSConfig = { 12 | matches: ["https://mp.weixin.qq.com/cgi-bin/*"] 13 | } 14 | 15 | const HOST_ID = "codebox-weixin" 16 | export const getShadowHostId: PlasmoGetShadowHostId = () => HOST_ID 17 | 18 | export const getOverlayAnchorList: PlasmoGetOverlayAnchorList = async () => 19 | document.querySelectorAll( 20 | ".weui-desktop-img-picker__item .weui-desktop-img-picker__img-title" 21 | ) 22 | 23 | const PlasmoOverlay: FC = ({ anchor }) => { 24 | function handleCopy() { 25 | try { 26 | const item = anchor.element.closest(".weui-desktop-img-picker__item") 27 | const img = item.querySelector(".weui-desktop-img-picker__img-thumb") 28 | const computedStyle = window.getComputedStyle(img) 29 | const backgroundImage = computedStyle.getPropertyValue("background-image") 30 | const imageUrlMatch = backgroundImage.match(/url\(["']?(.*?)["']?\)/) 31 | 32 | if (imageUrlMatch && imageUrlMatch[1]) { 33 | const imageUrl = imageUrlMatch[1] 34 | 35 | navigator.clipboard.writeText(imageUrl) 36 | alert("URL 已复制到剪贴板") 37 | } 38 | } catch (error) { 39 | console.error("复制失败:", error) 40 | alert("复制失败") 41 | } 42 | } 43 | 44 | return 45 | } 46 | 47 | export default PlasmoOverlay 48 | -------------------------------------------------------------------------------- /contents/oschina.tsx: -------------------------------------------------------------------------------- 1 | import type { 2 | PlasmoCSConfig, 3 | PlasmoCSUIProps, 4 | PlasmoGetOverlayAnchor, 5 | PlasmoGetShadowHostId, 6 | PlasmoGetStyle 7 | } from "plasmo" 8 | import React, { type FC } from "react" 9 | 10 | import { useMessage } from "@plasmohq/messaging/hook" 11 | import { useStorage } from "@plasmohq/storage/dist/hook" 12 | 13 | import TagBtnStyle from "~component/tagBtn/style" 14 | import ToolBox from "~component/ui/toolBox" 15 | import { saveHtml, saveMarkdown } from "~tools" 16 | import useCssCodeHook from "~utils/cssCodeHook" 17 | import { useEditMarkdown } from "~utils/editMarkdownHook" 18 | import { useParseMarkdown } from "~utils/parseMarkdownHook" 19 | import { Print } from "~utils/print" 20 | import Turndown from "~utils/turndown" 21 | 22 | export const config: PlasmoCSConfig = { 23 | matches: ["https://*.oschina.net/*"] 24 | } 25 | 26 | const turndownService = Turndown() 27 | const articleTitle = document 28 | .querySelector("head title") 29 | .innerText.trim() 30 | 31 | const HOST_ID = "codebox-oschina" 32 | export const getShadowHostId: PlasmoGetShadowHostId = () => HOST_ID 33 | 34 | export const getOverlayAnchor: PlasmoGetOverlayAnchor = async () => 35 | document.querySelector(".blog-detail-container") || 36 | document.querySelector(".news-detail-container") 37 | 38 | export const getStyle: PlasmoGetStyle = () => TagBtnStyle() 39 | 40 | const PlasmoOverlay: FC = ({ anchor }) => { 41 | const [parseContent, setParseContent] = useParseMarkdown() 42 | const [allShowTag, setAllShowTag] = useStorage("config-allShowTag", true) 43 | const [showTag, setShowTag] = useStorage("oschina-showTag", true) 44 | const [cssCode, runCss] = useCssCodeHook("oschina") 45 | const [content, setContent] = useEditMarkdown() 46 | 47 | useMessage(async (req, res) => { 48 | if (req.name == "oschina-isShow") { 49 | res.send({ isShow: true }) 50 | } 51 | if (req.name == "oschina-editMarkdown") { 52 | editMarkdown() 53 | } 54 | if (req.name == "oschina-downloadMarkdown") { 55 | downloadMarkdown() 56 | } 57 | if (req.name == "oschina-downloadHtml") { 58 | downloadHtml() 59 | } 60 | if (req.name == "oschina-downloadPdf") { 61 | downloadPdf() 62 | } 63 | }) 64 | 65 | function getDescription() { 66 | const summary = document.querySelector( 67 | 'meta[name="description"]' 68 | ).content 69 | summary && prompt("文章摘要:", summary) 70 | } 71 | 72 | function downloadPdf() { 73 | const article = document.querySelector(".article-box") 74 | if (article) { 75 | Print.print(article, { title: articleTitle }) 76 | .then(() => console.log("Printing complete")) 77 | .catch((error) => console.error("Printing failed:", error)) 78 | } 79 | } 80 | 81 | function editMarkdown() { 82 | const dom = document.querySelector(".article-box") 83 | setContent(dom, articleTitle) 84 | } 85 | 86 | function downloadMarkdown() { 87 | const html = document.querySelector(".article-box") 88 | const markdown = turndownService.turndown(html) 89 | saveMarkdown(markdown, articleTitle) 90 | } 91 | 92 | function downloadHtml() { 93 | const dom = document.querySelector(".article-box") 94 | saveHtml(dom, articleTitle) 95 | } 96 | 97 | function parseMarkdown() { 98 | const dom = document.querySelector(".article-box") 99 | setParseContent(dom) 100 | } 101 | 102 | return showTag && allShowTag ? ( 103 | 110 | ) : ( 111 | <> 112 | ) 113 | } 114 | 115 | export default PlasmoOverlay 116 | -------------------------------------------------------------------------------- /contents/paywallbuster.tsx: -------------------------------------------------------------------------------- 1 | import type { 2 | PlasmoCSConfig, 3 | PlasmoCSUIProps, 4 | PlasmoGetOverlayAnchorList, 5 | PlasmoGetShadowHostId 6 | } from "plasmo" 7 | import React, { type FC } from "react" 8 | 9 | import { useMessage } from "@plasmohq/messaging/dist/hook" 10 | import { useStorage } from "@plasmohq/storage/dist/hook" 11 | 12 | import ToolBox from "~component/ui/toolBox" 13 | import { i18n, saveHtml, saveMarkdown } from "~tools" 14 | import useCssCodeHook from "~utils/cssCodeHook" 15 | import { useEditMarkdown } from "~utils/editMarkdownHook" 16 | import { useParseMarkdown } from "~utils/parseMarkdownHook" 17 | import { Print } from "~utils/print" 18 | import Turndown from "~utils/turndown" 19 | 20 | export const config: PlasmoCSConfig = { 21 | matches: [ 22 | "https://paywallbuster.com/articles/*", 23 | "https://archive.is/*", 24 | "https://archive.ph/*" 25 | ] 26 | } 27 | 28 | const turndownService = Turndown() 29 | const articleTitle = document 30 | .querySelector("head title") 31 | .innerText.trim() 32 | 33 | export const getShadowHostId: PlasmoGetShadowHostId = () => 34 | "codebox-paywallbuster" 35 | const boxStyles = { 36 | box: { 37 | position: "fixed" as const, 38 | border: "1px solid #D9DADC", 39 | left: "25px", 40 | top: "30px", 41 | textAlign: "center" as const, 42 | width: "140px", 43 | padding: "5px", 44 | cursor: "pointer" 45 | } 46 | } 47 | const isPaywallbuster = location.hostname.includes("paywallbuster") 48 | 49 | const PlasmoOverlay: FC = () => { 50 | const [parseContent, setParseContent] = useParseMarkdown() 51 | const [allShowTag, setAllShowTag] = useStorage("config-allShowTag", true) 52 | const [showTag, setShowTag] = useStorage( 53 | "paywallbuster-showTag", 54 | true 55 | ) 56 | const [cssCode, runCss] = useCssCodeHook("paywallbuster") 57 | const [content, setContent] = useEditMarkdown() 58 | 59 | useMessage(async (req, res) => { 60 | if (req.name == "paywallbuster-isShow") { 61 | res.send({ isShow: true }) 62 | } 63 | if (req.name == "paywallbuster-editMarkdown") { 64 | editMarkdown() 65 | } 66 | if (req.name == "paywallbuster-downloadMarkdown") { 67 | downloadMarkdown() 68 | } 69 | if (req.name == "paywallbuster-downloadHtml") { 70 | downloadHtml() 71 | } 72 | if (req.name == "paywallbuster-downloadPdf") { 73 | downloadPdf() 74 | } 75 | }) 76 | 77 | function getDescription() { 78 | const summary = document.querySelector( 79 | 'meta[name="description"]' 80 | ).content 81 | summary && prompt("文章摘要:", summary) 82 | } 83 | 84 | function downloadPdf() { 85 | const article = document.querySelector("article") 86 | if (article) { 87 | Print.print(article, { title: articleTitle }) 88 | .then(() => console.log("Printing complete")) 89 | .catch((error) => console.error("Printing failed:", error)) 90 | } 91 | } 92 | 93 | function editMarkdown() { 94 | const dom = document.querySelector("article") 95 | setContent(dom, articleTitle) 96 | } 97 | 98 | function downloadMarkdown() { 99 | const html = document.querySelector("article") 100 | const markdown = turndownService.turndown(html) 101 | saveMarkdown(markdown, articleTitle) 102 | } 103 | 104 | function downloadHtml() { 105 | const dom = document.querySelector("article") 106 | saveHtml(dom, articleTitle) 107 | } 108 | 109 | function parseMarkdown() { 110 | const dom = document.querySelector("article") 111 | setParseContent(dom) 112 | } 113 | 114 | function handleOpenSource() { 115 | const iframe = document.querySelector("#content-frame") 116 | iframe && window.open(iframe.src, "_blank") 117 | } 118 | 119 | return showTag ? ( 120 | isPaywallbuster ? ( 121 |
122 | 打开源链接 123 |
124 | ) : ( 125 | 132 | ) 133 | ) : ( 134 | <> 135 | ) 136 | } 137 | 138 | export default PlasmoOverlay 139 | -------------------------------------------------------------------------------- /contents/php.tsx: -------------------------------------------------------------------------------- 1 | import type { 2 | PlasmoCSConfig, 3 | PlasmoCSUIProps, 4 | PlasmoGetOverlayAnchor, 5 | PlasmoGetShadowHostId, 6 | PlasmoGetStyle 7 | } from "plasmo" 8 | import React, { useEffect, useState, type FC } from "react" 9 | 10 | import { useMessage } from "@plasmohq/messaging/hook" 11 | import { useStorage } from "@plasmohq/storage/hook" 12 | 13 | import TagBtnStyle from "~component/tagBtn/style" 14 | import Tags from "~component/ui/tags" 15 | import ToolBox from "~component/ui/toolBox" 16 | import { addCss, i18n, removeCss, saveHtml, saveMarkdown } from "~tools" 17 | import useCssCodeHook from "~utils/cssCodeHook" 18 | import { useEditMarkdown } from "~utils/editMarkdownHook" 19 | import { useParseMarkdown } from "~utils/parseMarkdownHook" 20 | import { Print } from "~utils/print" 21 | import Turndown from "~utils/turndown" 22 | 23 | export const config: PlasmoCSConfig = { 24 | matches: ["https://*.php.cn/*"] 25 | } 26 | 27 | const turndownService = Turndown() 28 | const articleTitle = document 29 | .querySelector("head title") 30 | .innerText.trim() 31 | 32 | const HOST_ID = "codebox-php" 33 | export const getShadowHostId: PlasmoGetShadowHostId = () => HOST_ID 34 | 35 | export const getOverlayAnchor: PlasmoGetOverlayAnchor = async () => 36 | document.querySelector(".wzContent .wzctTitle") 37 | 38 | export const getStyle: PlasmoGetStyle = () => TagBtnStyle() 39 | 40 | const PlasmoOverlay: FC = ({ anchor }) => { 41 | const [parseContent, setParseContent] = useParseMarkdown() 42 | const [allShowTag, setAllShowTag] = useStorage("config-allShowTag", true) 43 | const [showTag, setShowTag] = useStorage("php-showTag", true) 44 | const [cssCode, runCss] = useCssCodeHook("php") 45 | const [copyCode] = useStorage("php-copyCode", true) 46 | const [closeLoginModal] = useStorage("php-closeLoginModal") 47 | const [history, setHistory] = useStorage("codebox-history") 48 | const [closeLog] = useStorage("config-closeLog", true) 49 | const [content, setContent] = useEditMarkdown() 50 | 51 | useEffect(() => { 52 | closeLog || console.log("PHP status", { closeLoginModal }) 53 | copyCodeFunc(copyCode) 54 | closeLoginModal && closeLoginModalFunc() 55 | }, [closeLoginModal]) 56 | 57 | useMessage(async (req, res) => { 58 | if (req.name == "php-isShow") { 59 | res.send({ isShow: true }) 60 | } 61 | if (req.name == "php-editMarkdown") { 62 | editMarkdown() 63 | } 64 | if (req.name == "php-downloadMarkdown") { 65 | downloadMarkdown() 66 | } 67 | if (req.name == "php-downloadHtml") { 68 | downloadHtml() 69 | } 70 | if (req.name == "oschina-downloadPdf") { 71 | downloadPdf() 72 | } 73 | }) 74 | 75 | function downloadPdf() { 76 | const article = document.querySelector( 77 | ".phpscMain .php-article" 78 | ) 79 | if (article) { 80 | Print.print(article, { title: articleTitle }) 81 | .then(() => console.log("Printing complete")) 82 | .catch((error) => console.error("Printing failed:", error)) 83 | } 84 | } 85 | 86 | /* 未登录复制代码 */ 87 | function copyCodeCssFunc(copyCode) { 88 | copyCode 89 | ? addCss( 90 | ` 91 | .php-article .code, 92 | .php-article{ 93 | -webkit-touch-callout: auto !important; 94 | -webkit-user-select: auto !important; 95 | -khtml-user-select: auto !important; 96 | -moz-user-select: auto !important; 97 | -ms-user-select: auto !important; 98 | user-select: auto !important; 99 | }`, 100 | `php-copyCode-css` 101 | ) 102 | : removeCss(`php-copyCode-css`) 103 | } 104 | 105 | function copyCodeFunc(copyCode) { 106 | copyCode && 107 | document.addEventListener("copy", function (event) { 108 | const selectedText = window.getSelection().toString() 109 | event.clipboardData.setData("text/plain", selectedText) 110 | event.preventDefault() 111 | }) 112 | copyCodeCssFunc(copyCode) 113 | } 114 | 115 | // 关闭登录弹框 116 | function closeLoginModalFunc() { 117 | const css = ` 118 | .layui-layer-shade, 119 | .layui-layer-iframe { 120 | display:none !important; 121 | }` 122 | addCss(css) 123 | } 124 | 125 | function getDescription() { 126 | const summary = document.querySelector( 127 | 'meta[name="description"]' 128 | ).content 129 | summary && prompt("文章摘要:", summary) 130 | } 131 | 132 | function editMarkdown() { 133 | const dom = document.querySelector(".phpscMain .php-article") 134 | setContent(dom, articleTitle) 135 | } 136 | 137 | function downloadMarkdown() { 138 | const html = document.querySelector(".phpscMain .php-article") 139 | const markdown = turndownService.turndown(html) 140 | saveMarkdown(markdown, articleTitle) 141 | } 142 | 143 | function downloadHtml() { 144 | const dom = document.querySelector(".phpscMain .php-article") 145 | saveHtml(dom, articleTitle) 146 | } 147 | 148 | function parseMarkdown() { 149 | const dom = document.querySelector(".phpscMain .php-article") 150 | setParseContent(dom) 151 | } 152 | 153 | return showTag && allShowTag ? ( 154 | 161 | ) : ( 162 | <> 163 | ) 164 | } 165 | 166 | export default PlasmoOverlay 167 | -------------------------------------------------------------------------------- /contents/pianshen.tsx: -------------------------------------------------------------------------------- 1 | import type { 2 | PlasmoCSConfig, 3 | PlasmoCSUIProps, 4 | PlasmoGetShadowHostId 5 | } from "plasmo" 6 | import React, { type FC } from "react" 7 | 8 | import { useMessage } from "@plasmohq/messaging/hook" 9 | import { useStorage } from "@plasmohq/storage/dist/hook" 10 | 11 | import ToolBox from "~component/ui/toolBox" 12 | import { saveHtml, saveMarkdown } from "~tools" 13 | import useCssCodeHook from "~utils/cssCodeHook" 14 | import { useEditMarkdown } from "~utils/editMarkdownHook" 15 | import { useParseMarkdown } from "~utils/parseMarkdownHook" 16 | import { Print } from "~utils/print" 17 | import Turndown from "~utils/turndown" 18 | 19 | export const config: PlasmoCSConfig = { 20 | matches: [ 21 | "https://*.pianshen.com/question/*", 22 | "http://*.pianshen.com/article/*" 23 | ] 24 | } 25 | 26 | const turndownService = Turndown() 27 | const articleTitle = document 28 | .querySelector("head title") 29 | .innerText.trim() 30 | 31 | export const getShadowHostId: PlasmoGetShadowHostId = () => "codebox-pianshen" 32 | 33 | const PlasmoOverlay: FC = () => { 34 | const [parseContent, setParseContent] = useParseMarkdown() 35 | const [allShowTag, setAllShowTag] = useStorage("config-allShowTag", true) 36 | const [showTag, setShowTag] = useStorage("pianshen-showTag", true) 37 | const [cssCode, runCss] = useCssCodeHook("pianshen") 38 | const [content, setContent] = useEditMarkdown() 39 | 40 | useMessage(async (req, res) => { 41 | if (req.name == "pianshen-isShow") { 42 | res.send({ isShow: true }) 43 | } 44 | if (req.name == "pianshen-editMarkdown") { 45 | editMarkdown() 46 | } 47 | if (req.name == "pianshen-downloadMarkdown") { 48 | downloadMarkdown() 49 | } 50 | if (req.name == "pianshen-downloadHtml") { 51 | downloadHtml() 52 | } 53 | if (req.name == "pianshen-downloadPdf") { 54 | downloadPdf() 55 | } 56 | }) 57 | 58 | function getDescription() { 59 | const summary = document.querySelector( 60 | 'meta[name="description"]' 61 | ).content 62 | summary && prompt("文章摘要:", summary) 63 | } 64 | 65 | function downloadPdf() { 66 | const article = document.querySelector("#article_content") 67 | if (article) { 68 | Print.print(article, { title: articleTitle }) 69 | .then(() => console.log("Printing complete")) 70 | .catch((error) => console.error("Printing failed:", error)) 71 | } 72 | } 73 | 74 | function editMarkdown() { 75 | const dom = document.querySelector("#article_content") 76 | setContent(dom, articleTitle) 77 | } 78 | 79 | function downloadMarkdown() { 80 | const html = document.querySelector("#article_content") 81 | const markdown = turndownService.turndown(html) 82 | saveMarkdown(markdown, articleTitle) 83 | } 84 | 85 | function downloadHtml() { 86 | const dom = document.querySelector("#article_content") 87 | saveHtml(dom, articleTitle) 88 | } 89 | 90 | function parseMarkdown() { 91 | const dom = document.querySelector("#article_content") 92 | setParseContent(dom) 93 | } 94 | 95 | return showTag && allShowTag ? ( 96 | 103 | ) : ( 104 | <> 105 | ) 106 | } 107 | 108 | export default PlasmoOverlay 109 | -------------------------------------------------------------------------------- /contents/segmentfault.tsx: -------------------------------------------------------------------------------- 1 | import type { 2 | PlasmoCSConfig, 3 | PlasmoCSUIProps, 4 | PlasmoGetOverlayAnchor, 5 | PlasmoGetShadowHostId, 6 | PlasmoGetStyle 7 | } from "plasmo" 8 | import React, { type FC } from "react" 9 | 10 | import { useMessage } from "@plasmohq/messaging/hook" 11 | import { useStorage } from "@plasmohq/storage/dist/hook" 12 | 13 | import TagBtnStyle from "~component/tagBtn/style" 14 | import Tags from "~component/ui/tags" 15 | import ToolBox from "~component/ui/toolBox" 16 | import { i18n, saveHtml, saveMarkdown } from "~tools" 17 | import useCssCodeHook from "~utils/cssCodeHook" 18 | import { useEditMarkdown } from "~utils/editMarkdownHook" 19 | import { useParseMarkdown } from "~utils/parseMarkdownHook" 20 | import { Print } from "~utils/print" 21 | import Turndown from "~utils/turndown" 22 | 23 | export const config: PlasmoCSConfig = { 24 | matches: ["https://*.segmentfault.com/**"] 25 | } 26 | 27 | const turndownService = Turndown() 28 | const articleTitle = document 29 | .querySelector("head title") 30 | .innerText.trim() 31 | 32 | const HOST_ID = "codebox-segmentfault" 33 | export const getShadowHostId: PlasmoGetShadowHostId = () => HOST_ID 34 | 35 | export const getOverlayAnchor: PlasmoGetOverlayAnchor = async () => 36 | document.querySelector(".article-wrap.container") 37 | 38 | export const getStyle: PlasmoGetStyle = () => TagBtnStyle() 39 | 40 | const PlasmoOverlay: FC = ({ anchor }) => { 41 | const [parseContent, setParseContent] = useParseMarkdown() 42 | const [allShowTag, setAllShowTag] = useStorage("config-allShowTag", true) 43 | const [showTag, setShowTag] = useStorage( 44 | "segmentfault-showTag", 45 | true 46 | ) 47 | const [cssCode, runCss] = useCssCodeHook("segmentfault") 48 | const [content, setContent] = useEditMarkdown() 49 | 50 | useMessage(async (req, res) => { 51 | if (req.name == "segmentfault-isShow") { 52 | res.send({ isShow: true }) 53 | } 54 | if (req.name == "segmentfault-editMarkdown") { 55 | editMarkdown() 56 | } 57 | if (req.name == "segmentfault-downloadMarkdown") { 58 | downloadMarkdown() 59 | } 60 | if (req.name == "segmentfault-downloadHtml") { 61 | downloadHtml() 62 | } 63 | if (req.name == "segmentfault-downloadPdf") { 64 | downloadPdf() 65 | } 66 | }) 67 | 68 | function getDescription() { 69 | const summary = document.querySelector( 70 | 'meta[name="description"]' 71 | ).content 72 | summary && prompt("文章摘要:", summary) 73 | } 74 | 75 | function downloadPdf() { 76 | const article = document.querySelector(".container") 77 | if (article) { 78 | Print.print(article, { title: articleTitle }) 79 | .then(() => console.log("Printing complete")) 80 | .catch((error) => console.error("Printing failed:", error)) 81 | } 82 | } 83 | 84 | function editMarkdown() { 85 | const dom = document.querySelector(".container") 86 | setContent(dom, articleTitle) 87 | } 88 | 89 | function downloadMarkdown() { 90 | const html = document.querySelector(".container") 91 | const markdown = turndownService.turndown(html) 92 | saveMarkdown(markdown, articleTitle) 93 | } 94 | 95 | function downloadHtml() { 96 | const dom = document.querySelector(".container") 97 | saveHtml(dom, articleTitle) 98 | } 99 | 100 | function parseMarkdown() { 101 | const dom = document.querySelector(".container") 102 | setParseContent(dom) 103 | } 104 | 105 | return showTag && allShowTag ? ( 106 | 113 | ) : ( 114 | <> 115 | ) 116 | } 117 | 118 | export default PlasmoOverlay 119 | -------------------------------------------------------------------------------- /index.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | .hide { 7 | display: none !important; 8 | } 9 | 10 | .show { 11 | display: block !important; 12 | } 13 | 14 | .App { 15 | width: 190px; 16 | margin: 0 auto; 17 | padding: 5px 8px; 18 | max-height: 590px; 19 | } 20 | 21 | .App-header .title { 22 | margin-bottom: 0; 23 | font-size: 18px; 24 | } 25 | 26 | .App-header .desc { 27 | margin: 5px 0; 28 | font-size: 12px; 29 | } 30 | 31 | .App-link { 32 | margin-top: 5px; 33 | font-size: 14px; 34 | } 35 | 36 | .App-link .item { 37 | display: flex; 38 | justify-content: space-between; 39 | align-items: center; 40 | margin-bottom: 5px; 41 | font-size: 12px; 42 | } 43 | 44 | .popup .App-body { 45 | max-height: 450px; 46 | overflow-y: scroll; 47 | overflow-x: hidden; 48 | } 49 | 50 | .App-body fieldset { 51 | padding: 5px 8px; 52 | } 53 | 54 | .App-body fieldset .item { 55 | display: flex; 56 | justify-content: space-between; 57 | align-items: center; 58 | margin-bottom: 5px; 59 | font-size: 12px; 60 | } 61 | 62 | .App-body fieldset .item textarea { 63 | padding: 5px; 64 | width: 140px; 65 | } 66 | 67 | /*开关*/ 68 | .codebox-offscreen { 69 | display: none; 70 | } 71 | 72 | .codebox-switch { 73 | position: relative; 74 | display: inline-block; 75 | width: 40px; 76 | height: 20px; 77 | background-color: #ccc; 78 | border-radius: 20px; 79 | transition: all 0.3s; 80 | } 81 | 82 | .codebox-switch::after { 83 | content: ""; 84 | position: absolute; 85 | width: 18px; 86 | height: 18px; 87 | border-radius: 18px; 88 | background-color: #fff; 89 | top: 1px; 90 | left: 1px; 91 | transition: all 0.3s; 92 | } 93 | 94 | input[type="checkbox"]:checked + .codebox-switch::after { 95 | transform: translateX(20px); 96 | } 97 | 98 | input[type="checkbox"]:checked + .codebox-switch { 99 | background-color: #7983ff; 100 | } 101 | 102 | .item.download { 103 | cursor: pointer; 104 | } 105 | 106 | .item.code { 107 | cursor: pointer; 108 | height: 20px; 109 | } 110 | 111 | .item.code .codeTxt { 112 | overflow: hidden; 113 | text-overflow: ellipsis; 114 | white-space: nowrap; 115 | width: 105px; 116 | } -------------------------------------------------------------------------------- /locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionName": { 3 | "message": "codebox-one click code copying without login" 4 | }, 5 | "extensionDescription": { 6 | "message": "Implement one click code copying without logging in; Support selecting code; Allow non following bloggers to read full text prompts;" 7 | }, 8 | "popupDescription": { 9 | "message": "one click code copying without login" 10 | }, 11 | "history": { 12 | "message": "Historical records" 13 | }, 14 | "sidePanel": { 15 | "message": "Side Panel" 16 | }, 17 | "more": { 18 | "message": "More settings" 19 | }, 20 | "support": { 21 | "message": "Support author updates" 22 | }, 23 | "privacy": { 24 | "message": "Privacy policy" 25 | }, 26 | "version": { 27 | "message": "version" 28 | }, 29 | "reset": { 30 | "message": "reset" 31 | }, 32 | "AppConfig": { 33 | "message": "App Config" 34 | }, 35 | "configCloseLog": { 36 | "message": "Close Log" 37 | }, 38 | "51ctoConfig": { 39 | "message": "51CTO Config" 40 | }, 41 | "51ctoCopyCode": { 42 | "message": "Copy Code" 43 | }, 44 | "51ctoCloseLoginModal": { 45 | "message": "Close Login Modal" 46 | }, 47 | "baiduConfig": { 48 | "message": "51CTO Config" 49 | }, 50 | "baiduCloseAIBox": { 51 | "message": "Close AI Modal" 52 | }, 53 | "cnblogsConfig": { 54 | "message": "cnblogs Config" 55 | }, 56 | "cnblogsCopyCode": { 57 | "message": "Copy Code" 58 | }, 59 | "csdnConfig": { 60 | "message": "CSDN Config" 61 | }, 62 | "csdnCloseAds": { 63 | "message": "Close Ads" 64 | }, 65 | "csdnCopyCode": { 66 | "message": "Copy Code" 67 | }, 68 | "csdnCloseFollow": { 69 | "message": "Close Follow" 70 | }, 71 | "csdnCloseVip": { 72 | "message": "Close Vip" 73 | }, 74 | "csdnAutoOpenCode": { 75 | "message": "Auto Open Code" 76 | }, 77 | "csdnCloseLoginModal": { 78 | "message": "Close Login Modal" 79 | }, 80 | "csdnCloseRedirectModal": { 81 | "message": "Close Redirect App Modal" 82 | }, 83 | "customConfig": { 84 | "message": "Custom Config" 85 | }, 86 | "customCssCode": { 87 | "message": "Custom Css Code" 88 | }, 89 | "jb51Config": { 90 | "message": "Jb51 Config" 91 | }, 92 | "jb51CloseAds": { 93 | "message": "Close Ads" 94 | }, 95 | "jb51CopyCode": { 96 | "message": "Copy Code" 97 | }, 98 | "jianshuConfig": { 99 | "message": "Jianshu Config" 100 | }, 101 | "jianshuCopyCode": { 102 | "message": "Copy Code" 103 | }, 104 | "jianshuCloseLoginModal": { 105 | "message": "Close Login Modal" 106 | }, 107 | "jianshuAutoOpenCode": { 108 | "message": "Auto Open Code" 109 | }, 110 | "phpConfig": { 111 | "message": "Php Config" 112 | }, 113 | "phpCopyCode": { 114 | "message": "Copy Code" 115 | }, 116 | "phpCloseLoginModal": { 117 | "message": "Close Login Modal" 118 | }, 119 | "zhihuConfig": { 120 | "message": "Zhihu Config" 121 | }, 122 | "mediumConfig": { 123 | "message": "medium Config" 124 | }, 125 | "zhihuCopyCode": { 126 | "message": "Copy Code" 127 | }, 128 | "zhihuCloseLoginModal": { 129 | "message": "Close Login Modal" 130 | }, 131 | "zhihuAutoOpenCode": { 132 | "message": "Auto Open Code" 133 | }, 134 | "getDescription": { 135 | "message": "Get Description" 136 | }, 137 | "edit": { 138 | "message": "Edit" 139 | }, 140 | "download": { 141 | "message": "Download" 142 | }, 143 | "editMarkdown": { 144 | "message": "Edit Markdown" 145 | }, 146 | "downloadMarkdown": { 147 | "message": "Download Markdown" 148 | }, 149 | "parseMarkdown": { 150 | "message": "Parse Markdown" 151 | }, 152 | "downloadHtml": { 153 | "message": "Download HTML" 154 | }, 155 | "downloadPdf": { 156 | "message": "Download PDF" 157 | }, 158 | "downloadImg": { 159 | "message": "Download Image" 160 | }, 161 | "downloadAllImg": { 162 | "message": "Download All Image" 163 | }, 164 | "getArticle": { 165 | "message": "Get All Article" 166 | }, 167 | "downloading": { 168 | "message": "Downloading" 169 | }, 170 | "copyCode": { 171 | "message": "Copy Code" 172 | }, 173 | "parse": { 174 | "message": "parse" 175 | }, 176 | "copy": { 177 | "message": "copy" 178 | }, 179 | "copied": { 180 | "message": "copied" 181 | }, 182 | "showTag": { 183 | "message": "Show Tag" 184 | }, 185 | "allShowTag": { 186 | "message": "All Tag" 187 | }, 188 | "code": { 189 | "message": "Code" 190 | }, 191 | "juejinConfig": { 192 | "message": "Juejin Config" 193 | }, 194 | "oschinaConfig": { 195 | "message": "Oschina Config" 196 | }, 197 | "segmentfaultConfig": { 198 | "message": "Segmentfault Config" 199 | }, 200 | "weixinConfig": { 201 | "message": "Wechat Config" 202 | }, 203 | "paywallbusterConfig": { 204 | "message": "Paywallbuster Config" 205 | }, 206 | "360docConfig": { 207 | "message": "360doc Config" 208 | }, 209 | "pianshenConfig": { 210 | "message": "PianShen Config" 211 | }, 212 | "luoguConfig": { 213 | "message": "LuoGu Config" 214 | }, 215 | "fullPageScreenshot": { 216 | "message": "Full Page Screenshot" 217 | }, 218 | "getThumbMedia": { 219 | "message": "get Thumb Media" 220 | }, 221 | "getMediaResources": { 222 | "message": "get Media Resources" 223 | }, 224 | "copyURL": { 225 | "message": "copy URL" 226 | }, 227 | "makerQRPost": { 228 | "message": "maker Post Image" 229 | } 230 | } -------------------------------------------------------------------------------- /locales/zh_CN/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionName": { 3 | "message": "codebox-一键复制代码/下载文章" 4 | }, 5 | "extensionDescription": { 6 | "message": "实现一键下载文章成html或markdown文件;无需登录一键复制代码;支持选中代码;或者代码右上角按钮的一键复制;解除关注博主即可阅读全文提示;去除登录弹窗;去除跳转APP弹窗;页面自定义样式." 7 | }, 8 | "popupDescription": { 9 | "message": "免登录一键复制代码/下载文章" 10 | }, 11 | "history": { 12 | "message": "历史记录" 13 | }, 14 | "sidePanel": { 15 | "message": "侧边栏" 16 | }, 17 | "more": { 18 | "message": "更多设置" 19 | }, 20 | "support": { 21 | "message": "支持作者更新" 22 | }, 23 | "privacy": { 24 | "message": "隐私政策" 25 | }, 26 | "version": { 27 | "message": "版本" 28 | }, 29 | "reset": { 30 | "message": "重置" 31 | }, 32 | "AppConfig": { 33 | "message": "App设置" 34 | }, 35 | "configCloseLog": { 36 | "message": "关闭日志" 37 | }, 38 | "51ctoConfig": { 39 | "message": "51CTO设置" 40 | }, 41 | "51ctoCopyCode": { 42 | "message": "一键复制代码" 43 | }, 44 | "51ctoCloseLoginModal": { 45 | "message": "关闭登录弹窗" 46 | }, 47 | "baiduConfig": { 48 | "message": "百度设置" 49 | }, 50 | "baiduCloseAIBox": { 51 | "message": "关闭AI对话框" 52 | }, 53 | "cnblogsConfig": { 54 | "message": "博客园设置" 55 | }, 56 | "cnblogsCopyCode": { 57 | "message": "一键复制代码" 58 | }, 59 | "csdnConfig": { 60 | "message": "CSDN设置" 61 | }, 62 | "csdnCloseAds": { 63 | "message": "关闭广告" 64 | }, 65 | "csdnCopyCode": { 66 | "message": "一键复制代码" 67 | }, 68 | "csdnCloseFollow": { 69 | "message": "关注阅读全文" 70 | }, 71 | "csdnCloseVip": { 72 | "message": "VIP阅读全文" 73 | }, 74 | "csdnAutoOpenCode": { 75 | "message": "自动展开代码块" 76 | }, 77 | "csdnCloseLoginModal": { 78 | "message": "关闭登录弹窗" 79 | }, 80 | "csdnCloseRedirectModal": { 81 | "message": "关闭跳转APP弹窗" 82 | }, 83 | "customConfig": { 84 | "message": "自定义设置" 85 | }, 86 | "customCssCode": { 87 | "message": "页面插入css代码" 88 | }, 89 | "jb51Config": { 90 | "message": "脚本之家设置" 91 | }, 92 | "jb51CloseAds": { 93 | "message": "关闭广告" 94 | }, 95 | "jb51CopyCode": { 96 | "message": "一键复制代码" 97 | }, 98 | "jianshuConfig": { 99 | "message": "简书设置" 100 | }, 101 | "jianshuCopyCode": { 102 | "message": "一键复制代码" 103 | }, 104 | "jianshuCloseLoginModal": { 105 | "message": "关闭登录弹窗" 106 | }, 107 | "jianshuAutoOpenCode": { 108 | "message": "自动展开全文" 109 | }, 110 | "phpConfig": { 111 | "message": "php中文网设置" 112 | }, 113 | "phpCopyCode": { 114 | "message": "一键复制代码" 115 | }, 116 | "phpCloseLoginModal": { 117 | "message": "关闭登录弹窗" 118 | }, 119 | "mediumConfig": { 120 | "message": "medium设置" 121 | }, 122 | "zhihuConfig": { 123 | "message": "知乎设置" 124 | }, 125 | "zhihuCopyCode": { 126 | "message": "一键复制代码" 127 | }, 128 | "zhihuCloseLoginModal": { 129 | "message": "关闭登录弹窗" 130 | }, 131 | "zhihuAutoOpenCode": { 132 | "message": "自动展开全文" 133 | }, 134 | "parse": { 135 | "message": "解析" 136 | }, 137 | "edit": { 138 | "message": "编辑" 139 | }, 140 | "download": { 141 | "message": "下载" 142 | }, 143 | "print": { 144 | "message": "打印" 145 | }, 146 | "getDescription": { 147 | "message": "文章摘要" 148 | }, 149 | "editMarkdown": { 150 | "message": "编辑 Markdown" 151 | }, 152 | "downloadMarkdown": { 153 | "message": "下载 Markdown" 154 | }, 155 | "parseMarkdown": { 156 | "message": "解析 Markdown" 157 | }, 158 | "downloadHtml": { 159 | "message": "下载 HTML" 160 | }, 161 | "downloadPdf": { 162 | "message": "下载 PDF" 163 | }, 164 | "downloadImg": { 165 | "message": "下载图片" 166 | }, 167 | "downloadAllImg": { 168 | "message": "下载所有图片" 169 | }, 170 | "getArticle": { 171 | "message": "获取全文" 172 | }, 173 | "downloading": { 174 | "message": "正在下载" 175 | }, 176 | "copyCode": { 177 | "message": "一键复制代码" 178 | }, 179 | "copy": { 180 | "message": "复制" 181 | }, 182 | "copied": { 183 | "message": "复制成功" 184 | }, 185 | "showTag": { 186 | "message": "显示标签" 187 | }, 188 | "allShowTag": { 189 | "message": "所有标签" 190 | }, 191 | "code": { 192 | "message": "代码" 193 | }, 194 | "juejinConfig": { 195 | "message": "掘金设置" 196 | }, 197 | "oschinaConfig": { 198 | "message": "oschina设置" 199 | }, 200 | "segmentfaultConfig": { 201 | "message": "思否设置" 202 | }, 203 | "weixinConfig": { 204 | "message": "微信设置" 205 | }, 206 | "paywallbusterConfig": { 207 | "message": "paywallbuster设置" 208 | }, 209 | "360docConfig": { 210 | "message": "360doc设置" 211 | }, 212 | "pianshenConfig": { 213 | "message": "pianshen设置" 214 | }, 215 | "luoguConfig": { 216 | "message": "洛谷设置" 217 | }, 218 | "fullPageScreenshot": { 219 | "message": "全页面截图" 220 | }, 221 | "getThumbMedia": { 222 | "message": "获取封面" 223 | }, 224 | "getMediaResources": { 225 | "message": "复制资源" 226 | }, 227 | "copyURL": { 228 | "message": "复制URL" 229 | }, 230 | "makerQRPost": { 231 | "message": "生成海报" 232 | } 233 | } -------------------------------------------------------------------------------- /options/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | codexbox-设置 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /options/index.module.scss: -------------------------------------------------------------------------------- 1 | .options { 2 | width: 1000px; 3 | margin: 0 auto; 4 | padding: 5px 8px; 5 | :global { 6 | .App-body { 7 | display: flex; 8 | flex-wrap: wrap; 9 | } 10 | .sortableItem { 11 | display: flex; 12 | justify-content: center; 13 | box-shadow: 0 0 0 calc(1px / 1) rgba(63, 63, 68, 0.05), 0 1px calc(3px / 1) 0 rgba(34, 33, 81, 0.15); 14 | width: 230px; 15 | padding: 5px 8px; 16 | border-radius: 4px; 17 | margin: 0 2px 10px; 18 | } 19 | 20 | .sortableItem fieldset { 21 | width: 180px; 22 | } 23 | 24 | .sortableItem .isShow-toggle { 25 | display: flex; 26 | width: 24px; 27 | align-items: center; 28 | justify-content: center; 29 | cursor: pointer; 30 | } 31 | 32 | .sortableItem .draggable-handle { 33 | display: flex; 34 | width: 12px; 35 | padding: 15px; 36 | align-items: center; 37 | justify-content: center; 38 | flex: 0 0 auto; 39 | touch-action: none; 40 | cursor: pointer; 41 | border-radius: 5px; 42 | border: none; 43 | outline: none; 44 | appearance: none; 45 | background-color: transparent; 46 | -webkit-tap-highlight-color: transparent; 47 | } 48 | 49 | .sortableItem .draggable-handle svg { 50 | fill: #6f7b88; 51 | flex: 0 0 auto; 52 | margin: auto; 53 | height: 100%; 54 | overflow: visible; 55 | } 56 | 57 | .sortableItem .draggable-handle:hover { 58 | background-color: rgba(0, 0, 0, 0.05); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /options/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from "react" 2 | 3 | import { ThemeProvider } from "~theme" 4 | import { i18n } from "~tools" 5 | 6 | import "~index.css" 7 | 8 | import Cto51 from "~component/options/51cto" 9 | import Baidu from "~component/options/baidu" 10 | import Cnblogs from "~component/options/cnblogs" 11 | import Config from "~component/options/config" 12 | import Csdn from "~component/options/csdn" 13 | import Custom from "~component/options/custom" 14 | import Jb51 from "~component/options/jb51" 15 | import Jianshu from "~component/options/jianshu" 16 | import Juejin from "~component/options/juejin" 17 | import Medium from "~component/options/medium" 18 | import Oschina from "~component/options/oschina" 19 | import Php from "~component/options/php" 20 | import Segmentfault from "~component/options/segmentfault" 21 | import Weixin from "~component/options/weixin" 22 | import Zhihu from "~component/options/zhihu" 23 | 24 | import styles from "./index.module.scss" 25 | 26 | export default function IndexOptions() { 27 | const csdnRef = useRef() 28 | const zhihuRef = useRef() 29 | const baiduRef = useRef() 30 | const juejinRef = useRef() 31 | const oschinaRef = useRef() 32 | const jianshuRef = useRef() 33 | const jb51Ref = useRef() 34 | const cnblogsRef = useRef() 35 | const cto51Ref = useRef() 36 | const phpRef = useRef() 37 | const segmentfaultRef = useRef() 38 | const weixinRef = useRef() 39 | const customRef = useRef() 40 | const appRef = useRef() 41 | 42 | function handleReset() { 43 | if (confirm("确认重置配置?")) { 44 | csdnRef.current && csdnRef.current.handleReset() 45 | zhihuRef.current && zhihuRef.current.handleReset() 46 | baiduRef.current && baiduRef.current.handleReset() 47 | jianshuRef.current && jianshuRef.current.handleReset() 48 | jb51Ref.current && jb51Ref.current.handleReset() 49 | cnblogsRef.current && cnblogsRef.current.handleReset() 50 | cto51Ref.current && cto51Ref.current.handleReset() 51 | phpRef.current && phpRef.current.handleReset() 52 | appRef.current && appRef.current.handleReset() 53 | } 54 | } 55 | 56 | return ( 57 | 58 |
59 |
60 |

CodeBox 🎉

61 |

{i18n("popupDescription")}

62 |
63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 |
80 |
81 |
82 | {i18n("version")}:{chrome.runtime.getManifest().version} 83 |
84 |
85 | 88 |
89 | 98 | 107 | 116 |
117 |
118 |
119 | ) 120 | } 121 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code-box", 3 | "displayName": "__MSG_extensionName__", 4 | "version": "1.3.5", 5 | "description": "__MSG_extensionDescription__", 6 | "author": "027xiguapi. <458813868@qq.com>", 7 | "scripts": { 8 | "dev": "plasmo dev", 9 | "build": "plasmo build --zip", 10 | "build:firefox": "plasmo build --zip --target=firefox-mv3", 11 | "package": "plasmo package", 12 | "clean": "rimraf build" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/027xiguapi/code-box.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/027xiguapi/code-box/issues" 20 | }, 21 | "homepage": "https://github.com/027xiguapi/code-box#readme", 22 | "dependencies": { 23 | "@ant-design/cssinjs": "^1.21.1", 24 | "@ant-design/icons": "^5.5.1", 25 | "@joplin/turndown": "^4.0.74", 26 | "@joplin/turndown-plugin-gfm": "^1.0.56", 27 | "@plasmohq/messaging": "^0.6.2", 28 | "@plasmohq/storage": "^1.11.0", 29 | "antd": "^5.21.1", 30 | "crypto-js": "^4.2.0", 31 | "dayjs": "^1.11.12", 32 | "file-saver": "^2.0.5", 33 | "html2canvas": "^1.4.1", 34 | "jssha": "^3.3.1", 35 | "jszip": "^3.10.1", 36 | "plasmo": "0.90.3", 37 | "qrcode": "^1.5.4", 38 | "react": "18.2.0", 39 | "react-dom": "18.2.0", 40 | "uuid": "^10.0.0" 41 | }, 42 | "devDependencies": { 43 | "@ianvs/prettier-plugin-sort-imports": "4.1.1", 44 | "@types/chrome": "0.0.258", 45 | "@types/node": "20.11.5", 46 | "@types/react": "18.2.48", 47 | "@types/react-dom": "18.2.18", 48 | "postcss": "^8.2.1", 49 | "postcss-modules": "^4.3.0", 50 | "prettier": "3.2.4", 51 | "sass": "^1.77.8", 52 | "sharp": "^0.33.5", 53 | "typescript": "5.3.3" 54 | }, 55 | "manifest": { 56 | "name": "__MSG_extensionName__", 57 | "default_locale": "en", 58 | "browser_specific_settings": { 59 | "gecko": { 60 | "id": "$PLASMO_PUBLIC_FIREFOX_EXT_ID" 61 | } 62 | }, 63 | "permissions": [ 64 | "activeTab", 65 | "downloads" 66 | ], 67 | "host_permissions": [ 68 | "https://*/*" 69 | ], 70 | "omnibox": { 71 | "keyword": "copy" 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | codexbox-弹框 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /popup/index.tsx: -------------------------------------------------------------------------------- 1 | import Content from "~component/options/content" 2 | import { ThemeProvider } from "~theme" 3 | import { i18n } from "~tools" 4 | 5 | import "~index.css" 6 | 7 | import { useEffect, useState } from "react" 8 | 9 | import { sendToBackground } from "@plasmohq/messaging" 10 | 11 | export default function IndexPopup() { 12 | const [sidePanel, setSidePanel] = useState(false) 13 | 14 | useEffect(() => { 15 | hanleOpenSidePanel(sidePanel) 16 | }, [sidePanel]) 17 | 18 | function hanleOpenSidePanel(active) { 19 | sendToBackground({ 20 | name: "sidepanel", 21 | body: { 22 | active: active 23 | } 24 | }) 25 | } 26 | 27 | return ( 28 | 29 |
30 |
31 |

CodeBox 🎉

32 |

{i18n("popupDescription")}

33 |
34 |
35 | 36 |
37 |
38 |
39 | {i18n("sidePanel")} 40 | setSidePanel(e.target.checked)} 47 | /> 48 | 49 |
50 | 59 | 68 | 77 |
78 |
79 |
80 | ) 81 | } 82 | -------------------------------------------------------------------------------- /public/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/1.jpg -------------------------------------------------------------------------------- /public/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/128x128.png -------------------------------------------------------------------------------- /public/1723096379951.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/1723096379951.jpg -------------------------------------------------------------------------------- /public/2.md: -------------------------------------------------------------------------------- 1 | # 使用 Plasmo 设置浏览器插件动态 Icon 2 | 3 | ## 背景 4 | 5 | 最近在开发浏览器插件`免登录一键复制 (codebox-浏览器插件)`时,想动态设置图标可以提升用户体验。本文将介绍如何使用 `Plasmo` 框架实现这一功能。 6 | 7 | ## 前提条件 8 | 9 | 1. 安装 `Plasmo`:确保你已经安装了 `Plasmo` 框架。如果没有,请参考 `Plasmo` 官方文档 进行安装。 10 | 2. 基础知识:了解基本的 `JavaScript` 和浏览器插件开发。 11 | 12 | ### 步骤一:创建 `Plasmo` 项目 13 | 14 | 首先,创建一个新的 `Plasmo` 项目: 15 | 16 | ```ssh 17 | npx plasmo init my-extension 18 | cd my-extension 19 | ``` 20 | 21 | ### 步骤二:安装 `Messaging` 依赖 22 | 23 | 然后,安装 `Plasmo` 的 `Messaging` 依赖: 24 | 25 | ```ssh 26 | pnpm install @plasmohq/messaging 27 | ``` 28 | 29 | ### 步骤三:配置 `manifest.json` 30 | `Plasmo` 会自动生成 `manifest.json` 文件,但我们需要手动添加 `action` 字段来支持动态图标: 31 | 32 | ```json 33 | { 34 | "manifest_version": 3, 35 | "name": "My Extension", 36 | "version": "1.0", 37 | "action": { 38 | "default_icon": { 39 | "16": "icons/icon16.png", 40 | "48": "icons/icon48.png", 41 | "128": "icons/icon128.png" 42 | } 43 | }, 44 | "background": { 45 | "service_worker": "background.js" 46 | } 47 | } 48 | ``` 49 | ### 步骤四:创建背景文件夹和文件 50 | 该 `@plasmohq/messaging` 库要求后台服务工作者位于 `background/index.ts `文件夹内,并且所有消息处理程序都位于`background/*`文件夹内。 51 | 52 | 如果您已经有`background.ts`或`background.js`文件,则必须创建一个`background`文件夹并将您的脚本移动到`background/index.ts`或`background/index.js`。 53 | 54 | 如果您还没有`background`文件夹,请创建一个`background`文件夹并创建一个新的、空的`background/index.ts`或`background/index.js`文件。 55 | 56 | 现在,您将能够在`background/`子文件夹中创建新的处理程序。例如,要创建`messages`名为 的处理程序`ping`,您需要创建`background/messages/ping.ts`。 57 | 58 | 此时,您的文件夹结构可能看起来像这样。 59 | 60 | ``` 61 | . 62 | ├── background 63 | │ ├── index.ts 64 | │ └── messages 65 | │ ├── tab.ts 66 | │ └── icon.ts 67 | ``` 68 | 69 | ### 步骤五:编写背景脚本 70 | 71 | 在 `background/icon.ts` 中,使用 `chrome.action.setIcon` 动态设置图标: 72 | 73 | ```tsx 74 | import defaultUrl from "raw:~/assets/icon.png" 75 | import activeUrl from "raw:~/assets/logo.png" 76 | 77 | import type { PlasmoMessaging } from "@plasmohq/messaging" 78 | 79 | const handler: PlasmoMessaging.MessageHandler = async (req, res) => { 80 | const [tab] = await chrome.tabs.query({ currentWindow: true, active: true }) 81 | const { active } = req.body 82 | 83 | if (active) { 84 | chrome.action.setIcon({ tabId: tab.id, path: activeUrl }, () => { 85 | console.log("set icon activeUrl") 86 | }) 87 | } else { 88 | chrome.action.setIcon({ tabId: tab.id, path: defaultUrl }, () => { 89 | console.log("set icon defaultUrl") 90 | }) 91 | } 92 | } 93 | 94 | export default handler 95 | ``` 96 | 97 | ### 步骤六:发送消息更改图标 98 | 在你的插件中,通过发送消息来更改图标。例如,在 `popup.js` 中: 99 | 100 | ```js 101 | import { sendToBackground } from "@plasmohq/messaging" 102 | 103 | export function setIcon(active: boolean) { 104 | sendToBackground({ 105 | name: "icon", 106 | body: { 107 | active: active 108 | } 109 | }) 110 | } 111 | ``` 112 | 113 | ### 步骤七:测试插件 114 | 115 | 1. 加载插件:在浏览器中打开 `chrome://extensions/`,启用开发者模式,加载已解压的扩展程序。 116 | 2. 测试功能:点击按钮,验证图标是否动态更改。 117 | 118 | ## 结论 119 | 120 | 通过上述步骤,你可以使用 `Plasmo` 框架轻松实现浏览器插件的动态图标设置。这不仅提升了用户体验,还展示了 `Plasmo` 的强大功能。 121 | 122 | 希望这篇文章对你有所帮助! 123 | 124 | >因为这款插件的源代码是开源的,完整代码大家可以直接看我的 Github 仓库: 125 | > 126 | > https://github.com/027xiguapi/code-box 127 | > 128 | 129 | 本浏览器插件可以用于`CSDN/知乎/脚本之家/博客园/博客园/51CTO博客/php中文网`等网站,实现无需登录一键复制代码;支持选中代码;或者代码右上角按钮的一键复制;解除关注博主即可阅读全文提示;去除登录弹窗;去除跳转APP弹窗。功能上已经可以满足要求。可能还有很多不足的地方,大家发现了问题或者有其他需求的话,欢迎向我反馈。 130 | 131 | 132 | ## 参考 133 | 134 | [【plasmo Messaging API】](https://docs.plasmo.com/framework/messaging) -------------------------------------------------------------------------------- /public/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/2.png -------------------------------------------------------------------------------- /public/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/3.jpg -------------------------------------------------------------------------------- /public/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/4.jpg -------------------------------------------------------------------------------- /public/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/5.jpg -------------------------------------------------------------------------------- /public/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/6.jpg -------------------------------------------------------------------------------- /public/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/7.jpg -------------------------------------------------------------------------------- /public/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/8.png -------------------------------------------------------------------------------- /public/app.md: -------------------------------------------------------------------------------- 1 | ## 名字: 2 | codebox一键复制网页代码浏览器插件 3 | 4 | ## github地址: 5 | https://github.com/027xiguapi/code-box 6 | 7 | ## 描述 8 | 9 | 本浏览器插件可以用于CSDN/知乎/脚本之家/博客园/博客园/51CTO博客/php中文网/掘金/微信等网站,一键下载文章成html或markdown文件;实现无需登录一键复制代码;支持选中代码;或者代码右上角按钮的一键复制;解除关注博主即可阅读全文提示;去除登录弹窗;去除跳转APP弹窗;页面自定义样式. 10 | 11 | ### 自定义 12 | 13 | - 插入自定义样式(css代码) 14 | - 自定义选择下载html 15 | - 自定义选择下载markdown 16 | - 自定义选择下载pdf 17 | 18 | ### CSDN 19 | 1. 一键下载文章html或markdown文件 20 | 2. 打开任意一个CSDN博客即可开始复制代码 21 | 3. 未登录CSDN状态下,支持选中代码 22 | 4. 未登录CSDN状态下,代码右上角按钮一键复制 23 | 5. 未登录CSDN状态下,不会再出现强制登录弹窗 24 | 6. 未关注博主状态下,不再提示关注博主即可阅读全文,且完整展示文章 25 | 7. 自动展开代码块 26 | 8. 移动端屏蔽跳转APP 27 | 28 | ### 知乎 29 | 1. 一键下载文章html或markdown文件 30 | 2. 一键复制代码 31 | 3. 未登录知乎状态下,不再提示展开阅读全文,且完整展示文章 32 | 4. 未登录知乎状态下,不会再出现强制登录弹窗 33 | 34 | ### 百度 35 | 1. 一键下载对话框html或markdown文件 36 | 2. 关闭AI对话框 37 | 38 | ### 简书 39 | 1. 一键下载文章html或markdown文件 40 | 2. 一键复制代码 41 | 3. 不再提示展开阅读全文,且完整展示文章 42 | 4. 不会再出现强制登录弹窗 43 | 44 | ### 脚本之家 45 | 1. 一键下载文章html或markdown文件 46 | 2. 打开任意一个脚本之家博客即可开始复制代码 47 | 3. 未登录脚本之家状态下,支持选中代码 48 | 4. 屏蔽广告 49 | 5. 移动端未登录脚本之家状态下,代码右上角按钮一键复制 50 | 51 | ### 博客园 52 | 1. 一键下载文章html或markdown文件 53 | 2. 一键复制代码 54 | 55 | ### 51CTO博客 56 | 1. 一键下载文章html或markdown文件 57 | 2. 未登录51CTO博客状态下,支持选中代码 58 | 3. 未登录51CTO博客状态下,代码右上角按钮一键复制 59 | 4. 未登录51CTO博客状态下,不会再出现强制登录弹窗 60 | 5. 移动端未登录51CTO博客状态下,代码右上角按钮一键复制 61 | 62 | ### php中文网 63 | 1. 一键下载文章html或markdown文件 64 | 2. 未登录php中文网状态下,支持选中代码 65 | 3. 未登录php中文网状态下,代码右上角按钮一键复制 66 | 4. 未登录php中文网状态下,不会再出现强制登录弹窗 67 | 5. 未登录php中文网状态下,移动端代码右上角按钮一键复制 68 | 69 | 70 | ## 教你怎么一键下载掘金文章 71 | 72 | ## 注意事项 73 | 在开始之前,请确保您遵守掘金平台的使用条款和版权声明。如果您将文章用于个人学习和备份,一般是可以的,但请勿非法传播或用于商业用途。 74 | 75 | ## 背景 76 | 77 | 在信息爆炸的时代,互联网已经成为获取知识和分享经验的主要渠道,特别是对于开发者和技术爱好者而言,像掘金这样的技术社区平台提供了丰富的优质内容。掘金聚集了大量关于编程、开发实践、项目管理等领域的高质量文章,这使得开发者们可以快速学习新技术、解决实际问题。然而,由于网络连接不稳定或者出于备份、离线阅读等需求,很多用户希望能够方便地将这些文章下载保存。然而,掘金平台本身并不直接提供一键下载文章的功能,因此用户需要寻找其他方法来实现这一需求。 78 | 79 | 在此背景下,本文旨在为用户提供几种便捷的工具和方法,帮助他们一键下载掘金文章,以便离线访问和个人存档。 80 | 81 | ## 方法 82 | ### 方法一:使用浏览器插件 83 | 84 | 很多浏览器插件可以轻松实现网页内容的下载和保存,适用于掘金文章。 85 | 86 | #### 步骤: 87 | 88 | 1. **安装插件**:我们可以使用诸如「codebox」或「Save Page WE」这样的插件,它们支持一键保存网页为HTML或Markdown文件。 89 | 90 | * 在谷歌浏览器中,打开浏览器的扩展程序商店。 91 | * 搜索“codebox”。 92 | * 点击“添加到浏览器”并安装。 93 | 94 | ![1723513894421.jpg](https://raw.githubusercontent.com/027xiguapi/code-box/main/public/webstore/1723513894421.jpg) 95 | ![img](https://raw.githubusercontent.com/027xiguapi/code-box/main/public/webstore/img.png) 96 | ![1724640161752](https://raw.githubusercontent.com/027xiguapi/code-box/main/public/webstore/1724640161752.jpg) 97 | 98 | 2. **保存文章**: 99 | * 安装完成后,打开掘金平台,并导航到您想要下载的文章页面。 100 | * 点击浏览器右上角插件的图标,选择保存页面。 101 | * 保存为HTML或Markdown格式即可。 102 | 103 | 104 | 105 | #### 优点: 106 | 107 | * 操作简单,适合批量保存网页。 108 | * 可以保存页面的原始格式,包括文字、图片和样式。 109 | 110 | #### 缺点: 111 | 112 | * 无法去除多余的广告或页面布局。 113 | 114 | 115 | ## 总结 116 | 117 | 以上介绍了一键下载掘金文章的方法:使用浏览器插件以及手动下载。每种方法各有优缺点,您可以根据自己的需求选择最合适的方式。 118 | 119 | ### 额外提示: 120 | 121 | 确保您下载的内容仅用于个人学习和备份,避免侵犯原作者的权益。 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /public/app.txt: -------------------------------------------------------------------------------- 1 | 描述 2 | 3 | 本浏览器插件可以用于CSDN/知乎/脚本之家/博客园/博客园/51CTO博客/php中文网/掘金/微信等网站,一键下载文章成html或markdown文件;实现无需登录一键复制代码;支持选中代码;或者代码右上角按钮的一键复制;解除关注博主即可阅读全文提示;去除登录弹窗;去除跳转APP弹窗;页面自定义样式. 4 | 5 | 注意:由于审核原因,新版一般会晚几天上架,请耐心等候。 6 | 7 | 若有任何问题,请在github上提交issue,评论区不做回复。 8 | github地址:https://github.com/027xiguapi/code-box 9 | 10 | 自定义 11 | 1. 插入自定义样式(css代码) 12 | 2. 自定义选择下载html 13 | 3. 自定义选择下载markdown 14 | 4. 自定义选择下载pdf 15 | 5. 一键下载所有图片 16 | 6. 在线编辑markdown 17 | 18 | 微信 19 | 1. 插入自定义样式(css代码) 20 | 2. 一键下载html 21 | 3. 一键下载markdown 22 | 4. 一键下载pdf 23 | 5. 一键下载所有图片 24 | 25 | 掘金 26 | 1. 插入自定义样式(css代码) 27 | 2. 一键选择下载html 28 | 3. 一键选择下载markdown 29 | 4. 一键选择下载pdf 30 | 31 | CSDN 32 | 1. 一键下载文章html、pdf或markdown文件 33 | 2. 打开任意一个CSDN博客即可开始复制代码 34 | 3. 未登录CSDN状态下,支持选中代码 35 | 4. 未登录CSDN状态下,代码右上角按钮一键复制 36 | 5. 未登录CSDN状态下,不会再出现强制登录弹窗 37 | 6. 未关注博主状态下,不再提示关注博主即可阅读全文,且完整展示文章 38 | 7. 自动展开代码块 39 | 8. 移动端屏蔽跳转APP 40 | 9. 插入自定义样式(css代码) 41 | 42 | 43 | 知乎 44 | 1. 一键下载文章html、pdf或markdown文件 45 | 2. 一键复制代码 46 | 3. 未登录知乎状态下,不再提示展开阅读全文,且完整展示文章 47 | 4. 未登录知乎状态下,不会再出现强制登录弹窗 48 | 5. 插入自定义样式(css代码) 49 | 50 | 51 | 百度 52 | 1. 一键下载对话框html或markdown文件 53 | 2. 关闭AI对话框 54 | 55 | 简书 56 | 1. 一键下载文章html或markdown文件 57 | 2. 一键复制代码 58 | 3. 不再提示展开阅读全文,且完整展示文章 59 | 4. 不会再出现强制登录弹窗 60 | 6. 插入自定义样式(css代码) 61 | 62 | 脚本之家 63 | 1. 一键下载文章html、pdf或markdown文件 64 | 2. 打开任意一个脚本之家博客即可开始复制代码 65 | 3. 未登录脚本之家状态下,支持选中代码 66 | 4. 屏蔽广告 67 | 5. 移动端未登录脚本之家状态下,代码右上角按钮一键复制 68 | 6. 插入自定义样式(css代码) 69 | 70 | 71 | 博客园 72 | - 插入自定义样式(css代码) 73 | - 自定义选择下载html 74 | - 自定义选择下载markdown 75 | - 自定义选择下载pdf 76 | 77 | 51CTO博客 78 | 1. 一键下载文章html、pdf或markdown文件 79 | 2. 未登录51CTO博客状态下,支持选中代码 80 | 3. 未登录51CTO博客状态下,代码右上角按钮一键复制 81 | 4. 未登录51CTO博客状态下,不会再出现强制登录弹窗 82 | 5. 移动端未登录51CTO博客状态下,代码右上角按钮一键复制 83 | 6. 插入自定义样式(css代码) 84 | 85 | php中文网 86 | 1. 一键下载文章html、pdf或markdown文件 87 | 2. 未登录php中文网状态下,支持选中代码 88 | 3. 未登录php中文网状态下,代码右上角按钮一键复制 89 | 4. 未登录php中文网状态下,不会再出现强制登录弹窗 90 | 5. 未登录php中文网状态下,移动端代码右上角按钮一键复制 91 | 6. 插入自定义样式(css代码) 92 | 93 | 94 | 95 | 96 | 火狐说明: 97 | 1.先 pnpm install 98 | 2.运行 pnpm build:firefox, 99 | 3.然后 运行 pnpm package:firefox, 100 | 4.运行结果在 build 文件夹内. 101 | 102 | 1. First run `pnpm install` 103 | 2. Next run `pnpm build:firefox`, 104 | 3. The running results are in the `build` folder 105 | 106 | 107 | -------------------------------------------------------------------------------- /public/config.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/config.jpg -------------------------------------------------------------------------------- /public/en/1400x560.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/en/1400x560.png -------------------------------------------------------------------------------- /public/en/440x280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/en/440x280.png -------------------------------------------------------------------------------- /public/en/en-config.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/en/en-config.jpg -------------------------------------------------------------------------------- /public/logo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 30 | 31 | 32 | 33 | 42 | 59 | 61 | 62 | 63 | 64 | 65 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 76 | 77 | 79 | 80 | 81 | 83 | 85 | 86 | 87 | 88 | 89 |
这是可编辑的内容。
90 | 91 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /public/logo.js: -------------------------------------------------------------------------------- 1 | const sharp = require("sharp") 2 | 3 | const input = "../assets/icon.png" 4 | 5 | const output = "../assets/logo.png" 6 | 7 | sharp(input) 8 | // .tint({ r: 255, g: 240, b: 16 }) 9 | .linear(0.8, 1.5) 10 | .toFile(output, (err, info) => { 11 | if (err) { 12 | console.error("处理图片出错:", err) 13 | return 14 | } 15 | console.log("图片处理完成:", info) 16 | }) 17 | -------------------------------------------------------------------------------- /public/logo/logo.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/privacy-policy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | codebox 隐私政策 9 | 15 | 16 | 17 | 18 |

隐私政策

19 |
生效日期:2024/07/18
20 |

导言

21 |

22 | codebox 是一款由 西瓜皮 (以下简称“我们”)提供的产品。 23 | 您在使用我们的服务时,我们可能会收集和使用您的相关信息。我们希望通过本《隐私政策》向您说明,在使用我们的服务时,我们如何收集、使用、储存和分享这些信息,以及我们为您提供的访问、更新、控制和保护这些信息的方式。 24 | 本《隐私政策》与您所使用的 codebox 服务息息相关,希望您仔细阅读,在需要时,按照本《隐私政策》的指引,作出您认为适当的选择。本《隐私政策》中涉及的相关技术词汇,我们尽量以简明扼要的表述,并提供进一步说明的链接,以便您的理解。 25 |

26 |

您使用或继续使用我们的服务,即意味着同意我们按照本《隐私政策》收集、使用、储存和分享您的相关信息。

27 |

如对本《隐私政策》或相关事宜有任何问题,请通过 458813868@qq.com 与我们联系。

28 | 29 |

1. 我们收集的信息

30 |

我们或我们的第三方合作伙伴提供服务时,可能会收集、储存和使用下列与您有关的信息。如果您不提供相关信息,可能无法注册成为我们的用户或无法享受我们提供的某些服务,或者无法达到相关服务拟达到的效果。

31 | 32 |

2. 信息的存储

33 | 2.1 信息存储的方式和期限 34 |
    35 |
  • 我们会通过安全的方式存储您的信息,包括本地存储(例如利用APP进行数据缓存)、数据库和服务器日志。
  • 36 |
  • 一般情况下,我们只会在为实现服务目的所必需的时间内或法律法规规定的条件下存储您的个人信息。
  • 37 |
38 | 39 | 2.2 信息存储的地域 40 |
    41 |
  • 我们会按照法律法规规定,将境内收集的用户个人信息存储于中国境内。
  • 42 |
  • 目前我们不会跨境传输或存储您的个人信息。将来如需跨境传输或存储的,我们会向您告知信息出境的目的、接收方、安全保证措施和安全风险,并征得您的同意。
  • 43 |
44 | 45 | 2.3 产品或服务停止运营时的通知 46 |
    47 |
  • 当我们的产品或服务发生停止运营的情况时,我们将以推送通知、公告等形式通知您,并在合理期限内删除您的个人信息或进行匿名化处理,法律法规另有规定的除外。
  • 48 |
49 | 50 |

3. 信息安全

51 |

52 | 我们使用各种安全技术和程序,以防信息的丢失、不当使用、未经授权阅览或披露。例如,在某些服务中,我们将利用加密技术(例如SSL)来保护您提供的个人信息。但请您理解,由于技术的限制以及可能存在的各种恶意手段,在互联网行业,即便竭尽所能加强安全措施,也不可能始终保证信息百分之百的安全。您需要了解,您接入我们的服务所用的系统和通讯网络,有可能因我们可控范围外的因素而出现问题。 53 |

54 | 55 |

4. 我们如何使用信息

56 |

我们可能将在向您提供服务的过程之中所收集的信息用作下列用途:

57 |
    58 |
  • 向您提供服务;
  • 59 |
  • 在我们提供服务时,用于身份验证、客户服务、安全防范、诈骗监测、存档和备份用途,确保我们向您提供的产品和服务的安全性;
  • 60 |
  • 帮助我们设计新服务,改善我们现有服务;
  • 61 |
  • 使我们更加了解您如何接入和使用我们的服务,从而针对性地回应您的个性化需求,例如语言设定、位置设定、个性化的帮助服务和指示,或对您和其他用户作出其他方面的回应;
  • 62 |
  • 向您提供与您更加相关的广告以替代普遍投放的广告;
  • 63 |
  • 评估我们服务中的广告和其他促销及推广活动的效果,并加以改善;
  • 64 |
  • 软件认证或管理软件升级;
  • 65 |
  • 让您参与有关我们产品和服务的调查。
  • 66 |
67 | 68 |

5. 信息共享

69 |

70 | 目前,我们不会主动共享或转让您的个人信息至第三方,如存在其他共享或转让您的个人信息或您需要我们将您的个人信息共享或转让至第三方情形时,我们会直接或确认第三方征得您对上述行为的明示同意。 71 |

72 |

73 | 为了投放广告,评估、优化广告投放效果等目的,我们需要向广告主及其代理商等第三方合作伙伴共享您的部分数据,要求其严格遵守我们关于数据隐私保护的措施与要求,包括但不限于根据数据保护协议、承诺书及相关数据处理政策进行处理,避免识别出个人身份,保障隐私安全。 74 |

75 |

76 | 我们不会向合作伙伴分享可用于识别您个人身份的信息(例如您的姓名或电子邮件地址),除非您明确授权。 77 |

78 |

79 | 我们不会对外公开披露所收集的个人信息,如必须公开披露时,我们会向您告知此次公开披露的目的、披露信息的类型及可能涉及的敏感信息,并征得您的明示同意。 80 |

81 |

82 | 随着我们业务的持续发展,我们有可能进行合并、收购、资产转让等交易,我们将告知您相关情形,按照法律法规及不低于本《隐私政策》所要求的标准继续保护或要求新的控制者继续保护您的个人信息。 83 |

84 |

85 | 另外,根据相关法律法规及国家标准,以下情形中,我们可能会共享、转让、公开披露个人信息无需事先征得您的授权同意: 86 |

87 |
    88 |
  • 与国家安全、国防安全直接相关的;
  • 89 |
  • 与公共安全、公共卫生、重大公共利益直接相关的;
  • 90 |
  • 犯罪侦查、起诉、审判和判决执行等直接相关的;
  • 91 |
  • 出于维护个人信息主体或其他个人的生命、财产等重大合法权益但又很难得到本人同意的;
  • 92 |
  • 个人信息主体自行向社会公众公开个人信息的;
  • 93 |
  • 从合法公开披露的信息中收集个人信息的,如合法的新闻报道、政府信息公开等渠道。
  • 94 |
95 | 96 |

6. 您的权利

97 |

98 | 在您使用我们的服务期间,我们可能会视产品具体情况为您提供相应的操作设置,以便您可以查询、删除、更正或撤回您的相关个人信息,您可参考相应的具体指引进行操作。此外,我们还设置了投诉举报渠道,您的意见将会得到及时的处理。如果您无法通过上述途径和方式行使您的个人信息主体权利,您可以通过本《隐私政策》中提供的联系方式提出您的请求,我们会按照法律法规的规定予以反馈。 99 |

100 | 101 | 102 |

7. 变更

103 |

104 | 我们可能适时修订本《隐私政策》的条款。当变更发生时,我们会在版本更新时向您提示新的《隐私政策》,并向您说明生效日期。请您仔细阅读变更后的《隐私政策》内容,若您继续使用我们的服务,即表示您同意我们按照更新后的《隐私政策》处理您的个人信息。 105 |

106 | 107 |

8. 未成年人保护

108 |

109 | 我们鼓励父母或监护人指导未满十八岁的未成年人使用我们的服务。我们建议未成年人鼓励他们的父母或监护人阅读本《隐私政策》,并建议未成年人在提交的个人信息之前寻求父母或监护人的同意和指导。 110 |

111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /public/slogan.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 36 | 37 | 38 |
39 | logo 40 |

codebox

41 |
One-click Code Copying/Downloading Articles
42 |
43 | 44 | -------------------------------------------------------------------------------- /public/webstore/0f8137ce6861f2a387095aef446fe60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/webstore/0f8137ce6861f2a387095aef446fe60.png -------------------------------------------------------------------------------- /public/webstore/1723513894421.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/webstore/1723513894421.jpg -------------------------------------------------------------------------------- /public/webstore/1724640034462.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/webstore/1724640034462.jpg -------------------------------------------------------------------------------- /public/webstore/1724640161752.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/webstore/1724640161752.jpg -------------------------------------------------------------------------------- /public/webstore/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/webstore/img.png -------------------------------------------------------------------------------- /public/wx/download.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/wx/download.jpg -------------------------------------------------------------------------------- /public/wx/gzh.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/wx/gzh.jpg -------------------------------------------------------------------------------- /public/wx/u_wx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/wx/u_wx.jpg -------------------------------------------------------------------------------- /public/wx/wx_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/wx/wx_logo.jpg -------------------------------------------------------------------------------- /public/wx/xcx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/wx/xcx.jpg -------------------------------------------------------------------------------- /public/zh/1400x560.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/zh/1400x560.png -------------------------------------------------------------------------------- /public/zh/440x280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/zh/440x280.png -------------------------------------------------------------------------------- /public/zh/config.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/027xiguapi/code-box/8478eecefcbd1828dc92e035f34ab395591c902f/public/zh/config.jpg -------------------------------------------------------------------------------- /server/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } -------------------------------------------------------------------------------- /server/WechatSender.js: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch" 2 | 3 | class WechatSender { 4 | constructor() { 5 | this.appid = process.env.WECHAT_APPID 6 | this.secret = process.env.WECHAT_SECRET 7 | 8 | if (!this.appid || !this.secret) { 9 | const errorMessage = 10 | "Error: WECHAT_APPID or WECHAT_SECRET is not set in the environment variables" 11 | console.error(errorMessage) 12 | throw new Error(errorMessage) 13 | } 14 | } 15 | 16 | async getAccessToken() { 17 | const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${this.appid}&secret=${this.secret}` 18 | const response = await fetch(url) 19 | const data = await response.json() 20 | if (data.errcode) { 21 | throw new Error(`Failed to get access token: ${data.errmsg}`) 22 | } 23 | return data.access_token 24 | } 25 | 26 | async addDraft(accessToken, event) { 27 | const url = `https://api.weixin.qq.com/cgi-bin/draft/add?access_token=${accessToken}` 28 | const response = await fetch(url, { 29 | method: "POST", 30 | body: JSON.stringify({ 31 | articles: [ 32 | { 33 | title: event.title, // 标题 34 | author: event.author, // 作者 35 | digest: event.digest, // 图文消息的摘要,仅有单图文消息才有摘要,多图文此处为空。如果本字段为没有填写,则默认抓取正文前54个字。 36 | content: event.content, // 图文消息的具体内容,支持HTML标签,必须少于2万字符,小于1M,且此处会去除JS,涉及图片url必须来源 "上传图文消息内的图片获取URL"接口获取。外部图片url将被过滤。 37 | content_source_url: event.content_source_url, //图文消息的原文地址,即点击“阅读原文”后的URL 38 | thumb_media_id: event.thumb_media_id, // 图文消息的封面图片素材id(必须是永久MediaID) 39 | need_open_comment: event.need_open_comment, // Uint32 是否打开评论,0不打开(默认),1打开 40 | only_fans_can_comment: event.only_fans_can_comment, // Uint32 是否粉丝才可评论,0所有人可评论(默认),1粉丝才可评论 41 | pic_crop_235_1: event.pic_crop_235_1, // 封面裁剪为2.35:1规格的坐标字段。 42 | pic_crop_1_1: event.pic_crop_1_1 // 封面裁剪为1:1规格的坐标字段,裁剪原理同pic_crop_235_1,裁剪后的图片必须符合规格要求。 43 | } 44 | ] 45 | }) 46 | }) 47 | const data = await response.json() 48 | if (data.errcode) { 49 | throw new Error(`Failed to add draft: ${data.errmsg}`) 50 | } 51 | return data.media_id 52 | } 53 | 54 | async publishArticle(accessToken, mediaId) { 55 | const url = `https://api.weixin.qq.com/cgi-bin/freepublish/submit?access_token=${accessToken}` 56 | const response = await fetch(url, { 57 | method: "POST", 58 | body: JSON.stringify({ 59 | media_id: mediaId 60 | }) 61 | }) 62 | const data = await response.json() 63 | if (data.errcode) { 64 | throw new Error(`Failed to publish article: ${data.errmsg}`) 65 | } 66 | return data.publish_id 67 | } 68 | 69 | async send(event) { 70 | try { 71 | const accessToken = await this.getAccessToken() 72 | const mediaId = await this.addDraft(accessToken, event) 73 | const articleId = await this.publishArticle(accessToken, mediaId) 74 | console.log(`Article published successfully. Article ID: ${articleId}`) 75 | return articleId 76 | } catch (error) { 77 | console.error("Error in WechatSender:", error.message) 78 | throw error 79 | } 80 | } 81 | } 82 | 83 | export default WechatSender 84 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wechat-sender", 3 | "version": "1.0.0", 4 | "description": "A Node.js module for creating and publishing WeChat articles", 5 | "main": "WechatSender.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js" 9 | }, 10 | "keywords": ["wechat", "article", "publisher"], 11 | "author": "Your Name", 12 | "license": "ISC", 13 | "dependencies": { 14 | "node-fetch": "^3.2.0" 15 | }, 16 | "devDependencies": { 17 | "@babel/preset-env": "^7.16.11", 18 | "jest": "^27.5.1" 19 | } 20 | } -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv" 2 | import express from "express" 3 | 4 | import WechatSender from "./WechatSender.js" 5 | 6 | dotenv.config() 7 | 8 | const app = express() 9 | app.use(express.json()) 10 | 11 | const wechatSender = new WechatSender() 12 | 13 | app.post("/api/article/send", async (req, res) => { 14 | try { 15 | const article = req.body 16 | const articleId = await wechatSender.send({ ...article }) 17 | res.json({ success: true, articleId }) 18 | } catch (error) { 19 | console.error("Error sending article:", error) 20 | res.status(500).json({ success: false, error: error.message }) 21 | } 22 | }) 23 | 24 | app.post("/api/article/submit", async (req, res) => { 25 | try { 26 | const article = req.body 27 | const accessToken = await wechatSender.getAccessToken() 28 | const mediaId = await wechatSender.addDraft(accessToken, { 29 | ...article 30 | }) 31 | res.json({ success: true, mediaId }) 32 | } catch (error) { 33 | console.error("Error submitting article:", error) 34 | res.status(500).json({ success: false, error: error.message }) 35 | } 36 | }) 37 | 38 | const PORT = process.env.PORT || 3000 39 | app.listen(PORT, () => { 40 | console.log(`Server is running on port ${PORT}`) 41 | }) 42 | 43 | export default app 44 | -------------------------------------------------------------------------------- /server/server.test.js: -------------------------------------------------------------------------------- 1 | import { jest } from "@jest/globals" 2 | import request from "supertest" 3 | 4 | import app from "./server.js" 5 | import WechatSender from "./WechatSender.js" 6 | 7 | jest.mock("./WechatSender.js") 8 | 9 | describe("Express App", () => { 10 | beforeEach(() => { 11 | WechatSender.mockClear() 12 | }) 13 | 14 | test("POST /api/send-article", async () => { 15 | const mockSend = jest.fn().mockResolvedValue("mock_article_id") 16 | WechatSender.mockImplementation(() => ({ 17 | send: mockSend 18 | })) 19 | 20 | const response = await request(app).post("/api/send-article").send({ 21 | title: "Test Title", 22 | subTitle: "Test Subtitle", 23 | content: "Test Content" 24 | }) 25 | 26 | expect(response.statusCode).toBe(200) 27 | expect(response.body).toEqual({ 28 | success: true, 29 | articleId: "mock_article_id" 30 | }) 31 | expect(mockSend).toHaveBeenCalledWith({ 32 | title: "Test Title", 33 | subTitle: "Test Subtitle", 34 | content: "Test Content" 35 | }) 36 | }) 37 | 38 | test("POST /api/submit-article", async () => { 39 | const mockGetAccessToken = jest.fn().mockResolvedValue("mock_access_token") 40 | const mockAddDraft = jest.fn().mockResolvedValue("mock_media_id") 41 | WechatSender.mockImplementation(() => ({ 42 | getAccessToken: mockGetAccessToken, 43 | addDraft: mockAddDraft 44 | })) 45 | 46 | const response = await request(app).post("/api/submit-article").send({ 47 | title: "Test Title", 48 | subTitle: "Test Subtitle", 49 | content: "Test Content" 50 | }) 51 | 52 | expect(response.statusCode).toBe(200) 53 | expect(response.body).toEqual({ 54 | success: true, 55 | mediaId: "mock_media_id" 56 | }) 57 | expect(mockGetAccessToken).toHaveBeenCalled() 58 | expect(mockAddDraft).toHaveBeenCalledWith("mock_access_token", { 59 | title: "Test Title", 60 | subTitle: "Test Subtitle", 61 | content: "Test Content" 62 | }) 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /sidepanel/index.module.scss: -------------------------------------------------------------------------------- 1 | .sidepanel { 2 | margin: 0 auto; 3 | padding: 10px 15px; 4 | :global { 5 | .wechat { 6 | text-align: center; 7 | .wechatDesc { 8 | font-size: 14px; 9 | color: red; 10 | } 11 | .wechatImg { 12 | margin: 0 auto; 13 | width: 150px; 14 | } 15 | } 16 | .valid { 17 | color: red; 18 | } 19 | .summary { 20 | .title { 21 | font-size: 16px; 22 | margin: 10px 0; 23 | } 24 | .content { 25 | font-size: 14px; 26 | } 27 | } 28 | .contentTitle { 29 | margin: 15px 0 10px 0; 30 | } 31 | .item { 32 | display: flex; 33 | justify-content: space-between; 34 | align-items: center; 35 | margin-bottom: 10px; 36 | font-size: 16px; 37 | } 38 | .item.download { 39 | cursor: pointer; 40 | } 41 | .item.code { 42 | cursor: pointer; 43 | height: 20px; 44 | } 45 | .item.code .codeTxt { 46 | overflow: hidden; 47 | text-overflow: ellipsis; 48 | white-space: nowrap; 49 | width: auto; 50 | flex: 1; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /sidepanel/index.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from "~theme" 2 | 3 | import "~index.css" 4 | 5 | import { DownloadOutlined, PushpinOutlined } from "@ant-design/icons" 6 | import qrcodeUrl from "raw:~/public/wx/gzh.jpg" 7 | 8 | import { sendToContentScript } from "@plasmohq/messaging" 9 | import { useMessage } from "@plasmohq/messaging/dist/hook" 10 | import { useStorage } from "@plasmohq/storage/dist/hook" 11 | 12 | import styles from "./index.module.scss" 13 | 14 | function IndexSidePanel() { 15 | const [codes] = useStorage("app-codes", []) 16 | const [summary, setSummary] = useStorage("app-summary", { 17 | title: "", 18 | score: "", 19 | content: "" 20 | }) 21 | 22 | useMessage((req: any, res: any) => { 23 | if (req.name === "isSidePanelOpen") { 24 | return true 25 | } 26 | if (req.name == "sidepanel") { 27 | req.body.active || window.close() 28 | } 29 | }) 30 | 31 | function pinCode(index) { 32 | sendToContentScript({ name: `custom-scrollIntoViewCode`, body: { index } }) 33 | } 34 | 35 | function downloadCode(index) { 36 | sendToContentScript({ name: `custom-downloadCode`, body: { index } }) 37 | } 38 | 39 | return ( 40 | 41 |
42 |
43 |

微信公众号

44 | 微信公众号 45 |
46 |
47 | {summary.content ? ( 48 |
49 |
50 | {summary.title}(评分:{summary.score}) 51 |
52 |
{summary.content}
53 |
54 | ) : ( 55 | <> 56 | )} 57 |

导航

58 | {codes.map((code, index) => ( 59 |
pinCode(index)} 62 | key={index}> 63 | 64 | {index + 1}-{JSON.stringify(code)} 65 | 66 | pinCode(index)} 74 | /> 75 | downloadCode(index)} 83 | /> 84 |
85 | ))} 86 |
87 |
88 |
89 | ) 90 | } 91 | 92 | export default IndexSidePanel 93 | -------------------------------------------------------------------------------- /tabs/feed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | codexbox-支持 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tabs/feed.module.scss: -------------------------------------------------------------------------------- 1 | .feed { 2 | display: flex; 3 | flex-direction: column; 4 | padding: 16px; 5 | :global { 6 | .item { 7 | text-align: center; 8 | .item-img { 9 | width: 80%; 10 | } 11 | .desc { 12 | font-size: 16px; 13 | display: block; 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tabs/feed.tsx: -------------------------------------------------------------------------------- 1 | import chromeUrl from "raw:~/public/webstore/1723513894421.jpg" 2 | import githubUrl from "raw:~/public/webstore/1724640034462.jpg" 3 | import edgeUrl from "raw:~/public/webstore/1724640161752.jpg" 4 | import firefoxUrl from "raw:~/public/webstore/img.png" 5 | import qrcodeUrl from "raw:~/public/wx/gzh.jpg" 6 | 7 | import styles from "./feed.module.scss" 8 | 9 | export default function FeedPage() { 10 | return ( 11 |
12 |
13 |

微信公众号

14 | 微信公众号 15 |
16 | 28 | 51 |
52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /tabs/history.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | codexbox-历史记录 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tabs/history.module.scss: -------------------------------------------------------------------------------- 1 | .history { 2 | display: flex; 3 | flex-direction: column; 4 | padding: 16px; 5 | :global { 6 | .content { 7 | width: 1000px; 8 | margin: 0 auto; 9 | .keyword-lighten { 10 | background-color: gold; 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /theme.tsx: -------------------------------------------------------------------------------- 1 | import ConfigProvider from "antd/es/config-provider" 2 | import type { ReactNode } from "react" 3 | 4 | export const ThemeProvider = ({ children = null as ReactNode }) => ( 5 | 11 | {children} 12 | 13 | ) 14 | -------------------------------------------------------------------------------- /tools.ts: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs" 2 | import { saveAs } from "file-saver" 3 | 4 | import { sendToBackground } from "@plasmohq/messaging" 5 | 6 | export function scrollToTop(element) { 7 | window.scrollTo({ 8 | top: element.offsetTop - 50, 9 | behavior: "smooth" // 可选,平滑滚动 10 | }) 11 | } 12 | 13 | export function addCss(code, id?) { 14 | const style = document.createElement("style") 15 | const css = document.createTextNode(code) 16 | style.setAttribute("data-id", id || "codebox-css") 17 | style.appendChild(css) 18 | document.head.appendChild(style) 19 | } 20 | 21 | export function removeCss(id) { 22 | var style = document.querySelector(`[data-id="${id}"]`) 23 | style && style.remove() 24 | } 25 | 26 | export function addJs(code) { 27 | const script = document.createElement("script") 28 | // const js = document.createTextNode(`(()=>{${code}})()`) 29 | const js = document.createTextNode(code) 30 | script.appendChild(js) 31 | document.head.appendChild(script) 32 | } 33 | 34 | export function setIcon(active: boolean) { 35 | sendToBackground({ 36 | name: "icon", 37 | body: { 38 | active: active 39 | } 40 | }) 41 | } 42 | 43 | export function saveTxt(txt: string, filename?: string) { 44 | if (txt) { 45 | const blob = new Blob([txt], { type: "text/plain;charset=utf-8" }) 46 | filename = filename || "CodeBox-page" 47 | saveAs(blob, `${filename}-${dayjs().format("YYYY-MM-DD HH:mm:ss")}.txt`) 48 | } 49 | } 50 | 51 | export function saveHtml(dom: Element, filename?: string) { 52 | if (dom) { 53 | const htmlContent = dom.outerHTML 54 | const blob = new Blob([htmlContent], { type: "text/html;charset=utf-8" }) 55 | filename = filename || "CodeBox-page" 56 | saveAs(blob, `${filename}-${dayjs().format("YYYY-MM-DD HH:mm:ss")}.html`) 57 | } 58 | } 59 | 60 | export function saveMarkdown(markdown: string, filename?: string) { 61 | if (markdown) { 62 | const blob = new Blob([markdown], { type: "text/markdown;charset=utf-8" }) 63 | filename = filename || "CodeBox-page" 64 | saveAs(blob, `${filename}-${dayjs().format("YYYY-MM-DD HH:mm:ss")}.md`) 65 | } 66 | } 67 | 68 | export function i18n(key: string) { 69 | return chrome.i18n.getMessage(key) 70 | } 71 | 72 | export function getMetaContentByProperty(metaProperty: string) { 73 | const metas = document.getElementsByTagName("meta") 74 | 75 | for (let i = 0; i < metas.length; i++) { 76 | if (metas[i].getAttribute("property") === metaProperty) { 77 | return metas[i].getAttribute("content") 78 | } 79 | } 80 | 81 | return "" 82 | } 83 | 84 | export function isValidUrl(urlString: string) { 85 | try { 86 | return Boolean(new URL(urlString)) 87 | } catch (e) { 88 | return false 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "plasmo/templates/tsconfig.base", 3 | "exclude": [ 4 | "node_modules" 5 | ], 6 | "include": [ 7 | ".plasmo/index.d.ts", 8 | "./**/*.ts", 9 | "./**/*.tsx" 10 | ], 11 | "compilerOptions": { 12 | "paths": { 13 | "~*": [ 14 | "./*" 15 | ] 16 | }, 17 | "baseUrl": "." 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /utils/coze.ts: -------------------------------------------------------------------------------- 1 | const cozeUrl = process.env.PLASMO_PUBLIC_COZE_URL 2 | const token = process.env.PLASMO_PUBLIC_COZE_TOKEN 3 | const workflowId = process.env.PLASMO_PUBLIC_COZE_WORKFLOWID 4 | const appId = process.env.PLASMO_PUBLIC_COZE_APPID 5 | 6 | export async function getSummary(url: string) { 7 | const data = { 8 | workflow_id: workflowId, 9 | app_id: appId, 10 | parameters: { 11 | url: url 12 | } 13 | } 14 | try { 15 | const res = await fetch(cozeUrl, { 16 | method: "POST", 17 | headers: { 18 | Authorization: `Bearer ${token}`, 19 | "Content-Type": "application/json" 20 | }, 21 | body: JSON.stringify(data) 22 | }).then((response) => response.json()) // 解析返回的 JSON 数据 23 | return res 24 | } catch (error) { 25 | console.error("Error:", error) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /utils/cssCodeHook.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react" 2 | 3 | import { useStorage } from "@plasmohq/storage/dist/hook" 4 | 5 | import { addCss, removeCss } from "~tools" 6 | 7 | export default function useCssCodeHook(name) { 8 | const [cssCode] = useStorage(`${name}-cssCode`) 9 | const [runCss] = useStorage(`${name}-runCss`) 10 | const [closeLog] = useStorage("config-closeLog", true) 11 | 12 | useEffect(() => { 13 | runCssFunc(runCss) 14 | }, [runCss]) 15 | 16 | /* 插入自定义css代码 */ 17 | function runCssFunc(runCss) { 18 | const id = `${name}-css` 19 | closeLog || console.log(`${name} 插入自定义css代码`, { cssCode, runCss }) 20 | runCss ? addCss(cssCode, id) : removeCss(id) 21 | } 22 | 23 | return [cssCode, runCss] 24 | } 25 | -------------------------------------------------------------------------------- /utils/downloadAllImg.ts: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs" 2 | import { saveAs } from "file-saver" 3 | import JSZip from "jszip" 4 | 5 | export async function downloadAllImagesAsZip(filename?: string) { 6 | const zip = new JSZip() // 创建 ZIP 文件 7 | const imgFolder = zip.folder("images") // 创建一个文件夹存储图片 8 | const images = document.querySelectorAll("img") // 获取所有 img 元素 9 | const fetchPromises = [] 10 | 11 | images.forEach((img, index) => { 12 | fetchPromises.push( 13 | new Promise((resolve) => { 14 | fetch(img.src) 15 | .then((response) => response.blob()) 16 | .then((blob) => { 17 | const imgElement = new Image() 18 | imgElement.crossOrigin = "Anonymous" 19 | imgElement.src = URL.createObjectURL(blob) 20 | imgElement.onload = function () { 21 | const canvas = document.createElement("canvas") 22 | canvas.width = imgElement.naturalWidth 23 | canvas.height = imgElement.naturalHeight 24 | const context = canvas.getContext("2d") 25 | context.drawImage(imgElement, 0, 0) // 将图片绘制到 Canvas 26 | canvas.toBlob((blob) => { 27 | const ext = "png" // 使用 PNG 格式 28 | imgFolder.file(`image_${index + 1}.${ext}`, blob) 29 | }) 30 | } 31 | imgElement.onerror = resolve 32 | }) 33 | }) 34 | ) 35 | }) 36 | 37 | await Promise.all(fetchPromises) // 等待所有图片处理完成 38 | 39 | zip 40 | .generateAsync({ type: "blob" }) // 生成 ZIP 文件 41 | .then((content) => { 42 | filename = filename || "CodeBox-page" 43 | saveAs( 44 | content, 45 | `${filename}-${dayjs().format("YYYY-MM-DD HH:mm:ss")}.zip` 46 | ) // 使用 FileSaver 保存 ZIP 文件 47 | }) 48 | .catch((err) => console.error(`Error generating ZIP: ${err}`)) 49 | } 50 | -------------------------------------------------------------------------------- /utils/drawImages.ts: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs" 2 | import { saveAs } from "file-saver" 3 | 4 | export default function DrawImages(images, filename) { 5 | if (images.length === 0) { 6 | console.error("No images captured.") 7 | return 8 | } 9 | 10 | const canvas = document.createElement("canvas") 11 | const context = canvas.getContext("2d") 12 | const firstImage = new Image() 13 | firstImage.onload = () => { 14 | canvas.width = firstImage.width 15 | canvas.height = images.length * firstImage.height 16 | 17 | let imagesLoaded = 0 18 | 19 | const drawImageOnCanvas = (image, index) => { 20 | context.drawImage(image, 0, index * firstImage.height) 21 | imagesLoaded++ 22 | 23 | if (imagesLoaded === images.length) { 24 | filename = filename || "CodeBox-page" 25 | canvas.toBlob(function (blob) { 26 | saveAs( 27 | blob, 28 | `${filename}-${dayjs().format("YYYY-MM-DD HH:mm:ss")}.png` 29 | ) 30 | }) 31 | } 32 | } 33 | 34 | images.forEach((dataUrl, index) => { 35 | const image = new Image() 36 | image.onload = () => drawImageOnCanvas(image, index) 37 | image.src = dataUrl 38 | }) 39 | } 40 | 41 | firstImage.src = images[0] 42 | } 43 | -------------------------------------------------------------------------------- /utils/editMarkdownHook.ts: -------------------------------------------------------------------------------- 1 | import { Storage } from "@plasmohq/storage" 2 | import { useStorage } from "@plasmohq/storage/dist/hook" 3 | 4 | import Turndown from "~utils/turndown" 5 | 6 | export function useEditMarkdown(option?) { 7 | const turndownService = Turndown(option) 8 | 9 | const [post, setPost] = useStorage({ 10 | key: "md-post", 11 | instance: new Storage({ 12 | area: "local" 13 | }) 14 | }) 15 | 16 | const handleSetPost = (selectorDom, articleTitle) => { 17 | const content = turndownService.turndown(selectorDom) 18 | const post = { 19 | content: content, 20 | title: articleTitle 21 | } 22 | setPost(JSON.stringify(post)) 23 | window.open("https://md.code-box.fun", "_blank") 24 | } 25 | 26 | return [post, handleSetPost] 27 | } 28 | -------------------------------------------------------------------------------- /utils/encryption.ts: -------------------------------------------------------------------------------- 1 | import * as CryptoJS from "crypto-js" 2 | 3 | export interface EncryptionInterface { 4 | getEncryptedString(data: string): string 5 | decryptSecretString(secret: string): string | null 6 | decryptEncSecret(entry: any): any | null 7 | getEncryptionStatus(): boolean 8 | updateEncryptionPassword(password: string): void 9 | setEncryptionKeyId(id: string): void 10 | getEncryptionKeyId(): string 11 | } 12 | 13 | export class Encryption implements EncryptionInterface { 14 | private password: string 15 | private keyId: string 16 | 17 | constructor(hash: string, keyId: string) { 18 | this.password = hash 19 | this.keyId = keyId 20 | } 21 | 22 | getEncryptedString(data: string): string { 23 | if (!this.password) { 24 | return data 25 | } 26 | return CryptoJS.AES.encrypt(data, this.password).toString() 27 | } 28 | 29 | decryptSecretString(secret: string): string | null { 30 | try { 31 | if (!this.password) { 32 | return secret 33 | } 34 | 35 | const decryptedSecret = CryptoJS.AES.decrypt( 36 | secret, 37 | this.password 38 | ).toString(CryptoJS.enc.Utf8) 39 | 40 | if (!decryptedSecret) { 41 | return null 42 | } 43 | 44 | if (decryptedSecret.length < 8) { 45 | return null 46 | } 47 | 48 | // Validate secret format 49 | if ( 50 | !/^[A-Z2-7]+=*$/i.test(decryptedSecret) && // Base32 format 51 | !/^[0-9a-f]+$/i.test(decryptedSecret) && // Hex format 52 | !/^blz-/.test(decryptedSecret) && // Blizzard format 53 | !/^bliz-/.test(decryptedSecret) && // Alternative Blizzard format 54 | !/^stm-/.test(decryptedSecret) // Steam format 55 | ) { 56 | return null 57 | } 58 | 59 | return decryptedSecret 60 | } catch (error) { 61 | return null 62 | } 63 | } 64 | 65 | decryptEncSecret(entry: any): any | null { 66 | try { 67 | if (!entry.encData || !this.password) { 68 | return null 69 | } 70 | 71 | const decryptedData = CryptoJS.AES.decrypt( 72 | entry.encData, 73 | this.password 74 | ).toString(CryptoJS.enc.Utf8) 75 | 76 | if (!decryptedData) { 77 | return null 78 | } 79 | 80 | return JSON.parse(decryptedData) 81 | } catch (error) { 82 | return null 83 | } 84 | } 85 | 86 | getEncryptionStatus(): boolean { 87 | return Boolean(this.password) 88 | } 89 | 90 | updateEncryptionPassword(password: string): void { 91 | this.password = password 92 | } 93 | 94 | setEncryptionKeyId(id: string): void { 95 | this.keyId = id 96 | } 97 | 98 | getEncryptionKeyId(): string { 99 | return this.keyId 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /utils/parseMarkdownHook.ts: -------------------------------------------------------------------------------- 1 | import { Storage } from "@plasmohq/storage" 2 | import { useStorage } from "@plasmohq/storage/dist/hook" 3 | 4 | import Turndown from "~utils/turndown" 5 | 6 | export function useParseMarkdown(option?) { 7 | const turndownService = Turndown(option) 8 | 9 | const [aiContent, setAiContent] = useStorage({ 10 | key: "ai-content", 11 | instance: new Storage({ 12 | area: "local" 13 | }) 14 | }) 15 | 16 | const [aiType, setAiType] = useStorage("app-aiType") 17 | 18 | const handleSetContent = (selectorDom, type?) => { 19 | let markdown = turndownService.turndown(selectorDom) 20 | markdown = `${markdown} 21 | 将上面的文字,翻译成中文并生成markdown` 22 | 23 | setAiContent(markdown) 24 | type = type || aiType 25 | 26 | switch (type) { 27 | case "kimi": 28 | window.open("https://kimi.moonshot.cn/", "_blank") 29 | break 30 | case "chatgpt": 31 | window.open("https://chatgpt.com", "_blank") 32 | break 33 | case "deepseek": 34 | window.open("https://chat.deepseek.com/", "_blank") 35 | break 36 | } 37 | } 38 | 39 | return [aiContent, handleSetContent] 40 | } 41 | -------------------------------------------------------------------------------- /utils/totp.ts: -------------------------------------------------------------------------------- 1 | import * as CryptoJS from "crypto-js" 2 | 3 | export class TOTP { 4 | private static readonly DIGITS: number = 6 5 | private static readonly PERIOD: number = 30 6 | 7 | /** 8 | * Generate a TOTP code from a secret key 9 | * @param secret Base32 encoded secret key 10 | * @returns TOTP code 11 | */ 12 | public static generateTOTP(secret: string): string { 13 | const epoch = Math.floor(Date.now() / 1000) 14 | const timeCounter = Math.floor(epoch / this.PERIOD) 15 | return this.generateTOTPAtCounter(secret, timeCounter) 16 | } 17 | 18 | /** 19 | * Validate if a secret key is in valid Base32 format 20 | * @param secret The secret key to validate 21 | * @returns boolean indicating if the secret is valid 22 | */ 23 | public static isValidSecret(secret: string): boolean { 24 | const base32Regex = /^[A-Z2-7]+=*$/ 25 | return base32Regex.test(secret.toUpperCase()) 26 | } 27 | 28 | /** 29 | * Generate TOTP at a specific counter value 30 | * @param secret Base32 encoded secret key 31 | * @param counter Time counter 32 | * @returns TOTP code 33 | */ 34 | private static generateTOTPAtCounter( 35 | secret: string, 36 | counter: number 37 | ): string { 38 | const decodedSecret = this.base32ToHex(secret) 39 | const timeHex = this.leftPad(counter.toString(16), 16) 40 | 41 | // Convert hex to WordArray for crypto-js 42 | const key = CryptoJS.enc.Hex.parse(decodedSecret) 43 | const message = CryptoJS.enc.Hex.parse(timeHex) 44 | 45 | // Calculate HMAC-SHA1 46 | const hmac = CryptoJS.HmacSHA1(message, key) 47 | const hmacResult = hmac.toString() 48 | 49 | const offset = parseInt(hmacResult.slice(-1), 16) 50 | const code = parseInt(hmacResult.substr(offset * 2, 8), 16) & 0x7fffffff 51 | 52 | return this.leftPad( 53 | (code % Math.pow(10, this.DIGITS)).toString(), 54 | this.DIGITS 55 | ) 56 | } 57 | 58 | /** 59 | * Convert Base32 string to hexadecimal 60 | * @param base32 Base32 encoded string 61 | * @returns Hexadecimal string 62 | */ 63 | private static base32ToHex(base32: string): string { 64 | const base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" 65 | let bits = "" 66 | const hex = [] 67 | 68 | base32 = base32.toUpperCase().replace(/=+$/, "") 69 | 70 | for (let i = 0; i < base32.length; i++) { 71 | const val = base32Chars.indexOf(base32.charAt(i)) 72 | if (val === -1) throw new Error("Invalid base32 character") 73 | bits += this.leftPad(val.toString(2), 5) 74 | } 75 | 76 | for (let i = 0; i + 8 <= bits.length; i += 8) { 77 | const chunk = bits.substr(i, 8) 78 | hex.push(parseInt(chunk, 2).toString(16).padStart(2, "0")) 79 | } 80 | 81 | return hex.join("") 82 | } 83 | 84 | /** 85 | * Left pad a string with zeros 86 | * @param str String to pad 87 | * @param len Desired length 88 | * @returns Padded string 89 | */ 90 | private static leftPad(str: string, len: number): string { 91 | return str.padStart(len, "0") 92 | } 93 | 94 | /** 95 | * Verify if the input code is valid for the given secret 96 | * @param secret Base32 encoded secret key 97 | * @param inputCode The code entered by the user 98 | * @returns boolean indicating if the code is valid 99 | */ 100 | public static verifyTOTP(secret: string, inputCode: string): boolean { 101 | const currentTOTP = this.generateTOTP(secret) 102 | return currentTOTP === inputCode 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /utils/turndown.ts: -------------------------------------------------------------------------------- 1 | import TurndownService from "@joplin/turndown" 2 | 3 | var turndownPluginGfm = require("@joplin/turndown-plugin-gfm") 4 | 5 | export default function Turndown(options?) { 6 | const gfm = turndownPluginGfm.gfm 7 | const tables = turndownPluginGfm.tables 8 | const strikethrough = turndownPluginGfm.strikethrough 9 | const turndownService = new TurndownService({ 10 | headingStyle: "atx", 11 | codeBlockStyle: "fenced" 12 | }) 13 | 14 | turndownService.use(gfm) 15 | turndownService.use(tables, strikethrough) 16 | 17 | // turndownService.keep(["h1", "h2"]) 18 | turndownService.remove(["script", "style"]) 19 | 20 | const addRules = options?.addRules 21 | for (let key in addRules) { 22 | const rule = addRules[key] 23 | if (rule) { 24 | turndownService.addRule(key, rule) 25 | } 26 | } 27 | 28 | return turndownService 29 | } 30 | --------------------------------------------------------------------------------