271 lines
5.8 KiB
Vue
271 lines
5.8 KiB
Vue
<template>
|
|
<client-only>
|
|
<vue-context ref="contextmenu" lazy>
|
|
<li v-for="(a, i) in additional" :key="i">
|
|
<a
|
|
role="button"
|
|
@click.prevent="a.handler"
|
|
@keypress.prevent="a.handler"
|
|
>
|
|
{{ a.name }}
|
|
</a>
|
|
</li>
|
|
<li v-if="additional.length" class="separator">
|
|
<a></a>
|
|
</li>
|
|
<li v-if="entries.length === 1">
|
|
<a
|
|
role="button"
|
|
@click.prevent="doSpeak()"
|
|
@keypress.prevent="doSpeak()"
|
|
>
|
|
Speak
|
|
</a>
|
|
</li>
|
|
<li v-if="type && quiz.db.length < entries.length">
|
|
<a
|
|
role="button"
|
|
@click.prevent="addToQuiz()"
|
|
@keypress.prevent="addToQuiz()"
|
|
>
|
|
Add to quiz
|
|
</a>
|
|
</li>
|
|
<li v-if="quiz.db.length">
|
|
<a
|
|
role="button"
|
|
@click.prevent="removeFromQuiz()"
|
|
@keypress.prevent="removeFromQuiz()"
|
|
>
|
|
Remove from quiz
|
|
</a>
|
|
</li>
|
|
<li v-if="entry" class="separator">
|
|
<a></a>
|
|
</li>
|
|
<li v-if="entry && type === 'vocabulary'">
|
|
<a
|
|
role="button"
|
|
@click="openInNewTab('Dictionary', { entry, type: 'vocabulary' })"
|
|
>
|
|
Show details
|
|
</a>
|
|
</li>
|
|
<li v-if="entry">
|
|
<a
|
|
role="button"
|
|
@click="openInNewTab('Dictionary', { entry, type: 'character' })"
|
|
>
|
|
{{ type === 'character' ? 'Show details' : 'Search for Kanji' }}
|
|
</a>
|
|
</li>
|
|
<li v-if="entry && type !== 'character'">
|
|
<a role="button" @click="openInNewTab('Dictionary', { entry, type })">
|
|
Search for vocabs
|
|
</a>
|
|
</li>
|
|
<li v-if="entry">
|
|
<a
|
|
role="button"
|
|
:href="`https://jisho.org/word/${encodeURIComponent(entry)}`"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
Open in Jisho.org
|
|
</a>
|
|
</li>
|
|
<li v-if="entry && type !== 'sentence'">
|
|
<a
|
|
role="button"
|
|
:href="`https://dictionary.goo.ne.jp/srch/all/${encodeURIComponent(
|
|
entry
|
|
)}/m0u`"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
Open in goo.ne.jp
|
|
</a>
|
|
</li>
|
|
<li v-if="id">
|
|
<a
|
|
role="button"
|
|
@click.prevent="doDelete()"
|
|
@keypress.prevent="doDelete()"
|
|
>
|
|
Delete
|
|
</a>
|
|
</li>
|
|
</vue-context>
|
|
</client-only>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { Vue, Component, Prop, Ref } from 'nuxt-property-decorator'
|
|
import { speak } from '~/assets/speak'
|
|
import { api, IType } from '~/assets/api'
|
|
|
|
@Component
|
|
export default class ContextMenu extends Vue {
|
|
@Prop() id?: string
|
|
@Prop() entry?: string
|
|
@Prop() audio?: {
|
|
mimetype?: string
|
|
url: string
|
|
}[]
|
|
@Prop() list?: string[]
|
|
@Prop() type?: IType
|
|
@Prop() source?: string
|
|
@Prop() direction?: string
|
|
@Prop() description?: string
|
|
|
|
@Prop({ default: () => [] }) additional!: {
|
|
name: string
|
|
handler: () => void
|
|
}[]
|
|
|
|
@Ref() contextmenu!: {
|
|
open: (evt: MouseEvent) => void
|
|
}
|
|
|
|
quiz: {
|
|
of?: {
|
|
entries: string[]
|
|
type: string
|
|
}
|
|
db: {
|
|
ids: string[]
|
|
}[]
|
|
} = {
|
|
db: [],
|
|
}
|
|
|
|
get entries() {
|
|
return this.list || (this.entry ? [this.entry] : [])
|
|
}
|
|
|
|
async setQuiz() {
|
|
if (this.entries.length && this.type) {
|
|
const { result } = await api.quiz_getManyByEntries({
|
|
entries: this.entries,
|
|
type: this.type,
|
|
direction: this.direction,
|
|
})
|
|
|
|
const entryMap = new Map<string, string[]>()
|
|
result.map(({ id, entry }) => {
|
|
const c = entryMap.get(entry!) || []
|
|
c.push(id!)
|
|
return entryMap.set(entry!, c)
|
|
})
|
|
|
|
this.quiz = {
|
|
of: {
|
|
entries: this.entries,
|
|
type: this.type,
|
|
},
|
|
db: Array.from(entryMap.values()).map((ids) => ({ ids })),
|
|
}
|
|
}
|
|
}
|
|
|
|
open(evt: MouseEvent) {
|
|
this.$nextTick(() => {
|
|
this.contextmenu.open(evt)
|
|
})
|
|
}
|
|
|
|
async doDelete() {
|
|
if (this.id) {
|
|
await api.extra_delete({
|
|
id: this.id,
|
|
})
|
|
}
|
|
|
|
this.$emit('deleted', this.id)
|
|
}
|
|
|
|
async doSpeak() {
|
|
if (this.entry) {
|
|
await speak(this.entry, { url: this.audio?.[0]?.url })
|
|
}
|
|
}
|
|
|
|
async addToQuiz() {
|
|
if (this.entries.length && this.type) {
|
|
const { result } = await api.quiz_create({
|
|
entries: this.entries,
|
|
type: this.type,
|
|
})
|
|
|
|
this.$buefy.snackbar.open(
|
|
`Added ${this.type}: ${this.entries.slice(0, 3).join(', ')}${
|
|
this.entries.length > 3 ? '...' : ''
|
|
} to quiz`
|
|
)
|
|
|
|
this.quiz = {
|
|
...this.quiz,
|
|
db: result,
|
|
}
|
|
|
|
this.$emit('quiz:added', {
|
|
entries: this.entries,
|
|
type: this.type,
|
|
db: result,
|
|
})
|
|
}
|
|
}
|
|
|
|
async removeFromQuiz() {
|
|
await api.quiz_deleteMany({
|
|
entries: this.entries,
|
|
type: this.type || 'vocabulary',
|
|
})
|
|
|
|
this.$buefy.snackbar.open(
|
|
`Removed ${this.type}: ${this.entries.slice(0, 3).join(', ')}${
|
|
this.entries.length > 3 ? '...' : ''
|
|
} from quiz`
|
|
)
|
|
|
|
const { db } = this.quiz
|
|
this.quiz = {
|
|
...this.quiz,
|
|
db: [],
|
|
}
|
|
|
|
this.$emit('quiz:removed', {
|
|
entries: this.entries,
|
|
type: this.type,
|
|
db,
|
|
})
|
|
}
|
|
|
|
openInNewTab(component: string, query: Record<string, string>) {
|
|
this.$accessor.ADD_TAB({
|
|
component,
|
|
query,
|
|
})
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.v-context {
|
|
li.separator {
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: center;
|
|
padding-top: 0.5rem;
|
|
padding-bottom: 0.5rem;
|
|
|
|
a {
|
|
background-color: lightgray;
|
|
height: 1px;
|
|
width: calc(100% - 1rem);
|
|
padding: 0;
|
|
}
|
|
}
|
|
}
|
|
</style>
|