|
|
@ -30,12 +30,10 @@ |
|
|
|
<div class="column is-6 entry-display"> |
|
|
|
<div |
|
|
|
class="character-display clickable font-han" |
|
|
|
@click="(evt) => openContext(evt, current, 'character')" |
|
|
|
@contextmenu.prevent=" |
|
|
|
(evt) => openContext(evt, current, 'character') |
|
|
|
" |
|
|
|
@click="(evt) => openContext(evt, entry)" |
|
|
|
@contextmenu.prevent="(evt) => openContext(evt, entry)" |
|
|
|
> |
|
|
|
{{ current }} |
|
|
|
{{ entry.entry }} |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="buttons has-addons"> |
|
|
@ -57,7 +55,7 @@ |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div v-if="tag.length" class="mb-4"> |
|
|
|
<div v-if="entry.tag.length" class="mb-4"> |
|
|
|
Tags: |
|
|
|
<b-taglist style="display: inline-flex"> |
|
|
|
<b-tag v-for="t in tag.slice(0, 5)" :key="t" type="is-info"> |
|
|
@ -66,11 +64,11 @@ |
|
|
|
</b-taglist> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div v-if="level && level <= 60" class="mb-4"> |
|
|
|
<div v-if="entry.level <= 60" class="mb-4"> |
|
|
|
<b-taglist attached> |
|
|
|
<b-tag type="is-dark">Level</b-tag> |
|
|
|
<b-tag type="is-primary"> |
|
|
|
{{ level }} |
|
|
|
{{ entry.level }} |
|
|
|
</b-tag> |
|
|
|
</b-taglist> |
|
|
|
</div> |
|
|
@ -91,14 +89,14 @@ |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="card-content"> |
|
|
|
{{ reading.join(' / ') }} |
|
|
|
{{ entry.reading.join(' / ') }} |
|
|
|
</div> |
|
|
|
</b-collapse> |
|
|
|
|
|
|
|
<b-collapse |
|
|
|
class="card" |
|
|
|
animation="slide" |
|
|
|
:open="!!translation.length" |
|
|
|
:open="!!entry.translation.length" |
|
|
|
> |
|
|
|
<div |
|
|
|
slot="trigger" |
|
|
@ -113,11 +111,11 @@ |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="card-content"> |
|
|
|
{{ translation.join(' / ') }} |
|
|
|
{{ entry.translation.join(' / ') }} |
|
|
|
</div> |
|
|
|
</b-collapse> |
|
|
|
|
|
|
|
<b-collapse class="card" animation="slide" :open="!!sub.length"> |
|
|
|
<b-collapse class="card" animation="slide" :open="!!entry.sub.length"> |
|
|
|
<div |
|
|
|
slot="trigger" |
|
|
|
slot-scope="props" |
|
|
@ -132,18 +130,22 @@ |
|
|
|
|
|
|
|
<div class="card-content"> |
|
|
|
<span |
|
|
|
v-for="h in sub" |
|
|
|
v-for="h in entry.sub" |
|
|
|
:key="h" |
|
|
|
class="font-han clickable" |
|
|
|
@click="(evt) => openContext(evt, h, 'character')" |
|
|
|
@contextmenu.prevent="(evt) => openContext(evt, h, 'character')" |
|
|
|
@click=" |
|
|
|
(evt) => openContext(evt, { entry: h, type: 'character' }) |
|
|
|
" |
|
|
|
@contextmenu.prevent=" |
|
|
|
(evt) => openContext(evt, { entry: h, type: 'character' }) |
|
|
|
" |
|
|
|
> |
|
|
|
{{ h }} |
|
|
|
</span> |
|
|
|
</div> |
|
|
|
</b-collapse> |
|
|
|
|
|
|
|
<b-collapse class="card" animation="slide" :open="!!sup.length"> |
|
|
|
<b-collapse class="card" animation="slide" :open="!!entry.sup.length"> |
|
|
|
<div |
|
|
|
slot="trigger" |
|
|
|
slot-scope="props" |
|
|
@ -158,18 +160,26 @@ |
|
|
|
|
|
|
|
<div class="card-content"> |
|
|
|
<span |
|
|
|
v-for="h in sup" |
|
|
|
v-for="h in entry.sup" |
|
|
|
:key="h" |
|
|
|
class="font-han clickable" |
|
|
|
@click="(evt) => openContext(evt, h, 'character')" |
|
|
|
@contextmenu.prevent="(evt) => openContext(evt, h, 'character')" |
|
|
|
@click=" |
|
|
|
(evt) => openContext(evt, { entry: h, type: 'character' }) |
|
|
|
" |
|
|
|
@contextmenu.prevent=" |
|
|
|
(evt) => openContext(evt, { entry: h, type: 'character' }) |
|
|
|
" |
|
|
|
> |
|
|
|
{{ h }} |
|
|
|
</span> |
|
|
|
</div> |
|
|
|
</b-collapse> |
|
|
|
|
|
|
|
<b-collapse class="card" animation="slide" :open="!!variants.length"> |
|
|
|
<b-collapse |
|
|
|
class="card" |
|
|
|
animation="slide" |
|
|
|
:open="!!entry.variant.length" |
|
|
|
> |
|
|
|
<div |
|
|
|
slot="trigger" |
|
|
|
slot-scope="props" |
|
|
@ -184,82 +194,20 @@ |
|
|
|
|
|
|
|
<div class="card-content"> |
|
|
|
<span |
|
|
|
v-for="h in variants" |
|
|
|
v-for="h in entry.variant" |
|
|
|
:key="h" |
|
|
|
class="font-han clickable" |
|
|
|
@click="(evt) => openContext(evt, h, 'character')" |
|
|
|
@contextmenu.prevent="(evt) => openContext(evt, h, 'character')" |
|
|
|
@click=" |
|
|
|
(evt) => openContext(evt, { entry: h, type: 'character' }) |
|
|
|
" |
|
|
|
@contextmenu.prevent=" |
|
|
|
(evt) => openContext(evt, { entry: h, type: 'character' }) |
|
|
|
" |
|
|
|
> |
|
|
|
{{ h }} |
|
|
|
</span> |
|
|
|
</div> |
|
|
|
</b-collapse> |
|
|
|
|
|
|
|
<b-collapse class="card" animation="slide" :open="!!vocabs.length"> |
|
|
|
<div |
|
|
|
slot="trigger" |
|
|
|
slot-scope="props" |
|
|
|
class="card-header" |
|
|
|
role="button" |
|
|
|
> |
|
|
|
<h2 class="card-header-title">Vocabularies</h2> |
|
|
|
<a role="button" class="card-header-icon"> |
|
|
|
<fontawesome :icon="props.open ? 'caret-down' : 'caret-up'" /> |
|
|
|
</a> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="card-content"> |
|
|
|
<div v-for="(v, i) in vocabs" :key="i" class="long-item"> |
|
|
|
<span |
|
|
|
class="clickable" |
|
|
|
@click="(evt) => openContext(evt, v.entry, 'vocabulary')" |
|
|
|
@contextmenu.prevent=" |
|
|
|
(evt) => openContext(evt, v.entry, 'vocabulary') |
|
|
|
" |
|
|
|
> |
|
|
|
{{ v.entry }} |
|
|
|
</span> |
|
|
|
|
|
|
|
<span v-if="v.alt" class="clickable"> |
|
|
|
{{ v.alt.join(' ') }} |
|
|
|
</span> |
|
|
|
|
|
|
|
<span class="pinyin">[{{ v.reading.join(' / ') }}]</span> |
|
|
|
|
|
|
|
<span>{{ v.translation.join(' / ') }}</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</b-collapse> |
|
|
|
|
|
|
|
<b-collapse class="card" animation="slide" :open="!!sentences.length"> |
|
|
|
<div |
|
|
|
slot="trigger" |
|
|
|
slot-scope="props" |
|
|
|
class="card-header" |
|
|
|
role="button" |
|
|
|
> |
|
|
|
<h2 class="card-header-title">Sentences</h2> |
|
|
|
<a role="button" class="card-header-icon"> |
|
|
|
<fontawesome :icon="props.open ? 'caret-down' : 'caret-up'" /> |
|
|
|
</a> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="card-content"> |
|
|
|
<div v-for="(s, i) in sentences" :key="i" class="long-item"> |
|
|
|
<span |
|
|
|
class="clickable" |
|
|
|
@click="(evt) => openContext(evt, s.entry[0], 'sentence')" |
|
|
|
@contextmenu.prevent=" |
|
|
|
(evt) => openContext(evt, s.entry[0], 'sentence') |
|
|
|
" |
|
|
|
> |
|
|
|
{{ s.entry[0] }} |
|
|
|
</span> |
|
|
|
|
|
|
|
<span>{{ s.translation[0] }}</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</b-collapse> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
@ -278,10 +226,31 @@ |
|
|
|
import { Component, Prop, Ref, Vue, Watch } from 'nuxt-property-decorator' |
|
|
|
import ContextMenu from '@/components/ContextMenu.vue' |
|
|
|
import { api, IDictionaryLookup, IEntryLookup, IType } from '~/assets/api' |
|
|
|
import { capitalize } from '~/assets/util' |
|
|
|
|
|
|
|
type ILookupEntry = Omit<IDictionaryLookup, 'id'> & IEntryLookup |
|
|
|
|
|
|
|
const makeLookupEntry = ( |
|
|
|
entry = '', |
|
|
|
type: IType = 'vocabulary' |
|
|
|
): ILookupEntry => { |
|
|
|
return { |
|
|
|
entry, |
|
|
|
list: [], |
|
|
|
type, |
|
|
|
reading: [], |
|
|
|
translation: [], |
|
|
|
audio: [], |
|
|
|
description: '', |
|
|
|
tag: [], |
|
|
|
sub: [], |
|
|
|
sup: [], |
|
|
|
variant: [], |
|
|
|
visual: [], |
|
|
|
amalgamation: [], |
|
|
|
component: [], |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Component<LookupTab>({ |
|
|
|
components: { |
|
|
|
ContextMenu, |
|
|
@ -300,13 +269,12 @@ export default class LookupTab extends Vue { |
|
|
|
@Prop({ default: () => ({}) }) query!: { |
|
|
|
q?: string |
|
|
|
entry?: string |
|
|
|
type?: 'character' | 'vocabulary' |
|
|
|
} |
|
|
|
|
|
|
|
@Prop() type!: 'character' | 'vocabulary' |
|
|
|
|
|
|
|
@Ref() context!: ContextMenu |
|
|
|
|
|
|
|
title = this.type === 'character' ? 'Kanji' : capitalize(this.type) |
|
|
|
title = 'Lookup' |
|
|
|
|
|
|
|
entries: ILookupEntry[] = [] |
|
|
|
i = 0 |
|
|
@ -320,6 +288,7 @@ export default class LookupTab extends Vue { |
|
|
|
selected: { |
|
|
|
entry: string |
|
|
|
type: string |
|
|
|
audio?: ILookupEntry['audio'] |
|
|
|
} = { |
|
|
|
entry: '', |
|
|
|
type: '', |
|
|
@ -328,8 +297,8 @@ export default class LookupTab extends Vue { |
|
|
|
q = '' |
|
|
|
q0 = '' |
|
|
|
|
|
|
|
get current() { |
|
|
|
return this.entries[this.i]?.entry || '' |
|
|
|
get entry() { |
|
|
|
return this.entries[this.i] || makeLookupEntry() |
|
|
|
} |
|
|
|
|
|
|
|
get additionalContext() { |
|
|
@ -338,7 +307,7 @@ export default class LookupTab extends Vue { |
|
|
|
{ |
|
|
|
name: 'Reload', |
|
|
|
handler: async () => { |
|
|
|
const { result } = await api.all_getRandom({ type: this.type }) |
|
|
|
const { result } = await api.all_getRandom({ type: 'vocabulary' }) |
|
|
|
this.q0 = result |
|
|
|
}, |
|
|
|
}, |
|
|
@ -348,12 +317,8 @@ export default class LookupTab extends Vue { |
|
|
|
return [] |
|
|
|
} |
|
|
|
|
|
|
|
openContext( |
|
|
|
evt: MouseEvent, |
|
|
|
entry = this.selected.entry, |
|
|
|
type = this.selected.type |
|
|
|
) { |
|
|
|
this.selected = { entry, type } |
|
|
|
openContext(evt: MouseEvent, entry = this.selected) { |
|
|
|
this.selected = entry |
|
|
|
this.context.open(evt) |
|
|
|
} |
|
|
|
|
|
|
@ -362,72 +327,23 @@ export default class LookupTab extends Vue { |
|
|
|
this.$emit('title', (q ? q + ' - ' : '') + this.title) |
|
|
|
this.entries = [] |
|
|
|
|
|
|
|
if (this.type === 'character') { |
|
|
|
if (/\p{sc=Han}/u.test(q)) { |
|
|
|
let qs = q.split('').filter((h) => /\p{sc=Han}/u.test(h)) |
|
|
|
qs = qs.filter((h, i) => qs.indexOf(h) === i) |
|
|
|
|
|
|
|
this.entries = qs.map( |
|
|
|
(entry) => |
|
|
|
({ |
|
|
|
entry, |
|
|
|
list: [], |
|
|
|
type: this.type, |
|
|
|
reading: [], |
|
|
|
translation: [], |
|
|
|
audio: [], |
|
|
|
description: '', |
|
|
|
tag: [], |
|
|
|
sub: [], |
|
|
|
sup: [], |
|
|
|
variant: [], |
|
|
|
visual: [], |
|
|
|
amalgamation: [], |
|
|
|
component: [], |
|
|
|
} as ILookupEntry) |
|
|
|
) |
|
|
|
} else { |
|
|
|
if (!q.trim()) { |
|
|
|
const { result } = await api.all_getRandom({ type: this.type }) |
|
|
|
|
|
|
|
this.q0 = result |
|
|
|
this.q = result |
|
|
|
return |
|
|
|
} else { |
|
|
|
const r = await api.all_getQuery({ |
|
|
|
q, |
|
|
|
type: this.type, |
|
|
|
distinct: 'id', |
|
|
|
}) |
|
|
|
this.entries = r.result.map( |
|
|
|
({ entry }) => |
|
|
|
({ |
|
|
|
entry, |
|
|
|
list: [], |
|
|
|
type: this.type, |
|
|
|
reading: [], |
|
|
|
translation: [], |
|
|
|
audio: [], |
|
|
|
description: '', |
|
|
|
tag: [], |
|
|
|
sub: [], |
|
|
|
sup: [], |
|
|
|
variant: [], |
|
|
|
visual: [], |
|
|
|
amalgamation: [], |
|
|
|
component: [], |
|
|
|
} as ILookupEntry) |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
if (q.trim()) { |
|
|
|
const r = await api.all_getQuery({ |
|
|
|
q, |
|
|
|
type: this.query.type, |
|
|
|
distinct: 'id', |
|
|
|
}) |
|
|
|
this.entries = r.result.map(({ entry, type }) => |
|
|
|
makeLookupEntry(entry, type) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
this.i = 0 |
|
|
|
} |
|
|
|
|
|
|
|
@Watch('current') |
|
|
|
@Watch('entry.entry') |
|
|
|
load() { |
|
|
|
if (this.current) { |
|
|
|
if (this.entry.entry) { |
|
|
|
this.loadComponents() |
|
|
|
this.loadVocabularies() |
|
|
|
this.loadSentences() |
|
|
@ -441,17 +357,18 @@ export default class LookupTab extends Vue { |
|
|
|
} |
|
|
|
|
|
|
|
async loadComponents() { |
|
|
|
if (!this.current.trim()) return |
|
|
|
const entry = this.entries[this.i] |
|
|
|
if (!entry) return |
|
|
|
|
|
|
|
const [comp, dict] = await Promise.all([ |
|
|
|
api.all_getComponent({ |
|
|
|
entry: this.current, |
|
|
|
type: this.type, |
|
|
|
entry: entry.entry, |
|
|
|
type: entry.type, |
|
|
|
}), |
|
|
|
api |
|
|
|
.all_getLookup({ |
|
|
|
entry: this.current, |
|
|
|
type: this.type, |
|
|
|
entry: entry.entry, |
|
|
|
type: entry.type, |
|
|
|
limit: 1, |
|
|
|
}) |
|
|
|
.then(({ result: [r] }) => r), |
|
|
@ -459,9 +376,9 @@ export default class LookupTab extends Vue { |
|
|
|
|
|
|
|
this.$set(this.entries, this.i, { |
|
|
|
...(dict || { |
|
|
|
entry: this.current, |
|
|
|
entry: entry.entry, |
|
|
|
list: [], |
|
|
|
type: this.type, |
|
|
|
type: entry.type, |
|
|
|
reading: [], |
|
|
|
translation: [], |
|
|
|
audio: [], |
|
|
@ -480,22 +397,25 @@ export default class LookupTab extends Vue { |
|
|
|
} |
|
|
|
|
|
|
|
async loadVocabularies() { |
|
|
|
if (this.type === 'character') { |
|
|
|
if (!/^\p{sc=Han}$/u.test(this.current)) { |
|
|
|
const entry = this.entries[this.i] |
|
|
|
if (!entry) return |
|
|
|
|
|
|
|
if (entry.type === 'character') { |
|
|
|
if (!/^\p{sc=Han}$/u.test(entry.entry)) { |
|
|
|
this.lookup.vocabulary = [] |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
this.lookup.vocabulary = await api |
|
|
|
.character_getVocabulary({ |
|
|
|
entry: this.current, |
|
|
|
entry: entry.entry, |
|
|
|
distinct: 'id', |
|
|
|
}) |
|
|
|
.then((r) => r.result) |
|
|
|
} else { |
|
|
|
this.lookup.vocabulary = await api |
|
|
|
.vocabulary_getVocabulary({ |
|
|
|
entry: this.current, |
|
|
|
entry: entry.entry, |
|
|
|
distinct: 'id', |
|
|
|
}) |
|
|
|
.then((r) => r.result) |
|
|
@ -503,22 +423,25 @@ export default class LookupTab extends Vue { |
|
|
|
} |
|
|
|
|
|
|
|
async loadSentences() { |
|
|
|
if (this.type === 'character') { |
|
|
|
if (!/^\p{sc=Han}$/u.test(this.current)) { |
|
|
|
const entry = this.entries[this.i] |
|
|
|
if (!entry) return |
|
|
|
|
|
|
|
if (entry.type === 'character') { |
|
|
|
if (!/^\p{sc=Han}$/u.test(entry.entry)) { |
|
|
|
this.lookup.sentence = [] |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
this.lookup.sentence = await api |
|
|
|
.character_getSentence({ |
|
|
|
entry: this.current, |
|
|
|
entry: entry.entry, |
|
|
|
distinct: 'id', |
|
|
|
}) |
|
|
|
.then((r) => r.result) |
|
|
|
} else { |
|
|
|
this.lookup.sentence = await api |
|
|
|
.vocabulary_getSentence({ |
|
|
|
entry: this.current, |
|
|
|
entry: entry.entry, |
|
|
|
distinct: 'id', |
|
|
|
}) |
|
|
|
.then((r) => r.result) |
|
|
|