update
This commit is contained in:
1 parent
ffbe5fed18
commit
b32007acd1
6 files changed
+939
-55
No files matched your search
+84
-28
@@ -9,17 +9,21 @@ export interface ILevelBrowserByType {
|
|||||||
|
|
||||||
export type ILevelBrowser = Record<ITypeLevel, ILevelBrowserByType>
|
export type ILevelBrowser = Record<ITypeLevel, ILevelBrowserByType>
|
||||||
|
|
||||||
export interface IUserSettings {
|
export interface IUserLocalSettings {
|
||||||
identifier: string
|
|
||||||
level: number
|
|
||||||
levelMin?: number
|
levelMin?: number
|
||||||
level_browser?: ILevelBrowser
|
level_browser?: ILevelBrowser
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IUserSettings extends IUserLocalSettings {
|
||||||
|
identifier: string
|
||||||
|
level: number
|
||||||
|
}
|
||||||
|
|
||||||
export interface IEntry {
|
export interface IEntry {
|
||||||
id: string
|
id: string
|
||||||
entry: string[]
|
list: string[]
|
||||||
reading: string[]
|
reading: string[]
|
||||||
|
audio: string[]
|
||||||
translation: string[]
|
translation: string[]
|
||||||
type: IType
|
type: IType
|
||||||
description: string
|
description: string
|
||||||
@@ -36,6 +40,17 @@ export interface ILibrary {
|
|||||||
isShared?: boolean
|
isShared?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IQuiz {
|
||||||
|
id: string
|
||||||
|
type: IType
|
||||||
|
direction: 'forward' | 'backward'
|
||||||
|
entry: string
|
||||||
|
nextReview?: string // format: date-time
|
||||||
|
wrongStreak?: number
|
||||||
|
hint: string
|
||||||
|
mnemonic: string
|
||||||
|
}
|
||||||
|
|
||||||
export type ILookup<T> = T & {
|
export type ILookup<T> = T & {
|
||||||
offset?: number
|
offset?: number
|
||||||
limit?: number
|
limit?: number
|
||||||
@@ -109,17 +124,23 @@ class Api {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
async user_updateSettings(u: Omit<IUserSettings, 'identifier' | 'level'>) {
|
async user_updateSettings(u: IUserLocalSettings) {
|
||||||
return await this.axios.patch('/api/user', u)
|
return await this.axios.patch('/api/user', u)
|
||||||
}
|
}
|
||||||
|
|
||||||
async quiz_getMany(payload: {
|
async quiz_getById(params: { id: string }) {
|
||||||
|
return await this.axios
|
||||||
|
.get('/api/quiz', { params })
|
||||||
|
.then((r) => r.data as IQuiz)
|
||||||
|
.catch(() => null)
|
||||||
|
}
|
||||||
|
|
||||||
|
async quiz_getManyByEntries(payload: {
|
||||||
entries: string[]
|
entries: string[]
|
||||||
select: string[]
|
type: string
|
||||||
type?: string
|
|
||||||
direction?: string
|
direction?: string
|
||||||
}) {
|
}) {
|
||||||
return await this.axios.post('/api/quiz/getMany', payload).then(
|
return await this.axios.post('/api/quiz/getManyByEntries', payload).then(
|
||||||
(r) =>
|
(r) =>
|
||||||
r.data as {
|
r.data as {
|
||||||
result: {
|
result: {
|
||||||
@@ -142,6 +163,10 @@ class Api {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async quiz_updateSrsLevel(params: { id: string; df: number }) {
|
||||||
|
return await this.axios.patch('/api/quiz/srsLevel', { params })
|
||||||
|
}
|
||||||
|
|
||||||
async quiz_create(payload: { entries: string[]; type: IType }) {
|
async quiz_create(payload: { entries: string[]; type: IType }) {
|
||||||
return await this.axios.put('/api/quiz', payload).then(
|
return await this.axios.put('/api/quiz', payload).then(
|
||||||
(r) =>
|
(r) =>
|
||||||
@@ -153,6 +178,12 @@ class Api {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async quiz_update(payload: Partial<IQuiz> & { id: string }) {
|
||||||
|
return await this.axios.patch('/api/quiz', payload, {
|
||||||
|
params: { id: payload.id },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async quiz_deleteMany(params: { entries: string[]; type: IType }) {
|
async quiz_deleteMany(params: { entries: string[]; type: IType }) {
|
||||||
return await this.axios.post('/api/quiz/deleteMany', params)
|
return await this.axios.post('/api/quiz/deleteMany', params)
|
||||||
}
|
}
|
||||||
@@ -185,39 +216,51 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async character_getRadical(params: { entry: string }) {
|
async character_getRadical(params: { entry: string }) {
|
||||||
return await this.axios.get('/api/character/radical', { params }).then(
|
return await this.axios
|
||||||
(r) =>
|
.get('/api/character/radical', { params })
|
||||||
r.data as {
|
.then(
|
||||||
result: {
|
(r) =>
|
||||||
|
r.data as {
|
||||||
sub: string[]
|
sub: string[]
|
||||||
sup: string[]
|
sup: string[]
|
||||||
variant: string[]
|
variant: string[]
|
||||||
visual: string[]
|
visual: string[]
|
||||||
} | null
|
}
|
||||||
}
|
)
|
||||||
)
|
.catch(() => null)
|
||||||
}
|
}
|
||||||
|
|
||||||
async character_getVocabulary(
|
async character_getVocabulary(
|
||||||
params: ILookup<{ entry: string; distinct?: 'id' }>
|
params: ILookup<{ entry: string; distinct?: 'id' }>
|
||||||
) {
|
) {
|
||||||
return await this.axios.get('/api/character/vocabulary', { params }).then(
|
params.offset = params.offset || 0
|
||||||
(r) =>
|
params.limit = params.limit || 5
|
||||||
r.data as ILookupResult<{
|
|
||||||
entry: string
|
return await this.axios
|
||||||
}>
|
.get('/api/character/vocabulary', { params })
|
||||||
)
|
.then((r) => r.data as ILookupResult<IEntry>)
|
||||||
}
|
}
|
||||||
|
|
||||||
async character_getSentence(
|
async character_getSentence(
|
||||||
params: ILookup<{ entry: string; distinct?: 'id' }>
|
params: ILookup<{ entry: string; distinct?: 'id' }>
|
||||||
) {
|
) {
|
||||||
return await this.axios.get('/api/character/sentence', { params }).then(
|
params.offset = params.offset || 0
|
||||||
(r) =>
|
params.limit = params.limit || 5
|
||||||
r.data as ILookupResult<{
|
|
||||||
entry: string
|
return await this.axios
|
||||||
}>
|
.get('/api/character/sentence', { params })
|
||||||
)
|
.then((r) => r.data as ILookupResult<IEntry>)
|
||||||
|
}
|
||||||
|
|
||||||
|
async vocabulary_getSentence(
|
||||||
|
params: ILookup<{ entry: string; distinct?: 'id' }>
|
||||||
|
) {
|
||||||
|
params.offset = params.offset || 0
|
||||||
|
params.limit = params.limit || 5
|
||||||
|
|
||||||
|
return await this.axios
|
||||||
|
.get('/api/vocabulary/sentence', { params })
|
||||||
|
.then((r) => r.data as ILookupResult<IEntry>)
|
||||||
}
|
}
|
||||||
|
|
||||||
async all_getRandom(params: { type: IType }) {
|
async all_getRandom(params: { type: IType }) {
|
||||||
@@ -263,6 +306,19 @@ class Api {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async all_tokenize(params: { entry: string }) {
|
||||||
|
return await this.axios
|
||||||
|
.get('/api/all/tokenize', {
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
(r) =>
|
||||||
|
r.data as {
|
||||||
|
result: string[]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
async all_getReading(params: { entry: string }) {
|
async all_getReading(params: { entry: string }) {
|
||||||
return await this.axios
|
return await this.axios
|
||||||
.get('/api/all/reading', {
|
.get('/api/all/reading', {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li v-if="entry">
|
<li v-if="entry">
|
||||||
<a role="button" @click="openInNewTab('Character', { q: entry })">
|
<a role="button" @click="openInNewTab('Character', { q: entry })">
|
||||||
{{ type === 'character' ? 'Show details' : 'Search for Hanzi' }}
|
{{ type === 'character' ? 'Show details' : 'Search for Kanji' }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="entry && type !== 'character'">
|
<li v-if="entry && type !== 'character'">
|
||||||
@@ -135,9 +135,8 @@ export default class ContextMenu extends Vue {
|
|||||||
|
|
||||||
async setQuiz() {
|
async setQuiz() {
|
||||||
if (this.entries.length && this.type) {
|
if (this.entries.length && this.type) {
|
||||||
const { result } = await api.quiz_getMany({
|
const { result } = await api.quiz_getManyByEntries({
|
||||||
entries: this.entries,
|
entries: this.entries,
|
||||||
select: ['id', 'entry'],
|
|
||||||
type: this.type,
|
type: this.type,
|
||||||
direction: this.direction,
|
direction: this.direction,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -154,9 +154,9 @@ import ContextMenu from '../ContextMenu.vue'
|
|||||||
|
|
||||||
type ILibraryEntry = Pick<
|
type ILibraryEntry = Pick<
|
||||||
IEntry,
|
IEntry,
|
||||||
'entry' | 'reading' | 'translation' | 'type'
|
'list' | 'reading' | 'translation' | 'type'
|
||||||
> & {
|
> & {
|
||||||
originalEntry: string
|
entry: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component<LibraryCard>({
|
@Component<LibraryCard>({
|
||||||
@@ -204,8 +204,8 @@ export default class LibraryCard extends Vue {
|
|||||||
isList = false
|
isList = false
|
||||||
|
|
||||||
list: ILibraryEntry[] = this.entries.map((el) => ({
|
list: ILibraryEntry[] = this.entries.map((el) => ({
|
||||||
originalEntry: el,
|
entry: el,
|
||||||
entry: [el],
|
list: [el],
|
||||||
reading: [],
|
reading: [],
|
||||||
translation: [],
|
translation: [],
|
||||||
type: this.type,
|
type: this.type,
|
||||||
@@ -267,7 +267,7 @@ export default class LibraryCard extends Vue {
|
|||||||
[...listMap].map(([k, v]) =>
|
[...listMap].map(([k, v]) =>
|
||||||
api
|
api
|
||||||
.all_getLookup({
|
.all_getLookup({
|
||||||
entry: v.originalEntry,
|
entry: v.entry,
|
||||||
type: this.type,
|
type: this.type,
|
||||||
forced: true,
|
forced: true,
|
||||||
limit: 1,
|
limit: 1,
|
||||||
@@ -290,7 +290,7 @@ export default class LibraryCard extends Vue {
|
|||||||
|
|
||||||
getTagClass(it: IEntry) {
|
getTagClass(it: IEntry) {
|
||||||
const srsLevel = Math.min(
|
const srsLevel = Math.min(
|
||||||
...it.entry.map((el) => this.srsLevel[el] || -Infinity)
|
...it.list.map((el) => this.srsLevel[el] || -Infinity)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (srsLevel === -Infinity) return 'is-light'
|
if (srsLevel === -Infinity) return 'is-light'
|
||||||
|
|||||||
@@ -0,0 +1,837 @@
|
|||||||
|
<template>
|
||||||
|
<b-modal class="quiz-modal" :active.sync="isQuizModal" @close="endQuiz">
|
||||||
|
<div class="card">
|
||||||
|
<div v-if="current.id" class="card-content">
|
||||||
|
<div
|
||||||
|
v-show="!isQuizShownAnswer"
|
||||||
|
class="content hover-parent"
|
||||||
|
style="cursor: pointer"
|
||||||
|
@click="addHint"
|
||||||
|
>
|
||||||
|
<div v-if="current.type === 'character'">
|
||||||
|
<div v-if="current.direction === 'ec'">
|
||||||
|
<h4>Hanzi English-Chinese</h4>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-html="
|
||||||
|
doMask((current.english || []).join(' / '), current.entry)
|
||||||
|
"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<h4>Hanzi Chinese-English</h4>
|
||||||
|
<div
|
||||||
|
class="font-han text-w-normal hanzi-display"
|
||||||
|
style="text-align: center"
|
||||||
|
>
|
||||||
|
{{ current.entry }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="current.type === 'vocabulary'">
|
||||||
|
<div v-if="current.direction === 'ec'">
|
||||||
|
<h4>Vocab English-Chinese</h4>
|
||||||
|
|
||||||
|
<ul v-if="Array.isArray(current.english)">
|
||||||
|
<li
|
||||||
|
v-for="(it, i) in current.english"
|
||||||
|
:key="i"
|
||||||
|
v-html="
|
||||||
|
doMask(
|
||||||
|
it,
|
||||||
|
current.entry,
|
||||||
|
...(current.reading || []),
|
||||||
|
...(current.alt || [])
|
||||||
|
)
|
||||||
|
"
|
||||||
|
></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
v-html="
|
||||||
|
doMask(
|
||||||
|
(current.english || []).join(' / '),
|
||||||
|
current.entry,
|
||||||
|
...(current.reading || []),
|
||||||
|
...(current.alt || [])
|
||||||
|
)
|
||||||
|
"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<h4 v-if="current.direction === 'te'">
|
||||||
|
Vocabulary Traditional-English
|
||||||
|
</h4>
|
||||||
|
<h4 v-else>Vocab Simplified-English</h4>
|
||||||
|
|
||||||
|
<div class="font-zh-simp text-w-normal" style="font-size: 2rem">
|
||||||
|
{{ current.entry }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<div v-if="current.direction === 'ec'">
|
||||||
|
<h4>Sentence English-Chinese</h4>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li v-for="(e, i) in current.english" :key="i">
|
||||||
|
{{ e }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<h4>Sentence Chinese-English</h4>
|
||||||
|
|
||||||
|
<h2 class="font-zh-simp text-w-normal">
|
||||||
|
{{ current.entry }}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 mb-4 hover-child">
|
||||||
|
<small>
|
||||||
|
{{ current.hint || 'Click to add hint' }}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-show="isQuizShownAnswer" class="content">
|
||||||
|
<div v-if="current.type === 'character'">
|
||||||
|
<div
|
||||||
|
class="hanzi-display has-context font-han"
|
||||||
|
@click="openContext"
|
||||||
|
@contextmenu.prevent="openContext"
|
||||||
|
style="text-align: center"
|
||||||
|
>
|
||||||
|
{{ current.entry }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="text-align: center; margin-bottom: 1rem">
|
||||||
|
{{ (current.reading || []).join(' | ') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="text-align: center">
|
||||||
|
{{ (current.english || []).join(' / ') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul v-if="getSentenceByCharacter(current.entry).length">
|
||||||
|
<li
|
||||||
|
v-for="(it, i) in getSentenceByCharacter(current.entry)"
|
||||||
|
:key="i"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="has-context"
|
||||||
|
:title="it.reading[0]"
|
||||||
|
@click="(ev) => openContext(ev, it.entry, 'sentence')"
|
||||||
|
@contextmenu.prevent="
|
||||||
|
(ev) => openContext(ev, it.entry, 'sentence')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ it.entry }}
|
||||||
|
</span>
|
||||||
|
<ul>
|
||||||
|
<li v-for="(e, j) in it.english" :key="j" class="english">
|
||||||
|
{{ e }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="current.type === 'vocabulary'">
|
||||||
|
<div
|
||||||
|
class="font-zh-simp text-w-normal has-context"
|
||||||
|
style="font-size: 2rem"
|
||||||
|
@click="openContext"
|
||||||
|
@contextmenu.prevent="openContext"
|
||||||
|
>
|
||||||
|
{{ current.simplified }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="current.alt && current.alt.length"
|
||||||
|
class="font-zh-trad text-w-normal"
|
||||||
|
style="font-size: 1.7rem"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-for="(it, i) in current.alt"
|
||||||
|
:key="i"
|
||||||
|
class="traditional has-context"
|
||||||
|
@click="(ev) => openContext(ev, it)"
|
||||||
|
@contextmenu.prevent="(ev) => openContext(ev, it)"
|
||||||
|
>
|
||||||
|
{{ it }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{{ (current.reading || []).join(' | ') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li v-for="(it, i) in current.english" :key="i">
|
||||||
|
{{ it }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul
|
||||||
|
v-if="current.sentences.length"
|
||||||
|
:key="dictionaryData.sentence.length"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
v-for="(it, i) in current.sentences"
|
||||||
|
:key="i"
|
||||||
|
class="sentence"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="has-context"
|
||||||
|
:title="dictionaryData.sentence[it].reading[0]"
|
||||||
|
@click="(ev) => openContext(ev, it, 'sentence')"
|
||||||
|
@contextmenu.prevent="(ev) => openContext(ev, it, 'sentence')"
|
||||||
|
>
|
||||||
|
{{ it }}
|
||||||
|
</span>
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
v-for="(e, j) in dictionaryData.sentence[it].english"
|
||||||
|
:key="j"
|
||||||
|
class="english"
|
||||||
|
>
|
||||||
|
{{ e }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<h2
|
||||||
|
class="font-zh-simp text-w-normal has-context"
|
||||||
|
:title="(current.reading || [])[0]"
|
||||||
|
@click="openContext"
|
||||||
|
@contextmenu.prevent="openContext"
|
||||||
|
>
|
||||||
|
{{ current.entry }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<ul v-if="current.english.length">
|
||||||
|
<li v-for="(it, i) in current.english" :key="i">
|
||||||
|
{{ it }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul v-if="current.vocabulary.length">
|
||||||
|
<li v-for="(it, i) in current.vocabulary" :key="i">
|
||||||
|
<span
|
||||||
|
class="has-context"
|
||||||
|
@click="(ev) => openContext(ev, it.entry, 'vocabulary')"
|
||||||
|
@contextmenu.prevent="
|
||||||
|
(ev) => openContext(ev, it.entry, 'vocabulary')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ it.entry }}
|
||||||
|
</span>
|
||||||
|
<span>[{{ it.reading.join(' | ') }}]</span>
|
||||||
|
<span>
|
||||||
|
{{ it.english.join(' / ') }}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="has-context mt-4 mb-4" @click="addMnemonic">
|
||||||
|
<small>
|
||||||
|
{{ current.mnemonic || 'Click to add mnemonic' }}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="current.tag && current.tag.length" class="mb-4">
|
||||||
|
Tags:
|
||||||
|
<b-taglist style="display: inline-flex">
|
||||||
|
<b-tag
|
||||||
|
v-for="t in current.tag.slice(0, 5)"
|
||||||
|
:key="t"
|
||||||
|
type="is-info"
|
||||||
|
>
|
||||||
|
{{ t }}
|
||||||
|
</b-tag>
|
||||||
|
</b-taglist>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="buttons-area">
|
||||||
|
<div v-if="quizIndex >= quizArray.length" class="buttons">
|
||||||
|
<button
|
||||||
|
ref="btnEndQuiz"
|
||||||
|
class="button is-warning"
|
||||||
|
@click="endQuiz"
|
||||||
|
@keypress="endQuiz"
|
||||||
|
>
|
||||||
|
End quiz
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="!isQuizShownAnswer" class="buttons">
|
||||||
|
<button
|
||||||
|
ref="btnShowAnswer"
|
||||||
|
class="button is-warning"
|
||||||
|
@click="isQuizShownAnswer = true"
|
||||||
|
@keypress="isQuizShownAnswer = true"
|
||||||
|
>
|
||||||
|
Show answer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div v-else class="buttons-panel">
|
||||||
|
<div class="buttons">
|
||||||
|
<button
|
||||||
|
ref="btnMarkRight"
|
||||||
|
class="button is-success"
|
||||||
|
@click="doMark('right')"
|
||||||
|
@keypress="doMark('right')"
|
||||||
|
>
|
||||||
|
Right
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
ref="btnMarkWrong"
|
||||||
|
class="button is-danger"
|
||||||
|
@click="doMark('wrong')"
|
||||||
|
@keypress="doMark('wrong')"
|
||||||
|
>
|
||||||
|
Wrong
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
ref="btnMarkRepeat"
|
||||||
|
class="button is-warning"
|
||||||
|
@click="doMark('repeat')"
|
||||||
|
@keypress="doMark('repeat')"
|
||||||
|
>
|
||||||
|
Repeat
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
<button
|
||||||
|
ref="btnHideAnswer"
|
||||||
|
class="button is-warning"
|
||||||
|
@click="isQuizShownAnswer = false"
|
||||||
|
@keypress="isQuizShownAnswer = false"
|
||||||
|
>
|
||||||
|
Hide answer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ContextMenu ref="context" :entry="ctxEntry" :type="ctxType" />
|
||||||
|
</b-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Vue, Component, Prop, Ref } from 'vue-property-decorator'
|
||||||
|
import { api, IEntry, IQuiz } from '~/assets/api'
|
||||||
|
import { shuffle } from '~/assets/util'
|
||||||
|
import ContextMenu from '../ContextMenu.vue'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
ContextMenu,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class QuizCard extends Vue {
|
||||||
|
@Prop({ required: true }) quizArray!: string[]
|
||||||
|
|
||||||
|
@Ref() context!: ContextMenu
|
||||||
|
|
||||||
|
ctxEntry = ''
|
||||||
|
ctxType = ''
|
||||||
|
|
||||||
|
isQuizShownAnswer = false
|
||||||
|
isQuizModal = false
|
||||||
|
quizIndex = -1
|
||||||
|
|
||||||
|
quizData: {
|
||||||
|
[quizId: string]: IQuiz
|
||||||
|
} = {}
|
||||||
|
|
||||||
|
dictionaryData = {
|
||||||
|
character: {} as Record<string, IEntry>,
|
||||||
|
vocabulary: {} as Record<
|
||||||
|
string,
|
||||||
|
IEntry & {
|
||||||
|
sentence_set: Record<string, boolean>
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
sentence: {} as Record<
|
||||||
|
string,
|
||||||
|
IEntry & {
|
||||||
|
vocabulary_set: Record<string, boolean>
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
}
|
||||||
|
|
||||||
|
get current() {
|
||||||
|
const it = this.quizData[this.quizArray[this.quizIndex]!]
|
||||||
|
|
||||||
|
if (!it || !it.type || !it.entry) {
|
||||||
|
return {} as Partial<IQuiz>
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...it,
|
||||||
|
...this.dictionaryData[it.type][it.entry],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fromCharacter(c: string, type: 'vocabulary' | 'sentence') {
|
||||||
|
return shuffle(
|
||||||
|
Object.entries(this.dictionaryData[type]).filter(([entry]) =>
|
||||||
|
entry.includes(c)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.slice(0, 5)
|
||||||
|
.map(([entry, v]) => ({
|
||||||
|
...v,
|
||||||
|
entry,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async startQuiz() {
|
||||||
|
this.quizIndex = -1
|
||||||
|
await this.initNextQuizItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
endQuiz() {
|
||||||
|
this.isQuizModal = false
|
||||||
|
this.$emit('quiz:ended')
|
||||||
|
}
|
||||||
|
|
||||||
|
async doMark(type: 'right' | 'wrong' | 'repeat') {
|
||||||
|
const id = this.quizArray[this.quizIndex] as string | undefined
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
api.quiz_updateSrsLevel({
|
||||||
|
id,
|
||||||
|
df: {
|
||||||
|
right: 1,
|
||||||
|
wrong: -1,
|
||||||
|
repeat: 0,
|
||||||
|
}[type],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.initNextQuizItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
async initNextQuizItem() {
|
||||||
|
this.isQuizShownAnswer = false
|
||||||
|
|
||||||
|
Array(2)
|
||||||
|
.fill(null)
|
||||||
|
.map((_, i) => {
|
||||||
|
const front = this.checkFront(i + 1)
|
||||||
|
|
||||||
|
if (!front) {
|
||||||
|
this.cacheQuizItem({
|
||||||
|
relativePosition: i + 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this.quizArray[this.quizIndex + 1]) {
|
||||||
|
await this.cacheQuizItem({ relativePosition: 0 })
|
||||||
|
}
|
||||||
|
|
||||||
|
let it: IQuiz | undefined
|
||||||
|
while (true) {
|
||||||
|
it = this.quizData[this.quizArray[++this.quizIndex]!]
|
||||||
|
|
||||||
|
if (it && it.entry && it.type) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.quizIndex >= this.quizArray.length) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isQuizModal = true
|
||||||
|
}
|
||||||
|
|
||||||
|
checkFront(relativePosition: number): boolean {
|
||||||
|
relativePosition++
|
||||||
|
const it = this.quizData[this.quizArray[this.quizIndex + relativePosition]!]
|
||||||
|
|
||||||
|
if (!it) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const { entry, type, direction } = it
|
||||||
|
|
||||||
|
if (!entry || !type || !direction) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return !!this.dictionaryData[type][entry]
|
||||||
|
}
|
||||||
|
|
||||||
|
openContext(ev: MouseEvent, entry?: string, type?: string) {
|
||||||
|
this.ctxEntry = entry || this.current.entry || ''
|
||||||
|
this.ctxType = type || this.current.type || ''
|
||||||
|
|
||||||
|
this.context.open(ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
doMask(s = '', ...ws: string[]) {
|
||||||
|
ws.map((w) => {
|
||||||
|
s = s.replace(
|
||||||
|
new RegExp(
|
||||||
|
`(^| |[^a-z])(${w
|
||||||
|
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||||
|
.replace(/\d/g, '\\d?')
|
||||||
|
.replace(/\s+/g, '\\s*')})($| |[^a-z])`,
|
||||||
|
'gi'
|
||||||
|
),
|
||||||
|
'$1<span class="gray-box">$2</span>$3'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
async cacheQuizItem(params: { quizId?: string; relativePosition?: number }) {
|
||||||
|
let { relativePosition = 0 } = params
|
||||||
|
relativePosition++
|
||||||
|
|
||||||
|
let { quizId } = params
|
||||||
|
quizId = quizId || this.quizArray[this.quizIndex + relativePosition]
|
||||||
|
|
||||||
|
if (!quizId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let q = this.quizData[quizId]
|
||||||
|
if (!q || !q.entry || !q.type || !q.direction) {
|
||||||
|
const r = await api.quiz_getById({
|
||||||
|
id: quizId,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (r) {
|
||||||
|
if (q) {
|
||||||
|
Object.assign(q, r)
|
||||||
|
} else {
|
||||||
|
q = {
|
||||||
|
...r,
|
||||||
|
id: quizId,
|
||||||
|
type: r.type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
q.id = quizId
|
||||||
|
q.type = q.type
|
||||||
|
q.entry = q.entry as string
|
||||||
|
|
||||||
|
this.$set(this.quizData, quizId, q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = q?.type
|
||||||
|
const entry = q?.entry
|
||||||
|
|
||||||
|
if (!entry || !type) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.dictionaryData[type][entry]) {
|
||||||
|
const {
|
||||||
|
result: [r],
|
||||||
|
} = await api.all_getLookup({
|
||||||
|
entry,
|
||||||
|
type,
|
||||||
|
limit: 1,
|
||||||
|
forced: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.dictionaryData[type][entry] = r!
|
||||||
|
this.$set(this.dictionaryData, type, this.dictionaryData[type])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'character') {
|
||||||
|
await Promise.allSettled([
|
||||||
|
(async () => {
|
||||||
|
const subtype = 'vocabulary'
|
||||||
|
|
||||||
|
if (this.fromCharacter(entry, subtype).length < 5) {
|
||||||
|
const { result } = await api.character_getVocabulary({ entry })
|
||||||
|
result.map((r) => {
|
||||||
|
if (this.dictionaryData[subtype][r.list[0]!]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dictionaryData[subtype][r.list[0]!] = {
|
||||||
|
...r,
|
||||||
|
sentence_set: {},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.$set(
|
||||||
|
this.dictionaryData,
|
||||||
|
subtype,
|
||||||
|
this.dictionaryData[subtype]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
(async () => {
|
||||||
|
const subtype = 'sentence'
|
||||||
|
|
||||||
|
if (this.fromCharacter(entry, subtype).length < 5) {
|
||||||
|
const { result } = await api.character_getSentence({ entry })
|
||||||
|
result.map((r) => {
|
||||||
|
if (this.dictionaryData[subtype][r.list[0]!]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dictionaryData[subtype][r.list[0]!] = {
|
||||||
|
...r,
|
||||||
|
vocabulary_set: {},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.$set(
|
||||||
|
this.dictionaryData,
|
||||||
|
subtype,
|
||||||
|
this.dictionaryData[subtype]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
])
|
||||||
|
} else if (type === 'vocabulary') {
|
||||||
|
if (
|
||||||
|
Object.keys(this.dictionaryData[type][entry]!.sentence_set).length < 5
|
||||||
|
) {
|
||||||
|
const subtype = 'sentence'
|
||||||
|
|
||||||
|
const { result } = await api.vocabulary_getSentence({ entry })
|
||||||
|
result.map((r) => {
|
||||||
|
if (this.dictionaryData[subtype][r.list[0]!]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dictionaryData[subtype][r.list[0]!] = {
|
||||||
|
...r,
|
||||||
|
vocabulary_set: {},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.dictionaryData[type][entry]!.sentence_set = result.reduce(
|
||||||
|
(prev, c) => ({
|
||||||
|
...prev,
|
||||||
|
[c.list[0]!]: true,
|
||||||
|
}),
|
||||||
|
{} as Record<string, boolean>
|
||||||
|
)
|
||||||
|
|
||||||
|
this.$set(this.dictionaryData, type, this.dictionaryData[type])
|
||||||
|
this.$set(this.dictionaryData, subtype, this.dictionaryData[subtype])
|
||||||
|
}
|
||||||
|
} else if (type === 'sentence') {
|
||||||
|
if (
|
||||||
|
!Object.keys(this.dictionaryData[type][entry]!.vocabulary_set).length
|
||||||
|
) {
|
||||||
|
const subtype = 'vocabulary'
|
||||||
|
|
||||||
|
const { result: tokens } = await api.all_tokenize({ entry })
|
||||||
|
|
||||||
|
await Promise.allSettled(
|
||||||
|
tokens.map(async (entry) => {
|
||||||
|
if (this.dictionaryData[subtype][entry]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
result: [r],
|
||||||
|
} = await api.all_getLookup({
|
||||||
|
entry,
|
||||||
|
type: subtype,
|
||||||
|
limit: 1,
|
||||||
|
forced: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.dictionaryData[subtype][entry] = {
|
||||||
|
...r!,
|
||||||
|
sentence_set: {},
|
||||||
|
}
|
||||||
|
this.$set(
|
||||||
|
this.dictionaryData,
|
||||||
|
subtype,
|
||||||
|
this.dictionaryData[subtype]
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addHint() {
|
||||||
|
const i = this.quizArray[this.quizIndex]
|
||||||
|
const it = this.quizData[i!]
|
||||||
|
|
||||||
|
if (!it) return
|
||||||
|
|
||||||
|
this.$buefy.dialog.prompt({
|
||||||
|
message: 'Hint',
|
||||||
|
inputAttrs: {
|
||||||
|
value: it.hint,
|
||||||
|
},
|
||||||
|
trapFocus: true,
|
||||||
|
onConfirm: async (hint) => {
|
||||||
|
await api.quiz_update({ hint, id: it.id })
|
||||||
|
it.hint = hint
|
||||||
|
this.$set(this.quizData, i!, it)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
addMnemonic() {
|
||||||
|
const i = this.quizArray[this.quizIndex]
|
||||||
|
const it = this.quizData[i!]
|
||||||
|
|
||||||
|
if (!it) return
|
||||||
|
|
||||||
|
this.$buefy.dialog.prompt({
|
||||||
|
message: 'Mnemonic',
|
||||||
|
inputAttrs: {
|
||||||
|
value: it.mnemonic,
|
||||||
|
},
|
||||||
|
trapFocus: true,
|
||||||
|
onConfirm: async (mnemonic) => {
|
||||||
|
await api.quiz_update({ mnemonic, id: it.id })
|
||||||
|
it.mnemonic = mnemonic
|
||||||
|
this.$set(this.quizData, i!, it)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.has-context {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-child {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-parent:hover .hover-child {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.traditional {
|
||||||
|
& + &::before {
|
||||||
|
content: ' | ';
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.quiz-modal .modal-content {
|
||||||
|
max-width: 500px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quiz-modal .buttons-area {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 1rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons-area .buttons {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quiz-modal .card-content {
|
||||||
|
min-height: 7.5rem;
|
||||||
|
max-height: calc(100vh - 300px);
|
||||||
|
overflow: scroll;
|
||||||
|
padding-bottom: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
margin-left: 1.5rem;
|
||||||
|
margin-right: 1.5rem;
|
||||||
|
border-bottom: 1px solid hsla(0, 0%, 50%, 0.25);
|
||||||
|
|
||||||
|
scrollbar-width: none; /* For Firefox */
|
||||||
|
-ms-overflow-style: none; /* For Internet Explorer and Edge */
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 0px; /* For Chrome, Safari, and Opera */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons-panel .buttons {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons-area button::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: currentColor;
|
||||||
|
visibility: hidden;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons-area button:not(:active)::before {
|
||||||
|
animation: ripple 0.4s cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
transition: visibility 0.4s step-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons-area button:active::before {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-modal .card-footer {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sentence {
|
||||||
|
li.english {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover li.english {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ripple {
|
||||||
|
0% {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
v-if="selected.entry.every((el) => el.length === 1)"
|
v-if="selected.entry.every((el) => el.length === 1)"
|
||||||
value="character"
|
value="character"
|
||||||
>
|
>
|
||||||
Hanzi
|
Kanji
|
||||||
</option>
|
</option>
|
||||||
</b-select>
|
</b-select>
|
||||||
</b-field>
|
</b-field>
|
||||||
@@ -222,14 +222,14 @@ export default class BrowseTab extends Vue {
|
|||||||
type: 'desc',
|
type: 'desc',
|
||||||
}
|
}
|
||||||
|
|
||||||
selected: Omit<IEntry, 'entry'> & {
|
selected: IEntry & {
|
||||||
entry: string
|
entry: string
|
||||||
list: string[]
|
|
||||||
} = {
|
} = {
|
||||||
id: '',
|
id: '',
|
||||||
entry: '',
|
entry: '',
|
||||||
list: [],
|
list: [],
|
||||||
reading: [],
|
reading: [],
|
||||||
|
audio: [],
|
||||||
translation: [],
|
translation: [],
|
||||||
type: 'vocabulary',
|
type: 'vocabulary',
|
||||||
description: '',
|
description: '',
|
||||||
@@ -296,10 +296,7 @@ export default class BrowseTab extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async doCreate() {
|
async doCreate() {
|
||||||
const { id } = await api.extra_create({
|
const { id } = await api.extra_create(this.selected)
|
||||||
...this.selected,
|
|
||||||
entry: this.selected.list,
|
|
||||||
})
|
|
||||||
this.selected.id = id
|
this.selected.id = id
|
||||||
|
|
||||||
await this.context.addToQuiz()
|
await this.context.addToQuiz()
|
||||||
@@ -314,7 +311,6 @@ export default class BrowseTab extends Vue {
|
|||||||
if (id) {
|
if (id) {
|
||||||
await api.extra_update({
|
await api.extra_update({
|
||||||
...this.selected,
|
...this.selected,
|
||||||
entry: this.selected.list,
|
|
||||||
id,
|
id,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -345,6 +341,7 @@ export default class BrowseTab extends Vue {
|
|||||||
entry: '',
|
entry: '',
|
||||||
list: [],
|
list: [],
|
||||||
reading: [],
|
reading: [],
|
||||||
|
audio: [],
|
||||||
translation: [],
|
translation: [],
|
||||||
type: 'vocabulary',
|
type: 'vocabulary',
|
||||||
description: '',
|
description: '',
|
||||||
@@ -354,15 +351,10 @@ export default class BrowseTab extends Vue {
|
|||||||
this.isEditModal = true
|
this.isEditModal = true
|
||||||
}
|
}
|
||||||
|
|
||||||
async onTableContextmenu(row: IEntry, evt: MouseEvent) {
|
async onTableContextmenu(row: IEntry & { entry: string }, evt: MouseEvent) {
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
|
|
||||||
const o = jsonClone(row)
|
this.selected = jsonClone(row)
|
||||||
this.selected = {
|
|
||||||
...o,
|
|
||||||
entry: o.entry[0]!,
|
|
||||||
list: o.entry,
|
|
||||||
}
|
|
||||||
this.context.open(evt)
|
this.context.open(evt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -416,7 +416,7 @@ export default class CharacterTab extends Vue {
|
|||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
(async () => {
|
(async () => {
|
||||||
const { result: r } = await api.character_getRadical({
|
const r = await api.character_getRadical({
|
||||||
entry: this.current,
|
entry: this.current,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -457,7 +457,7 @@ export default class CharacterTab extends Vue {
|
|||||||
result
|
result
|
||||||
.map((r) =>
|
.map((r) =>
|
||||||
api
|
api
|
||||||
.all_getLookup({ entry: r.entry, type: 'vocabulary', limit: 1 })
|
.all_getLookup({ entry: r.list[0]!, type: 'vocabulary', limit: 1 })
|
||||||
.then((r) => r.result[0]!)
|
.then((r) => r.result[0]!)
|
||||||
)
|
)
|
||||||
.filter((r) => r)
|
.filter((r) => r)
|
||||||
@@ -479,7 +479,7 @@ export default class CharacterTab extends Vue {
|
|||||||
result
|
result
|
||||||
.map((r) =>
|
.map((r) =>
|
||||||
api
|
api
|
||||||
.all_getLookup({ entry: r.entry, type: 'sentence', limit: 1 })
|
.all_getLookup({ entry: r.list[0]!, type: 'sentence', limit: 1 })
|
||||||
.then((r) => r.result[0]!)
|
.then((r) => r.result[0]!)
|
||||||
)
|
)
|
||||||
.filter((r) => r)
|
.filter((r) => r)
|
||||||
|
|||||||
Reference in new issue
Block a user