refact: refact current code base

This commit is contained in:
Kaiyi 2023-05-21 01:47:57 +08:00
parent 645e900926
commit a5a80d7c68
18 changed files with 542 additions and 2089 deletions

View File

@ -1,15 +0,0 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 140,
"bracketSpacing": true,
"semi": false,
"tabWidth": 2,
"jsxSingleQuote": false,
"overrides": [
{
"files": ".prettierrc",
"options": { "parser": "json" }
}
]
}

View File

@ -14,5 +14,5 @@
},
"css.lint.unknownAtRules": "ignore",
"css.validate": false,
"cSpell.words": ["usphone", "ukphone"]
"cSpell.words": ["rodio", "romaji", "ukphone", "usphone"]
}

View File

@ -33,13 +33,13 @@
本项目为 [Qwerty Learner](https://github.com/Kaiyiwing/qwerty-learner) 的 VSCode 插件版本,访问原始项目获得更好的体验。
(注:依赖 VSCode 最低版本为 1.53.0,如提示 ` it is not compatible with the current version of VS Code` 请升级 VSCode 版本)
(注:依赖 VSCode 最低版本为 1.53.0,如提示 `it is not compatible with the current version of VS Code` 请升级 VSCode 版本)
## ✨ 实现原理
因为 VSC 没有提供对 Keypress 的回调,所以实现上使用了较为取巧的方式,监听用户当前输入文档的改变,然后删除用户输入。 用户可以在任意代码、文档页面开启软件进行英语打字练习,插件会自动删除用户输入的文字,不会对文档内容造成影响。
目前存在的 Bug在用户输入速度较快(特别是同时按下多个键)时,可能会导致删除不完全,用户自行删除输入即可。目前处于项目初期,可能会频繁更新,还望见谅,
目前存在的 Bug在用户输入速度较快(特别是同时按下多个键)时,可能会导致删除不完全,用户自行删除输入即可。
## 🎛 使用说明
@ -64,9 +64,8 @@
- Change Chapter 可以切换章节
- Change Dictionary 可以切换字典
- Start/Pause 可以开关插件,功能等价于一键启动快捷键
- Open Read Only Mode 开启只读模式
- Close Read Only Mode 关闭只读模式
- Toggle Word Visibility 切换是否展示单词(默写模式)
- Toggle Read Only Mode 开关只读模式
命令面板快捷键
Mac: `cmd + shift + p`
@ -76,7 +75,7 @@ Win: `ctrl + shift + p`
可以在设置面板查找关键字 “Qwerty” 修改设置
```
```json
"qwerty-learner.highlightWrongColor": {
"type": "string",
"default": "#EE3D11",
@ -109,10 +108,12 @@ Win: `ctrl + shift + p`
"maximum": 100,
"description": "每个章节包含的单词数量"
},
"qwerty-learner.reWrite": {
"type": "boolean",
"default": false,
"description": "是否开启循环模式(循环单一章节)"
"qwerty-learner.wordExerciseTime": {
"type": "integer",
"default": 1,
"minimum": 1,
"maximum": 100,
"description": "每个单词的练习次数"
},
"qwerty-learner.readOnlyInterval": {
"type": "number",
@ -193,3 +194,4 @@ Win: `ctrl + shift + p`
- Essential Words
- suffix word
- word roots1
- ...

View File

@ -2,7 +2,7 @@
"name": "qwerty-learner",
"displayName": "Qwerty Learner",
"description": "为 Coder 设计的单词记忆与英语肌肉记忆锻炼软件 摸🐟版",
"version": "0.2.5",
"version": "0.2.6",
"publisher": "kaiyi",
"icon": "docs/logo.png",
"engines": {
@ -27,16 +27,21 @@
"url": "https://github.com/Kaiyiwing/qwerty-learner-vscode/"
},
"homepage": "https://github.com/Kaiyiwing/qwerty-learner-vscode",
"activationEvents": [
"onCommand:qwerty-learner.Start",
"onCommand:qwerty-learner.changeChapter",
"onCommand:qwerty-learner.changeDict"
],
"main": "./dist/extension",
"scripts": {
"pretest": "yarn run compile && yarn run lint",
"lint": "eslint src --ext ts",
"test": "node ./out/test/runTest.js",
"vscode:prepublish": "webpack --mode production",
"compile": "webpack --mode development",
"watch": "webpack --mode development --watch",
"test-compile": "tsc -p ./",
"vsce:publish": "vsce package"
},
"main": "./dist/index",
"contributes": {
"commands": [
{
"command": "qwerty-learner.Start",
"command": "qwerty-learner.start",
"title": "Qwerty Learner Start/Pause"
},
{
@ -47,34 +52,18 @@
"command": "qwerty-learner.changeDict",
"title": "Qwerty Learner Change Dictionary"
},
{
"command": "qwerty-learner.closeReadOnlyMode",
"title": "Qwerty Learner Close Read Only Mode"
},
{
"command": "qwerty-learner.openReadOnlyMode",
"title": "Qwerty Learner Open Read Only Mode"
},
{
"command": "qwerty-learner.toggleWordVisibility",
"title": "Qwerty Learner Toggle Word Visibility"
},
{
"command": "qwerty-learner.toggleReadOnlyMode",
"title": "Qwerty Learner Toggle Read Only Mode"
}
],
"menus": {
"commandPalette": [
{
"command": "qwerty-learner.closeReadOnlyMode",
"when": "qwerty-learner.readOnlyMode"
},
{
"command": "qwerty-learner.openReadOnlyMode",
"when": "!qwerty-learner.readOnlyMode"
}
]
},
"keybindings": [
{
"command": "qwerty-learner.Start",
"command": "qwerty-learner.start",
"key": "shift+alt+q",
"mac": "ctrl+shift+q",
"when": "editorTextFocus"
@ -83,16 +72,6 @@
"configuration": {
"title": "Qwerty Learner",
"properties": {
"qwerty-learner.highlightWrongColor": {
"type": "string",
"default": "#EE3D11",
"description": "输入错误时单词的颜色"
},
"qwerty-learner.highlightWrongDelay": {
"type": "number",
"default": 400,
"description": "输入错误时清空输入的延迟时间"
},
"qwerty-learner.keySound": {
"type": "boolean",
"default": true,
@ -115,10 +94,22 @@
"maximum": 100,
"description": "每个章节包含的单词数量"
},
"qwerty-learner.reWrite": {
"type": "boolean",
"default": false,
"description": "是否开启循环模式(循环单一章节)"
"qwerty-learner.wordExerciseTime": {
"type": "integer",
"default": 1,
"minimum": 1,
"maximum": 100,
"description": "每个单词的练习次数"
},
"qwerty-learner.highlightWrongColor": {
"type": "string",
"default": "#EE3D11",
"description": "输入错误时单词的颜色"
},
"qwerty-learner.highlightWrongDelay": {
"type": "number",
"default": 400,
"description": "输入错误时清空输入的延迟时间"
},
"qwerty-learner.readOnlyInterval": {
"type": "number",
@ -149,16 +140,6 @@
}
}
},
"scripts": {
"pretest": "yarn run compile && yarn run lint",
"lint": "eslint src --ext ts",
"test": "node ./out/test/runTest.js",
"vscode:prepublish": "webpack --mode production",
"compile": "webpack --mode development",
"watch": "webpack --mode development --watch",
"test-compile": "tsc -p ./",
"vsce:publish": "vsce package"
},
"devDependencies": {
"@types/glob": "^7.1.3",
"@types/lodash": "^4.14.168",

15
prettier.config.js Normal file
View File

@ -0,0 +1,15 @@
module.exports = {
singleQuote: true,
trailingComma: 'all',
printWidth: 140,
bracketSpacing: true,
semi: false,
tabWidth: 2,
jsxSingleQuote: false,
overrides: [
{
files: '.prettierrc',
options: { parser: 'json' },
},
],
}

View File

@ -1,269 +0,0 @@
import * as vscode from 'vscode'
import cet4 from './assets/CET4_T.json'
import { range } from 'lodash'
import { compareWord, getConfig, dicts, DictPickItem, getDictFile } from './utils'
import { soundPlayer } from './sound'
import { voicePlayer, getVoiceType } from './voice'
export function activate(context: vscode.ExtensionContext) {
const globalState = context.globalState
globalState.setKeysForSync(['chapter', 'order', 'dictKey'])
let chapterLength = getConfig('chapterLength')
let readOnlyMode = globalState.get('readOnlyMode', false)
let readOnlyInterval = getConfig('readOnlyInterval')
let readOnlyIntervalId: NodeJS.Timeout | null = null
let placeholder = getConfig('placeholder') // 用于控制word不可见时inputBar中是否出现占位符及样式
let wordVisibility = globalState.get('wordVisibility', true)
let prevOrder = globalState.get('order', 0)
if (prevOrder > chapterLength) {
prevOrder = 0
}
let isStart = false,
hasWrong = false,
chapter = globalState.get('chapter', 0),
order = prevOrder,
dict = cet4,
dictKey = 'cet4',
voiceType = getVoiceType(),
voiceLock = false
let wordList = dict.slice(chapter * chapterLength, (chapter + 1) * chapterLength)
let totalChapters = Math.ceil(dict.length / chapterLength)
let inputBarIndex = 0 // 当前inputBar将要输入到的下标
const wordBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, -100)
const inputBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, -101)
const transBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, -102)
changeDict(globalState.get('dictKey', 'cet4'))
function setupWord() {
if (order === wordList.length) {
if (!getConfig('reWrite')) {
if (chapter === totalChapters - 1) {
chapter = 0
} else {
chapter += 1
}
}
order = 0
wordList = dict.slice(chapter * chapterLength, (chapter + 1) * chapterLength)
}
let phonetic = ''
switch (getConfig('phonetic')) {
case 'us':
phonetic = wordList[order].usphone || ''
break
case 'uk':
phonetic = wordList[order].ukphone || ''
break
case 'close':
phonetic = ''
break
}
// API 字典会出现括号,但部分 vscode 插件会拦截括号的输入
wordList[order].name = wordList[order].name.replace('(', '').replace(')', '')
wordBar.text = `${dicts[dictKey].name} chp.${chapter + 1} ${order + 1}/${wordList.length} ${
wordVisibility ? wordList[order].name : ''
}`
if (wordVisibility || placeholder === '') {
inputBar.text = ''
} else {
// 拼接占位符
inputBar.text = placeholder.repeat(wordList[order].name.length)
}
inputBarIndex = 0
transBar.text = phonetic ? `/${phonetic}/ ` : ''
transBar.text += wordList[order].trans.join('; ')
if (voiceType && !voiceLock) {
voiceLock = true
voicePlayer(wordList[order].name, voiceType, () => {
voiceLock = false
})
}
updateGlobalState()
}
function refreshWordList() {
totalChapters = Math.ceil(dict.length / chapterLength)
wordList = dict.slice(chapter * chapterLength, (chapter + 1) * chapterLength)
setupWord()
}
function changeDict(key: string) {
if (key === 'cet4') {
dict = cet4
} else {
dict = getDictFile(dicts[key].url)
}
dictKey = key
refreshWordList()
}
function updateGlobalState() {
globalState.update('chapter', chapter)
globalState.update('order', order)
globalState.update('dictKey', dictKey)
}
vscode.workspace.onDidChangeConfiguration(function (event) {
const configList = ['qwerty-learner.chapterLength']
const affected = configList.some((item) => event.affectsConfiguration(item))
if (affected) {
chapter = 0
order = 0
chapterLength = getConfig('chapterLength')
refreshWordList()
}
// 修改了placeholder只需要更新placeholder
if (event.affectsConfiguration('qwerty-learner.placeholder')) {
placeholder = getConfig('placeholder')
setupWord()
}
})
vscode.workspace.onDidChangeTextDocument((e) => {
if (isStart && !readOnlyMode) {
const { uri } = e.document
// 避免破坏配置文件
if (uri.scheme.indexOf('vscode') !== -1) {
return
}
const { range, text, rangeLength } = e.contentChanges[0]
if (text !== '' && text.length === 1) {
// 删除用户输入的字符
const newRange = new vscode.Range(range.start.line, range.start.character, range.end.line, range.end.character + 1)
const editAction = new vscode.WorkspaceEdit()
editAction.delete(uri, newRange)
vscode.workspace.applyEdit(editAction)
if (!hasWrong) {
soundPlayer('click')
let curInput = ''
if (wordVisibility || placeholder === '') {
// 没有使用placeholder不需要特殊处理
inputBar.text += text
curInput = inputBar.text
} else {
// 拼接占位符 && 获取当前已经键入的值进行比较
inputBar.text = inputBar.text.substring(0, inputBarIndex) + text + inputBar.text.substring(inputBarIndex + 1)
inputBarIndex += 1
curInput = inputBar.text.substring(0, inputBarIndex)
}
const result = compareWord(wordList[order].name, curInput)
if (result === -2) {
order++
soundPlayer('success')
// 可能单词太短,播放发音回调还没执行,就输完切下一个词了,这里在切换下一个词前解发音锁
voiceLock = false
setupWord()
} else if (result >= 0) {
hasWrong = true
inputBar.color = getConfig('highlightWrongColor')
soundPlayer('wrong')
setTimeout(() => {
hasWrong = false
inputBar.color = undefined
setupWord()
}, getConfig('highlightWrongDelay'))
}
}
}
}
})
let startFunction = () => {
// 在第一次启动时,设置 qwerty-learner.readOnlyMode 的正确值
vscode.commands.executeCommand('setContext', 'qwerty-learner.readOnlyMode', readOnlyMode)
vscode.commands.executeCommand('setContext', 'qwerty-learner.wordVisibility', wordVisibility)
isStart = !isStart
if (isStart) {
wordBar.show()
inputBar.show()
transBar.show()
setupWord()
if (readOnlyMode) {
readOnlyIntervalId = setInterval(() => {
order++
setupWord()
}, readOnlyInterval)
}
} else {
wordBar.hide()
inputBar.hide()
transBar.hide()
if (readOnlyMode) {
removeInterval()
}
}
}
function removeInterval() {
if (readOnlyIntervalId !== null) {
clearInterval(readOnlyIntervalId)
readOnlyIntervalId = null
}
}
let startCom = vscode.commands.registerCommand('qwerty-learner.Start', startFunction)
let changeChapterCom = vscode.commands.registerCommand('qwerty-learner.changeChapter', async () => {
const inputChapter = await vscode.window.showQuickPick(
range(1, totalChapters + 1).map((i) => i.toString()),
{ placeHolder: `当前章节: ${chapter + 1}${totalChapters}章节` },
)
if (inputChapter !== undefined) {
chapter = parseInt(inputChapter) - 1
order = 0
refreshWordList()
}
})
let changeDictCom = vscode.commands.registerCommand('qwerty-learner.changeDict', async () => {
const dictList: DictPickItem[] = []
Object.keys(dicts).forEach((key) => {
dictList.push({ label: dicts[key].name, path: dicts[key].url, detail: dicts[key].description, key: key })
})
const inputDict = await vscode.window.showQuickPick(dictList, { placeHolder: `当前字典: ${dicts[dictKey].name}` })
if (inputDict !== undefined) {
chapter = 0
order = 0
changeDict(inputDict.key)
}
})
let closeReadOnlyMode = vscode.commands.registerCommand('qwerty-learner.closeReadOnlyMode', () => {
readOnlyMode = false
globalState.update('readOnlyMode', false)
vscode.commands.executeCommand('setContext', 'qwerty-learner.readOnlyMode', false)
removeInterval()
})
let openReadOnlyMode = vscode.commands.registerCommand('qwerty-learner.openReadOnlyMode', () => {
readOnlyMode = true
globalState.update('readOnlyMode', true)
vscode.commands.executeCommand('setContext', 'qwerty-learner.readOnlyMode', true)
isStart = false
startFunction()
})
let toggleWordVisibility = vscode.commands.registerCommand('qwerty-learner.toggleWordVisibility', () => {
wordVisibility = !wordVisibility
globalState.update('wordVisibility', wordVisibility)
vscode.commands.executeCommand('setContext', 'qwerty-learner.toggleWordVisibility', wordVisibility)
setupWord()
})
context.subscriptions.push(startCom)
context.subscriptions.push(changeChapterCom)
context.subscriptions.push(changeDictCom)
context.subscriptions.push(closeReadOnlyMode)
context.subscriptions.push(openReadOnlyMode)
context.subscriptions.push(toggleWordVisibility)
}
// this method is called when your extension is deactivated
export function deactivate() {
console.log('我裂开了')
}

168
src/index.ts Normal file
View File

@ -0,0 +1,168 @@
import { dictionaries } from './resource/dictionary'
import { DictPickItem } from './typings/index'
import * as vscode from 'vscode'
import cet4 from './assets/CET4_T.json'
import { range } from 'lodash'
import { compareWord, getConfig, getDictFile } from './utils'
import { soundPlayer } from './sound'
import { voicePlayer } from './resource/voice'
import PluginState from './utils/PluginState'
export function activate(context: vscode.ExtensionContext) {
const pluginState = new PluginState(context)
const wordBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, -100)
const inputBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, -101)
const transBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, -102)
vscode.workspace.onDidChangeTextDocument((e) => {
if (!pluginState.isStart) {
return
}
const { uri } = e.document
// 避免破坏配置文件
if (uri.scheme.indexOf('vscode') !== -1) {
return
}
const { range, text, rangeLength } = e.contentChanges[0]
if (!(text !== '' && text.length === 1)) {
return
}
// 删除用户输入的字符
const newRange = new vscode.Range(range.start.line, range.start.character, range.end.line, range.end.character + 1)
const editAction = new vscode.WorkspaceEdit()
editAction.delete(uri, newRange)
vscode.workspace.applyEdit(editAction)
if (pluginState.hasWrong) {
return
}
soundPlayer('click')
inputBar.text = pluginState.getCurrentInputBarContent(text)
const compareResult = pluginState.compareResult
if (compareResult === -2) {
// 用户完成单词输入
soundPlayer('success')
pluginState.finishWord()
initializeBar()
} else if (compareResult >= 0) {
pluginState.wrongInput()
inputBar.color = pluginState.highlightWrongColor
soundPlayer('wrong')
setTimeout(() => {
pluginState.clearWrong()
inputBar.color = undefined
initializeBar()
}, pluginState.highlightWrongDelay)
}
})
vscode.workspace.onDidChangeConfiguration((event) => {
if (event.affectsConfiguration('qwerty-learner.placeholder')) {
pluginState.placeholder = getConfig('placeholder')
initializeBar()
}
if (event.affectsConfiguration('qwerty-learner.chapterLength')) {
pluginState.chapterLength = getConfig('chapterLength')
initializeBar()
}
})
// 注册 vscode commands
context.subscriptions.push(
...[
vscode.commands.registerCommand('qwerty-learner.start', () => {
pluginState.isStart = !pluginState.isStart
if (pluginState.isStart) {
initializeBar()
wordBar.show()
inputBar.show()
transBar.show()
// todo: 只读模式相关设置
if (pluginState.readOnlyMode) {
setUpReadOnlyInterval()
}
// todo: 随机模式
} else {
wordBar.hide()
inputBar.hide()
transBar.hide()
removeReadOnlyInterval()
}
}),
vscode.commands.registerCommand('qwerty-learner.changeChapter', async () => {
const inputChapter = await vscode.window.showQuickPick(
range(1, pluginState.totalChapters + 1).map((i) => i.toString()),
{ placeHolder: `当前章节: ${pluginState.chapter + 1}${pluginState.totalChapters}章节` },
)
if (inputChapter !== undefined) {
pluginState.chapter = parseInt(inputChapter) - 1
initializeBar()
}
}),
vscode.commands.registerCommand('qwerty-learner.changeDict', async () => {
const dictList: DictPickItem[] = []
dictionaries.forEach((dict) => {
dictList.push({ label: dict.name, path: dict.url, detail: dict.description, key: dict.id })
})
const inputDict = await vscode.window.showQuickPick(dictList, { placeHolder: `当前字典: ${pluginState.dict.name}` })
if (inputDict !== undefined) {
pluginState.dictKey = inputDict.key
initializeBar()
}
}),
vscode.commands.registerCommand('qwerty-learner.toggleWordVisibility', () => {
pluginState.wordVisibility = !pluginState.wordVisibility
initializeBar()
}),
vscode.commands.registerCommand('qwerty-learner.toggleReadOnlyMode', () => {
pluginState.readOnlyMode = !pluginState.readOnlyMode
if (pluginState.readOnlyMode) {
setUpReadOnlyInterval()
} else {
removeReadOnlyInterval()
}
}),
],
)
function initializeBar() {
setUpWordBar()
setUpTransBar()
setUpInputBar()
}
function setUpWordBar() {
wordBar.text = pluginState.getInitialWordBarContent()
if (pluginState.shouldPlayVoice) {
pluginState.voiceLock = true
voicePlayer(pluginState.currentWord.name, () => {
pluginState.voiceLock = false
})
}
}
function setUpTransBar() {
transBar.text = pluginState.getInitialTransBarContent()
}
function setUpInputBar() {
inputBar.text = pluginState.getInitialInputBarContent()
}
function setUpReadOnlyInterval() {
if (!pluginState.readOnlyIntervalId) {
pluginState.readOnlyIntervalId = setInterval(() => {
pluginState.finishWord()
initializeBar()
}, pluginState.readOnlyInterval)
}
}
function removeReadOnlyInterval() {
if (pluginState.readOnlyIntervalId) {
clearInterval(pluginState.readOnlyIntervalId)
pluginState.readOnlyIntervalId = null
}
}
}

View File

@ -1,22 +1,7 @@
import * as vscode from 'vscode'
import path from 'path'
import fs from 'fs'
export type Dictionary = {
id: string
name: string
description: string
category: string
url: string
length: number
language: 'en' | 'romaji' | 'zh' | 'ja' | 'code' | 'de'
}
export type Dicts = {
[key: string]: Dictionary
}
import { Dictionary } from '@/typings'
// 使用与网页版 qwerty 一样的格式,方便共享数据
const dictInfos: Dictionary[] = [
export const dictionaries: Dictionary[] = [
{
id: 'cet4',
name: 'CET-4',
@ -667,35 +652,7 @@ const dictInfos: Dictionary[] = [
},
]
export const dicts: Dicts = {}
dictInfos.forEach((i) => (dicts[i.id] = i))
export function compareWord(word: string, input: string) {
// 错误返回错误索引,正确返回-2未完成输入且无错误返回-1
for (let i = 0; i < word.length; i++) {
if (typeof input[i] !== 'undefined') {
if (word[i] !== input[i]) {
return i
}
} else {
return -1
}
}
return -2
}
export function getConfig(key: string) {
return vscode.workspace.getConfiguration('qwerty-learner')[key]
}
export function getDictFile(dictPath: string) {
const filePath = path.join(__dirname, '..', 'assets/dicts', dictPath)
return JSON.parse(fs.readFileSync(filePath, 'utf8'))
}
export interface DictPickItem {
label: string
path: string
key: string
detail: string
}
/**
* An object-map from dictionary IDs to dictionary themselves.
*/
export const idDictionaryMap: Record<string, Dictionary> = Object.fromEntries(dictionaries.map((dict) => [dict.id, dict]))

View File

@ -1,4 +1,5 @@
import { getConfig } from './utils'
import { VoiceType } from './../../typings/index'
import { getConfig } from '../../utils'
interface NativeModule {
playerPlay(voiceUrl: string, callback: () => void): void
}
@ -30,30 +31,9 @@ if (!(NATIVE && NATIVE.playerPlay)) {
NATIVE = null
}
type VoiceType = 'us' | 'uk' | 'close'
export const getVoiceType = () => {
const voiceType: VoiceType = getConfig('voiceType')
let type
switch (voiceType) {
case 'us':
type = 2
break
case 'uk':
type = 1
break
case 'close':
type = ''
break
default:
type = ''
break
}
return type
}
export const voicePlayer = (word: string, type: string | number, callback: () => void) => {
export const voicePlayer = (word: string, callback: () => void) => {
if (NATIVE) {
const type = getConfig('voiceType') === 'us' ? 2 : 1
NATIVE.playerPlay(`https://dict.youdao.com/dictvoice?audio=${word}&type=${type}`, callback)
} else {
callback()

25
src/typings/index.ts Normal file
View File

@ -0,0 +1,25 @@
export type DictPickItem = {
label: string
path: string
key: string
detail: string
}
export type Word = {
name: string
trans: string[]
usphone?: string
ukphone?: string
}
export type VoiceType = 'us' | 'uk' | 'close'
export type Dictionary = {
id: string
name: string
description: string
category: string
url: string
length: number
language: 'en' | 'romaji' | 'zh' | 'ja' | 'code' | 'de'
}

239
src/utils/PluginState.ts Normal file
View File

@ -0,0 +1,239 @@
import { Dictionary } from '@/typings'
import { VoiceType } from './../typings/index'
import { idDictionaryMap } from './../resource/dictionary'
import { compareWord, getConfig, getDictFile } from '.'
import * as vscode from 'vscode'
import { Word } from '@/typings'
export default class PluginState {
private _globalState: vscode.Memento
private _dictKey: string
private dictWords: Word[]
public dict: Dictionary
public chapterLength: number
private _readOnlyMode: boolean
public readOnlyIntervalId: NodeJS.Timeout | null
public placeholder: string
private isShowPhonetic: boolean
public _wordVisibility: boolean
private currentExerciseCount: number
private _order: number
private _chapter: number
public isStart: boolean
public hasWrong: boolean
private curInput: string
public voiceLock: boolean
constructor(context: vscode.ExtensionContext) {
const globalState = context.globalState
this._globalState = globalState
globalState.setKeysForSync(['chapter', 'dictKey'])
this._dictKey = globalState.get('dictKey', 'cet4')
this.dict = idDictionaryMap[this._dictKey]
this.dictWords = []
this.loadDict()
this._order = globalState.get('order', 0)
this._chapter = globalState.get('chapter', 0)
this.isStart = false
this.hasWrong = false
this.curInput = ''
this.currentExerciseCount = 0
this.chapterLength = getConfig('chapterLength')
// this.readOnlyMode = globalState.get('readOnlyMode', false)
this._readOnlyMode = false
this.readOnlyIntervalId = null
this.placeholder = getConfig('placeholder') // 用于控制word不可见时inputBar中是否出现占位符及样式
this.isShowPhonetic = getConfig('phonetic')
this._wordVisibility = globalState.get('wordVisibility', true)
this.voiceLock = false
}
get chapter(): number {
return this._chapter
}
set chapter(value: number) {
this._chapter = value
this.order = 0
this._globalState.update('chapter', this._chapter)
this._globalState.update('order', this.order)
}
get order(): number {
return this._order
}
set order(value: number) {
this._order = value
this._globalState.update('order', this._order)
}
get dictKey(): string {
return this._dictKey
}
set dictKey(value: string) {
this.order = 0
this.chapter = 0
this._dictKey = value
this.dict = idDictionaryMap[this._dictKey]
this._globalState.update('dictKey', this._dictKey)
this.loadDict()
}
get wordExerciseTime(): number {
return getConfig('wordExerciseTime')
}
get wordList(): Word[] {
const wordList = this.dictWords.slice(this.chapter * this.chapterLength, (this.chapter + 1) * this.chapterLength)
wordList.forEach((word) => {
// API 字典会出现括号,但部分 vscode 插件会拦截括号的输入
word.name = word.name.replace('(', '').replace(')', '')
})
return wordList
}
get wordVisibility(): boolean {
return this._wordVisibility
}
set wordVisibility(value: boolean) {
this._wordVisibility = value
this._globalState.update('wordVisibility', this._wordVisibility)
}
get totalChapters(): number {
if (this.dictWords) {
return Math.ceil(this.dictWords.length / this.chapterLength)
} else {
return 0
}
}
get currentWord(): Word {
return this.wordList[this.order]
}
get compareResult(): number {
return compareWord(this.currentWord.name, this.curInput)
}
get highlightWrongColor(): string {
return getConfig('highlightWrongColor')
}
get highlightWrongDelay(): number {
return getConfig('highlightWrongDelay')
}
get readOnlyMode(): boolean {
return this._readOnlyMode
}
set readOnlyMode(value: boolean) {
this._readOnlyMode = value
this._globalState.update('readOnlyMode', this._readOnlyMode)
}
get readOnlyInterval(): number {
return getConfig('readOnlyInterval')
}
get voiceType(): VoiceType {
return getConfig('voiceType')
}
get shouldPlayVoice(): boolean {
return this.voiceType !== 'close' && !this.voiceLock
}
wrongInput() {
this.hasWrong = true
this.curInput = ''
}
clearWrong() {
this.hasWrong = false
}
finishWord() {
this.curInput = ''
this.currentExerciseCount += 1
if (this.currentExerciseCount >= this.wordExerciseTime) {
this.currentExerciseCount = 0
this.nextWord()
}
this.voiceLock = false
}
nextWord() {
if (this.order === this.wordList.length - 1) {
// 结束本章节
if (this.chapter === this.totalChapters - 1) {
this.chapter = 0
} else {
this.chapter += 1
}
this.order = 0
} else {
this.order += 1
}
}
getInitialWordBarContent() {
return `${this.dict.name} chp.${this.chapter + 1} ${this.order + 1}/${this.wordList.length} ${
this.wordVisibility ? this.currentWord.name : ''
}`
}
getInitialInputBarContent() {
let content = ''
if (this.wordVisibility || this.placeholder === '') {
content = ''
} else {
// 拼接占位符
content = this.placeholder.repeat(this.currentWord.name.length)
}
return content
}
getInitialTransBarContent() {
let content = this._getCurrentWordPhonetic() + this.currentWord.trans.join('; ')
content = content.replace(/\n/g, ' ')
return content
}
getCurrentInputBarContent(input: string) {
let content = ''
if (this.wordVisibility || this.placeholder === '') {
// 没有使用placeholder不需要特殊处理
this.curInput += input
content = this.curInput
} else {
// 拼接占位符 && 获取当前已经键入的值进行比较
this.curInput += input
content = this.curInput + this.placeholder.repeat(this.currentWord.name.length - this.curInput.length)
}
return content
}
private _getCurrentWordPhonetic() {
let phonetic = ''
switch (getConfig('phonetic')) {
case 'us':
phonetic = this.currentWord.usphone || ''
break
case 'uk':
phonetic = this.currentWord.ukphone || ''
break
case 'close':
phonetic = ''
break
}
return phonetic
}
private loadDict() {
this.dictWords = getDictFile(this.dict.url)
}
}

28
src/utils/index.ts Normal file
View File

@ -0,0 +1,28 @@
import * as vscode from 'vscode'
import path from 'path'
import fs from 'fs'
/**
* -2-1
*/
export function compareWord(word: string, input: string) {
for (let i = 0; i < word.length; i++) {
if (typeof input[i] !== 'undefined') {
if (word[i] !== input[i]) {
return i
}
} else {
return -1
}
}
return -2
}
export function getConfig(key: string) {
return vscode.workspace.getConfiguration('qwerty-learner')[key]
}
export function getDictFile(dictPath: string) {
const filePath = path.join(__dirname, '..', 'assets/dicts', dictPath)
return JSON.parse(fs.readFileSync(filePath, 'utf8'))
}

View File

@ -3,17 +3,19 @@
"module": "commonjs",
"target": "es6",
"outDir": "out",
"lib": ["es6"],
"lib": ["esnext"],
"types": ["node"],
"sourceMap": true,
"rootDir": "src",
"strict": true /* enable all strict type-checking options */,
"resolveJsonModule": true,
"esModuleInterop": true
/* Additional Checks */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
"esModuleInterop": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"typeRoots": ["./src/typings", "./node_modules/@types"]
},
"include": ["src"],
"exclude": ["node_modules", ".vscode-test"]
}

View File

@ -8,11 +8,11 @@ const path = require('path')
const config = {
target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/
entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
entry: './src/index.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
output: {
// the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
path: path.resolve(__dirname, 'dist'),
filename: 'extension.js',
filename: 'index.js',
libraryTarget: 'commonjs2',
devtoolModuleFilenameTemplate: '../[resource-path]',
},

File diff suppressed because it is too large Load Diff