├── packages ├── eagle │ ├── static │ │ ├── .nojekyll │ │ ├── img │ │ │ ├── english.png │ │ │ ├── favicon.ico │ │ │ ├── icons │ │ │ │ ├── icon-72x72.png │ │ │ │ ├── icon-128x128.png │ │ │ │ └── icon-512x512.png │ │ │ ├── backspace.svg │ │ │ ├── logo.svg │ │ │ ├── next.svg │ │ │ ├── previous.svg │ │ │ ├── pause.svg │ │ │ ├── bili-video-card-stats-icon.svg │ │ │ ├── play.svg │ │ │ └── english.svg │ │ └── manifest.json │ ├── docs │ │ ├── intro.md │ │ ├── lib │ │ │ ├── corpus.mdx │ │ │ └── pronunciation.mdx │ │ ├── marking │ │ │ ├── mark-train-styles.module.css │ │ │ ├── intro.mdx │ │ │ ├── daily-sentence.mdx │ │ │ ├── oxford-sentence-styles.module.css │ │ │ ├── mark-train.mdx │ │ │ └── oxford-sentence.mdx │ │ └── basic │ │ │ ├── pronunciation-features.mdx │ │ │ ├── grammarly.mdx │ │ │ └── vocabulary.mdx │ ├── src │ │ ├── components │ │ │ ├── Mp3Player │ │ │ │ ├── styles.module.css │ │ │ │ └── index.tsx │ │ │ ├── BiliVideo │ │ │ │ ├── index.tsx │ │ │ │ ├── VideoPlayer.tsx │ │ │ │ ├── styles.module.css │ │ │ │ └── VideoCard.tsx │ │ │ ├── CurrentTime │ │ │ │ ├── styles.module.css │ │ │ │ └── index.tsx │ │ │ └── SymbolVideo │ │ │ │ ├── styles.module.css │ │ │ │ └── index.tsx │ │ ├── types │ │ │ ├── symbol.ts │ │ │ └── Video.ts │ │ ├── pages │ │ │ ├── index.tsx │ │ │ └── index.module.css │ │ ├── features │ │ │ └── Homepage │ │ │ │ ├── styles.module.css │ │ │ │ └── index.tsx │ │ ├── utils │ │ │ └── helper.ts │ │ └── css │ │ │ └── custom.css │ ├── babel.config.js │ ├── blog │ │ ├── authors.yml │ │ └── 2021-08-01-mdx-blog-post.mdx │ ├── tsconfig.json │ ├── symbol │ │ ├── vowel │ │ │ ├── _category_.json │ │ │ ├── double.mdx │ │ │ └── single.mdx │ │ ├── consonant │ │ │ ├── _category_.json │ │ │ ├── nasal.mdx │ │ │ ├── plosive.mdx │ │ │ ├── fricative.mdx │ │ │ └── other.mdx │ │ ├── phonics.mdx │ │ ├── demo.mdx │ │ └── intro.mdx │ ├── sidebarsSymbol.js │ ├── .gitignore │ ├── sidebars.js │ ├── README.md │ ├── package.json │ ├── docusaurus.config.js │ └── data │ │ ├── symbol.ts │ │ ├── basic.ts │ │ └── sentences.ts └── spider │ ├── README.md │ ├── src │ ├── config │ │ ├── dev.ts │ │ ├── prod.ts │ │ └── index.ts │ ├── typings │ │ ├── global.d.ts │ │ └── bilibili.ts │ ├── core │ │ ├── global.ts │ │ ├── init.ts │ │ ├── exception │ │ │ ├── exception-code.ts │ │ │ ├── index.ts │ │ │ ├── unify-response.ts │ │ │ └── http-exception.ts │ │ └── swagger │ │ │ └── index.ts │ ├── app.ts │ └── app │ │ └── api │ │ └── v1 │ │ ├── sentence.ts │ │ └── bilibili.ts │ ├── nodemon.json │ ├── tsconfig.json │ └── package.json ├── .gitattributes ├── pnpm-workspace.yaml ├── .eslintignore ├── .editorconfig ├── .prettierignore ├── .npmrc ├── .gitignore ├── scripts ├── tsconfig.json ├── publishCI.ts ├── verifyCommit.ts ├── release.ts └── releaseUtils.ts ├── .prettierrc.json ├── .github ├── workflows │ ├── publish.yml │ ├── release-tag.yml │ └── ci.yml └── commit-convention.md ├── LICENSE ├── README.md ├── package.json └── .eslintrc.cjs /packages/eagle/static/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | build 3 | playground-temp 4 | temp 5 | -------------------------------------------------------------------------------- /packages/eagle/docs/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Tutorial Intro 6 | -------------------------------------------------------------------------------- /packages/eagle/src/components/Mp3Player/styles.module.css: -------------------------------------------------------------------------------- 1 | .audio { 2 | max-height: 2rem; 3 | cursor: pointer; 4 | } 5 | -------------------------------------------------------------------------------- /packages/eagle/static/img/english.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeffon/english/HEAD/packages/eagle/static/img/english.png -------------------------------------------------------------------------------- /packages/eagle/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeffon/english/HEAD/packages/eagle/static/img/favicon.ico -------------------------------------------------------------------------------- /packages/eagle/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | } 4 | -------------------------------------------------------------------------------- /packages/spider/README.md: -------------------------------------------------------------------------------- 1 | # spider 2 | 3 | 为 eagle 项目提供 API 接口。 4 | 5 | 该项目来自简化版的[koa-web](https://github.com/zeffon/koa-web). 6 | -------------------------------------------------------------------------------- /packages/eagle/static/img/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeffon/english/HEAD/packages/eagle/static/img/icons/icon-72x72.png -------------------------------------------------------------------------------- /packages/eagle/static/img/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeffon/english/HEAD/packages/eagle/static/img/icons/icon-128x128.png -------------------------------------------------------------------------------- /packages/eagle/static/img/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeffon/english/HEAD/packages/eagle/static/img/icons/icon-512x512.png -------------------------------------------------------------------------------- /packages/spider/src/config/dev.ts: -------------------------------------------------------------------------------- 1 | export const devConf = { 2 | ENV: 'dev', 3 | PORT: 8841, 4 | BASE_URL: 'http://127.0.0.1', 5 | PREFIX: '/spider', 6 | } 7 | -------------------------------------------------------------------------------- /packages/spider/src/config/prod.ts: -------------------------------------------------------------------------------- 1 | export const prodConf = { 2 | ENV: 'prod', 3 | PORT: 8841, 4 | BASE_URL: 'http://127.0.0.1', 5 | PREFIX: '/spider', 6 | } 7 | -------------------------------------------------------------------------------- /packages/eagle/blog/authors.yml: -------------------------------------------------------------------------------- 1 | zeffon: 2 | name: Zeffon Wu 3 | title: Front End Engineer 4 | url: https://github.com/zeffon 5 | image_url: https://github.com/zeffon.png 6 | -------------------------------------------------------------------------------- /packages/spider/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts,json", 4 | "ignore": ["src/**/*.spec.ts"], 5 | "exec": "tsx -r tsconfig-paths/register src/app.ts" 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | pnpm-lock.yaml 2 | pnpm-workspace.yaml 3 | .gitignore 4 | .prettierignore 5 | 6 | node_modules 7 | dist/ 8 | build/ 9 | .docusaurus/ 10 | 11 | packages/*/CHANGELOG.md 12 | LICENSE.md 13 | -------------------------------------------------------------------------------- /packages/eagle/src/components/BiliVideo/index.tsx: -------------------------------------------------------------------------------- 1 | import { VideoCard, VideoCardList, VideoCardPage } from './VideoCard' 2 | import VideoPlayer from './VideoPlayer' 3 | 4 | export { VideoCard, VideoCardList, VideoCardPage, VideoPlayer } 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | hoist-pattern[]=*eslint* 2 | hoist-pattern[]=*babel* 3 | hoist-pattern[]=@emotion/* 4 | hoist-pattern[]=postcss 5 | hoist-pattern[]=pug 6 | hoist-pattern[]=source-map-support 7 | hoist-pattern[]=ts-node 8 | strict-peer-dependencies=false 9 | -------------------------------------------------------------------------------- /packages/eagle/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@tsconfig/docusaurus/tsconfig.json", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/eagle/docs/lib/corpus.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | sidebar_label: 语料库 4 | title: '' 5 | slug: /corpus 6 | --- 7 | 8 | :::tip 老友记第一季 9 | 10 | [**`传送门`**](https://www.aliyundrive.com/s/4AJmGUN1aTK) 11 | 提取码: 7m7y 12 | 13 | ::: 14 | -------------------------------------------------------------------------------- /packages/eagle/symbol/vowel/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 2, 3 | "label": "元音", 4 | "collapsible": true, 5 | "collapsed": false, 6 | "link": { 7 | "type": "generated-index", 8 | "slug": "/vowel", 9 | "title": "元音总览" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/eagle/src/types/symbol.ts: -------------------------------------------------------------------------------- 1 | export interface SymbolProps { 2 | id: number 3 | bbc_idx?: number 4 | name: string 5 | yingyutu: { 6 | aid: string 7 | page: number 8 | } 9 | bbc?: { 10 | aid: string 11 | page: number 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/spider/src/typings/global.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-var */ 2 | import type { UnifyResponse } from '../core/exception/unify-response' 3 | 4 | declare global { 5 | var UnifyResponse: UnifyResponse 6 | var SUCCESS_CODE: number 7 | } 8 | 9 | export {} 10 | -------------------------------------------------------------------------------- /packages/eagle/symbol/consonant/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 3, 3 | "label": "辅音", 4 | "collapsible": true, 5 | "collapsed": false, 6 | "link": { 7 | "type": "generated-index", 8 | "slug": "/consonant", 9 | "title": "辅音总览" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/spider/src/core/global.ts: -------------------------------------------------------------------------------- 1 | import { UnifyResponse } from './exception/unify-response' 2 | 3 | class InitGlobal { 4 | constructor() { 5 | global.UnifyResponse = new UnifyResponse() 6 | global.SUCCESS_CODE = 0 7 | } 8 | } 9 | 10 | export default InitGlobal 11 | -------------------------------------------------------------------------------- /packages/spider/src/config/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * System env config 3 | */ 4 | import { devConf } from './dev' 5 | import { prodConf } from './prod' 6 | 7 | const env = process.env.NODE_ENV 8 | const CONFIG = env === 'production' ? prodConf : devConf 9 | 10 | export default CONFIG 11 | -------------------------------------------------------------------------------- /packages/eagle/sidebarsSymbol.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 4 | const sidebars = { 5 | symbol: [ 6 | { 7 | type: 'autogenerated', 8 | dirName: '.', 9 | }, 10 | ], 11 | } 12 | 13 | module.exports = sidebars 14 | -------------------------------------------------------------------------------- /packages/eagle/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /packages/spider/src/app.ts: -------------------------------------------------------------------------------- 1 | import Koa from 'koa' 2 | import CONFIG from './config' 3 | import InitManager from '~/core/init' 4 | 5 | const app = new Koa() 6 | 7 | new InitManager(app) 8 | 9 | app.listen(CONFIG.PORT, () => { 10 | console.log( 11 | `Please open ${CONFIG.BASE_URL}:${CONFIG.PORT}${CONFIG.PREFIX}/v1/doc.html`, 12 | ) 13 | }) 14 | export default app 15 | -------------------------------------------------------------------------------- /packages/eagle/src/types/Video.ts: -------------------------------------------------------------------------------- 1 | export interface VideoItemProps { 2 | aid: number // video id 3 | page?: number 4 | author: string // author name 5 | title: string 6 | intro?: string 7 | pic: string // cover url 8 | created?: number // create time 9 | play?: number // play count 10 | duration?: number // duration 11 | length?: string // duration 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # files 2 | .DS_Store 3 | node_modules 4 | dist 5 | build 6 | coverage/ 7 | 8 | # local env files 9 | .env.local 10 | .env.*.local 11 | 12 | # Log files 13 | /logs 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | .eslintcache 18 | 19 | # Editor directories and files 20 | .idea 21 | .vscode 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw* 27 | -------------------------------------------------------------------------------- /packages/eagle/static/img/backspace.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "include": ["."], 4 | "compilerOptions": { 5 | "module": "CommonJS", 6 | "target": "ES2020", 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "noUnusedLocals": true, 12 | "forceConsistentCasingInFileNames": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "printWidth": 80, 6 | "trailingComma": "all", 7 | "overrides": [ 8 | { 9 | "files": ["*.json5"], 10 | "options": { 11 | "singleQuote": false, 12 | "quoteProps": "preserve" 13 | } 14 | }, 15 | { 16 | "files": ["*.yml"], 17 | "options": { 18 | "singleQuote": false 19 | } 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/eagle/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext' 3 | import Layout from '@theme/Layout' 4 | import Homepage from '@site/src/features/Homepage' 5 | 6 | export default function Home(): JSX.Element { 7 | const { siteConfig } = useDocusaurusContext() 8 | return ( 9 | 10 |
11 | 12 |
13 |
14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /packages/eagle/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 996px) { 14 | .heroBanner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | -------------------------------------------------------------------------------- /packages/eagle/docs/lib/pronunciation.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0 3 | sidebar_label: 教材 4 | title: '' 5 | slug: /pronunciation 6 | --- 7 | 8 | :::tip 剑桥国际英语语音在用初级 9 | 10 | [**`传送门`**](https://www.aliyundrive.com/s/o8UVNBmHhzD) 11 | 提取码: 6t7i 12 | 13 | ::: 14 | 15 | :::info 剑桥国际英语语音在用中级 16 | 17 | [**`传送门`**](https://www.aliyundrive.com/s/evkXHjEqPD9) 18 | 提取码: 64cu 19 | 20 | ::: 21 | 22 | :::tip 剑桥国际英语语音在用高级 23 | 24 | [**`传送门`**](https://www.aliyundrive.com/s/UWQ484mHnGM) 25 | 提取码: kw76 26 | 27 | ::: 28 | -------------------------------------------------------------------------------- /packages/eagle/blog/2021-08-01-mdx-blog-post.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | slug: mdx-blog-post 3 | title: MDX Blog Post 4 | authors: [zeffon] 5 | tags: [docusaurus] 6 | --- 7 | 8 | Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/). 9 | 10 | :::tip 11 | 12 | Use the power of React to create interactive blog posts. 13 | 14 | ```js 15 | 16 | ``` 17 | 18 | 19 | 20 | ::: 21 | -------------------------------------------------------------------------------- /packages/eagle/symbol/phonics.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | sidebar_label: 自然拼读 4 | title: '' 5 | slug: /phonics 6 | --- 7 | 8 | import BilibiliEmbedRenderer from 'react-bilibili-embed-renderer' 9 | import { symbolPhonicsItems } from '@site/data/symbol' 10 | 11 | export const PhonicsContainer = () => { 12 | return ( 13 |
14 | 18 |
19 | ) 20 | } 21 | 22 | 23 | -------------------------------------------------------------------------------- /packages/eagle/docs/marking/mark-train-styles.module.css: -------------------------------------------------------------------------------- 1 | .BilibiliError { 2 | text-align: center; 3 | font-size: 1.5rem; 4 | } 5 | 6 | .BilibiliErrorMsg { 7 | height: 4rem; 8 | line-height: 4rem; 9 | text-align: center; 10 | color: #ef5a5a; 11 | } 12 | 13 | @media screen and (max-width: 495px) { 14 | .BiliVideoCard { 15 | width: 11.25rem; 16 | } 17 | .BiliVideoCard:nth-of-type(2n) { 18 | margin-right: 0; 19 | } 20 | } 21 | 22 | @media screen and (max-width: 420px) { 23 | .BiliVideoCard { 24 | flex-direction: row; 25 | width: 100%; 26 | } 27 | .BiliVideoCardImgWarp { 28 | margin-right: 1rem; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/spider/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "esnext", 5 | "strict": true, 6 | "module": "commonjs", 7 | "allowJs": true, 8 | "sourceMap": true, 9 | "outDir": "build", 10 | "noImplicitAny": true, 11 | "moduleResolution": "node", 12 | "allowSyntheticDefaultImports": true, 13 | "esModuleInterop": true, 14 | "skipLibCheck": true, 15 | "experimentalDecorators": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "paths": { 18 | "~/*": ["./src/*"] 19 | } 20 | }, 21 | "include": ["src/**/*"], 22 | "exclude": ["node_module", "build"] 23 | } 24 | -------------------------------------------------------------------------------- /packages/eagle/docs/marking/intro.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0 3 | sidebar_label: 语音标记 4 | title: '' 5 | slug: /marking 6 | --- 7 | 8 | :::info 9 | 10 | **语音标记**是将一整个句子标记出**连读**、**失去爆破**、**重读**、**停顿**、**语调**,然后逐词,逐短语,逐句地进行跟读练习的一种方法。 11 | 12 | 因为 pronunciation features(语音技巧)在文本上没有体现,如果直接听美剧跟读的话,会囫囵吞枣没有办法把语音技巧读出,所以我们在前两周依然需要眼和耳的辅助去学习语音技巧,在视觉上我们需要尝试使用语音标记(把一些语音技巧通过不同的符号标记在文本中,需要尝试把标记读出)用 A4 纸打印出素材,素材最好是牛津每日一句中的材料,简单易用好坚持并且不会产生挫败感。 13 | 14 | ::: 15 | 16 | :::tip 17 | 18 | 首先,可以先从[**`标记训练`**](./marking/train)中学习是怎么进行语音标记; 19 | 20 | 熟悉语音标记后,可以对[**`牛津160句`**](./marking/oxford)进行语音标记练习; 21 | 22 | 当然也可以对欧陆词典的[**`每日一句`**](./marking/sentence)进行语音标记练习。 23 | 24 | ::: 25 | -------------------------------------------------------------------------------- /packages/eagle/static/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/eagle/src/features/Homepage/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .prologue { 9 | text-align: center; 10 | font-size: 2rem; 11 | } 12 | 13 | .moduleContainer { 14 | display: flex; 15 | justify-content: center; 16 | } 17 | 18 | .moduleIndex { 19 | width: 6.25rem; 20 | height: 6.25rem; 21 | line-height: 6.25rem; 22 | border-radius: 50%; 23 | font-size: 4rem; 24 | color: #fff; 25 | background-color: var(--ifm-color-primary); 26 | margin: 2rem auto 1rem; 27 | } 28 | 29 | .moduleDescription { 30 | display: flex; 31 | flex-direction: column; 32 | gap: 1rem; 33 | line-height: 1.5; 34 | } 35 | -------------------------------------------------------------------------------- /packages/eagle/src/components/BiliVideo/VideoPlayer.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | import { useSize } from 'ahooks' 3 | import BilibiliEmbedRenderer from 'react-bilibili-embed-renderer' 4 | import styles from './styles.module.css' 5 | 6 | const VideoPlayer = ({ item }) => { 7 | const ref = useRef(null) 8 | const size = useSize(ref) 9 | return ( 10 | <> 11 | {item && ( 12 |
13 | 18 |
19 | )} 20 | 21 | ) 22 | } 23 | 24 | export default VideoPlayer 25 | -------------------------------------------------------------------------------- /packages/eagle/static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "English", 3 | "short_name": "english_blog", 4 | "theme_color": "#2196f3", 5 | "background_color": "#424242", 6 | "display": "standalone", 7 | "scope": "./", 8 | "start_url": "./index.html", 9 | "related_applications": [ 10 | { 11 | "platform": "webapp", 12 | "url": "https://english.zeffon.cn/manifest.json" 13 | } 14 | ], 15 | "icons": [ 16 | { 17 | "src": "img/icons/icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image/png" 20 | }, 21 | { 22 | "src": "img/icons/icon-128x128.png", 23 | "sizes": "128x128", 24 | "type": "image/png" 25 | }, 26 | { 27 | "src": "img/icons/icon-512x512.png", 28 | "sizes": "512x512", 29 | "type": "image/png" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /packages/eagle/symbol/consonant/nasal.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | sidebar_label: 鼻音 4 | title: '' 5 | --- 6 | 7 | import Tabs from '@theme/Tabs' 8 | import TabItem from '@theme/TabItem' 9 | import { nasal, bbcNasal } from '@site/data/symbol' 10 | import SymbolVideo, { 11 | YINGTUYU_MODE, 12 | BBC_MODE, 13 | BOTH_MODE, 14 | } from '@site/src/components/SymbolVideo' 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/eagle/symbol/consonant/plosive.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0 3 | sidebar_label: 塞音 4 | title: '' 5 | --- 6 | 7 | import Tabs from '@theme/Tabs' 8 | import TabItem from '@theme/TabItem' 9 | import { plosive, bbcPlosive } from '@site/data/symbol' 10 | import SymbolVideo, { 11 | YINGTUYU_MODE, 12 | BBC_MODE, 13 | BOTH_MODE, 14 | } from '@site/src/components/SymbolVideo' 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/eagle/symbol/consonant/fricative.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | sidebar_label: 擦音 4 | title: '' 5 | --- 6 | 7 | import Tabs from '@theme/Tabs' 8 | import TabItem from '@theme/TabItem' 9 | import { fricative, bbcFricative } from '@site/data/symbol' 10 | import SymbolVideo, { 11 | YINGTUYU_MODE, 12 | BBC_MODE, 13 | BOTH_MODE, 14 | } from '@site/src/components/SymbolVideo' 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/eagle/src/components/CurrentTime/styles.module.css: -------------------------------------------------------------------------------- 1 | .currentTime { 2 | display: flex; 3 | align-items: center; 4 | justify-content: space-evenly; 5 | width: 100%; 6 | max-height: 6.25rem; 7 | height: 6.25rem; 8 | box-sizing: border-box; 9 | border-radius: 0.5rem; 10 | color: #fff; 11 | background-image: linear-gradient( 12 | var(--ifm-color-primary), 13 | var(--ifm-color-primary-lightest) 14 | ); 15 | } 16 | 17 | .timeArea { 18 | font-size: 3.4375rem; 19 | letter-spacing: 0.0625rem; 20 | } 21 | 22 | .timeAreaSplit { 23 | position: relative; 24 | top: -0.375rem; 25 | margin: 0 0.0625rem; 26 | border-radius: 0; 27 | } 28 | 29 | .timeSecond { 30 | font-size: 1.875rem; 31 | margin-left: 0.75rem; 32 | } 33 | 34 | .dateArea { 35 | font-size: 1rem; 36 | } 37 | 38 | .dateAreaWeek { 39 | font-size: 1.25rem; 40 | } 41 | -------------------------------------------------------------------------------- /packages/eagle/symbol/vowel/double.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | sidebar_label: 双元音 4 | title: '' 5 | --- 6 | 7 | import Tabs from '@theme/Tabs' 8 | import TabItem from '@theme/TabItem' 9 | import { doubleVowels, bbcDoubleVowels } from '@site/data/symbol' 10 | import SymbolVideo, { 11 | YINGTUYU_MODE, 12 | BBC_MODE, 13 | BOTH_MODE, 14 | } from '@site/src/components/SymbolVideo' 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/eagle/symbol/vowel/single.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0 3 | sidebar_label: 单元音 4 | title: '' 5 | --- 6 | 7 | import Tabs from '@theme/Tabs' 8 | import TabItem from '@theme/TabItem' 9 | import { singleVowels, bbcSingleVowels } from '@site/data/symbol' 10 | import SymbolVideo, { 11 | YINGTUYU_MODE, 12 | BBC_MODE, 13 | BOTH_MODE, 14 | } from '@site/src/components/SymbolVideo' 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/spider/src/core/init.ts: -------------------------------------------------------------------------------- 1 | import type Koa from 'koa' 2 | import Koa2Cors from 'koa2-cors' 3 | import KoaBody from 'koa-body' 4 | import catchError from './exception' 5 | import InitGlobal from './global' 6 | import swaggerRouter from './swagger' 7 | 8 | export default class InitManager { 9 | private app: Koa 10 | 11 | constructor(app: Koa) { 12 | this.app = app 13 | this.initCore() 14 | } 15 | 16 | initCore() { 17 | new InitGlobal() // global var and methods 18 | this.app.use(Koa2Cors()) // cross-domain processing 19 | this.app.use(KoaBody({ multipart: true })) // body parameter processing 20 | this.app.use(catchError) // global exception handling 21 | this._initRoutesAndSwagger() // router and api docs 22 | } 23 | 24 | _initRoutesAndSwagger() { 25 | this.app.use(swaggerRouter.routes()).use(swaggerRouter.allowedMethods()) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/eagle/docs/basic/pronunciation-features.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0 3 | sidebar_label: 语音技巧 4 | title: '' 5 | slug: /pronunce 6 | --- 7 | 8 | import { useState } from 'react' 9 | import { VideoCardList, VideoPlayer } from '@site/src/components/BiliVideo' 10 | import { features } from '@site/data/basic' 11 | 12 | export const PronunciationFeatures = () => { 13 | const [curIndex, setIndex] = useState(0) 14 | return ( 15 | <> 16 | 17 | 18 | 19 | 20 | ) 21 | } 22 | 23 | :::tip 24 | 25 | 以下视频内容是来自 B 站的[**`英语兔`**](https://space.bilibili.com/483162496)的[**`语音技巧`**](https://space.bilibili.com/483162496/favlist?fid=975864096&ftype=create)视频教程。 26 | 27 | ::: 28 | 29 | 30 | -------------------------------------------------------------------------------- /packages/eagle/docs/basic/grammarly.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | sidebar_label: 语法 4 | title: '' 5 | slug: /grammarly 6 | --- 7 | 8 | import { useState } from 'react' 9 | import { VideoCardList, VideoPlayer } from '@site/src/components/BiliVideo' 10 | import { grammarlies } from '@site/data/basic' 11 | 12 | export const PronunciationFeatures = () => { 13 | const [curIndex, setIndex] = useState(0) 14 | return ( 15 | <> 16 | 17 | 18 | 23 | 24 | ) 25 | } 26 | 27 | :::tip 28 | 29 | 以下视频内容是来自 B 站的[**`英语兔`**](https://space.bilibili.com/483162496)的[**`语法`**](https://www.bilibili.com/video/BV1XY411J7aG/)视频教程。 30 | 31 | ::: 32 | 33 | 34 | -------------------------------------------------------------------------------- /scripts/publishCI.ts: -------------------------------------------------------------------------------- 1 | import { args, getPackageInfo, publishPackage, step } from './releaseUtils' 2 | 3 | async function main() { 4 | const tag = args._[0] 5 | 6 | if (!tag) { 7 | throw new Error('No tag specified') 8 | } 9 | 10 | let pkgName = 'eagle' 11 | let version 12 | 13 | if (tag.includes('@')) [pkgName, version] = tag.split('@') 14 | else version = tag 15 | 16 | if (version.startsWith('v')) version = version.slice(1) 17 | 18 | const { currentVersion, pkgDir } = getPackageInfo(pkgName) 19 | if (currentVersion !== version) 20 | throw new Error( 21 | `Package version from tag "${version}" mismatches with current version "${currentVersion}"`, 22 | ) 23 | 24 | step('Publishing package...') 25 | await publishPackage(pkgDir, version.includes('beta') ? 'beta' : undefined) 26 | } 27 | 28 | main().catch((err) => { 29 | console.error(err) 30 | process.exit(1) 31 | }) 32 | -------------------------------------------------------------------------------- /packages/eagle/docs/basic/vocabulary.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | sidebar_label: 词汇 4 | title: '' 5 | slug: /vocabulary 6 | --- 7 | 8 | import { useState } from 'react' 9 | import { VideoCardList, VideoPlayer } from '@site/src/components/BiliVideo' 10 | import { vocabularies } from '@site/data/basic' 11 | 12 | export const PronunciationFeatures = () => { 13 | const [curIndex, setIndex] = useState(0) 14 | return ( 15 | <> 16 | 17 | 18 | 23 | 24 | ) 25 | } 26 | 27 | :::tip 28 | 29 | 以下视频内容是来自 B 站的[**`英语兔`**](https://space.bilibili.com/483162496)的[**`词汇`**](https://space.bilibili.com/483162496/favlist?fid=1208491396&ftype=create)视频教程。 30 | 31 | ::: 32 | 33 | 34 | -------------------------------------------------------------------------------- /packages/eagle/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | docs: [ 17 | { 18 | type: 'autogenerated', 19 | dirName: '.', 20 | }, 21 | ], 22 | basic: [ 23 | { 24 | type: 'autogenerated', 25 | dirName: 'basic', 26 | }, 27 | ], 28 | marking: [ 29 | { 30 | type: 'autogenerated', 31 | dirName: 'marking', 32 | }, 33 | ], 34 | lib: [ 35 | { 36 | type: 'autogenerated', 37 | dirName: 'lib', 38 | }, 39 | ], 40 | } 41 | 42 | module.exports = sidebars 43 | -------------------------------------------------------------------------------- /packages/eagle/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /packages/spider/src/core/exception/exception-code.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom HTTP exception error code 3 | * [errorCode, 'tip message'] 4 | */ 5 | const CODE = new Map([ 6 | [0, 'ok'], 7 | [9999, 'Server Unknown Exception'], 8 | 9 | [10000, 'Generic Exception'], 10 | [10001, 'Generic parameter error'], 11 | [10002, 'The resource not found'], 12 | [10003, 'No suitable login method found'], 13 | [10004, 'The token is invalid or expired'], 14 | [10005, 'The user is not authorized'], 15 | [10006, 'Failed to login'], 16 | [10020, 'The user is not found'], 17 | [ 18 | 11001, 19 | 'Redis is not enabled. If you want to use redis, please set config.REDIS.ENABLED=true', 20 | ], 21 | 22 | [20000, 'User Module Generic Error'], 23 | [20001, 'The user does not exist or password is incorrect'], 24 | [20002, 'The system is busy or code is invalid, please try again later'], 25 | [20003, 'The username already exists'], 26 | ]) 27 | 28 | export default CODE 29 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10 7 | 8 | jobs: 9 | publish: 10 | # prevents this action from running on forks 11 | if: github.repository == 'zeffon/english' 12 | runs-on: ubuntu-latest 13 | environment: Release 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | 18 | - name: Install pnpm 19 | uses: pnpm/action-setup@v2.2.2 20 | 21 | - name: Set node version to 16.x 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: 16.x 25 | registry-url: https://registry.npmjs.org/ 26 | cache: "pnpm" 27 | 28 | - name: Install deps 29 | run: pnpm install 30 | 31 | - name: Publish package 32 | run: pnpm run ci-publish ${{ github.ref_name }} 33 | env: 34 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 35 | -------------------------------------------------------------------------------- /packages/spider/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spider", 3 | "private": true, 4 | "version": "0.5.0", 5 | "description": "The best scaffolding of building Koa2 restful API with TypeScript.", 6 | "engines": { 7 | "node": ">=12.2.0" 8 | }, 9 | "scripts": { 10 | "start": "nodemon", 11 | "prod": "set NODE_ENV=production&&nodemon", 12 | "build": "tsc && tsc-alias" 13 | }, 14 | "dependencies": { 15 | "axios": "^1.3.4", 16 | "koa": "^2.14.1", 17 | "koa-body": "^6.0.1", 18 | "koa-router": "^12.0.0", 19 | "koa2-cors": "^2.0.6", 20 | "puppeteer": "^19.7.3" 21 | }, 22 | "devDependencies": { 23 | "@types/koa": "^2.13.5", 24 | "@types/koa-router": "^7.4.4", 25 | "@types/koa2-cors": "^2.0.2", 26 | "@types/node": "^18.14.6", 27 | "koa-swagger-decorator": "^1.8.6", 28 | "nodemon": "^2.0.21", 29 | "tsc-alias": "^1.8.2", 30 | "tsconfig-paths": "^4.1.2", 31 | "tsx": "^3.12.3", 32 | "typescript": "^4.9.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/eagle/static/img/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/eagle/static/img/previous.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/spider/src/app/api/v1/sentence.ts: -------------------------------------------------------------------------------- 1 | import type { Context } from 'koa' 2 | import { 3 | description, 4 | prefix, 5 | request, 6 | summary, 7 | tags, 8 | } from 'koa-swagger-decorator' 9 | import puppeteer from 'puppeteer' 10 | 11 | const tag = tags(['sentence']) 12 | 13 | @prefix('/sentence') 14 | export default class SentenceController { 15 | @request('get', '/height') 16 | @summary('Get sentence height') 17 | @description('example: /sentence/height') 18 | @tag 19 | async getSentenceHeight(ctx: Context) { 20 | const browser = await puppeteer.launch({ 21 | headless: true, 22 | defaultViewport: { 23 | width: 500, 24 | height: 2000, 25 | }, 26 | }) 27 | const page = await browser.newPage() 28 | await page.goto('https://dict.eudic.net/home/dailysentence') 29 | let height = 0 30 | height = await page.evaluate(() => { 31 | height = document.getElementsByClassName('containter')[0].clientHeight 32 | return height 33 | }) 34 | await browser.close() 35 | ctx.body = { height } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/eagle/src/components/SymbolVideo/styles.module.css: -------------------------------------------------------------------------------- 1 | .symbolContainer { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | gap: 1.25rem; 6 | } 7 | .symbolContent { 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | flex-wrap: wrap; 12 | gap: 0.75rem; 13 | } 14 | 15 | .symbolItem { 16 | display: flex; 17 | align-items: center; 18 | box-sizing: border-box; 19 | border: 0.125rem solid var(--ifm-color-secondary-darkest); 20 | border-radius: 0.25rem; 21 | padding: 0.25rem 0.75rem; 22 | line-height: 1.5; 23 | transition: opacity 0.2s ease-out; 24 | cursor: pointer; 25 | } 26 | .symbolItem:hover { 27 | box-shadow: 0 0 0.125rem 0.0625rem var(--ifm-color-secondary-darkest); 28 | } 29 | .symbolItem.isActive { 30 | background-color: var(--site-color-checkbox-checked-bg); 31 | border: 0.125rem solid var(--ifm-color-primary-darkest); 32 | opacity: 0.9; 33 | } 34 | 35 | .symbolBothVideo { 36 | display: flex; 37 | flex-wrap: wrap; 38 | align-items: center; 39 | justify-content: center; 40 | gap: 1.5rem; 41 | } 42 | -------------------------------------------------------------------------------- /packages/eagle/static/img/pause.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/spider/src/core/swagger/index.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { SwaggerRouter } from 'koa-swagger-decorator' 3 | import CONFIG from '~/config' 4 | 5 | const topRouter = new SwaggerRouter({ prefix: CONFIG.PREFIX }) 6 | 7 | /** This is v1 routers */ 8 | const v1 = new SwaggerRouter() 9 | const v1Prefix = '/v1' 10 | if (CONFIG.ENV !== 'prod') { 11 | v1.swagger({ 12 | prefix: `${CONFIG.PREFIX}${v1Prefix}`, 13 | title: 'V1 API DOC', 14 | description: 'This is v1 api doc.', 15 | version: '0.1.0', 16 | swaggerHtmlEndpoint: '/doc.html', 17 | swaggerJsonEndpoint: '/json.html', 18 | swaggerOptions: { 19 | securityDefinitions: { 20 | api_key: { 21 | type: 'apiKey', 22 | in: 'header', 23 | name: 'Authorization', 24 | }, 25 | }, 26 | }, 27 | }) 28 | } 29 | 30 | // point to v1 apis directory 31 | // eslint-disable-next-line no-restricted-globals 32 | v1.mapDir(path.resolve(__dirname, `../../app/api/v1/`)) 33 | 34 | /** This is v2 routers */ 35 | // ... 36 | 37 | topRouter.use(v1Prefix, v1.routes()) 38 | export default topRouter 39 | -------------------------------------------------------------------------------- /scripts/verifyCommit.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'node:fs' 2 | import colors from 'picocolors' 3 | 4 | /** 5 | * get $1 from commit-msg script 6 | * Please refer to usage: https://github.com/toplenboren/simple-git-hooks 7 | */ 8 | const msgPath = process.argv[2] 9 | const msg = readFileSync(msgPath, 'utf-8').trim() 10 | 11 | const releaseRE = /^v\d/ 12 | const commitRE = 13 | /^(revert: )?(feat|fix|docs|dx|refactor|perf|test|workflow|build|ci|chore|types|wip|release|deps|style)(\(.+\))?: .{1,50}/ 14 | 15 | if (!releaseRE.test(msg) && !commitRE.test(msg)) { 16 | console.log() 17 | console.error( 18 | ` ${colors.bgRed(colors.white(' ERROR '))} ${colors.red( 19 | `invalid commit message format.`, 20 | )}\n\n` + 21 | colors.red( 22 | ` Proper commit message format is required for automated changelog generation. Examples:\n\n`, 23 | ) + 24 | ` ${colors.green(`feat: add 'comments' option`)}\n` + 25 | ` ${colors.green(`fix: handle events on blur (close #1)`)}\n\n` + 26 | colors.red(` See .github/commit-convention.md for more details.\n`), 27 | ) 28 | process.exit(1) 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-present, Zeffon Wu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/eagle/symbol/demo.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | sidebar_label: 音标示范 4 | title: '' 5 | slug: /demo 6 | --- 7 | 8 | import Tabs from '@theme/Tabs' 9 | import TabItem from '@theme/TabItem' 10 | import BilibiliEmbedRenderer from 'react-bilibili-embed-renderer' 11 | import { symbolDemoItems } from '@site/data/symbol' 12 | 13 | export const DemoContainer = () => { 14 | return ( 15 | 16 | {symbolDemoItems.map((item, idx) => { 17 | return ( 18 | 19 |
20 | 21 |
22 |
23 | ) 24 | })} 25 |
26 | ) 27 | } 28 | 29 | 30 | 31 | :::tip 32 | 33 | 本次选用的音标教程是以**英式(DJ)**英语进行学习。 34 | 35 | 来自 B 站的[**`英语兔`**](https://space.bilibili.com/483162496)的[**全网最适合中国人的免费音标课**](https://www.bilibili.com/video/av414096645?zw)。 36 | 37 | 来自 B 站的[**`一起暴击口语`**](https://space.bilibili.com/433248184)的[**小破站最好的版本!BBC 38 | 经典发音教程!英语国际音标,英式发音课程!**](https://www.bilibili.com/video/av54685652?zw) 39 | 40 | ::: 41 | -------------------------------------------------------------------------------- /packages/spider/src/typings/bilibili.ts: -------------------------------------------------------------------------------- 1 | export interface VideoInfo { 2 | cid: number 3 | page: number 4 | from: string 5 | part: string 6 | duration: number 7 | vid: string 8 | weblink: string 9 | dimension: { 10 | width: number 11 | height: number 12 | rotate: number 13 | } 14 | first_frame: string 15 | } 16 | 17 | export interface CollectVideoInfo { 18 | id: number 19 | type: number 20 | title: string 21 | cover: string 22 | intro: string 23 | page: number 24 | duration: number 25 | upper: { 26 | mid: number 27 | name: string 28 | face: string 29 | } 30 | attr: number 31 | cnt_info: { 32 | collect: number 33 | play: number 34 | danmaku: number 35 | } 36 | link: string 37 | ctime: number 38 | pubtime: number 39 | fav_time: number 40 | bv_id: string 41 | bvid: string 42 | season: null 43 | ogv: null 44 | ugc: { 45 | first_cid: number 46 | } 47 | } 48 | 49 | export interface CollectVideoInfo2 { 50 | aid: number 51 | title: string 52 | pubdate: number 53 | ctime: number 54 | state: number 55 | pic: string 56 | duration: number 57 | stat: { 58 | view: number 59 | } 60 | bvid: string 61 | ugc_pay: number 62 | interactive_video: boolean 63 | } 64 | -------------------------------------------------------------------------------- /packages/eagle/symbol/consonant/other.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | sidebar_label: 塞擦音、近音、边音和辅音连缀 4 | title: '' 5 | --- 6 | 7 | import Tabs from '@theme/Tabs' 8 | import TabItem from '@theme/TabItem' 9 | import { otherConsonant, bbcOtherConsonant } from '@site/data/symbol' 10 | import SymbolVideo, { 11 | YINGTUYU_MODE, 12 | BBC_MODE, 13 | BOTH_MODE, 14 | } from '@site/src/components/SymbolVideo' 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | :::info 29 | 30 |
塞擦音:/ tʃ /、 / dʒ /
31 |
近音:/ w /、 / r /、 / j /
32 |
边音:/ l /
33 |
辅音连缀:/ ts /、 / dz /、 / tr /、 / dr /
34 | 35 | ::: 36 | 37 | :::caution 38 | 39 | 音标共有**48**个,分为元音和辅音。但在在**`BBC 经典发音教程`**是没有**辅音连缀**这四个音标视频,所以就只有**44**个。 40 | 41 | 至于为什么现代有些 DJ 音标教程去掉辅音连缀这四个音标,可以查看[**`英语兔`**](https://space.bilibili.com/483162496)在辅音 `/ ts / & / dz /` 这节课程中最后的解答。 42 | 43 | ::: 44 | -------------------------------------------------------------------------------- /packages/eagle/static/img/bili-video-card-stats-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/workflows/release-tag.yml: -------------------------------------------------------------------------------- 1 | name: Add GitHub Release Tag 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10 7 | 8 | # $GITHUB_REF_NAME - https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables 9 | 10 | jobs: 11 | release: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Get pkgName for tag 17 | id: tag 18 | run: | 19 | # matching v2.0.0 / v2.0.0-beta.8 etc 20 | if [[ $GITHUB_REF_NAME =~ ^v.+ ]]; then 21 | pkgName="eagle" 22 | else 23 | # `%@*` truncates @ and version number from the right side. 24 | # https://stackoverflow.com/questions/9532654/expression-after-last-specific-character 25 | pkgName=${GITHUB_REF_NAME%@*} 26 | fi 27 | 28 | echo "::set-output name=pkgName::$pkgName" 29 | 30 | - name: Create Release for Tag 31 | id: release_tag 32 | uses: yyx990803/release-tag@master 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} 35 | with: 36 | tag_name: ${{ github.ref }} 37 | body: | 38 | Please refer to [CHANGELOG.md](https://github.com/zeffon/english/blob/${{ github.ref_name }}/packages/${{ steps.tag.outputs.pkgName }}/CHANGELOG.md) for details. 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

English

2 | 3 |
4 | 本英语学习方法来自知乎的帖子 - 怎么练好英语口语?的高分回答。 5 |
6 | 7 | ## 方法论 8 | 9 | ### 前序阶段 10 | 11 | 对英语口语的总体认识(发音、说话方式、语法、词汇等) 12 | 13 | ### 语音的训练 14 | 15 | - 【第一周·纠音练习】使用教材和口型模仿素材纠音。 16 | - 【第二周·语音标记】跟读牛津 160 句,每天 5 句话,进行语音标记并从小到大,逐词,逐短语,逐句地进行跟读练习,次数不限,最好可以脱口而出。 17 | 18 | ### 对话的流畅和拓展 19 | 20 | **第三&四&五&六周【词汇-句型-\*-想法】** 21 | 22 | - 【词】每天背 2-4 个单词,尝试记住尽量多的意思; 23 | - 【句】并且用所背单词进行造句练习; 24 | - 【想法】再把句子应用到该单词意思可以应用到的场景下; 25 | 26 | ### 语料的积累 27 | 28 | - 【第三周】每天 10 分钟老友记,10 分钟的老友记要当成 1 个小时来看,有没有中文字幕无所谓,保证理解,可以直接把原句复制粘贴到 word 文档中,打印出来,按照剧集中的语调大致做一下语音标记,开始逐词逐句朗读,一次性只能读一句话,该句读好以后读下一句。全部读好以后反复朗读,直到脱口而出。 29 | 30 | - 【第四周】按照第三周步骤操作,3 集以后,开始回顾,回顾过程中需要去掉字幕(中英文)。如有不理解,笔记记录,全部看完后打开屏蔽字幕,仔细分析听不懂的问题所在,并练习。继续跟读练习。 31 | 32 | - 【第五周】开始结合剧集和造句练习,把剧集中的词或者句联想场景并且应用进去,反复操练。 33 | 34 | - 【第六周】继续结合词汇造句练习,尝试把词和句联想应用进不同场景,并且演练。 35 | 36 | - 【第七&八周】对应美剧的台词,造句练习的联想语段,和每天学习过的单词进行复盘,背诵。这个阶段相对应该非常简单了,因为如果之前按照此方法做了的话,就会发现很多东西已经养成了口腔肌肉习惯,再背诵就很容易(能用语感顺下来-脱口而出)。 37 | 38 | ### 实战训练 39 | 40 | - 实战对象:外国留学生,英美澳新人 41 | - 训练内容:聊天 42 | - 注意:请待在自己的 comfort zone 里,尝试着把话题引到【造句练习的场景】中。 43 | - 请一定要自信,不要紧张。不要担心犯错,母语为英语的人依然会经常犯错,换位思考,如果一个中文犯错的外国人,我们会有什么感觉?完全可以容忍,也没啥感觉对吧?所以请一定不要担心犯错。 44 | 45 | ## License 46 | 47 | [MIT](https://opensource.org/licenses/MIT) 48 | 49 | Copyright (c) 2022-present, Zeffon Wu 50 | -------------------------------------------------------------------------------- /packages/eagle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eagle", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "docusaurus": "docusaurus", 6 | "start": "docusaurus start --port 8840", 7 | "build": "docusaurus build", 8 | "swizzle": "docusaurus swizzle", 9 | "deploy": "docusaurus deploy", 10 | "clear": "docusaurus clear", 11 | "serve": "docusaurus serve --port 8842", 12 | "write-translations": "docusaurus write-translations", 13 | "write-heading-ids": "docusaurus write-heading-ids", 14 | "typecheck": "tsc" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "2.3.1", 18 | "@docusaurus/plugin-pwa": "^2.3.1", 19 | "@docusaurus/preset-classic": "2.3.1", 20 | "@mdx-js/react": "^1.6.22", 21 | "ahooks": "^3.7.5", 22 | "clsx": "^1.2.1", 23 | "dayjs": "^1.11.7", 24 | "prism-react-renderer": "^1.3.5", 25 | "react": "^17.0.2", 26 | "react-bilibili-embed-renderer": "^1.2.1", 27 | "react-dom": "^17.0.2" 28 | }, 29 | "devDependencies": { 30 | "@docusaurus/module-type-aliases": "2.3.1", 31 | "@docusaurus/types": "^2.3.1", 32 | "@tsconfig/docusaurus": "^1.0.6", 33 | "typescript": "^4.9.3" 34 | }, 35 | "browserslist": { 36 | "production": [ 37 | ">0.5%", 38 | "not dead", 39 | "not op_mini all" 40 | ], 41 | "development": [ 42 | "last 1 chrome version", 43 | "last 1 firefox version", 44 | "last 1 safari version" 45 | ] 46 | }, 47 | "engines": { 48 | "node": ">=16.14" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/eagle/docs/marking/daily-sentence.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | sidebar_label: 每日一句 4 | title: '' 5 | slug: /marking/sentence 6 | --- 7 | 8 | import { useEffect } from 'react' 9 | import { useLocalStorageState } from 'ahooks' 10 | import dayjs from 'dayjs' 11 | import CurrentTime from '@site/src/components/CurrentTime' 12 | 13 | export const DailySentence = () => { 14 | const [heightStore, setHeightStore] = useLocalStorageState( 15 | 'sentence_height', 16 | { 17 | defaultValue: { height: 1200, date: '' }, 18 | }, 19 | ) 20 | useEffect(async () => { 21 | const date = dayjs().format('YYYY-MM-DD') 22 | if (heightStore && date !== heightStore.date) { 23 | const response = await fetch( 24 | `https://english.zeffon.cn/spider/v1/sentence/height`, 25 | ) 26 | const json = await response.json() 27 | const height = json.height || 1200 28 | setHeightStore({ height, date }) 29 | } 30 | }, []) 31 | return ( 32 |
33 | 39 |
40 | ) 41 | } 42 | 43 |
44 |
45 | 46 |
47 |
48 | 49 | 50 | 51 | :::tip 52 | 53 | 以上每日一句来自欧路词典的[**`英语每日一句`**](https://dict.eudic.net/home/dailysentence) 54 | 55 | ::: 56 | -------------------------------------------------------------------------------- /packages/eagle/src/utils/helper.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs' 2 | 3 | export const calcBiliPlayCount = (count: number) => { 4 | if (count > 10000) { 5 | return (count / 10000).toFixed(1) + '万' 6 | } 7 | return count 8 | } 9 | 10 | export const calcBiliVideoDate = (timestamp: number) => { 11 | const curTimestamp = dayjs().unix() 12 | const diff = curTimestamp - timestamp 13 | if (diff < 3600) { 14 | return Math.ceil(diff / 60) + '分钟前' 15 | } else if (diff < 86400) { 16 | return Math.ceil(diff / 3600) + '小时前' 17 | } else if (diff > 86400 && diff < 86400 * 365) { 18 | return dayjs(timestamp * 1000).format('MM-DD') 19 | } 20 | return dayjs(timestamp * 1000).format('YYYY-MM-DD') 21 | } 22 | 23 | export const calcBiliPlayDuration = (duration: number) => { 24 | if (duration < 60) { 25 | return `00:${duration < 10 ? `0${duration}` : duration}` 26 | } else if (duration >= 60 && duration < 3600) { 27 | const minute = Math.floor(duration / 60) 28 | const second = duration % 60 29 | return `${minute < 10 ? `0${minute}` : minute}:${ 30 | second < 10 ? `0${second}` : second 31 | }` 32 | } 33 | const hour = Math.floor(duration / 3600) 34 | const minute = Math.floor((duration - hour * 3600) / 60) 35 | const second = duration % 60 36 | return `${hour < 10 ? `0${hour}` : hour}:${ 37 | minute < 10 ? `0${minute}` : minute 38 | }:${second < 10 ? `0${second}` : second}` 39 | } 40 | 41 | export const scrollToTop = () => { 42 | const scrollTop = 43 | document.documentElement.scrollTop || document.body.scrollTop 44 | if (scrollTop > 0) { 45 | window.requestAnimationFrame(scrollToTop) 46 | window.scrollTo(0, scrollTop - scrollTop / 8) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/eagle/symbol/intro.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0 3 | sidebar_label: 音标介绍 4 | title: '' 5 | slug: / 6 | --- 7 | 8 | import Tabs from '@theme/Tabs' 9 | import TabItem from '@theme/TabItem' 10 | import BilibiliEmbedRenderer from 'react-bilibili-embed-renderer' 11 | import { symbolIntroItems } from '@site/data/symbol' 12 | 13 | export const IntroContainer = () => { 14 | return ( 15 | 16 | {symbolIntroItems.map((item, idx) => { 17 | return ( 18 | 23 |
24 | 25 |
26 |
27 | ) 28 | })} 29 |
30 | ) 31 | } 32 | 33 | 34 | 35 | :::tip 36 | 37 | 本次选用的音标教程是以**英式(DJ)**英语进行学习。 38 | 39 | 来自 B 站的[**`英语兔`**](https://space.bilibili.com/483162496)的[**全网最适合中国人的免费音标课**](https://www.bilibili.com/video/av414096645?zw)。 40 | 41 | 来自 B 站的[**`一起暴击口语`**](https://space.bilibili.com/433248184)的[**小破站最好的版本!BBC 42 | 经典发音教程!英语国际音标,英式发音课程!**](https://www.bilibili.com/video/av54685652?zw) 43 | 44 | ::: 45 | 46 | :::info 47 | 48 | 英语的语音分成 pronunciation(发音)和 pronunciation features(语音技巧)。 49 | 练习发音的核心是通过不同感官的辅助(眼,耳)帮助口腔肌肉养成发音习惯,第一周里我们需要使用【教材】和【口型模仿视频】来纠正 pronunciation,眼的辅助是指通过观察口型细节纠音,耳的辅助是指通过声音辨析去微调纠音,只有眼和耳的搭配才能达到最好的效果,正如我们从刚出生之日起学习中文发音一样。除此之外,还需纠正不同单词的发音,比如牛津 160 句作为前 2 周跟读素材中的单词发音,需要查字典,看音标,纠音。 50 | 51 | ::: 52 | 53 | :::caution 54 | 55 | 音标共有**48**个,分为元音和辅音。但在在**`BBC 经典发音教程`**是没有**辅音连缀**这四个音标视频,所以就只有**44**个。 56 | 57 | 至于为什么现代有些 DJ 音标教程去掉辅音连缀这四个音标,可以查看[**`英语兔`**](https://space.bilibili.com/483162496)在辅音 `/ ts / & / dz /` 这节课程中最后的解答。[**`传送`**](./consonant/other) 58 | 59 | ::: 60 | -------------------------------------------------------------------------------- /packages/spider/src/core/exception/index.ts: -------------------------------------------------------------------------------- 1 | import type Koa from 'koa' 2 | import CODE from './exception-code' 3 | import { HttpException } from './http-exception' 4 | 5 | const UNDEDINED_ERROR_TIP = 'undefined errorCode' 6 | 7 | /** 8 | * Global exception catch 9 | */ 10 | export default async function catchError( 11 | ctx: Koa.Context, 12 | next: any, 13 | ): Promise { 14 | try { 15 | await next() 16 | } catch (error: any) { 17 | const isHttpException = error instanceof HttpException 18 | const request = `${ctx.method} ${ctx.path}` 19 | 20 | if (isHttpException) { 21 | const message = getMessage(error) 22 | const code = getCode(error) 23 | ctx.status = error.status 24 | const data = { 25 | code, 26 | message, 27 | request, 28 | } 29 | ctx.body = data 30 | } else if (error.status !== 500) { 31 | const data = { 32 | code: 10001, 33 | message: error.message || CODE.get(10001), 34 | request, 35 | } 36 | ctx.body = data 37 | ctx.status = error.status || 500 38 | } else { 39 | const data = { 40 | code: 9999, 41 | message: CODE.get(9999), 42 | request, 43 | } 44 | ctx.body = data 45 | ctx.status = error.status || 500 46 | } 47 | } 48 | } 49 | 50 | /** 51 | * Get custom exception message 52 | * @param error 53 | * @returns message 54 | */ 55 | function getMessage(error: any): string { 56 | const message = 57 | typeof error.code === 'number' 58 | ? error.message || CODE.get(error.code) || UNDEDINED_ERROR_TIP 59 | : error.code 60 | return message 61 | } 62 | 63 | /** 64 | * Get custom error code 65 | * @param error 66 | * @returns code 67 | */ 68 | function getCode(error: any): number { 69 | const code = typeof error.code === 'number' ? error.code : 10000 70 | return code 71 | } 72 | -------------------------------------------------------------------------------- /packages/eagle/docs/marking/oxford-sentence-styles.module.css: -------------------------------------------------------------------------------- 1 | .searchContainer { 2 | margin-left: auto; 3 | margin-bottom: 1.5rem; 4 | } 5 | .searchContainer input { 6 | width: 13.75rem; 7 | height: 2rem; 8 | border-radius: 2rem; 9 | padding: 0.75rem; 10 | border: 0.0625rem solid gray; 11 | font-size: 1rem; 12 | } 13 | .searchContainer input:focus-visible { 14 | outline: none; 15 | border-color: var(--ifm-color-primary); 16 | } 17 | .searchContainer input::-webkit-outer-spin-button, 18 | .searchContainer input::-webkit-inner-spin-button { 19 | -webkit-appearance: none; 20 | appearance: none; 21 | margin: 0; 22 | } 23 | .searchContainer input { 24 | -moz-appearance: textfield; 25 | } 26 | 27 | .sentenceContainer { 28 | width: 100%; 29 | min-height: 5rem; 30 | box-sizing: border-box; 31 | display: flex; 32 | align-items: center; 33 | border: 0.125rem solid var(--ifm-color-primary); 34 | box-shadow: var(--ifm-global-shadow-md); 35 | border-radius: var(--ifm-card-border-radius); 36 | } 37 | .sentenceIndex { 38 | flex-shrink: 0; 39 | min-width: 5rem; 40 | min-height: 5rem; 41 | line-height: 5rem; 42 | text-align: center; 43 | font-size: 2.25rem; 44 | border-radius: var(--ifm-card-border-radius); 45 | letter-spacing: 0.0625rem; 46 | background-color: var(--ifm-color-primary-light); 47 | color: #fff; 48 | margin: -0.125rem; 49 | } 50 | .sentenceContent { 51 | width: 100%; 52 | padding: 0.5rem 0.75rem; 53 | font-size: 1.125rem; 54 | } 55 | 56 | .sentenceBtnContainer { 57 | width: 12.5rem; 58 | height: 3.5rem; 59 | display: flex; 60 | justify-content: space-between; 61 | align-items: center; 62 | color: #fff; 63 | background-color: var(--ifm-color-primary-light); 64 | margin: 0 auto; 65 | padding: 0 1.5rem; 66 | margin-top: 2rem; 67 | border-radius: 3.125rem; 68 | } 69 | .prevBtn, 70 | .nextBtn { 71 | cursor: pointer; 72 | } 73 | -------------------------------------------------------------------------------- /packages/eagle/docs/marking/mark-train.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | sidebar_label: 标记训练 4 | title: '' 5 | slug: /marking/train 6 | --- 7 | 8 | import { useState, useEffect } from 'react' 9 | import styles from './mark-train-styles.module.css' 10 | import { VideoCardPage, VideoPlayer } from '@site/src/components/BiliVideo' 11 | 12 | export const MarkingTrain = () => { 13 | const [list, setList] = useState([]) 14 | const [curIndex, setIndex] = useState(0) 15 | const [paging, setPaging] = useState({ count: 0, pn: 1, ps: 30 }) 16 | const [errorMsg, setErrorMsg] = useState('') 17 | useEffect(async () => { 18 | const response = await fetch( 19 | `https://english.zeffon.cn/spider/v1/bilibili/audio?page=${paging.pn}&count=30&mid=131058159`, 20 | ) 21 | const json = await response.json() 22 | const code = json.code 23 | if (code === 0) { 24 | setList(json.list) 25 | setPaging(json.paging) 26 | } else { 27 | setErrorMsg(json.message) 28 | } 29 | }, [paging.pn]) 30 | return ( 31 | <> 32 | 33 | {errorMsg ? ( 34 |
35 |
36 | 请求Bilibili接口异常:{errorMsg} 37 |
38 |
39 | 请自行前往, 40 | 传送门 41 |
42 |
43 | ) : ( 44 |
45 | 46 | 53 |
54 | )} 55 | 56 | ) 57 | } 58 | 59 | :::tip 60 | 61 | 以下视频内容是来自 B 站的[**`英语老师Lee`**](https://space.bilibili.com/131058159)的语音标记视频。 62 | 63 | ::: 64 | 65 | 66 | -------------------------------------------------------------------------------- /packages/eagle/src/components/SymbolVideo/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import BilibiliEmbedRenderer from 'react-bilibili-embed-renderer' 3 | import type { SymbolProps } from '@site/data/symbol' 4 | import styles from './styles.module.css' 5 | 6 | export const YINGTUYU_MODE = 'yingyutu' 7 | export const BBC_MODE = 'bbc' 8 | export const BOTH_MODE = 'both' 9 | 10 | type SymbolVideoProps = { 11 | data: SymbolProps[] 12 | mode: typeof YINGTUYU_MODE | typeof BBC_MODE | typeof BOTH_MODE 13 | } 14 | 15 | const SymbolVideo = ({ data, mode }: SymbolVideoProps): JSX.Element => { 16 | const [curSymbol, setSymbol] = useState(data[0]) 17 | return ( 18 |
19 |
20 | {data.map((item) => { 21 | return ( 22 |
setSymbol(item)} 25 | className={`${styles.symbolItem} ${ 26 | item.id === curSymbol.id && styles.isActive 27 | }`} 28 | > 29 | {item.name} 30 |
31 | ) 32 | })} 33 |
34 | {mode !== BOTH_MODE && ( 35 | 39 | )} 40 | {mode === BOTH_MODE && ( 41 |
42 | 48 | {curSymbol.bbc && ( 49 | 55 | )} 56 |
57 | )} 58 |
59 | ) 60 | } 61 | 62 | export default SymbolVideo 63 | -------------------------------------------------------------------------------- /packages/eagle/static/img/play.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "english-monorepo", 3 | "private": true, 4 | "license": "MIT", 5 | "engines": { 6 | "node": ">=14.18.0 || >=16.0.0" 7 | }, 8 | "scripts": { 9 | "preinstall": "npx only-allow pnpm", 10 | "postinstall": "simple-git-hooks", 11 | "format": "prettier --write --cache .", 12 | "lint": "eslint --cache .", 13 | "typecheck": "tsc -p scripts --noEmit", 14 | "start": "pnpm -r --parallel --filter=./packages/* run start", 15 | "build": "pnpm -r --filter=./packages/* run build", 16 | "release": "tsx scripts/release.ts", 17 | "ci-publish": "tsx scripts/publishCI.ts" 18 | }, 19 | "devDependencies": { 20 | "@types/fs-extra": "^11.0.1", 21 | "@types/minimist": "^1.2.2", 22 | "@types/node": "^18.14.6", 23 | "@types/prompts": "^2.4.2", 24 | "@types/semver": "^7.3.13", 25 | "@typescript-eslint/eslint-plugin": "^5.53.0", 26 | "@typescript-eslint/parser": "^5.53.0", 27 | "conventional-changelog-cli": "^2.2.2", 28 | "eslint": "^8.34.0", 29 | "eslint-define-config": "^1.15.0", 30 | "eslint-plugin-import": "^2.27.5", 31 | "eslint-plugin-node": "^11.1.0", 32 | "eslint-plugin-regexp": "^1.12.0", 33 | "execa": "^7.0.0", 34 | "fs-extra": "^11.1.0", 35 | "lint-staged": "^13.1.2", 36 | "minimist": "^1.2.8", 37 | "picocolors": "^1.0.0", 38 | "prettier": "2.8.4", 39 | "prompts": "^2.4.2", 40 | "semver": "^7.3.8", 41 | "simple-git-hooks": "^2.8.1", 42 | "tsx": "^3.12.3", 43 | "typescript": "^4.9.3" 44 | }, 45 | "simple-git-hooks": { 46 | "pre-commit": "pnpm exec lint-staged --concurrent false", 47 | "commit-msg": "pnpm exec tsx scripts/verifyCommit.ts $1" 48 | }, 49 | "lint-staged": { 50 | "*": [ 51 | "prettier --write --cache --ignore-unknown" 52 | ], 53 | "packages/*/{src,types}/**/*.ts": [ 54 | "eslint --cache --fix" 55 | ], 56 | "packages/**/*.d.ts": [ 57 | "eslint --cache --fix" 58 | ] 59 | }, 60 | "packageManager": "pnpm@7.27.1", 61 | "pnpm": { 62 | "overrides": { 63 | "eagle": "workspace:*" 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/eagle/src/components/CurrentTime/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react' 2 | import dayjs from 'dayjs' 3 | import styles from './styles.module.css' 4 | 5 | const CurrentTime = (): JSX.Element => { 6 | const weeks = ['日', '一', '二', '三', '四', '五', '六'] 7 | 8 | const timer = useRef() 9 | const [rendered, setRendered] = useState(false) 10 | const [year, setYear] = useState('') 11 | const [month, setMonth] = useState('') 12 | const [week, setWeek] = useState('') 13 | const [date, setDate] = useState('') 14 | const [hour, setHour] = useState('') 15 | const [minute, setMinute] = useState('') 16 | const [second, setSecond] = useState('') 17 | 18 | const calcTime = () => { 19 | const time = dayjs() 20 | const year = time.year() 21 | const month = time.month() 22 | const week = time.day() 23 | const date = time.date() 24 | const hour = time.hour() 25 | const minute = time.minute() 26 | const second = time.second() 27 | setRendered(true) 28 | setYear(year) 29 | setMonth(month + 1) 30 | setWeek(weeks[week]) 31 | setDate(date) 32 | setHour(hour > 9 ? hour : `0${hour}`) 33 | setMinute(minute > 9 ? minute : `0${minute}`) 34 | setSecond(second > 9 ? second : `0${second}`) 35 | } 36 | 37 | useEffect(() => { 38 | timer.current = setInterval(calcTime, 1000) 39 | return () => { 40 | clearTimeout(timer.current) 41 | } 42 | }, []) 43 | 44 | return ( 45 | <> 46 | {rendered && ( 47 |
48 |
49 | {hour} 50 | : 51 | {minute} 52 | {second} 53 |
54 |
55 |
星期{week}
56 |
57 | {year}年{month}月{date}日 58 |
59 |
60 |
61 | )} 62 | 63 | ) 64 | } 65 | 66 | export default CurrentTime 67 | -------------------------------------------------------------------------------- /packages/eagle/src/components/Mp3Player/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from 'react' 2 | import PlayIcon from '@site/static/img/play.svg' 3 | import PauseIcon from '@site/static/img/pause.svg' 4 | import styles from './styles.module.css' 5 | 6 | interface IAudioProps extends React.AudioHTMLAttributes { 7 | src: string 8 | } 9 | const wrapEvent = (userEvent: any, proxyEvent?: any) => { 10 | return (event: any) => { 11 | try { 12 | proxyEvent && proxyEvent(event) 13 | } finally { 14 | userEvent && userEvent(event) 15 | } 16 | } 17 | } 18 | 19 | const useAudio = (props: IAudioProps) => { 20 | const ref = useRef(null) 21 | const [paused, setPaused] = useState(true) 22 | 23 | const onPlay = () => { 24 | setPaused(false) 25 | } 26 | const onPause = () => { 27 | setPaused(true) 28 | } 29 | const element = React.createElement('audio', { 30 | ...props, 31 | ref, 32 | onPlay: wrapEvent(onPlay), 33 | onPause: wrapEvent(onPause), 34 | onEnded: wrapEvent(onPause), 35 | }) 36 | 37 | let lockPlay: boolean = false 38 | 39 | const controls = { 40 | play: () => { 41 | const el = ref.current 42 | if (!el) { 43 | return undefined 44 | } 45 | 46 | if (!lockPlay) { 47 | const promise = el.play() 48 | const isPromise = typeof promise === 'object' 49 | 50 | if (isPromise) { 51 | lockPlay = true 52 | const resetLock = () => { 53 | lockPlay = false 54 | } 55 | promise.then(resetLock, resetLock) 56 | } 57 | return promise 58 | } 59 | return undefined 60 | }, 61 | pause: () => { 62 | const el = ref.current 63 | if (el && !lockPlay) { 64 | setPaused(true) 65 | return el.pause() 66 | } 67 | }, 68 | } 69 | 70 | return [ 71 |
72 | {element} 73 | {paused ? ( 74 | 75 | ) : ( 76 | 77 | )} 78 |
, 79 | controls, 80 | ref, 81 | ] as const 82 | } 83 | 84 | export default useAudio 85 | -------------------------------------------------------------------------------- /packages/spider/src/core/exception/unify-response.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CreateSuccess, 3 | DeleteSuccess, 4 | ForbiddenException, 5 | GetSuccess, 6 | NotFoundException, 7 | ParameterException, 8 | ServerErrorException, 9 | UnAuthenticatedException, 10 | UpdateSuccess, 11 | } from './http-exception' 12 | 13 | /** 14 | * Unify Response 15 | */ 16 | export class UnifyResponse { 17 | /** 18 | * Get success 19 | * @param code errorCode 20 | * @param message tip message 21 | */ 22 | getSuccess({ code = global.SUCCESS_CODE, message = '' }): void { 23 | throw new GetSuccess(code, message) 24 | } 25 | 26 | /** 27 | * Create Success 28 | * @param code errorCode 29 | * @param message tip message 30 | */ 31 | createSuccess({ code = global.SUCCESS_CODE, message = '' }): void { 32 | throw new CreateSuccess(code, message) 33 | } 34 | 35 | /** 36 | * Update Success 37 | * @param code errorCode 38 | * @param message tip message 39 | */ 40 | updateSuccess({ code = global.SUCCESS_CODE, message = '' }): void { 41 | throw new UpdateSuccess(code, message) 42 | } 43 | 44 | /** 45 | * 删除成功 46 | * @param code errorCode 47 | * @param message tip message 48 | */ 49 | deleteSuccess({ code = global.SUCCESS_CODE, message = '' }): void { 50 | throw new DeleteSuccess(code, message) 51 | } 52 | 53 | /** 54 | * Parameter Exception 55 | * @param codeOrMessage errorCode | error message 56 | */ 57 | parameterException(codeOrMessage: number | string): void { 58 | throw new ParameterException(codeOrMessage) 59 | } 60 | 61 | /** 62 | * Un Authenticated Exception 63 | * @param code errorCode 64 | */ 65 | unAuthenticatedException(code: number): void { 66 | throw new UnAuthenticatedException(code) 67 | } 68 | 69 | /** 70 | * Forbidden Exception 71 | * @param code errorCode 72 | */ 73 | forbiddenException(code: number): void { 74 | throw new ForbiddenException(code) 75 | } 76 | 77 | /** 78 | * Not Found Exception 79 | * @param code errorCode 80 | */ 81 | notFoundException(code: number): void { 82 | throw new NotFoundException(code) 83 | } 84 | 85 | /** 86 | * server Error 87 | * @param codeOrMessage errorCode | error message 88 | */ 89 | serverErrorException(codeOrMessage: number | string): void { 90 | throw new ServerErrorException(codeOrMessage) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /packages/spider/src/core/exception/http-exception.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom HTTP Exception 3 | */ 4 | export class HttpException extends Error { 5 | code: number | string 6 | status: number 7 | constructor(code: number | string) { 8 | super() 9 | this.code = code 10 | this.status = 500 11 | } 12 | } 13 | 14 | export class ParameterException extends HttpException { 15 | constructor(code: number | string) { 16 | super(code) 17 | this.code = code 18 | this.status = 400 19 | } 20 | } 21 | 22 | export class UnAuthenticatedException extends HttpException { 23 | constructor(code: number) { 24 | super(code) 25 | this.code = code 26 | this.status = 401 27 | } 28 | } 29 | 30 | export class ForbiddenException extends HttpException { 31 | constructor(code: number) { 32 | super(code) 33 | this.code = code 34 | this.status = 403 35 | } 36 | } 37 | 38 | export class NotFoundException extends HttpException { 39 | constructor(code: number) { 40 | super(code) 41 | this.code = code 42 | this.status = 404 43 | } 44 | } 45 | 46 | export class ServerErrorException extends HttpException { 47 | constructor(code: number | string) { 48 | super(code) 49 | this.code = code 50 | this.status = 500 51 | } 52 | } 53 | 54 | export class Success extends HttpException { 55 | constructor(code: number, message: string) { 56 | super(code) 57 | this.code = code 58 | this.status = 200 59 | this.message = message 60 | } 61 | } 62 | 63 | export class GetSuccess extends Success { 64 | constructor(code: number, message: string) { 65 | super(code, message) 66 | this.code = code 67 | this.status = 200 68 | this.message = message 69 | } 70 | } 71 | 72 | export class CreateSuccess extends Success { 73 | constructor(code: number, message: string) { 74 | super(code, message) 75 | this.code = code 76 | this.status = 201 77 | this.message = message 78 | } 79 | } 80 | 81 | export class UpdateSuccess extends Success { 82 | constructor(code: number, message: string) { 83 | super(code, message) 84 | this.code = code 85 | this.status = 200 86 | this.message = message 87 | } 88 | } 89 | 90 | export class DeleteSuccess extends Success { 91 | constructor(code: number, message: string) { 92 | super(code, message) 93 | this.code = code 94 | this.status = 200 95 | this.message = message 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /.github/commit-convention.md: -------------------------------------------------------------------------------- 1 | ## Git Commit Message Convention 2 | 3 | #### TL;DR: 4 | 5 | Messages must be matched by the following regex: 6 | 7 | 8 | ```js 9 | /^(revert: )?(feat|fix|docs|dx|refactor|perf|style|test|workflow|build|ci|chore|types|wip|release|deps)(\(.+\))?: .{1,50}/ 10 | ``` 11 | 12 | #### Examples 13 | 14 | Appears under "Features" header, `dev` subheader: 15 | 16 | ``` 17 | feat(dev): add 'comments' option 18 | ``` 19 | 20 | Appears under "Bug Fixes" header, `dev` subheader, with a link to issue #28: 21 | 22 | ``` 23 | fix(dev): fix dev error 24 | 25 | close #28 26 | ``` 27 | 28 | Appears under "Performance Improvements" header, and under "Breaking Changes" with the breaking change explanation: 29 | 30 | ``` 31 | perf(build): remove 'foo' option 32 | 33 | BREAKING CHANGE: The 'foo' option has been removed. 34 | ``` 35 | 36 | The following commit and commit `667ecc1` do not appear in the changelog if they are under the same release. If not, the revert commit appears under the "Reverts" header. 37 | 38 | ``` 39 | revert: feat(compiler): add 'comments' option 40 | 41 | This reverts commit 667ecc1654a317a13331b17617d973392f415f02. 42 | ``` 43 | 44 | ### Full Message Format 45 | 46 | A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**: 47 | 48 | ``` 49 | (): 50 | 51 | 52 | 53 |