├── .env.example ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .prettierrc.yaml ├── README.md ├── docs ├── wrp_demo.gif └── wrp_demo_2.gif ├── netlify.toml ├── package.json ├── packages ├── core │ ├── build.js │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── Dictionary.ts │ │ ├── ReadHistory.md │ │ ├── ReadHistory.ts │ │ ├── Translator.ts │ │ ├── core │ │ │ ├── detect.ts │ │ │ ├── extract.ts │ │ │ ├── getTarget.ts │ │ │ ├── index.ts │ │ │ └── summary.ts │ │ ├── db │ │ │ ├── index.ts │ │ │ └── indexed.ts │ │ ├── index.ts │ │ ├── module.d.ts │ │ ├── theme.ts │ │ ├── types │ │ │ ├── index.ts │ │ │ ├── message.ts │ │ │ └── type.ts │ │ └── utils │ │ │ ├── analytics.ts │ │ │ ├── dom.ts │ │ │ ├── host.ts │ │ │ ├── marker.ts │ │ │ ├── request.ts │ │ │ ├── setting.ts │ │ │ └── touch.ts │ └── tsconfig.json ├── extension │ ├── README.md │ ├── build.js │ ├── esbuild │ │ └── svgr.js │ ├── package.json │ ├── postcss.config.js │ ├── res │ │ ├── chrome │ │ │ └── manifest.json │ │ ├── chrome_v3 │ │ │ └── manifest.json │ │ ├── firefox │ │ │ └── manifest.json │ │ ├── firefox_v3 │ │ │ └── manifest.json │ │ └── share │ │ │ ├── _locales │ │ │ ├── en │ │ │ │ └── messages.json │ │ │ ├── en_GB │ │ │ │ └── messages.json │ │ │ ├── en_US │ │ │ │ └── messages.json │ │ │ ├── zh_CN │ │ │ │ └── messages.json │ │ │ └── zh_TW │ │ │ │ └── messages.json │ │ │ ├── logo-128.png │ │ │ ├── logo-16.png │ │ │ ├── logo-48.png │ │ │ └── logo.png │ ├── src │ │ ├── components │ │ │ ├── Popup │ │ │ │ ├── CoverOrEnable │ │ │ │ │ ├── index.style.ts │ │ │ │ │ └── index.tsx │ │ │ │ ├── EnableToggle │ │ │ │ │ └── index.tsx │ │ │ │ ├── Footer │ │ │ │ │ ├── index.style.ts │ │ │ │ │ └── index.tsx │ │ │ │ ├── Header │ │ │ │ │ ├── MoreMenu │ │ │ │ │ │ ├── index.style.ts │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── index.style.ts │ │ │ │ │ └── index.tsx │ │ │ │ ├── ModeOption │ │ │ │ │ ├── Items.tsx │ │ │ │ │ ├── index.style.ts │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── items.style.ts │ │ │ │ ├── PopupContext.ts │ │ │ │ ├── index.style.ts │ │ │ │ ├── index.tsx │ │ │ │ └── reducer │ │ │ │ │ ├── actions.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── reducer.ts │ │ │ └── index.tsx │ │ ├── content │ │ │ ├── App │ │ │ │ ├── PopupCard │ │ │ │ │ ├── index.style.ts │ │ │ │ │ └── index.tsx │ │ │ │ ├── context.ts │ │ │ │ ├── index.tsx │ │ │ │ └── reducer │ │ │ │ │ ├── action.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── reducer.ts │ │ │ ├── README.md │ │ │ ├── index.tsx │ │ │ ├── message.ts │ │ │ └── root.tsx │ │ ├── module.d.ts │ │ ├── pages │ │ │ ├── Popup │ │ │ │ ├── index.html │ │ │ │ └── index.tsx │ │ │ ├── background │ │ │ │ ├── background.ts │ │ │ │ └── index.ts │ │ │ ├── content-frame │ │ │ │ ├── index.html │ │ │ │ └── index.ts │ │ │ ├── dev-content │ │ │ │ ├── index.html │ │ │ │ └── index.tsx │ │ │ ├── index │ │ │ │ ├── index.html │ │ │ │ └── index.tsx │ │ │ ├── options │ │ │ │ ├── index.html │ │ │ │ └── index.tsx │ │ │ └── popup │ │ │ │ ├── index.html │ │ │ │ └── index.tsx │ │ ├── resource │ │ │ ├── icon │ │ │ │ ├── open-window.svg │ │ │ │ ├── trigger-all.svg │ │ │ │ ├── trigger-disable.svg │ │ │ │ └── trigger-main.svg │ │ │ ├── images │ │ │ │ └── netlify.png │ │ │ └── svg │ │ │ │ └── logo-name.svg │ │ ├── service │ │ │ ├── action.ts │ │ │ ├── content.ts │ │ │ ├── contextMenus.ts │ │ │ ├── core.ts │ │ │ ├── index.ts │ │ │ ├── message.ts │ │ │ ├── runtime.ts │ │ │ └── tabs.ts │ │ ├── style │ │ │ └── global.css │ │ ├── types │ │ │ ├── extension.ts │ │ │ ├── index.ts │ │ │ ├── message.ts │ │ │ └── storage │ │ │ │ ├── index.ts │ │ │ │ ├── local.ts │ │ │ │ ├── session.ts │ │ │ │ └── sync.ts │ │ ├── uitls │ │ │ ├── config.ts │ │ │ ├── extension.ts │ │ │ ├── setting.ts │ │ │ └── util.ts │ │ └── worker │ │ │ ├── loader.ts │ │ │ └── service.worker.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ └── webpack.config.js ├── inject │ ├── build.js │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── App │ │ │ ├── App.tsx │ │ │ ├── CSSGlobal.tsx │ │ │ ├── app.style.ts │ │ │ ├── index.ts │ │ │ ├── reducer │ │ │ │ ├── action.ts │ │ │ │ ├── index.ts │ │ │ │ └── reducer.ts │ │ │ ├── useClickAway.ts │ │ │ ├── useMessage.ts │ │ │ └── useSetup.ts │ │ ├── content │ │ │ ├── extension.ts │ │ │ ├── filter.ts │ │ │ ├── handler │ │ │ │ ├── handler.ts │ │ │ │ ├── index.ts │ │ │ │ ├── pointer.ts │ │ │ │ └── simple.ts │ │ │ ├── index.ts │ │ │ ├── message.ts │ │ │ ├── options.ts │ │ │ ├── parent.ts │ │ │ ├── utils.ts │ │ │ └── website.ts │ │ ├── index.tsx │ │ ├── module.d.ts │ │ ├── polyfill │ │ │ ├── history.ts │ │ │ └── index.ts │ │ ├── type │ │ │ └── index.ts │ │ └── website.tsx │ └── tsconfig.json ├── ui │ ├── build.js │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── components │ │ │ ├── AnchorModal │ │ │ │ ├── index.style.ts │ │ │ │ └── index.tsx │ │ │ ├── CoverLayer │ │ │ │ ├── index.style.ts │ │ │ │ └── index.tsx │ │ │ ├── Explanation │ │ │ │ ├── Answer.tsx │ │ │ │ ├── Explanation.style.tsx │ │ │ │ ├── Explanation.tsx │ │ │ │ ├── Loading.tsx │ │ │ │ ├── More.tsx │ │ │ │ ├── Nothing.tsx │ │ │ │ ├── Pronunciation.tsx │ │ │ │ ├── SvgBorder.tsx │ │ │ │ ├── config.ts │ │ │ │ ├── index.ts │ │ │ │ └── usePosition.ts │ │ │ ├── Point.tsx │ │ │ ├── README.md │ │ │ └── Translation │ │ │ │ ├── Box.style.ts │ │ │ │ ├── Box.tsx │ │ │ │ ├── Card.tsx │ │ │ │ ├── README.md │ │ │ │ ├── Translation.tsx │ │ │ │ ├── boxConfig.ts │ │ │ │ ├── index.ts │ │ │ │ └── usePosition.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useEscapeHide.ts │ │ │ ├── useFontSize.ts │ │ │ ├── useIsBottomSheet.ts │ │ │ ├── useObserver.ts │ │ │ └── useSwipeSheet.ts │ │ ├── index.tsx │ │ ├── module.d.ts │ │ └── style │ │ │ ├── _mixins.scss │ │ │ └── index.ts │ └── tsconfig.json ├── website-view │ ├── netlify.toml │ ├── package.json │ ├── src │ │ └── index.html │ └── yarn.lock └── website │ ├── README.md │ ├── assets │ ├── illustration │ │ ├── history.svg │ │ ├── home_first_left.svg │ │ ├── home_first_right.svg │ │ ├── home_second.svg │ │ ├── offline.svg │ │ ├── tree_space.svg │ │ └── word_browse.svg │ ├── logo.svg │ ├── logo_name.svg │ └── svg │ │ ├── about.svg │ │ ├── book.svg │ │ ├── clean.svg │ │ ├── explore.svg │ │ ├── github.svg │ │ ├── js.svg │ │ ├── no_js.svg │ │ ├── start.svg │ │ └── word.svg │ ├── cache.js │ ├── components │ ├── Blank │ │ ├── BlankProgress.tsx │ │ ├── BlankReadHistory.tsx │ │ └── BlankWordHistory.tsx │ ├── BottomNav │ │ ├── index.style.ts │ │ └── index.tsx │ ├── Explore │ │ ├── NavigationBar.style.ts │ │ ├── NavigationBar.tsx │ │ ├── SkeletonItem.tsx │ │ ├── SwipeableView.tsx │ │ └── _cardContainer.scss │ ├── Home │ │ ├── GoBar.style.ts │ │ ├── GoBar.tsx │ │ ├── ItemCard.style.ts │ │ ├── ItemCard.tsx │ │ └── index.ts │ ├── Integration │ │ ├── GoogleAnalytics.tsx │ │ ├── TawkWidget.tsx │ │ ├── TidioChat.tsx │ │ └── index.ts │ ├── KeepAlivePage.tsx │ ├── Me │ │ └── ReadHistoryItem.tsx │ ├── PageLoading.tsx │ ├── QRScanner.tsx │ ├── README.md │ ├── View │ │ ├── Content │ │ │ ├── Blank.tsx │ │ │ ├── Failed.tsx │ │ │ ├── README.md │ │ │ ├── ReaderMode.tsx │ │ │ ├── index.ts │ │ │ ├── render.d.ts │ │ │ ├── render.tsx │ │ │ └── style.ts │ │ ├── Control │ │ │ ├── Address.tsx │ │ │ ├── Options.tsx │ │ │ ├── SiteProfile.tsx │ │ │ └── index.tsx │ │ ├── FrameView │ │ │ ├── index.style.ts │ │ │ └── index.tsx │ │ ├── Share │ │ │ └── index.tsx │ │ ├── ViewContext.ts │ │ ├── agent │ │ │ ├── content.ts │ │ │ ├── fallback.ts │ │ │ ├── index.ts │ │ │ ├── options.ts │ │ │ ├── pipeline │ │ │ │ ├── doc.ts │ │ │ │ ├── history.ts │ │ │ │ ├── index.ts │ │ │ │ ├── inject.ts │ │ │ │ ├── noscript.ts │ │ │ │ ├── readerMode.tsx │ │ │ │ └── recap.ts │ │ │ ├── precheck.ts │ │ │ ├── proxy.ts │ │ │ └── type.d.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useInitialize.ts │ │ │ ├── useLoad.ts │ │ │ ├── useMessage.ts │ │ │ └── useSetup.ts │ │ ├── index.ts │ │ ├── reducer │ │ │ ├── actions.ts │ │ │ ├── index.ts │ │ │ └── reducer.ts │ │ └── utils │ │ │ ├── analytics.ts │ │ │ ├── encoding.ts │ │ │ ├── key.ts │ │ │ └── utils.ts │ ├── Word │ │ ├── WordItem.tsx │ │ └── index.ts │ └── blank │ │ ├── BlankReadHistory.tsx │ │ └── BlankWordHistory.tsx │ ├── data │ ├── explore │ │ ├── computerDocs.ts │ │ ├── gettingStarted.ts │ │ ├── index.ts │ │ ├── music.ts │ │ ├── navigation.ts │ │ ├── news.ts │ │ ├── other.ts │ │ └── recommended.ts │ └── index.ts │ ├── hooks │ └── useRepo.ts │ ├── module.d.ts │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── pages │ ├── 404 │ │ └── index.tsx │ ├── _app.tsx │ ├── _document.tsx │ ├── _offline.tsx │ ├── about │ │ ├── index.style.ts │ │ └── index.tsx │ ├── domains │ │ └── index.tsx │ ├── experiment │ │ ├── content │ │ │ └── blank.tsx │ │ ├── core │ │ │ └── index.tsx │ │ ├── cover.tsx │ │ ├── db.tsx │ │ ├── index.tsx │ │ ├── loading.tsx │ │ ├── offline.tsx │ │ ├── svg.tsx │ │ ├── t.tsx │ │ └── ui.tsx │ ├── explore │ │ ├── index.tsx │ │ ├── old.module.scss │ │ └── old.tsx │ ├── extension │ │ ├── index.tsx │ │ └── survey.tsx │ ├── home.tsx │ ├── index.style.ts │ ├── index.tsx │ ├── me │ │ └── read-history.tsx │ ├── privacy │ │ └── index.tsx │ ├── reading │ │ └── index.tsx │ ├── redirect.tsx │ ├── start │ │ ├── index.tsx │ │ └── qr.tsx │ └── word │ │ ├── index.style.ts │ │ └── index.tsx │ ├── postcss.config.js │ ├── public │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-167x167.png │ ├── apple-touch-icon-80x80.png │ ├── apple-touch-icon.png │ ├── apple │ │ ├── 1170_2532.png │ │ ├── 1640_2360.png │ │ └── 1668_2388.png │ ├── content.js │ ├── deep-reading.svg │ ├── favicon.png │ ├── google534dab6ae99cffb0.html │ ├── img │ │ ├── Friends_logo.jpeg │ │ └── Friends_logo.png │ ├── logo.png │ ├── logo.svg │ ├── logo_180.png │ ├── logo_512.png │ ├── logo_512_maskable.png │ ├── logo_fallback.svg │ ├── manifest.json │ ├── qrcode_for_weixin.jpg │ ├── robots.txt │ └── wrp_demo.gif │ ├── store │ ├── actions.ts │ ├── context.ts │ ├── index.ts │ └── reducer.ts │ ├── styles │ ├── card.style.ts │ ├── global.css │ └── nprogress.scss │ ├── tailwind.config.js │ ├── test │ └── html │ │ ├── index.ts │ │ └── typescript.com_html.ts │ ├── tsconfig.json │ └── utils │ ├── contentful.ts │ ├── history.ts │ ├── image.ts │ └── index.ts ├── pnpm-workspace.yaml ├── tasks ├── build.bat ├── build.sh ├── codepack.sh └── start.py └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | LOOKUP_API="'https://1773134661611650.cn-shanghai.fc.aliyuncs.com/2016-08-15/proxy/wrp.LATEST/wrp_server/iciba'" 2 | TRANSLATE_API="'https://1773134661611650.cn-shanghai.fc.aliyuncs.com/2016-08-15/proxy/wrp.LATEST/wrp_server/alimt'" 3 | SHANGHAI_PROXY_API="'https://1773134661611650.cn-shanghai.fc.aliyuncs.com/2016-08-15/proxy/wrp.LATEST/wrp_server/proxy'" 4 | TOKYO_PROXY_API="'https://1773134661611650.cn-hongkong.fc.aliyuncs.com/2016-08-15/proxy/wrp_hk.LATEST/wrp_server_hk/proxy'" 5 | 6 | VIEW_SRC="'https://vw-ox8hv.ondigitalocean.app/'" 7 | 8 | CONTENTFUL_SPACE_ID="'3ry...'" 9 | CONTENTFUL_CDA_TOKEN="'NPQ...'" 10 | CONTENTFUL_ENV_ID="'master'" 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # production 7 | build/ 8 | dist/ 9 | **/es/ 10 | .next/ 11 | **/website/out/ 12 | 13 | **/public/service-worker.js 14 | **/public/workbox-*.js 15 | **/public/*.js.map 16 | **/public/fallback-*.js 17 | 18 | tasks/deep-reading-code 19 | tasks/deep-reading-code.zip 20 | PLAN.md 21 | test/ 22 | 23 | material/ 24 | 25 | 26 | *.del 27 | **/.env 28 | **/.env.test 29 | 30 | 31 | # misc 32 | .idea 33 | .DS_Store 34 | .env.local 35 | .env.development.local 36 | .env.test.local 37 | .env.production.local 38 | .eslintcache 39 | .sass-cache 40 | 41 | ### required for digitalocean deployment 42 | # yarn.lock 43 | 44 | pnpm-lock.yaml 45 | npm-debug.log* 46 | yarn-debug.log* 47 | yarn-error.log* 48 | .pnpm-debug.log 49 | 50 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | # .prettierrc or .prettierrc.yaml 2 | trailingComma: "es5" 3 | tabWidth: 4 4 | semi: false 5 | singleQuote: true -------------------------------------------------------------------------------- /docs/wrp_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baotlake/deep-reading/e230b02bda2dd4c1e54d5c93c23ae51909ac23d9/docs/wrp_demo.gif -------------------------------------------------------------------------------- /docs/wrp_demo_2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baotlake/deep-reading/e230b02bda2dd4c1e54d5c93c23ae51909ac23d9/docs/wrp_demo_2.gif -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | base = "./" 3 | publish = "packages/website/out" 4 | command = "yarn export" 5 | 6 | # [[plugins]] 7 | # package = "@netlify/plugin-nextjs" 8 | -------------------------------------------------------------------------------- /packages/core/build.js: -------------------------------------------------------------------------------- 1 | const esbuild = require('esbuild') 2 | const { exec } = require('node:child_process') 3 | 4 | esbuild.build({ 5 | entryPoints: ['./src/index.ts'], 6 | bundle: true, 7 | outdir: 'es', 8 | format: 'esm', 9 | watch: { 10 | onRebuild(err, result) { 11 | if (!err) { 12 | console.log('watch build succeded: ', result) 13 | exec('npx tsc --emitDeclarationOnly') 14 | } 15 | } 16 | }, 17 | external: [] 18 | }) 19 | 20 | exec('npx tsc --emitDeclarationOnly') 21 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wrp/core", 3 | "version": "0.0.1", 4 | "private": true, 5 | "author": "baotlake baotlake@gmail.com", 6 | "license": "SEE LISENSE IN LISENSE.md", 7 | "scripts": { 8 | "start:fast": "node build.js", 9 | "start": "rollup -c rollup.config.js -w", 10 | "build": "rollup -c rollup.config.js" 11 | }, 12 | "main": "es/index.js", 13 | "module": "es/index.js", 14 | "types": "es/index.d.ts", 15 | "files": [ 16 | "es/*", 17 | "src/*", 18 | "dist/*" 19 | ], 20 | "eslintConfig": { 21 | "extends": "react-app" 22 | }, 23 | "browserslist": { 24 | "production": [ 25 | ">0.2%", 26 | "not dead", 27 | "not op_mini all" 28 | ], 29 | "development": [ 30 | "last 1 chrome version", 31 | "last 1 firefox version", 32 | "last 1 safari version" 33 | ] 34 | }, 35 | "dependencies": { 36 | "@types/lodash": "^4.14.170", 37 | "@types/mark.js": "^8.11.8", 38 | "mark.js": "^8.11.1" 39 | }, 40 | "devDependencies": { 41 | "@rollup/plugin-alias": "^3.1.9", 42 | "@rollup/plugin-commonjs": "^21.0.1", 43 | "@rollup/plugin-node-resolve": "^13.1.3", 44 | "@rollup/plugin-replace": "^3.0.1", 45 | "@rollup/plugin-typescript": "^8.3.0", 46 | "@types/node": "^18.11.0", 47 | "cross-env": "^7.0.3", 48 | "esbuild": "^0.14.10", 49 | "rollup": "^2.67.0", 50 | "rollup-plugin-terser": "^7.0.2", 51 | "rollup-plugin-typescript2": "^0.31.2", 52 | "rollup-plugin-visualizer": "^5.6.0" 53 | }, 54 | "babel": { 55 | "plugins": [ 56 | "lodash" 57 | ], 58 | "presets": [ 59 | "@babel/preset-react", 60 | "@babel/env" 61 | ] 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/core/rollup.config.js: -------------------------------------------------------------------------------- 1 | // import typescript from "@rollup/plugin-typescript" 2 | import typescript from "rollup-plugin-typescript2"; 3 | import { nodeResolve } from "@rollup/plugin-node-resolve"; 4 | import commonjs from "@rollup/plugin-commonjs"; 5 | import replace from "@rollup/plugin-replace"; 6 | 7 | import path from "path"; 8 | import dotenv from "dotenv"; 9 | 10 | const env = 11 | dotenv.config({ 12 | path: path.join(__dirname, "../../.env.test"), 13 | }).parsed || {}; 14 | 15 | const define = Object.keys(env).reduce( 16 | (p, a) => ({ ...p, ["process.env." + a]: env[a] }), 17 | {} 18 | ); 19 | 20 | const config = { 21 | input: ["./src/index.ts"], 22 | output: { 23 | dir: "./es", 24 | format: "es", 25 | preserveModules: true, 26 | preserveModulesRoot: "./src", 27 | }, 28 | plugins: [typescript()], 29 | }; 30 | 31 | 32 | export default config; 33 | -------------------------------------------------------------------------------- /packages/core/src/ReadHistory.md: -------------------------------------------------------------------------------- 1 | 2 | ```Typescript 3 | interface HistoryItem { 4 | href: string // page url 5 | title: string // page title 6 | icon: string // page favicon 7 | description: string // page description 8 | scrollXY: [number, number] // page scroll position 9 | createdAt: number // 首次打开时间戳 10 | updatedAt: number // 最后阅读时间戳 11 | time: number // 有效阅读时间 12 | } 13 | ``` -------------------------------------------------------------------------------- /packages/core/src/Translator.ts: -------------------------------------------------------------------------------- 1 | import { translateApi } from './utils/request' 2 | 3 | type Data = { 4 | original: string 5 | translation: string 6 | } 7 | 8 | export class Translator { 9 | constructor() { 10 | } 11 | 12 | public async translate(text: string) { 13 | const body = new URLSearchParams() 14 | body.append('text', text) 15 | 16 | const data = await translateApi(body) 17 | console.log('translate data', data) 18 | 19 | return { 20 | original: text, 21 | translated: data.Data.Translated 22 | } 23 | } 24 | 25 | static detectLang(text: string) { 26 | // 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/src/core/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export { extractWordRange, extractSentenceRange } from './extract' 3 | export { getTarget, getTargetByPoint } from './getTarget' 4 | 5 | export { detectRefusedDisplay, detectCSP, isArticleContent } from './detect' 6 | 7 | export { 8 | abstract, 9 | recapTitle, 10 | recapDescription, 11 | } from './summary' -------------------------------------------------------------------------------- /packages/core/src/db/index.ts: -------------------------------------------------------------------------------- 1 | export { open } from './indexed' 2 | -------------------------------------------------------------------------------- /packages/core/src/db/indexed.ts: -------------------------------------------------------------------------------- 1 | 2 | const data = { 3 | name: 'deep-reading', 4 | version: 1, 5 | openPromise: null as null | Promise 6 | } 7 | 8 | async function handleUpgradeNeeded(e: IDBVersionChangeEvent) { 9 | const database: IDBDatabase = (e.target as IDBRequest).result 10 | if (e.oldVersion < 1) { 11 | database.createObjectStore('words', { 12 | keyPath: 'word' 13 | }) 14 | database.createObjectStore('read-history', { 15 | autoIncrement: true, 16 | }) 17 | database.createObjectStore('setting', { 18 | keyPath: 'key', 19 | }) 20 | } 21 | } 22 | 23 | export async function open() { 24 | const { name, version, openPromise } = data 25 | if (openPromise) return openPromise 26 | 27 | const request = indexedDB.open(name, version) 28 | request.onupgradeneeded = handleUpgradeNeeded 29 | data.openPromise = new Promise((resolve) => { 30 | request.onsuccess = () => { 31 | const database = request.result 32 | database.onerror = console.error 33 | resolve(database) 34 | } 35 | }) 36 | 37 | // TEMP_migrate() 38 | 39 | return data.openPromise 40 | } 41 | 42 | 43 | // May 18 2022 - delete history db 44 | async function TEMP_migrate() { 45 | 46 | if (indexedDB.databases) { 47 | const databases = await indexedDB.databases() 48 | 49 | const historyDB = databases.find(db => db.name === 'history') 50 | if (historyDB) indexedDB.deleteDatabase('history') 51 | 52 | return 53 | } 54 | 55 | // firefox compatible 56 | indexedDB.deleteDatabase('history') 57 | 58 | } -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | extractWordRange, 3 | extractSentenceRange, 4 | getTarget, 5 | getTargetByPoint, 6 | isArticleContent, 7 | abstract, 8 | recapTitle, 9 | recapDescription, 10 | } from './core' 11 | 12 | export { Dictionary } from './Dictionary' 13 | export { Translator } from './Translator' 14 | export { ReadHistory as ReadingHistory } from './ReadHistory' 15 | export { ReadHistory } from './ReadHistory' 16 | 17 | export { open as openDB } from './db' 18 | 19 | export { setSetting, getSetting, removeSetting } from './utils/setting' 20 | export { collect, eventCollect } from './utils/analytics' 21 | export { getCoparent, getCoparentElement, client2pageRect } from './utils/dom' 22 | export { markRange, Marker } from './utils/marker' 23 | 24 | export type { 25 | CoreMessage, 26 | CoreMessage as MessageData, 27 | WordData, 28 | MessageType, 29 | TargetType, 30 | } from './types' 31 | 32 | export { detectRefusedDisplay, detectCSP } from './core' 33 | 34 | export { themeOptions } from './theme' 35 | -------------------------------------------------------------------------------- /packages/core/src/module.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.scss" { 2 | const content: any 3 | export default content 4 | } 5 | -------------------------------------------------------------------------------- /packages/core/src/theme.ts: -------------------------------------------------------------------------------- 1 | import type { ThemeOptions } from "@mui/material" 2 | 3 | export const themeOptions: ThemeOptions = { 4 | palette: { 5 | primary: { 6 | main: '#007CA3', 7 | // contrastText: '', 8 | }, 9 | // secondary: { 10 | // main: '', 11 | // contrastText: '', 12 | // }, 13 | // error: { 14 | // main: '', 15 | // contrastText: '', 16 | // }, 17 | // text: { 18 | // primary: '', 19 | // secondary: '', 20 | // disabled: '', 21 | // } 22 | } 23 | } -------------------------------------------------------------------------------- /packages/core/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export type { MessageType } from './message' 2 | 3 | export type { 4 | CoreMessage, 5 | } from './message' 6 | 7 | export type { 8 | WordData, 9 | TargetType, 10 | } from './type' 11 | -------------------------------------------------------------------------------- /packages/core/src/types/type.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | type Definition = [string, string] 4 | export interface WordData { 5 | word: string 6 | pronunciation: { 7 | symbol_am: string 8 | symbol_en: string 9 | symbol_other: string 10 | audio_am: string 11 | audio_en: string 12 | audio_other: string 13 | } 14 | answer: Definition[] 15 | star: boolean 16 | timestamp: number 17 | } 18 | 19 | export type TargetType = 'all' | 'main' | 'none' // | 'cover' 20 | 21 | -------------------------------------------------------------------------------- /packages/core/src/utils/host.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export const proxyHostList = [ 4 | "www.wikipedia.org", 5 | "www.wikibooks.org", 6 | "wikibooks.org", 7 | "m.wikipedia.org", 8 | "en.wikipedia.org", 9 | "en.m.wikipedia.org", 10 | "twitter.com", 11 | "t.co", 12 | "news.google.com", 13 | "developer.chrome.com", 14 | "www.bbc.com", 15 | "bbc.com", 16 | "blog.diigo.com", 17 | "diigo.com", 18 | "www.kali.org", 19 | "gutenberg.net.au", 20 | "golang.org", 21 | "developer.android.com", 22 | "www.bbc.co.uk", 23 | "github.com", 24 | // "reactjs.org", 25 | ] 26 | -------------------------------------------------------------------------------- /packages/core/src/utils/request.ts: -------------------------------------------------------------------------------- 1 | // 2 | 3 | export async function lookUpApi(word: string) { 4 | const apiUrl = process.env.LOOKUP_API 5 | const url = `${apiUrl}?w=${word.toLowerCase()}` 6 | 7 | return fetch(url, {}).then( 8 | (response) => { 9 | return response.json() 10 | }, 11 | (reason) => { 12 | return reason 13 | } 14 | ) 15 | } 16 | 17 | export async function translateApi(bodyData: URLSearchParams) { 18 | const apiUrl = process.env.TRANSLATE_API as string 19 | const url = apiUrl 20 | 21 | return fetch(url, { 22 | method: 'POST', 23 | body: bodyData, 24 | }).then( 25 | (response) => { 26 | return response.json() 27 | }, 28 | (reason) => { 29 | return reason 30 | } 31 | ) 32 | } 33 | 34 | export type ServerPoint = 'shanghai' | 'tokyo' 35 | 36 | export async function proxyApi(href: string, point?: ServerPoint) { 37 | let apiUrl = process.env.SHANGHAI_PROXY_API 38 | switch (point) { 39 | case 'shanghai': 40 | apiUrl = process.env.SHANGHAI_PROXY_API 41 | break 42 | case 'tokyo': 43 | apiUrl = process.env.TOKYO_PROXY_API 44 | break 45 | default: 46 | apiUrl = process.env.SHANGHAI_PROXY_API 47 | } 48 | 49 | const url = `${apiUrl}?url=${encodeURIComponent(href)}` 50 | console.log('proxyApi: url ', url) 51 | return fetch(url, {}) 52 | // return fetch(url, {}).then( 53 | // (response) => { 54 | // return response.text() 55 | // }, 56 | // (reason) => { 57 | // return reason 58 | // } 59 | // ) 60 | } 61 | -------------------------------------------------------------------------------- /packages/core/src/utils/setting.ts: -------------------------------------------------------------------------------- 1 | import { open } from '../db' 2 | 3 | 4 | const STORE_NAME = 'setting' 5 | 6 | async function getStore(mode: IDBTransactionMode = 'readonly') { 7 | const db = await open() 8 | const transaction = db.transaction(STORE_NAME, mode) 9 | const objectStore = transaction.objectStore(STORE_NAME) 10 | 11 | return objectStore 12 | } 13 | 14 | export async function getSetting(key: string): Promise { 15 | const objectStore = await getStore() 16 | const request = objectStore.get(key) 17 | 18 | const data = await new Promise((resolve, reject) => { 19 | request.onsuccess = () => { 20 | resolve(request.result) 21 | } 22 | request.onerror = () => { 23 | resolve(undefined) 24 | } 25 | }) 26 | return data 27 | } 28 | 29 | export async function setSetting>(value: { key: string } & T) { 30 | const objectStore = await getStore('readwrite') 31 | objectStore.put(value) 32 | } 33 | 34 | export async function removeSetting(key: string) { 35 | const objectStore = await getStore('readwrite') 36 | const request = objectStore.delete(key) 37 | 38 | return new Promise((resolve, reject) => { 39 | request.onsuccess = () => { 40 | resolve(request.result) 41 | } 42 | request.onerror = () => { 43 | reject() 44 | } 45 | }) 46 | } -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "outDir": "./es", 10 | "declaration": true, 11 | "allowJs": true, 12 | "skipLibCheck": true, 13 | "esModuleInterop": true, 14 | "allowSyntheticDefaultImports": true, 15 | "strict": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "module": "esnext", 19 | "moduleResolution": "node", 20 | "resolveJsonModule": true, 21 | "isolatedModules": true, 22 | "noImplicitAny": true, 23 | "removeComments": true, 24 | "preserveConstEnums": true, 25 | "sourceMap": true, 26 | "jsx": "react-jsx", 27 | "baseUrl": "./", 28 | "paths": { 29 | "@wrp/ui": [ 30 | "../ui" 31 | ] 32 | } 33 | }, 34 | "include": [ 35 | "src/**/*" 36 | ], 37 | "exclude": [ 38 | "node_modules", 39 | "**/*.spec.ts" 40 | ] 41 | } -------------------------------------------------------------------------------- /packages/extension/README.md: -------------------------------------------------------------------------------- 1 | # Deep Reading Extension 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/extension/esbuild/svgr.js: -------------------------------------------------------------------------------- 1 | 2 | // config https://github.com/gregberge/svgr/blob/main/packages/core/src/config.ts 3 | // state https://github.com/gregberge/svgr/blob/main/packages/core/src/state.ts 4 | 5 | const svgrPlugin = (config = {}, state = {}) => ({ 6 | name: 'svgr-plugin', 7 | setup(build) { 8 | const fs = require('fs') 9 | const { transform } = require('@svgr/core') 10 | 11 | build.onLoad({ filter: /\.svg$/}, async (args) => { 12 | const text = await fs.promises.readFile(args.path, 'utf8') 13 | const contents = await transform(text, { ...config }, { ...state }) 14 | 15 | return { 16 | contents, 17 | loader: config.typescript ? 'tsx' : 'jsx', 18 | } 19 | }) 20 | } 21 | }) 22 | 23 | module.exports = svgrPlugin -------------------------------------------------------------------------------- /packages/extension/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require("tailwindcss"), 4 | require("autoprefixer"), 5 | require("postcss-nested"), 6 | ], 7 | }; 8 | -------------------------------------------------------------------------------- /packages/extension/res/chrome/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "__MSG_extension_name__", 4 | "version": "0.1.2", 5 | "version_name": "0.1.2", 6 | "description": "__MSG_extension_description__", 7 | "default_locale": "zh_CN", 8 | "icons": { 9 | "128": "./logo-128.png", 10 | "48": "./logo-48.png", 11 | "16": "./logo-16.png" 12 | }, 13 | "background": { 14 | "scripts": ["background.js"], 15 | "persistent": false 16 | }, 17 | "content_scripts": [ 18 | { 19 | "matches": [""], 20 | "run_at": "document_end", 21 | "js": ["content.js"] 22 | } 23 | ], 24 | "browser_action": { 25 | "default_title": "Deep Reading", 26 | "default_popup": "popup.html" 27 | }, 28 | "options_page": "options.html", 29 | "permissions": ["tabs", "activeTab", "storage", "contextMenus"], 30 | "web_accessible_resources": ["content-frame.html", "content-frame.chunk.js"] 31 | } 32 | -------------------------------------------------------------------------------- /packages/extension/res/chrome_v3/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "__MSG_extension_name__", 4 | "version": "0.1.1", 5 | "version_name": "0.1.1", 6 | "description": "__MSG_extension_description__", 7 | "default_locale": "zh_CN", 8 | "icons": { 9 | "128": "./logo-128.png", 10 | "48": "./logo-48.png", 11 | "16": "./logo-16.png" 12 | }, 13 | "background": { 14 | "service_worker": "service.worker.js" 15 | }, 16 | "content_scripts": [ 17 | { 18 | "matches": [""], 19 | "run_at": "document_end", 20 | "js": ["content.js"] 21 | } 22 | ], 23 | "action": { 24 | "default_title": "Deep Reading", 25 | "default_popup": "popup.html" 26 | }, 27 | "options_page": "options.html", 28 | "permissions": ["tabs", "activeTab", "storage", "scripting", "contextMenus"], 29 | "web_accessible_resources": [ 30 | { 31 | "resources": ["content-frame.html", "content-frame.chunk.js"], 32 | "matches": [""] 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /packages/extension/res/firefox/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "__MSG_extension_name__", 4 | "version": "0.1.1", 5 | "description": "__MSG_extension_description__", 6 | "default_locale": "zh_CN", 7 | "icons": { 8 | "128": "./logo-128.png", 9 | "48": "./logo-48.png", 10 | "16": "./logo-16.png" 11 | }, 12 | "background": { 13 | "scripts": ["background.js"], 14 | "persistent": false 15 | }, 16 | "content_scripts": [ 17 | { 18 | "matches": [""], 19 | "run_at": "document_end", 20 | "js": ["content.js"] 21 | } 22 | ], 23 | "browser_action": { 24 | "default_title": "Deep Reading 翻译", 25 | "default_popup": "popup.html" 26 | }, 27 | "permissions": ["webRequest", "", "storage", "contextMenus"], 28 | "browser_specific_settings": { 29 | "gecko": { 30 | "id": "deep-reading@baotlake" 31 | } 32 | }, 33 | "web_accessible_resources": ["content-frame.html", "content-frame.chunk.js"] 34 | } 35 | -------------------------------------------------------------------------------- /packages/extension/res/firefox_v3/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "__MSG_extension_name__", 4 | "version": "0.1.1", 5 | "description": "__MSG_extension_description__", 6 | "default_locale": "zh_CN", 7 | "icons": { 8 | "128": "./logo-128.png", 9 | "48": "./logo-48.png", 10 | "16": "./logo-16.png" 11 | }, 12 | "background": { 13 | "service_worker": "service.worker.js" 14 | }, 15 | "content_scripts": [ 16 | { 17 | "matches": [""], 18 | "run_at": "document_end", 19 | "js": ["content.js"] 20 | } 21 | ], 22 | "action": { 23 | "default_title": "Deep Reading", 24 | "default_popup": "popup.html" 25 | }, 26 | "options_page": "popup.html", 27 | "permissions": ["webRequest", "", "storage"], 28 | "web_accessible_resources": ["content-frame.html", "content-frame.chunk.js"] 29 | } 30 | -------------------------------------------------------------------------------- /packages/extension/res/share/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extension_name": { 3 | "message": "Deep Reading Translate" 4 | }, 5 | "extension_description": { 6 | "message": "秒查词、秒翻译,轻松阅读英语原文。兼容Android设备浏览器" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/extension/res/share/_locales/en_GB/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extension_name": { 3 | "message": "Deep Reading Translate" 4 | }, 5 | "extension_description": { 6 | "message": "秒查词、秒翻译,轻松阅读英语原文。兼容Android设备浏览器" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/extension/res/share/_locales/en_US/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extension_name": { 3 | "message": "Deep Reading Translate" 4 | }, 5 | "extension_description": { 6 | "message": "秒查词、秒翻译,轻松阅读英语原文。兼容Android设备浏览器" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/extension/res/share/_locales/zh_CN/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extension_name": { 3 | "message": "青轻查词翻译" 4 | }, 5 | "extension_description": { 6 | "message": "秒查词、秒翻译,轻松阅读英语原文。兼容Android设备浏览器" 7 | } 8 | } 9 | 10 | -------------------------------------------------------------------------------- /packages/extension/res/share/_locales/zh_TW/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extension_name": { 3 | "message": "青轻查词翻译" 4 | }, 5 | "extension_description": { 6 | "message": "秒查词、秒翻译,轻松阅读英语原文。兼容Android设备浏览器" 7 | } 8 | } 9 | 10 | -------------------------------------------------------------------------------- /packages/extension/res/share/logo-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baotlake/deep-reading/e230b02bda2dd4c1e54d5c93c23ae51909ac23d9/packages/extension/res/share/logo-128.png -------------------------------------------------------------------------------- /packages/extension/res/share/logo-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baotlake/deep-reading/e230b02bda2dd4c1e54d5c93c23ae51909ac23d9/packages/extension/res/share/logo-16.png -------------------------------------------------------------------------------- /packages/extension/res/share/logo-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baotlake/deep-reading/e230b02bda2dd4c1e54d5c93c23ae51909ac23d9/packages/extension/res/share/logo-48.png -------------------------------------------------------------------------------- /packages/extension/res/share/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baotlake/deep-reading/e230b02bda2dd4c1e54d5c93c23ae51909ac23d9/packages/extension/res/share/logo.png -------------------------------------------------------------------------------- /packages/extension/src/components/Popup/CoverOrEnable/index.style.ts: -------------------------------------------------------------------------------- 1 | import ButtonBase from '@mui/material/ButtonBase' 2 | import { styled, alpha } from '@mui/material/styles' 3 | import LayersRoundedIcon from '@mui/icons-material/LayersRounded' 4 | import HelpOutlineIcon from '@mui/icons-material/HelpOutline' 5 | import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew' 6 | import BoltIcon from '@mui/icons-material/Bolt' 7 | 8 | export const Box = styled('div')({ 9 | margin: '1.25em auto', 10 | }) 11 | 12 | export const Button = styled(ButtonBase)(({ theme }) => ({ 13 | display: 'flex', 14 | width: '100%', 15 | fontSize: '1em', 16 | padding: '0.625em 1.25em', 17 | justifyContent: 'start', 18 | borderRadius: '0.5em', 19 | borderWidth: 1, 20 | borderStyle: 'solid', 21 | borderColor: alpha(theme.palette.primary.main, 0.3), 22 | color: theme.palette.primary.main, 23 | 24 | '> span': { 25 | color: theme.palette.primary.dark, 26 | fontSize: '0.875em', 27 | } 28 | })) 29 | 30 | export const LayersIcon = styled(LayersRoundedIcon)({ 31 | marginRight: '0.5em', 32 | fontSize: '1.25em', 33 | }) 34 | 35 | export const HelpIcon = styled(HelpOutlineIcon)({ 36 | marginLeft: '0.5em', 37 | fontSize: '1.25em', 38 | }) 39 | 40 | export const PowerIcon = styled(PowerSettingsNewIcon)({ 41 | marginRight: '0.5em', 42 | fontSize: '1.25em', 43 | }) 44 | 45 | export const StyledBoltIcon = styled(BoltIcon)({ 46 | marginLeft: '0.2em', 47 | fontSize: '1.25em', 48 | }) -------------------------------------------------------------------------------- /packages/extension/src/components/Popup/CoverOrEnable/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | Box, 4 | Button, 5 | LayersIcon, 6 | HelpIcon, 7 | PowerIcon, 8 | StyledBoltIcon, 9 | } from './index.style' 10 | 11 | type Props = { 12 | enable: boolean 13 | onClickCover?: () => void 14 | onClickEnable?: () => void 15 | } 16 | 17 | export function CoverOrEnable({ enable, onClickCover, onClickEnable }: Props) { 18 | return ( 19 | 20 | {enable ? ( 21 | 30 | ) : ( 31 | 40 | )} 41 | 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /packages/extension/src/components/Popup/EnableToggle/index.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react' 2 | 3 | import Paper from '@mui/material/Paper' 4 | import Button from '@mui/material/Button' 5 | import Typography from '@mui/material/Typography' 6 | import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew' 7 | import { sendMessage, setSyncStorage } from '../../../uitls/extension' 8 | import { ExtMessageData } from '../../../types/message' 9 | import { setEnable } from '../reducer' 10 | import { PopupContext } from '../PopupContext' 11 | import { SyncStorage } from '../../../types' 12 | 13 | 14 | export function EnableToggle() { 15 | 16 | const { state, dispatch } = useContext(PopupContext) 17 | 18 | const hanldeClick = () => { 19 | const newValue = !state.enable 20 | console.log('enable newValue', newValue) 21 | setSyncStorage({ enable: newValue }) 22 | dispatch(setEnable(newValue)) 23 | sendMessage({ type: newValue ? 'enable' : 'disable' }) 24 | } 25 | 26 | return ( 27 | 34 |
35 | 36 | {state.enable ? '已启用' : '未启用'} 37 | 38 | 39 | {state.enable ? '青轻阅读,秒查词,秒翻译' : '点击右侧开关启用'} 40 | 41 |
42 | 47 |
48 | ) 49 | } -------------------------------------------------------------------------------- /packages/extension/src/components/Popup/Footer/index.style.ts: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles' 2 | import OpenSvg from '../../../resource/icon/open-window.svg?svgr' 3 | 4 | 5 | export const Box = styled('div')({ 6 | display: 'flex', 7 | width: '100%', 8 | padding: '0.5em 1.5em', 9 | alignItems: 'center', 10 | boxSizing: 'border-box', 11 | justifyContent: 'space-evenly', 12 | }) 13 | 14 | export const Link = styled('a')(({ theme }) => ({ 15 | fontSize: '0.75em', 16 | textDecoration: 'none', 17 | color: theme.palette.primary.dark, 18 | 19 | '&:hover': { 20 | color: theme.palette.primary.main, 21 | } 22 | })) 23 | 24 | export const OpenIcon = styled(OpenSvg)({ 25 | marginLeft: '0.25em', 26 | }) -------------------------------------------------------------------------------- /packages/extension/src/components/Popup/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Link, OpenIcon } from './index.style' 2 | 3 | export function Footer() { 4 | return ( 5 | 6 | 10 | GitHub 11 | 12 | 13 | 支持 14 | 15 | 16 | 隐私 17 | 18 | 19 | 移动端 20 | 21 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /packages/extension/src/components/Popup/Header/MoreMenu/index.style.ts: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles' 2 | import ButtonBase from '@mui/material/ButtonBase' 3 | 4 | export const Box = styled('div')({ 5 | position: 'relative', 6 | }) 7 | 8 | export const Menu = styled('div')({ 9 | position: 'absolute', 10 | top: '100%', 11 | marginTop: '0.375em', 12 | right: 0, 13 | background: 'white', 14 | border: '1px solid rgba(0,0,0,0.1)', 15 | borderRadius: '0.375em', 16 | zIndex: 10, 17 | padding: '0.375em 0', 18 | }) 19 | 20 | export const MenuItem = styled(ButtonBase)(({ theme }) => ({ 21 | fontSize: '0.875em', 22 | padding: '0 1em', 23 | whiteSpace: 'nowrap', 24 | display: 'flex', 25 | 26 | '&:hover': { 27 | color: theme.palette.primary.main, 28 | }, 29 | })) -------------------------------------------------------------------------------- /packages/extension/src/components/Popup/Header/index.style.ts: -------------------------------------------------------------------------------- 1 | import { styled, alpha } from "@mui/material/styles" 2 | import ButtonBase from '@mui/material/ButtonBase' 3 | import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew' 4 | import MoreHorizIcon from '@mui/icons-material/MoreHoriz' 5 | import LogoSvg from '../../../resource/svg/logo-name.svg?svgr' 6 | 7 | export const Box = styled('div')({ 8 | display: 'flex', 9 | alignItems: 'center', 10 | justifyContent: 'space-between', 11 | }) 12 | 13 | export const Logo = styled(LogoSvg)({ 14 | width: '7.5em', 15 | height: '2.875em', 16 | cursor: 'pointer', 17 | marginRight: 'auto', 18 | }) 19 | 20 | export const Button = styled(ButtonBase)(({ theme }) => ({ 21 | fontSize: '1em', 22 | width: '1.875em', 23 | height: '1.875em', 24 | borderRadius: '0.5em', 25 | border: '1px solid rgba(0,0,0,0.1)', 26 | marginLeft: '1em', 27 | 28 | '&.warning': { 29 | color: theme.palette.warning.main, 30 | borderColor: alpha(theme.palette.warning.main, 0.3) 31 | }, 32 | '&.primary': { 33 | borderColor: alpha(theme.palette.primary.light, 1), 34 | background: alpha(theme.palette.primary.main, 1), 35 | color: 'white', 36 | } 37 | })) 38 | 39 | export const PowerIcon = styled(PowerSettingsNewIcon)({ 40 | fontSize: '1.25em', 41 | }) 42 | 43 | export const MoreIcon = styled(MoreHorizIcon)({ 44 | fontSize: '1.25em', 45 | }) -------------------------------------------------------------------------------- /packages/extension/src/components/Popup/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames' 2 | import { Box, Logo, Button, PowerIcon, MoreIcon } from './index.style' 3 | import { MoreMenu } from './MoreMenu' 4 | 5 | type Props = { 6 | enable: boolean 7 | logoUrl?: string 8 | handleOnOff?: () => void 9 | } 10 | 11 | export function Header({ enable, logoUrl, handleOnOff }: Props) { 12 | return ( 13 | 14 | logoUrl && open(logoUrl, '_blank')} /> 15 | 16 | 23 | 24 | 25 | 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /packages/extension/src/components/Popup/ModeOption/Items.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames' 2 | import AllSvg from '../../../resource/icon/trigger-all.svg?svgr' 3 | import MainSvg from '../../../resource/icon/trigger-main.svg?svgr' 4 | import DisableSvg from '../../../resource/icon/trigger-disable.svg?svgr' 5 | import { Button } from './items.style' 6 | import type { State } from '../reducer' 7 | 8 | type Props = { 9 | mode: State['globalTargetType'] 10 | onChange?: (mode: State['globalTargetType']) => void 11 | } 12 | 13 | export function Items({ mode, onChange }: Props) { 14 | return ( 15 | <> 16 | 25 | 34 | 43 | 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /packages/extension/src/components/Popup/ModeOption/index.style.ts: -------------------------------------------------------------------------------- 1 | import Box from '@mui/material/Box' 2 | import Tab from '@mui/material/Tab' 3 | import AllInclusiveIcon from '@mui/icons-material/AllInclusive' 4 | import PublicIcon from '@mui/icons-material/Public' 5 | import { styled } from '@mui/material/styles' 6 | 7 | export const Wrapper = styled(Box)({ 8 | position: 'relative', 9 | 10 | '&.inactive': { 11 | opacity: 0.3, 12 | filter: 'grayscale(1)', 13 | } 14 | }) 15 | 16 | export const GlobalIcon = styled(AllInclusiveIcon)({}) 17 | 18 | export const HostIcon = styled('img')({ 19 | width: '1.25em', 20 | height: '1.25em', 21 | }) 22 | 23 | export const WebsiteIcon = styled(PublicIcon)({}) 24 | 25 | 26 | export const StyledTab = styled(Tab)({ 27 | minHeight: 'auto', 28 | }) -------------------------------------------------------------------------------- /packages/extension/src/components/Popup/ModeOption/items.style.ts: -------------------------------------------------------------------------------- 1 | import ButtonBase from '@mui/material/ButtonBase' 2 | import Typography from '@mui/material/Typography' 3 | import { styled, alpha } from '@mui/material/styles' 4 | 5 | 6 | export const Button = styled(ButtonBase)(({ theme }) => ({ 7 | display: 'flex', 8 | width: '100%', 9 | fontSize: '1em', 10 | padding: '0.625em 1.25em', 11 | margin: '0.875em 0', 12 | borderRadius: '0.5em', 13 | alignItems: 'center', 14 | justifyContent: 'start', 15 | color: 'rgba(0,0,0,0.85)', 16 | borderWidth: 1, 17 | borderStyle: 'solid', 18 | borderColor: 'rgba(0,0,0,0.1)', 19 | 20 | '&:hover': { 21 | color: theme.palette.primary.main, 22 | }, 23 | 24 | '&.active': { 25 | background: alpha(theme.palette.primary.main, 0.15), 26 | color: theme.palette.primary.dark, 27 | borderColor: alpha(theme.palette.primary.main, 0.6), 28 | fontWeight: 'bold', 29 | }, 30 | 31 | '> svg': { 32 | marginRight: '0.5em', 33 | height: '1.25em', 34 | width: '1.25em', 35 | }, 36 | 37 | '> span': { 38 | fontSize: '0.875em', 39 | } 40 | })) 41 | -------------------------------------------------------------------------------- /packages/extension/src/components/Popup/PopupContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext, Dispatch } from 'react' 2 | import { initialState } from './reducer' 3 | import type { Action } from './reducer' 4 | 5 | 6 | export const PopupContext = createContext({ state: initialState, dispatch: null as null | Dispatch }) 7 | 8 | export default PopupContext -------------------------------------------------------------------------------- /packages/extension/src/components/Popup/index.style.ts: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles' 2 | 3 | export const Main = styled('div')({ 4 | boxSizing: 'border-box', 5 | padding: '1em 1.5em', 6 | fontFamily: 'Roboto', 7 | }) 8 | -------------------------------------------------------------------------------- /packages/extension/src/components/Popup/reducer/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export { initialState, reducer } from './reducer' 3 | export type { State } from './reducer' 4 | 5 | export { 6 | setEnable, 7 | setActiveTab, 8 | setScope, 9 | setMode, 10 | setGlobalTargetType, 11 | setHostTargetType, 12 | } from './actions' 13 | 14 | export type { Action } from './actions' -------------------------------------------------------------------------------- /packages/extension/src/components/Popup/reducer/reducer.ts: -------------------------------------------------------------------------------- 1 | import type { TargetType } from '@wrp/core' 2 | import type { Action } from './actions' 3 | 4 | type ScopeTab = 'global' | 'host' 5 | type Tab = chrome.tabs.Tab 6 | 7 | export type State = { 8 | enable: boolean 9 | scope: ScopeTab 10 | activeTab: null | Tab 11 | hostname: string 12 | globalTargetType: TargetType 13 | hostTargetType: TargetType 14 | hostCustomized: boolean 15 | } 16 | 17 | export const initialState: State = { 18 | enable: true, 19 | scope: 'global', 20 | activeTab: null, 21 | hostname: '', 22 | globalTargetType: 'all', 23 | hostTargetType: 'main', 24 | hostCustomized: false, 25 | } 26 | 27 | export function reducer(state: State, action: Action): State { 28 | switch (action.type) { 29 | case 'setEnable': 30 | case 'setActiveTab': 31 | case 'setScope': 32 | case 'setMode': 33 | case 'setGlobalTargetType': 34 | case 'setHostTargetType': 35 | return { ...state, ...action.payload } 36 | default: 37 | return { ...state } 38 | } 39 | } -------------------------------------------------------------------------------- /packages/extension/src/components/index.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baotlake/deep-reading/e230b02bda2dd4c1e54d5c93c23ae51909ac23d9/packages/extension/src/components/index.tsx -------------------------------------------------------------------------------- /packages/extension/src/content/App/PopupCard/index.style.ts: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles' 2 | 3 | export const Box = styled('div')({ 4 | position: 'fixed', 5 | width: '100vw', 6 | height: '100vh', 7 | background: 'rgba(0,0,0,0.2)', 8 | top: 0, 9 | left: 0, 10 | zIndex: 9e10, 11 | animation: '0.18s ease-in-out 0s 1 both fadeIn', 12 | 13 | '@keyframes fadeIn': { 14 | '0%': { 15 | opacity: 0, 16 | }, 17 | '100%': { 18 | opacity: 1, 19 | } 20 | } 21 | }) 22 | 23 | export const Card = styled('div')({ 24 | position: 'absolute', 25 | top: '2em', 26 | right: '2em', 27 | width: '300px', 28 | boxShadow: '0 0 20px rgba(0,0,0,0.2)', 29 | background: 'white', 30 | borderRadius: '0.5em', 31 | animation: '0.18s ease-in-out 0s 1 both sideInRight', 32 | 33 | 34 | '@keyframes sideInRight': { 35 | '0%': { 36 | transform: 'translate(100%, 0%) scale(1)' 37 | }, 38 | '100%': { 39 | transform: 'translate(0%, 0%) scale(1)' 40 | }, 41 | } 42 | }) 43 | -------------------------------------------------------------------------------- /packages/extension/src/content/App/PopupCard/index.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useRef } from 'react' 2 | import { useEscapeHide } from '@wrp/ui' 3 | import { AppContext } from '../context' 4 | import classNames from 'classnames' 5 | import { setPopupVisible } from '../reducer' 6 | import { Popup } from '../../../components/Popup' 7 | import ClickAwayListener from '@mui/material/ClickAwayListener' 8 | import { Box, Card } from './index.style' 9 | 10 | 11 | export function PopupCard() { 12 | 13 | const { state: { popup, tab }, dispatch } = useContext(AppContext) 14 | 15 | useEscapeHide(popup.visible, () => { 16 | dispatch(setPopupVisible(false)) 17 | }) 18 | 19 | const handleClickAway = () => { 20 | dispatch(setPopupVisible(false)) 21 | } 22 | 23 | return ( 24 | <> 25 | { 26 | popup.visible && ( 27 | 32 | 33 | 34 | dispatch(setPopupVisible(false))} 37 | /> 38 | 39 | 40 | 41 | ) 42 | } 43 | 44 | ) 45 | } -------------------------------------------------------------------------------- /packages/extension/src/content/App/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react' 2 | import { initialState } from './reducer' 3 | 4 | 5 | export const AppContext = createContext({ 6 | state: initialState, 7 | dispatch: null, 8 | }) -------------------------------------------------------------------------------- /packages/extension/src/content/App/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useReducer } from 'react' 2 | import { initialState, reducer, setPopupVisible, showContentPopup } from './reducer' 3 | import { themeOptions } from '@wrp/core' 4 | import { addContentMessageListener, App as CoreApp } from '@wrp/inject' 5 | import { ExtMessageData } from '../../types/message' 6 | import { AppContext } from './context' 7 | import { PopupCard } from './PopupCard' 8 | import { ThemeProvider, createTheme } from '@mui/material' 9 | import { getURL } from "../../uitls/extension" 10 | 11 | const theme = createTheme(themeOptions) 12 | const contentFrameUrl = getURL('/content-frame.html') 13 | 14 | export function App() { 15 | const [state, dispatch] = useReducer(reducer, initialState) 16 | 17 | useEffect(() => { 18 | const handleContentMessage = (data: ExtMessageData) => { 19 | switch (data.type) { 20 | case 'showContentPopup': 21 | dispatch(showContentPopup(data.payload.tab)) 22 | break 23 | } 24 | } 25 | 26 | const removeListener = addContentMessageListener(handleContentMessage) 27 | return () => { 28 | removeListener() 29 | } 30 | }, []) 31 | 32 | return ( 33 | <> 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /packages/extension/src/content/App/reducer/action.ts: -------------------------------------------------------------------------------- 1 | 2 | export function setPopupVisible(value: boolean) { 3 | return { 4 | type: 'popupVisible' as 'popupVisible', 5 | payload: { 6 | visible: value, 7 | } 8 | } 9 | } 10 | 11 | export function showContentPopup(tab: chrome.tabs.Tab) { 12 | 13 | return { 14 | type: 'showContentPopup' as 'showContentPopup', 15 | payload: { 16 | popup: { 17 | visible: true, 18 | }, 19 | tab, 20 | } 21 | } 22 | } 23 | 24 | type ActionFunction = 25 | | typeof setPopupVisible 26 | | typeof showContentPopup 27 | 28 | export type Action = ReturnType -------------------------------------------------------------------------------- /packages/extension/src/content/App/reducer/index.ts: -------------------------------------------------------------------------------- 1 | export { initialState, reducer } from './reducer' 2 | 3 | export { showContentPopup, setPopupVisible } from './action' 4 | -------------------------------------------------------------------------------- /packages/extension/src/content/App/reducer/reducer.ts: -------------------------------------------------------------------------------- 1 | import { Action } from './action' 2 | 3 | type Tab = chrome.tabs.Tab 4 | 5 | export const initialState = { 6 | popup: { 7 | visible: false, 8 | }, 9 | tab: null as Tab, 10 | } 11 | 12 | type State = typeof initialState 13 | 14 | export function reducer(state: State, action: Action) { 15 | switch (action.type) { 16 | case 'showContentPopup': 17 | return { 18 | ...state, 19 | ...action.payload, 20 | popup: { ...state.popup, ...action.payload.popup }, 21 | } 22 | case 'popupVisible': 23 | return { ...state, popup: { ...state.popup, ...action.payload } } 24 | default: 25 | return state 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/extension/src/content/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## content scripts can access the following chrome API 4 | - i18n 5 | - storage 6 | - runtime 7 | + connect 8 | + getManifest 9 | + getURL 10 | + id 11 | + onConnect 12 | + onMessage 13 | + sendMessage 14 | -------------------------------------------------------------------------------- /packages/extension/src/content/index.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { MessageData, TargetType } from '@wrp/core' 3 | import { addContentMessageListener } from '@wrp/inject' 4 | import { ExtMessageData } from '../types/message' 5 | import { addMessageListener, sendMessage } from '../uitls/extension' 6 | import { hanldeExtMessage, handleContentMessage } from './message' 7 | 8 | 9 | function main() { 10 | console.log('deep reading content', globalThis.CONTENT_INITED) 11 | if (globalThis.CONTENT_INITED === true) return 12 | globalThis.CONTENT_INITED = true 13 | addMessageListener(hanldeExtMessage) 14 | addContentMessageListener(handleContentMessage) 15 | 16 | sendMessage({ 17 | type: 'contentActive', 18 | }) 19 | } 20 | 21 | main() 22 | -------------------------------------------------------------------------------- /packages/extension/src/content/message.ts: -------------------------------------------------------------------------------- 1 | import { sendContentMessage } from '@wrp/inject' 2 | import { ExtMessageData } from '../types/message' 3 | import { init, enable, disable, showContentPopup } from './root' 4 | 5 | type Sender = chrome.runtime.MessageSender 6 | type SendResponse = (response: boolean) => void 7 | export function hanldeExtMessage(data: ExtMessageData, sender: Sender, sendResponse: SendResponse) { 8 | sendResponse(true) 9 | 10 | console.warn(data, sender) 11 | switch (data.type) { 12 | case 'initContent': 13 | init(data) 14 | break 15 | case 'enable': 16 | enable() 17 | break 18 | case 'disable': 19 | disable() 20 | break 21 | case 'showContentPopup': 22 | showContentPopup(data) 23 | break 24 | case 'translateResult': 25 | case 'lookUpResult': 26 | case 'setCoverVisible': 27 | case 'setTargetType': 28 | sendContentMessage(data) 29 | break 30 | // broadcast message 31 | default: 32 | break 33 | } 34 | } 35 | 36 | export function handleContentMessage(data: ExtMessageData) { 37 | switch (data.type) { 38 | default: 39 | break 40 | } 41 | } -------------------------------------------------------------------------------- /packages/extension/src/module.d.ts: -------------------------------------------------------------------------------- 1 | 2 | declare module '*.js?raw' { 3 | const content: string 4 | export default content 5 | } 6 | 7 | declare module '*.module.scss' { 8 | const content: { [index: string]: string } 9 | export default content 10 | } 11 | 12 | declare module '*.scss?raw' { 13 | const content: string 14 | export default content 15 | } 16 | 17 | declare module '*.css?raw' { 18 | const content: string 19 | export default content 20 | } 21 | 22 | declare module '*.png' { 23 | const content: any 24 | export default content 25 | } 26 | 27 | declare module '*.svg?svgr' { 28 | import React from "react" 29 | const SVG: React.VFC> 30 | export default SVG 31 | } 32 | 33 | declare const __webpack_public_path__: string 34 | 35 | declare const __DEV__: boolean 36 | -------------------------------------------------------------------------------- /packages/extension/src/pages/Popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/extension/src/pages/Popup/index.tsx: -------------------------------------------------------------------------------- 1 | import { render } from 'react-dom' 2 | import { Popup } from '../../components/Popup' 3 | import { ExtMessageData } from '../../types' 4 | import { sendMessage } from '../../uitls/extension' 5 | import { createTheme, ThemeProvider } from '@mui/material' 6 | import { styled } from '@mui/material/styles' 7 | import { themeOptions } from '@wrp/core' 8 | 9 | import '../../style/global.css' 10 | 11 | const Box = styled('div')({ 12 | fontSize: '16px', 13 | width: '18.75em', 14 | margin: 'auto', 15 | outline: '1px solid rgba(0,0,0,0.1)', 16 | }) 17 | 18 | const theme = createTheme(themeOptions) 19 | 20 | function App() { 21 | 22 | return ( 23 | 24 | 25 | window.close()} 27 | /> 28 | 29 | 30 | ) 31 | } 32 | 33 | const target = document.querySelector('#root') 34 | render(, target) 35 | 36 | sendMessage({ type: 'popupActive' }) 37 | -------------------------------------------------------------------------------- /packages/extension/src/pages/background/background.ts: -------------------------------------------------------------------------------- 1 | import { 2 | handleMessage, 3 | handleInstalled, 4 | handleStartup, 5 | handleMenuClick, 6 | handleActionClick, 7 | } from '../../service' 8 | 9 | import { 10 | addMessageListener, 11 | addInstalledListener, 12 | addStartupListener, 13 | addContextMenusListener, 14 | addClickedActionListener, 15 | } from '../../uitls/extension' 16 | 17 | addMessageListener(handleMessage) 18 | addInstalledListener(handleInstalled) 19 | addStartupListener(handleStartup) 20 | addContextMenusListener(handleMenuClick) 21 | addClickedActionListener(handleActionClick) 22 | 23 | console.log('background.js') 24 | -------------------------------------------------------------------------------- /packages/extension/src/pages/background/index.ts: -------------------------------------------------------------------------------- 1 | import './background' 2 | -------------------------------------------------------------------------------- /packages/extension/src/pages/content-frame/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/extension/src/pages/content-frame/index.ts: -------------------------------------------------------------------------------- 1 | import { MessageData, MessageType } from '@wrp/core'; 2 | import { addMessageListener, getCurrentTab } from '../../uitls/extension' 3 | 4 | 5 | const data = { 6 | tabId: -1, 7 | } 8 | 9 | getCurrentTab().then((tab) => { 10 | data.tabId = tab.id 11 | }) 12 | 13 | type PlayPronunciationMessageData = Extract 14 | 15 | function playPronunciation(message: PlayPronunciationMessageData) { 16 | const audio = new Audio(message.data.url) 17 | audio.play() 18 | } 19 | 20 | function handleMessage(message: MessageData, sender: chrome.runtime.MessageSender) { 21 | const senderTabId = sender.tab?.id 22 | const { tabId } = data 23 | switch (message.type) { 24 | case 'playPronunciation': 25 | senderTabId === tabId && playPronunciation(message) 26 | break 27 | } 28 | } 29 | 30 | addMessageListener(handleMessage) -------------------------------------------------------------------------------- /packages/extension/src/pages/dev-content/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/extension/src/pages/dev-content/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import { render } from 'react-dom' 3 | 4 | import '../../content/index' 5 | 6 | import "../../style/global.css" 7 | 8 | 9 | function App() { 10 | 11 | const [html, setHtml] = useState('loading...') 12 | 13 | useEffect(() => { 14 | const params = new URLSearchParams(location.search) 15 | const url = __DEV__ && params.get('url') || 'https://wrp.vercel.app/privacy' 16 | const text = __DEV__ && params.get('text') 17 | 18 | if (text) { 19 | setHtml(text) 20 | } 21 | if (!text) { 22 | fetch(url).then((res) => res.text()) 23 | .then((text) => { 24 | const parser = new DOMParser() 25 | const doc = parser.parseFromString(text, 'text/html') 26 | const base = doc.createElement('base') 27 | base.href = new URL(url).origin 28 | doc.head.insertBefore(base, doc.head.firstElementChild) 29 | setHtml(doc.body.parentElement.innerHTML) 30 | }) 31 | } 32 | }, []) 33 | 34 | return ( 35 |
36 | {/*
text
*/} 37 |
38 |
39 | ) 40 | } 41 | 42 | const root = document.getElementById('root') 43 | render(, root) 44 | -------------------------------------------------------------------------------- /packages/extension/src/pages/index/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/extension/src/pages/index/index.tsx: -------------------------------------------------------------------------------- 1 | import { render } from 'react-dom' 2 | 3 | 4 | function App() { 5 | 6 | return ( 7 | <> 8 |

index.html

9 | 10 | ) 11 | } 12 | 13 | const target = document.getElementById('root') 14 | render(, target) 15 | -------------------------------------------------------------------------------- /packages/extension/src/pages/options/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/extension/src/pages/options/index.tsx: -------------------------------------------------------------------------------- 1 | import { render } from 'react-dom' 2 | import { getURL, setPopup } from '../../uitls/extension' 3 | import { Popup } from '../../components/Popup' 4 | import { createTheme, ThemeProvider } from '@mui/material/styles' 5 | import { themeOptions } from '@wrp/core' 6 | 7 | import { Switch } from '@mui/material' 8 | 9 | if (__DEV__) { 10 | // location.href = getURL('/dev-content.html') 11 | } 12 | 13 | 14 | const theme = createTheme(themeOptions) 15 | 16 | function App() { 17 | 18 | const handleChange = (value: boolean) => { 19 | if (value) { 20 | setPopup({ 21 | popup: '', 22 | }) 23 | } else { 24 | setPopup({ 25 | popup: '/popup.html' 26 | }) 27 | } 28 | } 29 | 30 | return ( 31 | 32 | {/* */} 33 | 34 |
35 | Content Popup 36 | 37 | handleChange(checked)} /> 38 | 39 |
40 |
41 | ) 42 | } 43 | 44 | const root = document.getElementById('root') 45 | 46 | render(, root) 47 | 48 | -------------------------------------------------------------------------------- /packages/extension/src/pages/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/extension/src/pages/popup/index.tsx: -------------------------------------------------------------------------------- 1 | import { render } from 'react-dom' 2 | import { Popup } from '../../components/Popup' 3 | import { ExtMessageData } from '../../types' 4 | import { sendMessage } from '../../uitls/extension' 5 | import { createTheme, ThemeProvider } from '@mui/material' 6 | import { styled } from '@mui/material/styles' 7 | import { themeOptions } from '@wrp/core' 8 | 9 | import '../../style/global.css' 10 | 11 | const Box = styled('div')({ 12 | fontSize: '16px', 13 | width: '18.75em', 14 | margin: 'auto', 15 | outline: '1px solid rgba(0,0,0,0.1)', 16 | }) 17 | 18 | const theme = createTheme(themeOptions) 19 | 20 | function App() { 21 | 22 | return ( 23 | 24 | 25 | window.close()} 27 | /> 28 | 29 | 30 | ) 31 | } 32 | 33 | const target = document.querySelector('#root') 34 | render(, target) 35 | 36 | sendMessage({ type: 'popupActive' }) 37 | -------------------------------------------------------------------------------- /packages/extension/src/resource/icon/open-window.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/extension/src/resource/icon/trigger-disable.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/extension/src/resource/images/netlify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baotlake/deep-reading/e230b02bda2dd4c1e54d5c93c23ae51909ac23d9/packages/extension/src/resource/images/netlify.png -------------------------------------------------------------------------------- /packages/extension/src/service/action.ts: -------------------------------------------------------------------------------- 1 | import { ExtMessageData } from "../types"; 2 | import { sendMessageToTab } from "../uitls/extension"; 3 | 4 | 5 | export function handleActionClick(tab: chrome.tabs.Tab) { 6 | console.log('handle action clicked', tab) 7 | sendMessageToTab(tab.id, { 8 | type: 'showContentPopup', 9 | payload: { 10 | tab: tab, 11 | } 12 | }) 13 | } -------------------------------------------------------------------------------- /packages/extension/src/service/contextMenus.ts: -------------------------------------------------------------------------------- 1 | import { createContextMenus, updateContextMenus } from '../uitls/extension' 2 | import { toggleCoverVisible, checkContent } from './content' 3 | 4 | const COVER_MENU_ID = 'cover' 5 | 6 | export function createMenu() { 7 | createContextMenus({ 8 | contexts: ['all'], 9 | id: COVER_MENU_ID, 10 | title: '专注蒙层', 11 | enabled: true, 12 | visible: true, 13 | documentUrlPatterns: ['https://*/*'], 14 | }) 15 | console.log('create menus') 16 | } 17 | 18 | export function handleMenuClick(info: chrome.contextMenus.OnClickData, tab: chrome.tabs.Tab) { 19 | console.log('click menus: ', info, tab) 20 | const id = info.menuItemId 21 | switch (id) { 22 | case 'cover': 23 | checkContent().then(() => { 24 | toggleCoverVisible(tab.id) 25 | }) 26 | break 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/extension/src/service/core.ts: -------------------------------------------------------------------------------- 1 | import { Dictionary, MessageData, MessageType, Translator, eventCollect } from "@wrp/core" 2 | import { sendMessageToTab } from "../uitls/extension" 3 | 4 | 5 | type LookUpMessageData = Extract 6 | type MessageSender = chrome.runtime.MessageSender 7 | 8 | export async function lookUp(data: LookUpMessageData, sender: MessageSender) { 9 | const dictionary = new Dictionary() 10 | const result = await dictionary.search(data.text) 11 | 12 | const tabId = sender.tab?.id 13 | tabId && sendMessageToTab(tabId, { 14 | type: 'lookUpResult', 15 | data: result, 16 | }) 17 | 18 | console.log('sendMessageToTab', result) 19 | 20 | eventCollect({ 21 | ec: 'extension', 22 | ea: "lookup", 23 | // el: data., 24 | ev: 1, 25 | }) 26 | } 27 | 28 | 29 | type TranslateMessageData = Extract 30 | export async function translate(data: TranslateMessageData, sender: MessageSender) { 31 | const translator = new Translator() 32 | const result = await translator.translate(data.text) 33 | const tabId = sender.tab?.id 34 | 35 | tabId && sendMessageToTab(tabId, { 36 | type: 'translateResult', 37 | data: result, 38 | }) 39 | 40 | console.log('sendMessageToTab', result) 41 | } -------------------------------------------------------------------------------- /packages/extension/src/service/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export { handleMessage } from './message' 3 | export { handleInstalled, handleStartup } from './runtime' 4 | export { handleMenuClick } from './contextMenus' 5 | export { handleActionClick } from './action' 6 | -------------------------------------------------------------------------------- /packages/extension/src/service/message.ts: -------------------------------------------------------------------------------- 1 | import type { MessageData } from "@wrp/core" 2 | import { ExtMessageData } from "../types/message" 3 | 4 | import { lookUp, translate } from './core' 5 | import { 6 | checkContent, 7 | handleContentActive, 8 | handleOnOff, 9 | handleTargetType, 10 | handleCoverVisibleChange, 11 | handleSetCoverVisible, 12 | } from './content' 13 | import { openPage } from "./tabs" 14 | 15 | type MessageSender = chrome.runtime.MessageSender 16 | 17 | export function handleMessage(message: MessageData | ExtMessageData, sender: MessageSender, response: (res?: boolean) => void) { 18 | response(true) 19 | console.log(message, sender) 20 | const data = { ...message } 21 | switch (data.type) { 22 | case 'contentActive': 23 | handleContentActive(data, sender) 24 | break 25 | case 'enable': 26 | case 'disable': 27 | handleOnOff(data.type === 'enable') 28 | break 29 | case 'lookUp': 30 | lookUp(data, sender) 31 | break 32 | case 'translate': 33 | translate(data, sender) 34 | break 35 | case 'setTargetType': 36 | handleTargetType(data) 37 | break 38 | case 'popupActive': 39 | checkContent() 40 | break 41 | case 'setCoverVisible': 42 | handleSetCoverVisible(data, sender) 43 | break 44 | case 'coverVisibleChange': 45 | handleCoverVisibleChange(data, sender) 46 | break 47 | case 'openPage': 48 | openPage(data, sender) 49 | break 50 | } 51 | 52 | return true 53 | } 54 | -------------------------------------------------------------------------------- /packages/extension/src/service/runtime.ts: -------------------------------------------------------------------------------- 1 | import { eventCollect } from '@wrp/core' 2 | import { getManifest, setUninstallURL } from "../uitls/extension" 3 | import { setCoverVisible } from '../uitls/setting' 4 | import { createMenu } from './contextMenus' 5 | import { injectContent } from './tabs' 6 | 7 | 8 | type InstalledDetails = chrome.runtime.InstalledDetails 9 | 10 | export function handleInstalled(details: InstalledDetails) { 11 | console.log('handleInstalled: ', details) 12 | 13 | if (details.reason === 'install') { 14 | injectContent() 15 | 16 | eventCollect({ 17 | ec: 'extension', 18 | ea: 'install', 19 | el: 'Install', 20 | ev: 1, 21 | }) 22 | } 23 | 24 | if (details.reason === 'update') { 25 | eventCollect({ 26 | ec: 'extension', 27 | ea: 'update', 28 | el: 'Update-' + getManifest().version, 29 | ev: 1, 30 | }) 31 | } 32 | 33 | createMenu() 34 | 35 | setUninstallURL('https://wrp.netlify.app/extension/survey') 36 | } 37 | 38 | export function handleStartup() { 39 | console.log('handleStartup') 40 | setCoverVisible(-1, false) 41 | 42 | eventCollect({ 43 | ec: 'extension', 44 | ea: 'startup', 45 | el: 'Startup', 46 | ev: 1, 47 | }) 48 | 49 | createMenu() 50 | } -------------------------------------------------------------------------------- /packages/extension/src/service/tabs.ts: -------------------------------------------------------------------------------- 1 | import { ExtMessageData } from '../types' 2 | import { contentScripts } from '../uitls/config' 3 | import { createTab, executeScript, queryTabs } from '../uitls/extension' 4 | 5 | type ChangeInfo = chrome.tabs.TabChangeInfo 6 | type Tab = chrome.tabs.Tab 7 | type Sender = chrome.runtime.MessageSender 8 | 9 | export function handleTabsUpdated(tabId: number, changeInfo: ChangeInfo, tab: Tab) { 10 | 11 | } 12 | 13 | 14 | export async function injectContent() { 15 | console.log('inject content') 16 | const tabs = await queryTabs({}) 17 | await Promise.all(tabs.map((tab) => { 18 | console.log('executeScript ', tab.id) 19 | return executeScript({ 20 | files: contentScripts, 21 | target: { 22 | tabId: tab.id, 23 | } 24 | }) 25 | })) 26 | } 27 | 28 | 29 | type OpenPageMessage = Extract 30 | export async function openPage(message: OpenPageMessage, sender: Sender) { 31 | const { url } = message.payload 32 | return await createTab({ 33 | url, 34 | windowId: sender.tab?.windowId, 35 | }) 36 | } -------------------------------------------------------------------------------- /packages/extension/src/style/global.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@700&display=swap'); */ 6 | 7 | #root { 8 | --sc: #1b82fe; 9 | --sc-rgb: 27, 130, 254; 10 | 11 | --c-dark: #595959; 12 | --c-light: #ffffff; 13 | --b-radius: 5px; 14 | 15 | --blur: blur(30px); 16 | --blur-bg: rgba(255, 255, 255, 0.9); 17 | 18 | --t-back-c: #ffffff; 19 | --t-back-c2: #eaeaea; 20 | --t-fore-c: #555555; 21 | --t-fore-c2: #696969; 22 | 23 | --t-c-e: 255, 0, 0; 24 | --t-c-s: 27, 130, 254; 25 | 26 | --shadow: 0 0 0 1px rgba(var(--sc-rgb), 1); 27 | --shadow-arrow: 0 0 0 1.3px rgba(var(--sc-rgb), 1); 28 | --shadow-2: 0 0 0 3px rgba(var(--sc-rgb), 0.3); 29 | --shadow-3: 2px 2px 10px rgba(200, 200, 200, 0.5); 30 | --font-size: 14px !important; 31 | 32 | --z-index: 99999999999999999999; 33 | } 34 | -------------------------------------------------------------------------------- /packages/extension/src/types/extension.ts: -------------------------------------------------------------------------------- 1 | 2 | import type { TargetType } from '@wrp/core' 3 | 4 | -------------------------------------------------------------------------------- /packages/extension/src/types/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export { ExtMessageData } from './message' 3 | export { SyncStorage, LocalStorage, SessionStorage } from './storage' 4 | -------------------------------------------------------------------------------- /packages/extension/src/types/message.ts: -------------------------------------------------------------------------------- 1 | import type { TargetType, MessageData } from '@wrp/core' 2 | 3 | interface ShowContentPopupMessage { 4 | type: 'showContentPopup', 5 | payload: { 6 | tab: chrome.tabs.Tab, 7 | } 8 | } 9 | 10 | interface ContentStartMessage { 11 | type: 'contentActive' 12 | // payload: { 13 | // url: string 14 | // } 15 | } 16 | 17 | interface InitContentMessage { 18 | type: 'initContent' 19 | payload: { 20 | enable: boolean 21 | mode: TargetType 22 | customized: boolean 23 | coverVisible: boolean 24 | } 25 | } 26 | 27 | interface OpenPageMessage { 28 | type: 'openPage' 29 | payload: { 30 | url: string 31 | target?: string 32 | } 33 | } 34 | 35 | interface NoPalyloadMessage { 36 | type: 37 | | 'enable' 38 | | 'disable' 39 | | 'popupActive' 40 | | 'hello' 41 | | '' 42 | } 43 | 44 | export type ExtMessageData = 45 | | MessageData 46 | | ShowContentPopupMessage 47 | | ContentStartMessage 48 | | InitContentMessage 49 | | OpenPageMessage 50 | | NoPalyloadMessage 51 | 52 | -------------------------------------------------------------------------------- /packages/extension/src/types/storage/index.ts: -------------------------------------------------------------------------------- 1 | export type { LocalStorage } from './local' 2 | export type { SyncStorage } from './sync' 3 | export type { SessionStorage } from './session' -------------------------------------------------------------------------------- /packages/extension/src/types/storage/local.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | export type LocalStorage = {} & {} -------------------------------------------------------------------------------- /packages/extension/src/types/storage/session.ts: -------------------------------------------------------------------------------- 1 | 2 | type Cover = { 3 | cover: Record 4 | } 5 | 6 | export type SessionStorage = Cover & {} 7 | -------------------------------------------------------------------------------- /packages/extension/src/types/storage/sync.ts: -------------------------------------------------------------------------------- 1 | import type { TargetType } from '@wrp/core' 2 | 3 | type HostMode = { 4 | host_mode: Record 5 | } 6 | 7 | type Enable = { 8 | enable: boolean 9 | } 10 | 11 | export type SyncStorage = HostMode & Enable 12 | -------------------------------------------------------------------------------- /packages/extension/src/uitls/config.ts: -------------------------------------------------------------------------------- 1 | import type { TargetType } from '@wrp/core' 2 | 3 | export const defaultTriggerMode: Record = { 4 | '*': "main" 5 | } 6 | 7 | export const contentScripts = ['content.js'] 8 | 9 | export const storeId = { 10 | edge: 'acnfkkjcdomnfjdgkmcgilhnnopjbngk', 11 | firefox: '4adf7e49-9fe5-42d3-af48-0efb83b13fa8', 12 | } 13 | -------------------------------------------------------------------------------- /packages/extension/src/uitls/util.ts: -------------------------------------------------------------------------------- 1 | 2 | export {} -------------------------------------------------------------------------------- /packages/extension/src/worker/loader.ts: -------------------------------------------------------------------------------- 1 | new Worker(new URL('./service.worker.ts', import.meta.url)) -------------------------------------------------------------------------------- /packages/extension/src/worker/service.worker.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import { 4 | handleMessage, 5 | handleInstalled, 6 | handleStartup, 7 | handleMenuClick, 8 | handleActionClick, 9 | } from '../service' 10 | import { 11 | addMessageListener, 12 | addInstalledListener, 13 | addStartupListener, 14 | addContextMenusListener, 15 | addClickedActionListener, 16 | } from "../uitls/extension" 17 | 18 | 19 | addMessageListener(handleMessage) 20 | addInstalledListener(handleInstalled) 21 | addStartupListener(handleStartup) 22 | addContextMenusListener(handleMenuClick) 23 | addClickedActionListener(handleActionClick) 24 | 25 | -------------------------------------------------------------------------------- /packages/extension/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import("tailwindcss").Config} */ 2 | module.exports = { 3 | content: ["./src/**/*.tsx", "./src/**/*.css"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /packages/extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "strict": false, 13 | "forceConsistentCasingInFileNames": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": false, 19 | "removeComments": true, 20 | "preserveConstEnums": true, 21 | "sourceMap": true, 22 | "jsx": "react-jsx", 23 | "paths": { 24 | "@wrp/core": [ 25 | "../core" 26 | ], 27 | "@wrp/ui": [ 28 | "../ui" 29 | ], 30 | "@wrp/inject": [ 31 | "../inject" 32 | ] 33 | } 34 | }, 35 | "include": [ 36 | "src/**/*" 37 | ], 38 | "exclude": [ 39 | "node_modules", 40 | "**/*.spec.ts" 41 | ] 42 | } -------------------------------------------------------------------------------- /packages/inject/build.js: -------------------------------------------------------------------------------- 1 | const esbuild = require('esbuild') 2 | const { exec } = require('node:child_process') 3 | 4 | esbuild.build({ 5 | entryPoints: ['./src/website.tsx'], 6 | bundle: true, 7 | minify: true, 8 | outdir: 'dist', 9 | target: 'esnext', 10 | watch: true, 11 | }) 12 | 13 | esbuild.build({ 14 | entryPoints: ['./src/index.tsx'], 15 | bundle: true, 16 | outdir: 'es', 17 | format: 'esm', 18 | watch: { 19 | onRebuild(err, result) { 20 | if (!err) { 21 | console.log('watch build succeded: ', result) 22 | exec('npx tsc --emitDeclarationOnly') 23 | } 24 | } 25 | }, 26 | external: [ 27 | 'react', 28 | 'react-dom', 29 | '@mui/*', 30 | '@emotion/*', 31 | 'classnames', 32 | ] 33 | }) 34 | 35 | exec('npx tsc --emitDeclarationOnly') 36 | -------------------------------------------------------------------------------- /packages/inject/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wrp/inject", 3 | "version": "0.0.1", 4 | "private": true, 5 | "author": "baotlake baotlake@gmail.com", 6 | "license": "SEE LISENSE IN LISENSE.md", 7 | "scripts": { 8 | "start:fast": "node build.js", 9 | "start": "cross-env NODE_ENV=development rollup -c rollup.config.js -w", 10 | "build": "cross-env NODE_ENV=production rollup -c rollup.config.js" 11 | }, 12 | "main": "es/index.js", 13 | "module": "es/index.js", 14 | "types": "es/index.d.ts", 15 | "files": [ 16 | "./es/*", 17 | "./dist/*" 18 | ], 19 | "dependencies": { 20 | "@wrp/core": "*", 21 | "@wrp/ui": "*", 22 | "lodash-es": "^4.17.21", 23 | "react": "^17.0.2", 24 | "react-dom": "^17.0.2", 25 | "tslib": "^2.4.0" 26 | }, 27 | "devDependencies": { 28 | "@rollup/plugin-alias": "^3.1.9", 29 | "@rollup/plugin-commonjs": "^21.0.1", 30 | "@rollup/plugin-node-resolve": "^13.1.3", 31 | "@rollup/plugin-replace": "^3.0.1", 32 | "@rollup/plugin-typescript": "^8.3.0", 33 | "@types/chrome": "^0.0.197", 34 | "@types/lodash-es": "^4.17.4", 35 | "cross-env": "^7.0.3", 36 | "esbuild": "^0.14.10", 37 | "rollup": "^2.67.0", 38 | "rollup-plugin-terser": "^7.0.2", 39 | "rollup-plugin-typescript2": "^0.31.2", 40 | "rollup-plugin-visualizer": "^5.6.0" 41 | } 42 | } -------------------------------------------------------------------------------- /packages/inject/src/App/CSSGlobal.tsx: -------------------------------------------------------------------------------- 1 | import GlobalStyles from '@mui/material/GlobalStyles' 2 | 3 | export function CSSGlobal() { 4 | const style = { 5 | 'mark[class^="dr-highlight-"]': { 6 | margin: '0 !important', 7 | padding: '0 !important', 8 | color: 'inherit !important', 9 | fontSize: 'inherit !important', 10 | fontWeight: 'inherit !important', 11 | lineHeight: 'inherit !important', 12 | backgroundColor: 'rgba(0, 194, 255, 0.24)', 13 | } 14 | } 15 | return ( 16 | 17 | ) 18 | } -------------------------------------------------------------------------------- /packages/inject/src/App/app.style.ts: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles' 2 | 3 | export const Base = styled('div')({ 4 | position: 'absolute', 5 | top: 0, 6 | left: 0, 7 | right: 0, 8 | zIndex: 99999999999999999, 9 | color: 'black', 10 | textAlign: 'left', 11 | fontSize: '16px', 12 | fontFamily: 'sans-serif', 13 | }) as any 14 | 15 | export const InvisibleFrame = styled('iframe')` 16 | position: fixed; 17 | bottom: 0; 18 | right: 0; 19 | height: 0; 20 | width: 0; 21 | opacity: 0; 22 | visibility: none; 23 | border: none; 24 | outline: none; 25 | ` as any -------------------------------------------------------------------------------- /packages/inject/src/App/index.ts: -------------------------------------------------------------------------------- 1 | export { App } from './App' 2 | export { CSSGlobal } from './CSSGlobal' -------------------------------------------------------------------------------- /packages/inject/src/App/reducer/action.ts: -------------------------------------------------------------------------------- 1 | import type { State } from "./reducer" 2 | 3 | export function setState(state: Partial) { 4 | return { 5 | type: 'setState' as 'setState', 6 | payload: { 7 | ...state, 8 | } 9 | } 10 | } 11 | 12 | type ActionFunction = typeof setState 13 | 14 | export type Action = ReturnType 15 | -------------------------------------------------------------------------------- /packages/inject/src/App/reducer/index.ts: -------------------------------------------------------------------------------- 1 | export type { State } from './reducer' 2 | export { initialState, reducer } from './reducer' 3 | export { setState } from './action' 4 | export type { Action } from './action' -------------------------------------------------------------------------------- /packages/inject/src/App/reducer/reducer.ts: -------------------------------------------------------------------------------- 1 | 2 | import type { Action } from './action' 3 | 4 | type ExplanationStatus = 'loading' | 'success' | 'failed' 5 | 6 | export const initialState = { 7 | position: [0, 0] as [number, number], 8 | explanationVisible: false, 9 | explanationStatus: 'loading' as ExplanationStatus, 10 | explanationOutbound: false, 11 | wordData: {} as any, 12 | explanationZIndex: 0, 13 | translateVisible: false, 14 | translateData: {} as any, 15 | translatePosition: null as DOMRect | null, 16 | translateOutbound: false, 17 | mediaCSPViolation: false, 18 | coverVisible: false, 19 | anchorUrl: '', 20 | anchorTitle: '', 21 | anchorVisible: false, 22 | anchorElement: null as null | HTMLAnchorElement, 23 | } 24 | 25 | export type State = typeof initialState 26 | 27 | export function reducer(state: State, action: Action) { 28 | switch (action.type) { 29 | case 'setState': 30 | return { ...state, ...action.payload } 31 | default: 32 | return { ...state } 33 | } 34 | } -------------------------------------------------------------------------------- /packages/inject/src/content/extension.ts: -------------------------------------------------------------------------------- 1 | import { 2 | handleClick, 3 | handlePointerDown, 4 | handlePointerUp, 5 | handleScroll, 6 | handleContentMessage, 7 | handleTouchEnd, 8 | } from './handler' 9 | 10 | import { addContentMessageListener } from './message' 11 | import { options } from './options' 12 | 13 | 14 | let registered = false 15 | let removeContentListener: () => void 16 | 17 | export function start() { 18 | if (registered) return 19 | registered = true 20 | window.addEventListener('pointerdown', handlePointerDown) 21 | window.addEventListener('pointerup', handlePointerUp) 22 | window.addEventListener('touchend', handleTouchEnd) 23 | window.addEventListener('click', handleClick, true) 24 | window.addEventListener('scroll', handleScroll) 25 | removeContentListener = addContentMessageListener(handleContentMessage) 26 | options.preventClickLink = false 27 | } 28 | 29 | export function remove() { 30 | registered = false 31 | window.removeEventListener('pointerdown', handlePointerDown) 32 | window.removeEventListener('pointerup', handlePointerUp) 33 | window.removeEventListener('touchend', handleTouchEnd) 34 | window.removeEventListener('click', handleClick, true) 35 | window.removeEventListener('scroll', handleScroll) 36 | removeContentListener && removeContentListener() 37 | options.preventClickLink = false 38 | } 39 | -------------------------------------------------------------------------------- /packages/inject/src/content/handler/handler.ts: -------------------------------------------------------------------------------- 1 | import { sendContentMessage } from '../message' 2 | import { proxyFaild, tracePosition } from '../utils' 3 | import { options } from '../options' 4 | import { debounce } from 'lodash-es' 5 | import { Marker } from '@wrp/core' 6 | import { InjectMessage } from '../../type' 7 | 8 | const scroll = { 9 | y: 0, 10 | x: 0, 11 | } 12 | 13 | export function handleMessage(e: MessageEvent) { 14 | const message = e.data 15 | switch (message.type) { 16 | case 'restoreScroll': 17 | const top = message?.payload?.scrollY ?? scroll.y 18 | const left = message?.payload?.scrollX ?? scroll.x 19 | console.log('restoreScroll', top, left) 20 | window.scrollTo(left, top) 21 | break 22 | case 'fallbackLoadError': 23 | proxyFaild(message) 24 | break 25 | } 26 | } 27 | 28 | export function handleContentMessage(data: InjectMessage) { 29 | switch (data.type) { 30 | case 'setTargetType': 31 | options.targetType = data.payload.type 32 | break 33 | } 34 | } 35 | 36 | const debouncedReportScroll = debounce(() => { 37 | sendContentMessage({ 38 | type: 'scroll', 39 | payload: { 40 | scrollX: scroll.x, 41 | scrollY: scroll.y, 42 | }, 43 | }) 44 | }, 600) 45 | 46 | export function handleScroll(e: Event) { 47 | const { scrollX, scrollY } = window 48 | scroll.x = scrollX || scroll.x 49 | scroll.y = scrollY || scroll.y 50 | 51 | debouncedReportScroll() 52 | } 53 | -------------------------------------------------------------------------------- /packages/inject/src/content/handler/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | handleReadyStateChange, 3 | handleDOMContentLoaded, 4 | handleLoad, 5 | handleError, 6 | } from './simple' 7 | 8 | export { 9 | handleMessage, 10 | handleScroll, 11 | handleContentMessage, 12 | } from './handler' 13 | 14 | export { 15 | handleClick, 16 | handlePointerDown, 17 | handlePointerUp, 18 | handleTouchEnd, 19 | } from './pointer' -------------------------------------------------------------------------------- /packages/inject/src/content/handler/simple.ts: -------------------------------------------------------------------------------- 1 | import { sendContentMessage } from '../message' 2 | import { 3 | detectRefused, 4 | abstractProfile, 5 | } from '../utils' 6 | import type { MessageData } from "@wrp/core" 7 | 8 | export function handleReadyStateChange(e: Event) { 9 | sendContentMessage({ 10 | type: 'readyStateChange', 11 | state: document.readyState, 12 | }) 13 | 14 | if (document.readyState === 'complete') { 15 | detectRefused() 16 | abstractProfile() 17 | } 18 | } 19 | 20 | export function handleDOMContentLoaded(e: Event) { 21 | sendContentMessage({ 22 | type: 'DOMContentLoaded' 23 | }) 24 | } 25 | 26 | export function handleLoad(e: Event) { 27 | sendContentMessage({ 28 | type: 'load' 29 | }) 30 | } 31 | 32 | export function handleError(e: ErrorEvent | Event) { 33 | console.warn('error a', e) 34 | 35 | const target = e.target 36 | if (!(target instanceof HTMLElement)) return 37 | if (!e.isTrusted) return 38 | 39 | if (target instanceof HTMLLinkElement) { 40 | sendContentMessage({ 41 | type: 'loadError', 42 | payload: { 43 | name: 'link', 44 | rel: target.rel, 45 | href: target.attributes.getNamedItem('href')?.value + '', 46 | url: target.href, 47 | } 48 | }) 49 | } 50 | } -------------------------------------------------------------------------------- /packages/inject/src/content/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | sendMessage, 3 | addMessageListener, 4 | sendContentMessage, 5 | addContentMessageListener, 6 | } from './message' 7 | 8 | export { 9 | start as startExtensionContent, 10 | remove as removeExtensionContent, 11 | } from './extension' 12 | 13 | export { 14 | options as config 15 | } from './options' 16 | -------------------------------------------------------------------------------- /packages/inject/src/content/options.ts: -------------------------------------------------------------------------------- 1 | import type { TargetType } from '@wrp/core' 2 | 3 | let targetType: TargetType = 'none' 4 | let preventClickLink = false 5 | let coverVisible = false 6 | 7 | export const options = { 8 | get targetType() { 9 | return targetType 10 | }, 11 | set targetType(mode: TargetType) { 12 | targetType = mode 13 | }, 14 | 15 | get preventClickLink() { 16 | return preventClickLink 17 | }, 18 | set preventClickLink(value: boolean) { 19 | preventClickLink = value 20 | }, 21 | 22 | get coverVisible() { 23 | return coverVisible 24 | }, 25 | set coverVisible(value: boolean) { 26 | coverVisible = value 27 | } 28 | } 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /packages/inject/src/content/parent.ts: -------------------------------------------------------------------------------- 1 | 2 | const window = globalThis 3 | let realParent = window.parent 4 | 5 | export function getParent() { 6 | return realParent 7 | } 8 | 9 | 10 | export function insulate() { 11 | if (window.parent === globalThis.window) return 12 | realParent = window.parent 13 | // @ts-ignore 14 | window.parent = window 15 | } 16 | 17 | -------------------------------------------------------------------------------- /packages/inject/src/index.tsx: -------------------------------------------------------------------------------- 1 | 2 | export { App, CSSGlobal } from './App' 3 | 4 | export { 5 | start, 6 | remove, 7 | } from './content/extension' 8 | 9 | export { 10 | config, 11 | sendContentMessage, 12 | addContentMessageListener, 13 | } from './content' 14 | -------------------------------------------------------------------------------- /packages/inject/src/module.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.scss" { 2 | const content: any 3 | export default content 4 | } 5 | -------------------------------------------------------------------------------- /packages/inject/src/polyfill/history.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | const real = { 4 | replaceState: null as null | History['replaceState'], 5 | } 6 | 7 | export function polyfillHistory() { 8 | real.replaceState = globalThis.history.replaceState 9 | globalThis.history.replaceState = console.log 10 | } 11 | 12 | export function restore() { 13 | if(real.replaceState) { 14 | globalThis.history.replaceState = real.replaceState 15 | } 16 | } -------------------------------------------------------------------------------- /packages/inject/src/polyfill/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import { polyfillHistory } from './history' 3 | 4 | 5 | export function polyfill() { 6 | polyfillHistory() 7 | } 8 | 9 | -------------------------------------------------------------------------------- /packages/inject/src/type/index.ts: -------------------------------------------------------------------------------- 1 | import type { CoreMessage } from '@wrp/core' 2 | 3 | export interface PageRect extends DOMRect { 4 | scrollX: number 5 | scrollY: number 6 | } 7 | 8 | interface TapWordMessage { 9 | type: 'tapWord' 10 | payload: { 11 | text: string 12 | position: DOMRect 13 | element: Element 14 | } 15 | } 16 | 17 | interface LookupRangeMessage { 18 | type: 'lookupRange' 19 | payload: { 20 | range: Range 21 | } 22 | } 23 | 24 | interface TranslateRangeMessage { 25 | type: 'translateRange' 26 | payload: { 27 | range: Range 28 | } 29 | } 30 | 31 | interface AnchorMessage { 32 | type: 'anchor' 33 | payload: { 34 | element: HTMLAnchorElement 35 | url: string 36 | title: string 37 | } 38 | } 39 | 40 | type Message = 41 | | TapWordMessage 42 | | LookupRangeMessage 43 | | TranslateRangeMessage 44 | | AnchorMessage 45 | 46 | export type InjectMessage = CoreMessage | Message 47 | 48 | export type Action = 'lookup' | 'translate' | 'link' 49 | 50 | export interface TransformDiv extends HTMLDivElement { 51 | transform?: (x: number, y: number) => void 52 | } 53 | 54 | export type MarkPreffix = 'dr-highlight-' 55 | -------------------------------------------------------------------------------- /packages/inject/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "outDir": "./es", 10 | "declaration": true, 11 | "allowJs": true, 12 | "skipLibCheck": true, 13 | "esModuleInterop": true, 14 | "allowSyntheticDefaultImports": true, 15 | "strict": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "module": "esnext", 19 | "moduleResolution": "node", 20 | "resolveJsonModule": true, 21 | "isolatedModules": true, 22 | "noImplicitAny": true, 23 | "removeComments": true, 24 | "preserveConstEnums": true, 25 | "sourceMap": true, 26 | "jsx": "react-jsx", 27 | "noEmit": false, 28 | "baseUrl": "./", 29 | "rootDir": "./src", 30 | "paths": { 31 | "@wrp/ui": [ 32 | "../ui" 33 | ], 34 | "@wrp/core": [ 35 | "../core" 36 | ], 37 | } 38 | }, 39 | "include": [ 40 | "src/**/*", 41 | ], 42 | "exclude": [ 43 | "node_modules", 44 | "**/*.spec.ts" 45 | ] 46 | } -------------------------------------------------------------------------------- /packages/ui/build.js: -------------------------------------------------------------------------------- 1 | const esbuild = require('esbuild') 2 | const { exec } = require('node:child_process') 3 | 4 | esbuild.build({ 5 | entryPoints: ['./src/index.tsx'], 6 | bundle: true, 7 | outdir: 'es', 8 | format: 'esm', 9 | watch: { 10 | onRebuild(err, result) { 11 | if (!err) { 12 | console.log('watch build succeded: ', result) 13 | exec('npx tsc --emitDeclarationOnly') 14 | } 15 | } 16 | }, 17 | external: [ 18 | 'react', 19 | 'react-dom', 20 | '@mui/*', 21 | 'classnames', 22 | ] 23 | }) 24 | 25 | exec('npx tsc --emitDeclarationOnly') -------------------------------------------------------------------------------- /packages/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wrp/ui", 3 | "version": "0.0.1", 4 | "private": true, 5 | "author": "baotlake baotlake@gmail.com", 6 | "license": "SEE LISENSE IN LISENSE.md", 7 | "scripts": { 8 | "start:fast": "node build.js", 9 | "start": "rollup -c rollup.config.js -w", 10 | "build": "rollup -c rollup.config.js" 11 | }, 12 | "main": "es/index.js", 13 | "module": "es/index.js", 14 | "types": "es/index.d.ts", 15 | "files": [ 16 | "es/*", 17 | "src/*" 18 | ], 19 | "browserslist": { 20 | "production": [ 21 | ">0.2%", 22 | "not dead", 23 | "not op_mini all" 24 | ], 25 | "development": [ 26 | "last 1 chrome version", 27 | "last 1 firefox version", 28 | "last 1 safari version" 29 | ] 30 | }, 31 | "dependencies": { 32 | "@emotion/react": "^11.10.4", 33 | "@emotion/styled": "^11.10.4", 34 | "@mui/base": "5.0.0-alpha.101", 35 | "@mui/lab": "5.0.0-alpha.103", 36 | "@mui/material": "^5.10.9", 37 | "@mui/system": "^5.10.9", 38 | "@types/react": "^17.0.50", 39 | "@types/react-dom": "^17.0.17", 40 | "classnames": "^2.3.2", 41 | "react": "^17.0.2", 42 | "react-dom": "^17.0.2" 43 | }, 44 | "devDependencies": { 45 | "@rollup/plugin-alias": "^3.1.9", 46 | "@rollup/plugin-commonjs": "^22.0.2", 47 | "@rollup/plugin-node-resolve": "^14.1.0", 48 | "@rollup/plugin-replace": "^4.0.0", 49 | "@rollup/plugin-typescript": "^8.5.0", 50 | "cross-env": "^7.0.3", 51 | "esbuild": "^0.15.9", 52 | "rollup": "^2.79.1", 53 | "rollup-plugin-terser": "^7.0.2", 54 | "rollup-plugin-typescript2": "^0.34.0", 55 | "rollup-plugin-visualizer": "^5.8.2" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/ui/rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from "rollup-plugin-typescript2"; 2 | 3 | const config = { 4 | input: ["./src/index.tsx"], 5 | output: { 6 | dir: "./es", 7 | format: "es", 8 | preserveModules: true, 9 | preserveModulesRoot: "./src", 10 | }, 11 | plugins: [typescript()], 12 | }; 13 | 14 | 15 | export default config -------------------------------------------------------------------------------- /packages/ui/src/components/Explanation/Answer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | export default function Answer({ answer }: { answer: string[][] }) { 3 | if (!answer) return <> 4 | return ( 5 | <> 6 | {answer.map((item, index) => ( 7 |
8 | {item[0]} 9 | {item[1]} 10 |
11 | ))} 12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /packages/ui/src/components/Explanation/Loading.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Loading = () => ( 4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | ) 12 | 13 | export default Loading 14 | -------------------------------------------------------------------------------- /packages/ui/src/components/Explanation/More.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function More(props: { 4 | list: string[] 5 | collapsed: boolean 6 | loadWordData: (word: string) => void 7 | setCollapsed: (value: boolean) => void 8 | }) { 9 | // const renderMore = (moreList, unfold) => { 10 | let Childs = [] 11 | if (!Array.isArray(props.list)) return <> 12 | if (!props.collapsed) { 13 | // Expanded state 14 | Childs = props.list.map((word) => { 15 | return props.loadWordData(word)}>{word} 16 | }) 17 | Childs.push( 18 | props.setCollapsed(false)} 21 | >{`<`} 22 | ) 23 | } else { 24 | // Folded state 25 | if (props.list.length >= 1) { 26 | Childs.push( 27 | props.loadWordData(props.list[0])}> 28 | {props.list[0]} 29 | 30 | ) 31 | } 32 | if (props.list.length > 1) { 33 | Childs.push( 34 | props.setCollapsed(false)} 37 | >{`<`} 38 | ) 39 | } 40 | } 41 | return <>{Childs} 42 | } 43 | -------------------------------------------------------------------------------- /packages/ui/src/components/Explanation/Nothing.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { styled } from '@mui/material/styles' 3 | 4 | 5 | 6 | const NothingBox = styled('div')({ 7 | 8 | }) 9 | 10 | export default function Nothing() { 11 | 12 | return ( 13 | 14 | 15 | 16 | ) 17 | } -------------------------------------------------------------------------------- /packages/ui/src/components/Explanation/config.ts: -------------------------------------------------------------------------------- 1 | export const config = { 2 | width: 255, 3 | height: 120, 4 | 5 | borderRadius: 6, 6 | arrowHeight: 15, 7 | arrowWidth: 26, 8 | shadowRadius: 15, 9 | } 10 | -------------------------------------------------------------------------------- /packages/ui/src/components/Explanation/index.ts: -------------------------------------------------------------------------------- 1 | import Explanation from './Explanation' 2 | export { Explanation } -------------------------------------------------------------------------------- /packages/ui/src/components/Point.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export function Point({ 4 | position, 5 | size, 6 | color, 7 | }: { 8 | position: [number, number] 9 | size?: number 10 | color?: string 11 | }) { 12 | size = size || 4 13 | color = color || 'red' 14 | 15 | return ( 16 |
28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /packages/ui/src/components/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Explanation 3 | 4 | -------------------------------------------------------------------------------- /packages/ui/src/components/Translation/README.md: -------------------------------------------------------------------------------- 1 | 2 | Translation dialog pin & drag 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/ui/src/components/Translation/Translation.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from 'react' 2 | import Card from './Card' 3 | import Box from './Box' 4 | import { useIsBottomSheet } from '../../hooks' 5 | 6 | type Props = { 7 | visible: boolean 8 | data: any 9 | rect?: DOMRect | null 10 | onClose?: () => void 11 | pin?: 'sheet' | 'modal' 12 | outbound?: boolean 13 | } 14 | 15 | function Translation( 16 | { visible, data, rect, onClose, pin, outbound }: Props, 17 | ref: React.ForwardedRef 18 | ) { 19 | 20 | const forceValue = pin === 'sheet' ? true : pin === 'modal' ? false : undefined 21 | const isBottomSheet = useIsBottomSheet(forceValue) 22 | 23 | return ( 24 | <> 25 | { 26 | isBottomSheet ? : 39 | } 40 | 41 | ) 42 | } 43 | 44 | export default forwardRef(Translation) -------------------------------------------------------------------------------- /packages/ui/src/components/Translation/boxConfig.ts: -------------------------------------------------------------------------------- 1 | export const config = { 2 | width: 255, 3 | height: 120, 4 | 5 | borderRadius: 6, 6 | 7 | arrowSize: 15, 8 | shadowRadius: 12, 9 | } 10 | -------------------------------------------------------------------------------- /packages/ui/src/components/Translation/index.ts: -------------------------------------------------------------------------------- 1 | import Card from './Card' 2 | import Box from './Box' 3 | 4 | import Translation from './Translation' 5 | 6 | 7 | export { 8 | Box as TranslateBox, 9 | Card as TranslateCard, 10 | Translation, 11 | } 12 | -------------------------------------------------------------------------------- /packages/ui/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export { useFontSize } from './useFontSize' 3 | export { useIsBottomSheet } from './useIsBottomSheet' 4 | export { useSwipeSheet } from './useSwipeSheet' 5 | export { useEscapeHide } from './useEscapeHide' 6 | export { useObserver } from './useObserver' -------------------------------------------------------------------------------- /packages/ui/src/hooks/useEscapeHide.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | 3 | export function useEscapeHide(visible: boolean, onClose?: (value: boolean) => void) { 4 | useEffect(() => { 5 | const handleKeyDown = (e: KeyboardEvent) => { 6 | if (e.key == 'Escape') { 7 | onClose && onClose(false) 8 | } 9 | } 10 | if (visible) { 11 | window.addEventListener('keydown', handleKeyDown) 12 | return () => { 13 | window.removeEventListener('keydown', handleKeyDown) 14 | } 15 | } 16 | }, [visible, onClose]) 17 | } 18 | -------------------------------------------------------------------------------- /packages/ui/src/hooks/useFontSize.ts: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | 3 | export function useFontSize() { 4 | const [style, setStyle] = useState({ 5 | fontSize: 16, 6 | }) 7 | 8 | 9 | useEffect(() => { 10 | 11 | const setFontSize = () => { 12 | const viewportMeta = document.querySelector('meta[name="viewport"]') 13 | const innerWidth = window.innerWidth 14 | const screenWidth = window.screen.width 15 | 16 | if (!viewportMeta && innerWidth * 1.2 > screenWidth) { 17 | setStyle({ 18 | fontSize: 16 * innerWidth / screenWidth 19 | }) 20 | } else { 21 | setStyle({ 22 | fontSize: 16, 23 | }) 24 | } 25 | } 26 | 27 | setFontSize() 28 | 29 | window.addEventListener('resize', setFontSize) 30 | 31 | return () => { 32 | window.removeEventListener('resize', setFontSize) 33 | } 34 | }, []) 35 | 36 | return style 37 | } 38 | -------------------------------------------------------------------------------- /packages/ui/src/hooks/useIsBottomSheet.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | 4 | export function useIsBottomSheet(forceValue?: boolean) { 5 | 6 | const [isBottomSheet, setIsBottomSheet] = useState(true) 7 | 8 | useEffect(() => { 9 | const setMode = () => { 10 | const isTouchScreen = navigator.maxTouchPoints > 0 11 | const viewWidth = Math.min(window.innerWidth, window.screen.width) 12 | 13 | const value = forceValue ?? (isTouchScreen && viewWidth < 650) 14 | setIsBottomSheet(value) 15 | } 16 | 17 | setMode() 18 | 19 | window.addEventListener('resize', setMode) 20 | 21 | return () => { 22 | window.removeEventListener('resize', setMode) 23 | } 24 | }, []) 25 | 26 | return isBottomSheet 27 | } -------------------------------------------------------------------------------- /packages/ui/src/index.tsx: -------------------------------------------------------------------------------- 1 | export { Explanation } from './components/Explanation' 2 | export { 3 | Translation, 4 | TranslateBox, 5 | TranslateCard, 6 | } from './components/Translation' 7 | export { AnchorModal } from './components/AnchorModal' 8 | export { CoverLayer } from './components/CoverLayer' 9 | export { Point } from './components/Point' 10 | 11 | export { 12 | useFontSize, 13 | useIsBottomSheet, 14 | useSwipeSheet, 15 | useEscapeHide, 16 | useObserver, 17 | } from './hooks' 18 | -------------------------------------------------------------------------------- /packages/ui/src/module.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.scss' { 2 | const content: any 3 | export default content 4 | } 5 | 6 | declare module '*.scss?raw' { 7 | const content: string 8 | export default content 9 | } 10 | -------------------------------------------------------------------------------- /packages/ui/src/style/_mixins.scss: -------------------------------------------------------------------------------- 1 | // 2 | 3 | // "X" 图标 4 | @mixin close-icon { 5 | position: relative; 6 | width: 100%; 7 | height: 100%; 8 | 9 | &::before { 10 | content: ' '; 11 | display: block; 12 | position: absolute; 13 | top: 50%; 14 | left: 15%; 15 | width: 70%; 16 | height: 1px; 17 | transform: rotate(45deg); 18 | background: var(--t-fore-c); 19 | } 20 | &::after { 21 | content: ' '; 22 | display: block; 23 | position: absolute; 24 | top: 50%; 25 | left: 15%; 26 | width: 70%; 27 | height: 1px; 28 | transform: rotate(-45deg); 29 | background: var(--t-fore-c); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/ui/src/style/index.ts: -------------------------------------------------------------------------------- 1 | import type { CSSObject } from "@mui/material/styles" 2 | 3 | 4 | export const absoluteCenterAlign: CSSObject = { 5 | position: 'absolute', 6 | top: '50%', 7 | left: '50%', 8 | transform: 'translate(-50%, -50%)', 9 | } 10 | 11 | export const svgBorderStyle: CSSObject = { 12 | ...absoluteCenterAlign, 13 | width: '100%', 14 | height: '100%', 15 | boxSizing: 'content-box', 16 | pointerEvents: 'none', 17 | 18 | '> svg': { 19 | position: 'absolute', 20 | top: 0, 21 | left: 0, 22 | width: '100%', 23 | height: '100%', 24 | }, 25 | 26 | 'path': { 27 | transition: 'all 0.2s', 28 | } 29 | } 30 | 31 | export const closeButtonStyle = (size: string) => ({ 32 | position: 'absolute', 33 | top: 0, 34 | right: 0, 35 | width: size, 36 | height: size, 37 | cursor: 'pointer', 38 | boxSizing: 'content-box', 39 | color: '#8a8a8a', 40 | WebkitTapHighlightColor: 'transparent', 41 | }) -------------------------------------------------------------------------------- /packages/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "es6", 5 | "lib": [ 6 | "dom", 7 | "dom.iterable", 8 | "esnext" 9 | ], 10 | "outDir": "./es", 11 | "preserveSymlinks": true, 12 | "declaration": true, 13 | "declarationMap": true, 14 | "allowJs": true, 15 | "skipLibCheck": true, 16 | "esModuleInterop": true, 17 | "allowSyntheticDefaultImports": true, 18 | "strict": false, 19 | "forceConsistentCasingInFileNames": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "moduleResolution": "node", 22 | "resolveJsonModule": true, 23 | "isolatedModules": false, 24 | // "noEmit": true, 25 | "jsx": "react-jsx", 26 | "baseUrl": "./", 27 | "rootDir": "./src", 28 | "paths": { 29 | "@wrp/core": [ 30 | "../core" 31 | ], 32 | } 33 | }, 34 | "include": [ 35 | "./src/**/*" 36 | ], 37 | "exclude": [ 38 | "node_modules", 39 | "**/*.spec.ts", 40 | "./dist", 41 | "./es" 42 | ] 43 | } -------------------------------------------------------------------------------- /packages/website-view/netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | base = "packages/website-view" 3 | publish = "./src" 4 | command = "echo 'build'" 5 | 6 | -------------------------------------------------------------------------------- /packages/website-view/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@deep-reading/website-view", 3 | "version": "0.0.1", 4 | "private": true, 5 | "author": "baotlake baotlake@gmail.com", 6 | "license": "SEE LISENSE IN LISENSE.md", 7 | "scripts": { 8 | "build": "echo 'done'" 9 | }, 10 | "dependencies": {}, 11 | "devDependencies": {}, 12 | "main": "index.js" 13 | } 14 | -------------------------------------------------------------------------------- /packages/website-view/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /packages/website-view/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/nprogress@^0.2.0": 6 | version "0.2.0" 7 | resolved "https://registry.yarnpkg.com/@types/nprogress/-/nprogress-0.2.0.tgz#86c593682d4199212a0509cc3c4d562bbbd6e45f" 8 | integrity sha512-1cYJrqq9GezNFPsWTZpFut/d4CjpZqA0vhqDUPFWYKF1oIyBz5qnoYMzR+0C/T96t3ebLAC1SSnwrVOm5/j74A== 9 | -------------------------------------------------------------------------------- /packages/website/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 历史记录 3 | - 显示历史记录 4 | 5 | 6 | ## 内容缓存 7 | - 缓存页面内容 8 | -------------------------------------------------------------------------------- /packages/website/assets/illustration/home_first_right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/website/assets/illustration/tree_space.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/website/assets/svg/about.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/website/assets/svg/book.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/website/assets/svg/clean.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /packages/website/assets/svg/explore.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/website/assets/svg/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/website/assets/svg/js.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/website/assets/svg/no_js.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/website/assets/svg/start.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/website/components/Blank/BlankProgress.tsx: -------------------------------------------------------------------------------- 1 | import Box from '@mui/material/Box' 2 | import CircularProgress from '@mui/material/CircularProgress' 3 | 4 | 5 | export function BlankProgress() { 6 | 7 | return ( 8 | 9 | 10 | 11 | ) 12 | } -------------------------------------------------------------------------------- /packages/website/components/Blank/BlankReadHistory.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import Box from '@mui/material/Box' 4 | import Typography from '@mui/material/Typography' 5 | import classNames from 'classnames' 6 | 7 | import HistoryIllus from '../../assets/illustration/history.svg?svgr' 8 | 9 | 10 | type Props = { 11 | className?: string 12 | } 13 | 14 | export function BlankReadHistory({ className }: Props) { 15 | 16 | return ( 17 | 20 | 26 | 29 | 30 | 33 | 没有任何记录哎~ 34 | 35 | 36 | ) 37 | } -------------------------------------------------------------------------------- /packages/website/components/Blank/BlankWordHistory.tsx: -------------------------------------------------------------------------------- 1 | 2 | import Box from "@mui/material/Box" 3 | import Typography from '@mui/material/Typography' 4 | 5 | import BrowseIllusSvg from '../../assets/illustration/word_browse.svg?svgr' 6 | 7 | export function BlankWordHistory() { 8 | 9 | return ( 10 | 11 | 17 | 18 | 19 | 23 | 阅读中查过的词会出现在这里~ 24 | 25 | 26 | ) 27 | } -------------------------------------------------------------------------------- /packages/website/components/BottomNav/index.style.ts: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/system' 2 | 3 | export const Container = styled('div')({ 4 | position: 'fixed', 5 | width: '100vw', 6 | bottom: 0, 7 | left: 0, 8 | right: 0, 9 | boxShadow: `0 0 1em rgba(200,200,200,0.5)`, 10 | zIndex: 10, 11 | paddingBottom: `env(safe-area-inset-bottom)`, 12 | background: 'white', 13 | 14 | '&.hidden': { 15 | bottom: '-100%', 16 | } 17 | }) -------------------------------------------------------------------------------- /packages/website/components/Explore/NavigationBar.style.ts: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/system' 2 | 3 | export const Wrapper = styled('div')({ 4 | position: 'fixed', 5 | width: '100%', 6 | top: 0, 7 | left: 0, 8 | padding: 0, 9 | paddingTop: '0.5em', 10 | overflowX: 'scroll', 11 | background: 'var(--t-back-c)', 12 | boxShadow: 'var(--shadow-3)', 13 | zIndex: 99, 14 | 15 | '&::-webkit-scrollbar': { 16 | display: 'none', 17 | } 18 | }) 19 | 20 | export const Container = styled('ul')({ 21 | display: 'flex', 22 | padding: '0 0.5em', 23 | boxSizing: 'border-box', 24 | margin: 'inherit', 25 | }) 26 | 27 | export const Item = styled('li')(({ theme }) => ({ 28 | padding: '0.5em', 29 | margin: '0 0.5em', 30 | listStyle: 'none', 31 | textAlign: 'center', 32 | minWidth: '4em', 33 | 34 | '&.selected': { 35 | color: theme.palette.primary.main, 36 | fontWeigth: 'blob', 37 | position: 'relative', 38 | 39 | '&::before': { 40 | content: '""', 41 | width: '100%', 42 | height: 3, 43 | bottom: 0, 44 | left: 0, 45 | borderRadius: 2, 46 | position: 'absolute', 47 | background: theme.palette.primary.main, 48 | } 49 | } 50 | })) 51 | 52 | -------------------------------------------------------------------------------- /packages/website/components/Explore/NavigationBar.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames' 2 | import React, { useRef } from 'react' 3 | 4 | import { Wrapper, Container, Item } from './NavigationBar.style' 5 | 6 | export default function NavigationBar(props: { 7 | selected: number 8 | list: any[] 9 | setIndex: (index: number) => void 10 | }) { 11 | const scrollView = useRef((null as unknown) as HTMLDivElement) 12 | 13 | const wheel = (e: React.WheelEvent) => { 14 | scrollView.current.scroll({ 15 | left: scrollView.current.scrollLeft + e.deltaY, 16 | behavior: 'smooth', 17 | }) 18 | } 19 | 20 | return ( 21 | <> 22 | 26 | 27 | {props.list.map((item, index) => ( 28 | props.setIndex(index)} 34 | key={item.key} 35 | > 36 | {item.title} 37 | 38 | ))} 39 | 40 | 41 | 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /packages/website/components/Explore/SkeletonItem.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from '@mui/material' 2 | import { styled } from '@mui/system' 3 | 4 | const Wrapper = styled('div')({ 5 | display: 'flex', 6 | margin: '10px 0', 7 | }) 8 | 9 | const ImageBox = styled('div')({ 10 | padding: '16px', 11 | }) 12 | 13 | const TextBox = styled('div')({ 14 | width: '80%', 15 | display: 'flex', 16 | flexDirection: 'column', 17 | justifyContent: 'space-evenly', 18 | }) 19 | 20 | export function SkeletonItem() { 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /packages/website/components/Explore/_cardContainer.scss: -------------------------------------------------------------------------------- 1 | 2 | @mixin media-query { 3 | display: grid; 4 | .card-container { 5 | display: grid; 6 | grid-column-gap: 20px; 7 | grid-row-gap: 20px; 8 | } 9 | 10 | // 40 + 280 + 280 + 20 11 | @media screen and (max-width: 620px) { 12 | .card-container { 13 | grid: auto-flow 80px / 1fr; 14 | } 15 | } 16 | 17 | // 620 + 280 + 20 18 | @media screen and (min-width: 620px) and (max-width: 920px) { 19 | .card-container { 20 | grid: auto-flow 80px / 1fr 1fr; 21 | } 22 | } 23 | // 900 + 280 + 20 24 | @media screen and (min-width: 920px) and (max-width: 1220px) { 25 | .card-container { 26 | grid: auto-flow 80px / 1fr 1fr 1fr; 27 | } 28 | } 29 | // 1200 + 280 + 20 30 | @media screen and (min-width: 1220px) and (max-width: 1520px) { 31 | .card-container { 32 | grid: auto-flow 80px / 1fr 1fr 1fr 1fr; 33 | } 34 | } 35 | 36 | @media screen and (min-width: 1520px) { 37 | .card-container { 38 | grid: auto-flow 80px / 1fr 1fr 1fr 1fr 1fr; 39 | } 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /packages/website/components/Home/ItemCard.style.ts: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/system' 2 | import { alpha } from '@mui/material' 3 | 4 | export const A = styled('a')(({ theme }) => ({ 5 | textDecoration: 'none', 6 | color: 'rgba(0,0,0,0.8)', 7 | boxSizing: 'border-box', 8 | boxShadow: '2px 2px 10px rgba(200, 200, 200, 0.5)', 9 | borderRadius: '5px', 10 | maxWidth: '560px', 11 | minWidth: '280px', 12 | height: '80px', 13 | padding: '10px 16px 10px 80px', 14 | position: 'relative', 15 | 16 | '&:hover': { 17 | boxShadow: `0 0 0 3px ${alpha(theme.palette.primary.main, 0.3)}` 18 | } 19 | })) 20 | 21 | export const Title = styled('div')({ 22 | textOverflow: 'ellipsis', 23 | whiteSpace: 'nowrap', 24 | overflow: 'hidden', 25 | fontSize: '16px', 26 | fontWeight: 'bold', 27 | lineHeight: '24px', 28 | }) 29 | 30 | export const ImageWrapper = styled('div')({ 31 | position: 'absolute', 32 | top: 0, 33 | left: 0, 34 | height: '80px', 35 | width: '80px', 36 | padding: '16px', 37 | boxSizing: 'border-box', 38 | 39 | '> img': { 40 | width: '100%', 41 | height: '100%', 42 | objectFit: 'contain', 43 | } 44 | }) 45 | 46 | export const Description = styled('div')({ 47 | fontSize: '12px', 48 | lineHeight: '18px', 49 | height: '36px', 50 | overflow: 'hidden', 51 | textOverflow: 'ellipsis', 52 | position: 'relative', 53 | display: '-webkit-box', 54 | webkitLineClamp: 2, 55 | WebkitBoxOrient: 'vertical', 56 | }) -------------------------------------------------------------------------------- /packages/website/components/Home/ItemCard.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import { A, Title, ImageWrapper, Description } from './ItemCard.style' 3 | 4 | const imgFallback = '/logo_fallback.svg' 5 | 6 | type Props = { 7 | url: string 8 | title: string 9 | des?: string 10 | icon?: string 11 | } 12 | 13 | export default function ItemCard({ url, title, des, icon }: Props) { 14 | return ( 15 | 19 | 22 | 23 | {title} 24 | 25 | 26 | favicon 34 | 35 | 36 | {des} 37 | 38 | 39 | 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /packages/website/components/Home/index.ts: -------------------------------------------------------------------------------- 1 | import ItemCard from './ItemCard' 2 | import GoBar from './GoBar' 3 | 4 | export { ItemCard, GoBar } 5 | -------------------------------------------------------------------------------- /packages/website/components/Integration/GoogleAnalytics.tsx: -------------------------------------------------------------------------------- 1 | import Script from 'next/script' 2 | 3 | const __DEV__ = process.env.NODE_ENV == "development" 4 | 5 | export function GoogleAnalytics() { 6 | 7 | return ( 8 | <> 9 | {__DEV__ ? ( 10 | 14 | ) : ( 15 | <> 16 |