mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-24 05:05:20 -04:00
(ts) migrate nouns components to composition API
This commit is contained in:
parent
3bfbb19bae
commit
455c59498d
@ -1,50 +1,31 @@
|
||||
<template>
|
||||
<span class="position-relative">
|
||||
<template v-if="declensionTemplate">
|
||||
<a v-if="!open" href="#" :class="tooltip && visible ? 'fw-bold' : ''" @click.prevent="visible = !visible"><Spelling :text="word" /></a>
|
||||
<ul v-if="visible" :class="['list-unstyled', 'small', open ? '' : 'm-2 p-3 pe-5 border bg-light', tooltip ? 'tooltip' : '']">
|
||||
<li v-for="(declined, c) in declensionTemplate.decline(word, plural)" class="text-nowrap">
|
||||
<strong>{{ c }} <small v-if="!condense">({{ cases[c] }})</small></strong> {{ declined.join(' / ') }}
|
||||
</li>
|
||||
<li v-if="tooltip" class="close"><a href="#" @click.prevent="visible = false"><Icon v="times" /></a></li>
|
||||
</ul>
|
||||
</template>
|
||||
<Spelling v-else :text="word" />
|
||||
</span>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import cases from '~/data/nouns/cases.js';
|
||||
import type { NounDeclension } from '~/src/classes.ts';
|
||||
import { nounDeclensionTemplates } from '~/src/data.ts';
|
||||
|
||||
<script>
|
||||
import cases from '../data/nouns/cases.js';
|
||||
import { nounDeclensionTemplates } from '../src/data.ts';
|
||||
const props = withDefaults(defineProps<{
|
||||
word: string;
|
||||
plural?: boolean;
|
||||
singularOptions?: string[];
|
||||
template?: NounDeclension;
|
||||
open?: boolean;
|
||||
condense?: boolean;
|
||||
tooltip?: boolean;
|
||||
}>(), {
|
||||
plural: false,
|
||||
});
|
||||
|
||||
export default {
|
||||
props: {
|
||||
word: { required: true },
|
||||
plural: { type: Boolean },
|
||||
singularOptions: { },
|
||||
template: { },
|
||||
open: { type: Boolean },
|
||||
condense: { type: Boolean },
|
||||
tooltip: { type: Boolean },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
declensionTemplate: this.template || this.findTemplate(),
|
||||
cases,
|
||||
visible: this.open,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
template() {
|
||||
this.declensionTemplate = this.template || this.findTemplate();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
findTemplate() {
|
||||
const visible = ref(props.open);
|
||||
|
||||
const declensionTemplate = computed(() => {
|
||||
return props.template ?? findTemplate();
|
||||
});
|
||||
|
||||
const findTemplate = (): NounDeclension | null => {
|
||||
let longestMatch = 0;
|
||||
let templates = [];
|
||||
let templates: NounDeclension[] = [];
|
||||
for (const t of nounDeclensionTemplates) {
|
||||
const matchLength = t.matches(this.word, this.plural);
|
||||
const matchLength = t.matches(props.word, props.plural);
|
||||
if (matchLength === 0) {
|
||||
continue;
|
||||
}
|
||||
@ -60,9 +41,9 @@ export default {
|
||||
return null;
|
||||
} else if (templates.length === 1) {
|
||||
return templates[0];
|
||||
} else if (this.plural && this.singularOptions) {
|
||||
} else if (props.plural && props.singularOptions) {
|
||||
for (const t of templates) {
|
||||
for (const s of this.singularOptions) {
|
||||
for (const s of props.singularOptions) {
|
||||
if (t.matches(s)) {
|
||||
return t;
|
||||
}
|
||||
@ -71,11 +52,32 @@ export default {
|
||||
}
|
||||
|
||||
return templates[0];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="position-relative">
|
||||
<template v-if="declensionTemplate">
|
||||
<a v-if="!open" href="#" :class="tooltip && visible ? 'fw-bold' : ''" @click.prevent="visible = !visible"><Spelling :text="word" /></a>
|
||||
<ul v-if="visible" :class="['list-unstyled', 'small', open ? '' : 'm-2 p-3 pe-5 border bg-light', tooltip ? 'tooltip' : '']">
|
||||
<li
|
||||
v-for="(declined, caseName) in declensionTemplate.decline(word, plural)"
|
||||
:key="caseName"
|
||||
class="text-nowrap"
|
||||
>
|
||||
<strong>
|
||||
{{ caseName }}
|
||||
<small v-if="!condense">({{ (cases as Record<string, string>)[caseName] }})</small>
|
||||
</strong>
|
||||
{{ declined.join(' / ') }}
|
||||
</li>
|
||||
<li v-if="tooltip" class="close"><a href="#" @click.prevent="visible = false"><Icon v="times" /></a></li>
|
||||
</ul>
|
||||
</template>
|
||||
<Spelling v-else :text="word" />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
ul.tooltip {
|
||||
position: absolute;
|
||||
|
@ -1,7 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import type { genders, MinimalNoun } from '~/src/classes.ts';
|
||||
|
||||
defineProps<{
|
||||
noun: MinimalNoun;
|
||||
gender: typeof genders[number];
|
||||
}>();
|
||||
|
||||
const config = useConfig();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ul class="list-singular">
|
||||
<li v-for="w in noun[gender]">
|
||||
<li v-for="(w, i) in noun[gender]" :key="i">
|
||||
<Abbreviation v-slot="{ word }" :v="w">
|
||||
<Declension
|
||||
v-if="gender === 'neutr' && config.nouns.declension"
|
||||
@ -13,7 +24,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
<ul v-if="config.nouns.plurals" class="list-plural">
|
||||
<li v-for="w in noun[`${gender}Pl`]">
|
||||
<li v-for="(w, i) in noun[`${gender}Pl`]" :key="i">
|
||||
<Abbreviation v-slot="{ word }" :v="w">
|
||||
<Declension
|
||||
v-if="gender === 'neutr' && config.nouns.declension"
|
||||
@ -28,23 +39,3 @@
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
import useConfig from '../composables/useConfig.ts';
|
||||
import type { genders, MinimalNoun } from '../src/classes.ts';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
noun: { required: true, type: Object as PropType<MinimalNoun> },
|
||||
gender: { required: true, type: String as PropType<typeof genders[number]> },
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
config: useConfig(),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -1,37 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import type { genders } from '~/src/classes.ts';
|
||||
|
||||
const props = defineProps<{
|
||||
gender: typeof genders[number];
|
||||
}>();
|
||||
|
||||
const iconName = computed((): string => {
|
||||
const iconNames = {
|
||||
masc: 'mars',
|
||||
fem: 'venus',
|
||||
neutr: 'neuter',
|
||||
};
|
||||
return iconNames[props.gender];
|
||||
});
|
||||
const longIdentifier = computed((): string => {
|
||||
const longIdentifiers = {
|
||||
masc: 'masculine',
|
||||
fem: 'feminine',
|
||||
neutr: 'neuter',
|
||||
};
|
||||
return longIdentifiers[props.gender];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="text-nowrap">
|
||||
<Icon :v="iconName" />
|
||||
<span><T>nouns.{{ longIdentifier }}</T></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
import type { genders } from '../src/classes.ts';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
gender: { required: true, type: String as PropType<typeof genders[number]> },
|
||||
},
|
||||
computed: {
|
||||
iconName(): string {
|
||||
const iconNames = {
|
||||
masc: 'mars',
|
||||
fem: 'venus',
|
||||
neutr: 'neuter',
|
||||
};
|
||||
return iconNames[this.gender];
|
||||
},
|
||||
longIdentifier(): string {
|
||||
const longIdentifiers = {
|
||||
masc: 'masculine',
|
||||
fem: 'feminine',
|
||||
neutr: 'neuter',
|
||||
};
|
||||
return longIdentifiers[this.gender];
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -1,3 +1,91 @@
|
||||
<script setup lang="ts">
|
||||
import type { Config } from '~/locale/config.ts';
|
||||
import { genders } from '~/src/classes.ts';
|
||||
import type { Noun, MinimalNoun } from '~/src/classes.ts';
|
||||
import { abbreviations } from '~/src/data.ts';
|
||||
|
||||
const emptyForm = (config: Config): MinimalNoun => {
|
||||
return {
|
||||
masc: [''],
|
||||
fem: [''],
|
||||
neutr: [''],
|
||||
mascPl: config.nouns.pluralsRequired ? [''] : [],
|
||||
femPl: config.nouns.pluralsRequired ? [''] : [],
|
||||
neutrPl: config.nouns.pluralsRequired ? [''] : [],
|
||||
categories: [],
|
||||
sources: [],
|
||||
base: null,
|
||||
};
|
||||
};
|
||||
|
||||
const emit = defineEmits<{
|
||||
submit: [];
|
||||
}>();
|
||||
|
||||
const { $translator: translator } = useNuxtApp();
|
||||
const config = useConfig();
|
||||
|
||||
const section = useTemplateRef<HTMLElement>('section');
|
||||
const templateFilterInput = useTemplateRef<HTMLInputElement>('templateFilterInput');
|
||||
|
||||
const form = ref(emptyForm(config));
|
||||
|
||||
const submitting = ref(false);
|
||||
const afterSubmit = ref(false);
|
||||
|
||||
const templateBase = ref('');
|
||||
const templateFilter = ref('');
|
||||
const templateVisible = ref(false);
|
||||
|
||||
const dialogue = useDialogue();
|
||||
const applyTemplate = async (template: MinimalNoun): Promise<void> => {
|
||||
if (JSON.stringify(form.value) !== JSON.stringify(emptyForm(config))) {
|
||||
await dialogue.confirm(translator.translate('nouns.template.overwrite'));
|
||||
}
|
||||
form.value = template;
|
||||
templateVisible.value = false;
|
||||
await nextTick();
|
||||
section.value?.scrollIntoView();
|
||||
};
|
||||
const submit = async () => {
|
||||
submitting.value = true;
|
||||
try {
|
||||
await dialogue.postWithAlertOnError('/api/nouns/submit', form.value);
|
||||
|
||||
afterSubmit.value = true;
|
||||
form.value = emptyForm(config);
|
||||
templateVisible.value = false;
|
||||
templateBase.value = '';
|
||||
focus(false);
|
||||
emit('submit');
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
const edit = (word: Noun): void => {
|
||||
form.value = {
|
||||
masc: word.masc,
|
||||
fem: word.fem,
|
||||
neutr: word.neutr,
|
||||
mascPl: word.mascPl,
|
||||
femPl: word.femPl,
|
||||
neutrPl: word.neutrPl,
|
||||
categories: word.categories,
|
||||
sources: word.sources,
|
||||
base: word.id,
|
||||
};
|
||||
focus();
|
||||
};
|
||||
const focus = (editable = true): void => {
|
||||
if (editable) {
|
||||
afterSubmit.value = false;
|
||||
}
|
||||
section.value?.scrollIntoView();
|
||||
};
|
||||
|
||||
defineExpose({ edit, focus });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section v-if="config.nouns.enabled && $user()" ref="section" class="scroll-mt-7">
|
||||
<div v-if="afterSubmit" class="alert alert-success text-center">
|
||||
@ -81,7 +169,7 @@
|
||||
<Icon v="filter" />
|
||||
</span>
|
||||
<input
|
||||
ref="templateFilter"
|
||||
ref="templateFilterInput"
|
||||
v-model="templateFilter"
|
||||
class="form-control form-control-sm border-primary"
|
||||
:placeholder="$t('crud.filterLong')"
|
||||
@ -89,7 +177,7 @@
|
||||
<button
|
||||
v-if="templateFilter"
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
@click="templateFilter = ''; $tRefs.templateFilter?.focus()"
|
||||
@click="templateFilter = ''; templateFilterInput?.focus()"
|
||||
>
|
||||
<Icon v="times" />
|
||||
</button>
|
||||
@ -139,109 +227,3 @@
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
|
||||
import useConfig from '../composables/useConfig.ts';
|
||||
import useDialogue from '../composables/useDialogue.ts';
|
||||
import type { Config } from '../locale/config.ts';
|
||||
import { genders } from '../src/classes.ts';
|
||||
import type { Noun, MinimalNoun } from '../src/classes.ts';
|
||||
import { abbreviations } from '../src/data.ts';
|
||||
|
||||
interface Refs {
|
||||
templateFilter: HTMLInputElement | undefined;
|
||||
}
|
||||
|
||||
const emptyForm = (config: Config): MinimalNoun => {
|
||||
return {
|
||||
masc: [''],
|
||||
fem: [''],
|
||||
neutr: [''],
|
||||
mascPl: config.nouns.pluralsRequired ? [''] : [],
|
||||
femPl: config.nouns.pluralsRequired ? [''] : [],
|
||||
neutrPl: config.nouns.pluralsRequired ? [''] : [],
|
||||
categories: [],
|
||||
sources: [],
|
||||
base: null,
|
||||
};
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
emits: ['submit'],
|
||||
setup() {
|
||||
const config = useConfig();
|
||||
const section = useTemplateRef<HTMLElement>('section');
|
||||
return {
|
||||
config,
|
||||
dialogue: useDialogue(),
|
||||
section,
|
||||
form: ref(emptyForm(config)),
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
submitting: false,
|
||||
afterSubmit: false,
|
||||
templateBase: '',
|
||||
templateFilter: '',
|
||||
templateVisible: false,
|
||||
abbreviations,
|
||||
genders,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
$tRefs(): Refs {
|
||||
return this.$refs as unknown as Refs;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async applyTemplate(template: MinimalNoun): Promise<void> {
|
||||
if (JSON.stringify(this.form) !== JSON.stringify(emptyForm(this.config))) {
|
||||
await this.dialogue.confirm(this.$t('nouns.template.overwrite'));
|
||||
}
|
||||
this.form = template;
|
||||
this.templateVisible = false;
|
||||
this.$nextTick(() => {
|
||||
this.$el.scrollIntoView();
|
||||
});
|
||||
},
|
||||
async submit() {
|
||||
this.submitting = true;
|
||||
try {
|
||||
await this.dialogue.postWithAlertOnError('/api/nouns/submit', this.form);
|
||||
|
||||
this.afterSubmit = true;
|
||||
this.form = emptyForm(this.config);
|
||||
this.templateVisible = false;
|
||||
this.templateBase = '';
|
||||
this.focus(false);
|
||||
this.$emit('submit');
|
||||
} finally {
|
||||
this.submitting = false;
|
||||
}
|
||||
},
|
||||
edit(word: Noun): void {
|
||||
this.form = {
|
||||
masc: word.masc,
|
||||
fem: word.fem,
|
||||
neutr: word.neutr,
|
||||
mascPl: word.mascPl,
|
||||
femPl: word.femPl,
|
||||
neutrPl: word.neutrPl,
|
||||
categories: word.categories,
|
||||
sources: word.sources,
|
||||
base: word.id,
|
||||
};
|
||||
this.focus();
|
||||
},
|
||||
focus(editable = true): void {
|
||||
if (editable) {
|
||||
this.afterSubmit = false;
|
||||
}
|
||||
this.section?.scrollIntoView();
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -1084,7 +1084,7 @@ export class NounDeclension {
|
||||
}
|
||||
}
|
||||
|
||||
matches(word: string, plural: boolean): number {
|
||||
matches(word: string, plural?: boolean): number {
|
||||
const plurality = plural ? 'plural' : 'singular';
|
||||
const rep = Object.keys(this[plurality])[0];
|
||||
for (const ending of this[plurality][rep] || []) {
|
||||
@ -1109,15 +1109,8 @@ export class NounDeclension {
|
||||
const options = this[plurality];
|
||||
|
||||
return buildDict(function*() {
|
||||
for (const k in options) {
|
||||
if (!options.hasOwnProperty(k)) {
|
||||
continue;
|
||||
}
|
||||
yield [
|
||||
k,
|
||||
// TODO: Check whether it is sensible to include a guard clause
|
||||
options[k]!.map((o) => base + o),
|
||||
];
|
||||
for (const [caseName, caseSuffixes] of Object.entries(options)) {
|
||||
yield [caseName, caseSuffixes?.map((caseSuffix) => base + caseSuffix) ?? []];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user