Skip to content

Commit

Permalink
chore: init web ui
Browse files Browse the repository at this point in the history
  • Loading branch information
ourongxing committed Sep 5, 2024
1 parent beba93f commit ae938f7
Show file tree
Hide file tree
Showing 4,115 changed files with 10,906 additions and 13,857,130 deletions.
The diff you're trying to view is too large. We only load the first 3000 changed files.
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
node_modules
database/*.db
database/*.db
.output/
.vercel/
.vinxi/
libraries/
exported/
61 changes: 13 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,86 +1,51 @@
# maimemo-export

> 中文含义并非导出自墨墨背单词,而是使用 [ECDICT](https://github.com/skywind3000/ECDICT-ultimate) 的数据,无法做到与墨墨背单词一致。
用于导出墨墨背单词的词库,并生成适用于 List 背单词,不背单词,欧陆词典等的自定义词库

## Usage

> 必须使用 Android 手机,并且必须 Root,小白勿试。release 中有已经导出好的词库,可以直接下载使用。
#### Install

> 需要安装 [bun](https://bun.sh/)
```shell
git clone https://github.com/ourongxing/maimemo-export.git
cd maimemo-export
pnpm i
```

#### Get Database

1. 下载词典数据库,[点击下载](ecdict-ultimate-sqlite.zip),解压得到 `ultimate.db`,放入 `database` 文件夹中(没有就自己新建)。
2. 获取手机上的数据库文件,连接好手机,打开 USB 调试,命令行输入
```shell
pnpm adb
```
#### Option
```ts
// src/index.ts
const exportThesaurus = async (
// 词库名
books: string[],
// MaimemoDB 为本地词库,NotePad 为云词库
db: MaimemoDB | NotePad,
option?: ExportOpt
) => {
```
```shell
pnpm adb
```

```ts
type ExportOpt = {
// 导出文件类型
types?: ("txt" | "csv" | "list")[] // default: ["txt","csv","list"]
// 导出路径
dir?: string // ./thesaurus
// 1.词库中仅背过的单词 2.仅没背的单词 3. false 为全部
memorized?: // default: false
| {
type: "memorized" | "unmemorized"
data: string[]
}
| string[]
| false
// 1.仅单词 2.仅短语 3. true 为单词,false 为全部
word?: "word" | "phrase" | boolean // default: false
// 覆盖已有文件
override?: boolean // default: false
bookOpt?: BookOption
}
```
```ts
type BookOption = {
// 1. 首字母 2. 书上默认顺序
order?: "initials" | "book" // default: "book"
// 顺序反转
reverse?: boolean // default: false
}
```
#### Export

```shell
pnpm dev
```

## Download

仓库内已经导出墨墨背单词所有本地词库,包括联网更新的词库,不包括云词库,多达上千种词库,可以在仓库中选择需要的下载([下载单个文件的方法](https://blog.csdn.net/u010801439/article/details/81478592))。

- csv:带有中文含义,可导入 List 背单词。
- list:带有 List 分组,可导入欧陆词典。
- txt:仅单词,可导入不背单词。

## Acknowledgements

1. 导出方法来自于 [怎么把墨墨背单词里的词库导出来? - 你说什么的回答](https://www.zhihu.com/question/392654371/answer/1345899232)
2. 词典来自于 [skywind3000/ECDICT-ultimate](https://github.com/skywind3000/ECDICT-ultimate)
3. 词库来自于 [墨墨背单词](https://www.maimemo.com/)

## License

MIT © ourongxing
60 changes: 60 additions & 0 deletions app.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { createApp } from "vinxi"
import reactRefresh from "@vitejs/plugin-react"
import tsconfigPaths from "vite-tsconfig-paths"
import UnoCSS from "unocss/vite"
import AutoImport from "unplugin-auto-import/vite"

const plugins = [
tsconfigPaths(),
UnoCSS(),
reactRefresh(),
AutoImport({
include: [
/\.[tj]sx$/,
],
imports: [{
clsx: [
["default", "c"],
],
}],
}),
]

export default createApp({
server: {
preset: "node",
experimental: {
websocket: true,
},
},
routers: [
{
type: "static",
name: "public",
dir: "./public",
},
{
name: "websocket",
type: "http",
handler: "./ws.ts",
target: "server",
base: "/_ws",
plugins: () => plugins,
},
{
type: "http",
name: "trpc",
base: "/trpc",
handler: "./trpcServer.ts",
target: "server",
plugins: () => plugins,
},
{
type: "spa",
name: "client",
handler: "./index.html",
target: "browser",
plugins: () => plugins,
},
],
})
34 changes: 34 additions & 0 deletions app/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"
import { Title } from "./components/Title"
import { StatusBar } from "./components/StatusBar"
import { NavBar } from "./components/NavBar"
import { Options } from "./components/Options"
import { ShowInfo } from "./components/ShowInfo"
import { WordTable } from "~/components/WordTable"

import "./styles/globals.css"
import "virtual:uno.css"
import "@unocss/reset/tailwind.css"

// 所有页面都有的
export function App() {
return (
<div className="max-w-screen-lg mx-auto">
<div id="main" className="flex flex-col h-100vh px4 py6 lg:py10 gap4">
<Title />
<StatusBar />
<NavBar />
<div className="grid grid-cols-5 border-base border rounded">
<div className="col-span-3 border-base border-r">
<WordTable />
</div>
<div className="col-span-2 flex flex-col">
<Options />
<ShowInfo />
</div>
</div>
</div>
<ReactQueryDevtools position="right" />
</div>
)
}
55 changes: 55 additions & 0 deletions app/atom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { PrimitiveAtom } from "jotai"
import { atom } from "jotai"
import type { ExportOptions, ExportState, PreviewLib } from "@/types"

function atomWithLocalStorage<T>(key: string, initialValue: T): PrimitiveAtom<T> {
const getInitialValue = () => {
const item = localStorage.getItem(key)
if (item !== null)
return JSON.parse(item)

return initialValue
}
const baseAtom = atom(getInitialValue())
const derivedAtom = atom(
get => get(baseAtom),
(get, set, update) => {
const nextValue
= typeof update === "function" ? update(get(baseAtom)) : update
set(baseAtom, nextValue)
localStorage.setItem(key, JSON.stringify(nextValue))
},
)
return derivedAtom
}

export const initPreviewLib: PreviewLib = {
name: "",
id: 0,
target: "word",
type: "base",
preview: false,
}
export const previewLibAtom = atom<PreviewLib>({
...initPreviewLib,
})

export const exporterOptionsAtom = atomWithLocalStorage<ExportOptions>("options", {
target: ["word", "list", "translation"],
exculedMemorized: false,
folderName: "",
override: false,
})

export const exportStateAtom = atom<ExportState>({
status: "idle",
range: "all",
logs: [],
selected: [],
})

export const databaseStatusAtom = atom({
maimemo_base: false,
maimemo_cloud: false,
ecdict: false,
})
119 changes: 119 additions & 0 deletions app/components/Common.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import clsx from "clsx"
import type { HTMLProps, InputHTMLAttributes } from "react"
import { useEffect, useRef, useState } from "react"

export function Switch({ ...props }: HTMLProps<HTMLInputElement>) {
return (
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={props.checked}
className="sr-only peer"
onChange={props.onChange}
/>
<div className={clsx("w-9 h-5 bg-primary bg-op-15 peer-focus:outline-none peer-focus:ring-0 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-primary", props.className)} />
</label>
)
}

export function Selector({ options, className, ...props }: { options: { value: string, label: string }[] } & HTMLProps<HTMLSelectElement>,
) {
return (
<select
name="model"
className={clsx(className, "w-full bg-slate bg-op-15 rounded appearance-none accent-slate text-center focus:(bg-op-20 ring-0 outline-none)")}
{...props}
>
{
options.map(option => <option key={option.value} value={option.value}>{option.label}</option>)
}
</select>
)
}

export function IndeterminateCheckbox({
indeterminate,
className = "",
...rest
}: { indeterminate?: boolean } & HTMLProps<HTMLInputElement>) {
const ref = useRef<HTMLInputElement>(null!)

useEffect(() => {
if (typeof indeterminate === "boolean")
ref.current.indeterminate = !rest.checked && indeterminate
}, [ref, indeterminate])

return (
<input
type="checkbox"
ref={ref}
className={clsx(className, "cursor-pointer accent-primary-600")}
{...rest}
/>
)
}

export function CheckBox({ options, ...props }: { options: { key: string, label: string, checked?: boolean }[] } & HTMLProps<HTMLInputElement>) {
return (
<div>
{
options.map(({ key, label, checked }) => (
<div key={key}>
<input
type="checkbox"
id={key}
checked={checked}
{...props}
/>
<label className="ml-2" htmlFor={key}>{label}</label>
</div>
))
}
</div>
)
}

export function SettingItem({ ...props }: {
children: React.ReactNode
icon?: string
label: string
} & HTMLProps<HTMLDivElement>) {
return (
<div className={clsx("flex items-center p1 justify-between hover:bg-slate hover:bg-op-10 rounded", props.className)}>
<div className="flex items-center">
<button type="button" className={props.icon} />
<span className="">{props.label}</span>
</div>
{props.children}
</div>
)
}

export function DebouncedInput({
value: initialValue,
onChange,
debounce = 500,
...props
}: {
value: string | number
onChange: (value: string | number) => void
debounce?: number
} & Omit<InputHTMLAttributes<HTMLInputElement>, "onChange">) {
const [value, setValue] = useState(initialValue)

useEffect(() => {
setValue(initialValue)
}, [initialValue])

useEffect(() => {
const timeout = setTimeout(() => {
onChange(value)
}, debounce)

return () => clearTimeout(timeout)
}, [value])

return (
<input {...props} value={value} onChange={e => setValue(e.target.value)} />
)
}
Loading

0 comments on commit ae938f7

Please sign in to comment.