add contextmenu
This commit is contained in:
1 parent
1c53acbe2c
commit
f0bd330d04
18 files changed
+912
-131
No files matched your search
+114
@@ -0,0 +1,114 @@
|
||||
import _axios, { AxiosInstance } from 'axios'
|
||||
|
||||
class Api {
|
||||
axios: AxiosInstance
|
||||
_isAuthorized = true
|
||||
_user = ''
|
||||
|
||||
constructor() {
|
||||
this.axios = _axios.create()
|
||||
}
|
||||
|
||||
async setWanikaniApiKey(apiKey?: string) {
|
||||
const setApiKey = () => {
|
||||
if (apiKey) {
|
||||
this.axios.defaults.headers.common.Authorization = `Bearer ${apiKey}`
|
||||
} else {
|
||||
delete this.axios.defaults.headers.common.Authorization
|
||||
}
|
||||
}
|
||||
|
||||
setApiKey()
|
||||
const r = await this.user_getSettings()
|
||||
|
||||
if (r) return r
|
||||
apiKey = ''
|
||||
setApiKey()
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
async isAuthorized(): Promise<boolean> {
|
||||
if (this._isAuthorized && !this._user) {
|
||||
const r = await this.user_getSettings()
|
||||
if (r) {
|
||||
this._user = r.identifier
|
||||
this._isAuthorized = true
|
||||
}
|
||||
}
|
||||
|
||||
return this._isAuthorized
|
||||
}
|
||||
|
||||
async user_getSettings() {
|
||||
const r = await this.axios
|
||||
.get('/api/user', {
|
||||
params: {
|
||||
select: 'level,levelMin,identifier',
|
||||
},
|
||||
})
|
||||
.then(
|
||||
(r) =>
|
||||
r.data as {
|
||||
level: number
|
||||
levelMin?: number
|
||||
identifier: string
|
||||
}
|
||||
)
|
||||
.then((r) => ({
|
||||
...r,
|
||||
levelMin: r.levelMin || 1,
|
||||
}))
|
||||
.catch(() => null)
|
||||
|
||||
if (!r) {
|
||||
return
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
async user_updateSettings(u: { levelMin: number }) {
|
||||
return await this.axios.patch('/api/user', u)
|
||||
}
|
||||
|
||||
async quiz_getMany(params: {
|
||||
entries: string[]
|
||||
select: string[]
|
||||
type?: string
|
||||
direction?: string
|
||||
}) {
|
||||
return await this.axios.post('/api/quiz/getMany', params).then(
|
||||
(r) =>
|
||||
r.data as {
|
||||
result: {
|
||||
id: string
|
||||
entry: string
|
||||
}[]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async quiz_create(params: { entries: string[]; type: string }) {
|
||||
return await this.axios.put('/api/quiz', params).then(
|
||||
(r) =>
|
||||
r.data as {
|
||||
result: {
|
||||
ids: string[]
|
||||
}[]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async quiz_deleteMany(params: { entries: string[]; type: string }) {
|
||||
return await this.axios.post('/api/quiz/deleteMany', params)
|
||||
}
|
||||
|
||||
async extra_delete(params: { id: string }) {
|
||||
return await this.axios.delete('/extra/delete', {
|
||||
params,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const api = new Api()
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,82 @@
|
||||
const allVoices: Record<string, string> = {}
|
||||
|
||||
// eslint-disable-next-line array-callback-return
|
||||
speechSynthesis.getVoices().map((v) => {
|
||||
allVoices[v.lang] = v.lang
|
||||
})
|
||||
|
||||
speechSynthesis.onvoiceschanged = () => {
|
||||
// eslint-disable-next-line array-callback-return
|
||||
speechSynthesis.getVoices().map((v) => {
|
||||
allVoices[v.lang] = v.lang
|
||||
})
|
||||
}
|
||||
|
||||
export async function speak(
|
||||
s: string,
|
||||
opts: {
|
||||
lang?: string
|
||||
forceOffline?: boolean
|
||||
} = {}
|
||||
) {
|
||||
if (!opts.forceOffline) {
|
||||
const audio = new Audio(
|
||||
`/api/tts?q=${encodeURIComponent(s)}&lang=${encodeURIComponent(
|
||||
opts.lang || 'ja'
|
||||
)}`
|
||||
)
|
||||
await audio.play().catch(() =>
|
||||
speak(s, {
|
||||
...opts,
|
||||
forceOffline: true,
|
||||
})
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
let lang = 'zh'
|
||||
|
||||
const voices = Object.keys(allVoices)
|
||||
const stage1 = () => voices.filter((v) => v === lang)[0]
|
||||
const stage2 = () => {
|
||||
const m1 = lang.substr(0, 2)
|
||||
const m2 = lang.substr(3, 2)
|
||||
const r1 = new RegExp(`^${m1}[-_]${m2}`, 'i')
|
||||
return voices.filter((v) => r1.test(v))[0]
|
||||
}
|
||||
const stage3 = () => {
|
||||
const m1 = lang.substr(0, 2).toLocaleLowerCase()
|
||||
return voices.filter((v) => v.toLocaleLowerCase().startsWith(m1))[0]
|
||||
}
|
||||
|
||||
lang = stage1() || stage2() || stage3() || ''
|
||||
|
||||
if (lang) {
|
||||
const utterance = new SpeechSynthesisUtterance(s)
|
||||
utterance.lang = lang
|
||||
speechSynthesis.speak(utterance)
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
utterance.onend = () => {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', (ev) => {
|
||||
if (
|
||||
ev.target instanceof HTMLElement &&
|
||||
['INPUT', 'TEXTAREA'].includes(ev.target.tagName.toLocaleUpperCase())
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if (ev.key === 's') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const s = window.getSelection()!.toString()
|
||||
if (s) {
|
||||
speak(s)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,76 @@
|
||||
export const doClick = async (el?: Element | null, timeout?: number) => {
|
||||
if (!el) return
|
||||
|
||||
if (el.classList?.add) {
|
||||
el.classList.add('active')
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const { click } = el as any
|
||||
if (typeof click === 'function') {
|
||||
const r = click()
|
||||
if (r instanceof Promise) {
|
||||
await r
|
||||
} else if (timeout) {
|
||||
await new Promise((resolve) => setTimeout(resolve, timeout))
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (el.classList?.remove) {
|
||||
el.classList.remove('active')
|
||||
}
|
||||
}
|
||||
|
||||
export const doMapKeypress = (
|
||||
evt: KeyboardEvent,
|
||||
map: Record<string, (() => void) | string | HTMLElement>
|
||||
) => {
|
||||
let action = map[evt.key]
|
||||
while (typeof action === 'string') {
|
||||
action = map[action]
|
||||
}
|
||||
if (typeof action === 'function') {
|
||||
action()
|
||||
} else if (action instanceof HTMLElement) {
|
||||
doClick(action)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuffles array in place. ES6 version
|
||||
* @param {Array} a items An array containing the items.
|
||||
*/
|
||||
export function shuffle<T>(a: T[]) {
|
||||
for (let i = a.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1))
|
||||
;[a[i], a[j]] = [a[j], a[i]]
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
export function sample<T>(arr: T[], size: number): T[] {
|
||||
const allN = Array(arr.length)
|
||||
.fill(null)
|
||||
.map((_, i) => i)
|
||||
const outN: number[] = []
|
||||
|
||||
while (allN.length && outN.length < size) {
|
||||
outN.push(...allN.splice(Math.floor(Math.random() * allN.length), 1))
|
||||
}
|
||||
|
||||
return outN.map((n) => arr[n])
|
||||
}
|
||||
|
||||
export function capitalize(s: string): string {
|
||||
if (s.includes(' ')) {
|
||||
return s
|
||||
.split(' ')
|
||||
.map((s0) => capitalize(s0))
|
||||
.join(' ')
|
||||
}
|
||||
|
||||
if (!s) return s
|
||||
return s[0].toLocaleUpperCase() + s.substr(1)
|
||||
}
|
||||
Reference in new issue
Block a user