├── 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 |
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 |
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 |
2 |
3 |
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 |
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 |