mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-17 11:35:33 -04:00
(ts) migrate sources components to composition API with typescript
This commit is contained in:
parent
50a9c7afbe
commit
0b602028ee
@ -62,7 +62,7 @@
|
||||
<p><strong><T>sources.referenced</T><T>quotation.colon</T></strong></p>
|
||||
<ul class="list-unstyled">
|
||||
<li v-for="source in s.el.sourcesData">
|
||||
<Source :source="source" />
|
||||
<SourceItem :source="source" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -1,3 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { getPronoun } from '~/src/buildPronoun.ts';
|
||||
import type { Pronoun, Source } from '~/src/classes.ts';
|
||||
import { pronouns } from '~/src/data.ts';
|
||||
|
||||
const props = defineProps<{
|
||||
pronoun?: Pronoun;
|
||||
sources: Record<string, Source[] | undefined>;
|
||||
}>();
|
||||
|
||||
const { $translator: translator } = useNuxtApp();
|
||||
|
||||
const glue = ` ${translator.translate('pronouns.or')} `;
|
||||
|
||||
const visibleSources = computed((): Record<string, Source[]> => {
|
||||
return Object.fromEntries(
|
||||
Object.entries(props.sources).filter(([_, sources]) => sources),
|
||||
) as Record<string, Source[]>;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="Object.keys(visibleSources).length">
|
||||
<h2 class="h4">
|
||||
@ -25,33 +46,3 @@
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
import { getPronoun } from '../src/buildPronoun.ts';
|
||||
import type { Source } from '../src/classes.ts';
|
||||
import { pronouns } from '../src/data.ts';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
pronoun: { },
|
||||
sources: { required: true, type: Object as PropType<Record<string, Source[] | undefined>> },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pronouns,
|
||||
getPronoun,
|
||||
glue: ` ${this.$t('pronouns.or')} `,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
visibleSources(): Record<string, Source[]> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(this.sources).filter(([_, sources]) => sources),
|
||||
) as Record<string, Source[]>;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -1,3 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import { LazyHydrationWrapper } from 'vue3-lazy-hydration';
|
||||
|
||||
import type { Source } from '~/src/classes.ts';
|
||||
import { pronounLibrary } from '~/src/data.ts';
|
||||
|
||||
const props = defineProps<{
|
||||
source: Source;
|
||||
manage?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
'edit-source': [Source];
|
||||
}>();
|
||||
|
||||
const { $translator: translator } = useNuxtApp();
|
||||
|
||||
const config = useConfig();
|
||||
const dialogue = useDialogue();
|
||||
|
||||
const deleted = ref(false);
|
||||
const versionsShown = ref(false);
|
||||
const showSpoiler = ref(false);
|
||||
|
||||
const approve = async () => {
|
||||
await dialogue.postWithAlertOnError(`/api/sources/approve/${props.source.id}`);
|
||||
props.source.approved = true;
|
||||
props.source.base_id = null;
|
||||
};
|
||||
const hide = async () => {
|
||||
await dialogue.postWithAlertOnError(`/api/sources/hide/${props.source.id}`);
|
||||
props.source.approved = false;
|
||||
};
|
||||
const remove = async () => {
|
||||
await dialogue.confirm(translator.translate('crud.removeConfirm'), 'danger');
|
||||
|
||||
await dialogue.postWithAlertOnError(`/api/sources/remove/${props.source.id}`);
|
||||
deleted.value = true;
|
||||
};
|
||||
|
||||
const addMarks = (t: string) => {
|
||||
return t.replace(/\[\[/g, '<mark>').replace(/]]/g, '</mark>');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LazyHydrationWrapper when-visible>
|
||||
<div v-if="!deleted" class="my-2 clearfix">
|
||||
@ -44,7 +89,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<a href="#" class="badge bg-light text-dark border border-primary btn-sm m-1" @click.prevent="$emit('edit-source', source)">
|
||||
<a href="#" class="badge bg-light text-dark border border-primary btn-sm m-1" @click.prevent="emit('edit-source', source)">
|
||||
<Icon v="pen" />
|
||||
<span class="btn-label">
|
||||
<T>crud.edit</T>
|
||||
@ -101,7 +146,7 @@
|
||||
>{{ $locales[version.locale].name }}</a>:
|
||||
</strong>
|
||||
</h4>
|
||||
<Source :source="version" />
|
||||
<SourceItem :source="version" />
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
@ -110,60 +155,6 @@
|
||||
</LazyHydrationWrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { LazyHydrationWrapper } from 'vue3-lazy-hydration';
|
||||
|
||||
import useConfig from '../composables/useConfig.ts';
|
||||
import useDialogue from '../composables/useDialogue.ts';
|
||||
import { pronounLibrary } from '../src/data.ts';
|
||||
|
||||
export default {
|
||||
components: { LazyHydrationWrapper },
|
||||
props: {
|
||||
source: { required: true },
|
||||
manage: { type: Boolean },
|
||||
},
|
||||
emits: ['edit-source'],
|
||||
setup() {
|
||||
return {
|
||||
config: useConfig(),
|
||||
dialogue: useDialogue(),
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pronounLibrary,
|
||||
deleted: false,
|
||||
versionsShown: false,
|
||||
showSpoiler: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async approve() {
|
||||
await this.dialogue.postWithAlertOnError(`/api/sources/approve/${this.source.id}`);
|
||||
this.source.approved = true;
|
||||
this.source.base = null;
|
||||
this.$forceUpdate();
|
||||
},
|
||||
async hide() {
|
||||
await this.dialogue.postWithAlertOnError(`/api/sources/hide/${this.source.id}`);
|
||||
this.source.approved = false;
|
||||
this.$forceUpdate();
|
||||
},
|
||||
async remove() {
|
||||
await this.dialogue.confirm(this.$t('crud.removeConfirm'), 'danger');
|
||||
|
||||
await this.dialogue.postWithAlertOnError(`/api/sources/remove/${this.source.id}`);
|
||||
this.deleted = true;
|
||||
this.$forceUpdate();
|
||||
},
|
||||
addMarks(t) {
|
||||
return t.replace(/\[\[/g, '<mark>').replace(/]]/g, '</mark>');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "assets/variables";
|
||||
|
@ -1,3 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import type { Source, Filter, Pronoun } from '~/src/classes.ts';
|
||||
import { makeId } from '~/src/helpers.ts';
|
||||
|
||||
const props = defineProps<{
|
||||
sources: Source[];
|
||||
pronoun?: Pronoun;
|
||||
filter?: Filter;
|
||||
manage?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits< {
|
||||
'edit-source': [Source];
|
||||
}>();
|
||||
|
||||
const sourcesUnique = computed(() => {
|
||||
const sourcesMap: Record<string, Source> = {};
|
||||
for (const source of props.sources) {
|
||||
sourcesMap[source.id] = source;
|
||||
}
|
||||
return Object.values(sourcesMap);
|
||||
});
|
||||
|
||||
const edit = (source: Source) => {
|
||||
// TODO it should be possible to do it nicer
|
||||
emit('edit-source', source);
|
||||
};
|
||||
|
||||
const visibleSources = computed(() => {
|
||||
return sourcesUnique.value.filter((source) => !props.filter || source.matches(props.filter));
|
||||
});
|
||||
|
||||
const notEmpty = computed(() => {
|
||||
return visibleSources.value.length > 0;
|
||||
});
|
||||
|
||||
const listId = makeId(6);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="notEmpty">
|
||||
<slot></slot>
|
||||
@ -8,59 +47,8 @@
|
||||
<SourcesChart :sources="sources" :label="pronoun ? pronoun.name() : ''" />
|
||||
<ul class="list-unstyled">
|
||||
<li v-for="source in visibleSources" :key="`${source.id}-${listId}`" class="my-2 clearfix">
|
||||
<Source :source="source" :manage="manage" @edit-source="edit" />
|
||||
<SourceItem :source="source" :manage="manage" @edit-source="edit" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { makeId } from '../src/helpers.ts';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
sources: { required: true },
|
||||
pronoun: { },
|
||||
filter: { default: '' },
|
||||
filterType: { default: '' },
|
||||
manage: { type: Boolean },
|
||||
},
|
||||
emits: ['edit-source'],
|
||||
data() {
|
||||
const sourcesMap = {};
|
||||
for (const source of this.sources) {
|
||||
sourcesMap[source.id] = source;
|
||||
}
|
||||
|
||||
return {
|
||||
listId: makeId(6),
|
||||
sourcesUnique: Object.values(sourcesMap),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
visibleSources() {
|
||||
return this.sourcesUnique.filter(this.isVisible);
|
||||
},
|
||||
notEmpty() {
|
||||
return this.visibleSources.length > 0;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isVisible(source) {
|
||||
if (this.filterType && this.filterType !== source.type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.filter) {
|
||||
return source.index.includes(this.filter.toLowerCase());
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
edit(source) {
|
||||
// TODO it should be possible to do it nicer
|
||||
this.$emit('edit-source', source);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -1,3 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import type { SourceRaw, Source } from '~/src/classes.ts';
|
||||
|
||||
const props = defineProps<{
|
||||
sources: (SourceRaw | Source)[] | null;
|
||||
label: string;
|
||||
}>();
|
||||
|
||||
const open = ref(false);
|
||||
|
||||
const publishDates = computed(() => {
|
||||
if (props.sources === null) {
|
||||
return null;
|
||||
}
|
||||
const dates: Record<number, number> = {};
|
||||
let count = 0;
|
||||
let min, max;
|
||||
for (const source of props.sources) {
|
||||
if (source.year) {
|
||||
if (dates[source.year] === undefined) {
|
||||
dates[source.year] = 0;
|
||||
}
|
||||
dates[source.year]++;
|
||||
count++;
|
||||
if (min === undefined || source.year < min) {
|
||||
min = source.year;
|
||||
}
|
||||
if (max === undefined || source.year > max) {
|
||||
max = source.year;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Object.keys(dates).length < 2 || count < 5 || min === undefined || max === undefined) {
|
||||
return null;
|
||||
}
|
||||
for (let i = min + 1; i < max; i++) {
|
||||
if (dates[i] === undefined) {
|
||||
dates[i] = 0;
|
||||
}
|
||||
}
|
||||
return dates;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="publishDates !== null && $isGranted('sources')" class="card">
|
||||
<a class="card-header cursor-pointer" href="#" @click.prevent="open = !open">
|
||||
@ -14,51 +58,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
sources: { required: true },
|
||||
label: { required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
open: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
publishDates() {
|
||||
if (this.sources === null) {
|
||||
return null;
|
||||
}
|
||||
const dates = {};
|
||||
let count = 0;
|
||||
let min, max;
|
||||
for (const source of this.sources) {
|
||||
if (source.year) {
|
||||
if (dates[source.year] === undefined) {
|
||||
dates[source.year] = 0;
|
||||
}
|
||||
dates[source.year]++;
|
||||
count++;
|
||||
if (min === undefined || source.year < min) {
|
||||
min = source.year;
|
||||
}
|
||||
if (max === undefined || source.year > max) {
|
||||
max = source.year;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Object.keys(dates).length < 2 || count < 5) {
|
||||
return null;
|
||||
}
|
||||
for (let i = min + 1; i < max; i++) {
|
||||
if (dates[i] === undefined) {
|
||||
dates[i] = 0;
|
||||
}
|
||||
}
|
||||
return dates;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -1,3 +1,68 @@
|
||||
<script setup lang="ts">
|
||||
import type SourceSubmitForm from '~/components/SourceSubmitForm.vue';
|
||||
import { Source, SourceLibrary } from '~/src/classes.ts';
|
||||
import type { SourceRaw } from '~/src/classes.ts';
|
||||
import { pronouns, pronounLibrary } from '~/src/data.ts';
|
||||
|
||||
definePageMeta({
|
||||
translatedPaths: (config) => translatedPathByConfigModule(config.sources),
|
||||
});
|
||||
|
||||
const { $translator: translator } = useNuxtApp();
|
||||
const config = useConfig();
|
||||
useSimpleHead({
|
||||
title: translator.translate('sources.headerLonger'),
|
||||
description: translator.translate('sources.subheader'),
|
||||
}, translator);
|
||||
|
||||
const filter = useFilterWithCategory();
|
||||
|
||||
const { data: sources } = await useFetch<SourceRaw[]>('/api/sources', { lazy: true });
|
||||
const sourceLibrary = computed(() => {
|
||||
if (sources.value === null) {
|
||||
return undefined;
|
||||
}
|
||||
return new SourceLibrary(config, sources.value);
|
||||
});
|
||||
|
||||
const tocShown = ref(false);
|
||||
const glue = ` ${translator.translate('pronouns.or')} `;
|
||||
const submitShown = ref(false);
|
||||
|
||||
const tocPronounGroups = computed(() => {
|
||||
const pronounGroups = pronounLibrary.split((pronoun) => {
|
||||
if (sourceLibrary.value === undefined) {
|
||||
return false;
|
||||
}
|
||||
return sourceLibrary.value.getForPronoun(pronoun.canonicalName).length > 0;
|
||||
}, false);
|
||||
return [...pronounGroups].filter(([_, groupPronouns]) => groupPronouns.length > 0);
|
||||
});
|
||||
|
||||
const categories = computed(() => {
|
||||
return Object.entries(Source.TYPES)
|
||||
.filter(([type]) => type)
|
||||
.map(([type, icon]) => ({
|
||||
key: type,
|
||||
text: translator.translate(`sources.type.${type}`),
|
||||
icon,
|
||||
}));
|
||||
});
|
||||
|
||||
const toId = (str: string): string => {
|
||||
return str.replace(/\//g, '-').replace(/&/g, '_');
|
||||
};
|
||||
|
||||
const form = useTemplateRef<InstanceType<typeof SourceSubmitForm>>('form');
|
||||
|
||||
const edit = (source: Source) => {
|
||||
submitShown.value = true;
|
||||
nextTick(() => {
|
||||
form.value?.edit(source);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page v-if="config.sources.enabled">
|
||||
<h2>
|
||||
@ -96,8 +161,7 @@
|
||||
<SourceList
|
||||
:sources="sourceLibrary.getForPronoun(pronoun.canonicalName)"
|
||||
:pronoun="pronoun"
|
||||
:filter="filter.text"
|
||||
:filter-type="filter.category"
|
||||
:filter="filter"
|
||||
manage
|
||||
@edit-source="edit"
|
||||
>
|
||||
@ -116,8 +180,7 @@
|
||||
<section v-if="sourceLibrary.getForPronoun(multiple).length">
|
||||
<SourceList
|
||||
:sources="sourceLibrary.getForPronoun(multiple)"
|
||||
:filter="filter.text"
|
||||
:filter-type="filter.category"
|
||||
:filter="filter"
|
||||
manage
|
||||
@edit-source="edit"
|
||||
>
|
||||
@ -134,8 +197,7 @@
|
||||
<section v-if="sourceLibrary.getForPronoun('', pronounLibrary)">
|
||||
<SourceList
|
||||
:sources="sourceLibrary.getForPronoun('', pronounLibrary)"
|
||||
:filter="filter.text"
|
||||
:filter-type="filter.category"
|
||||
:filter="filter"
|
||||
manage
|
||||
@edit-source="edit"
|
||||
>
|
||||
@ -150,95 +212,3 @@
|
||||
<AdPlaceholder :phkey="['content-1', 'content-mobile-1']" />
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { useNuxtApp, useFetch } from 'nuxt/app';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
|
||||
import type SourceSubmitForm from '../components/SourceSubmitForm.vue';
|
||||
import useConfig from '../composables/useConfig.ts';
|
||||
import useSimpleHead from '../composables/useSimpleHead.ts';
|
||||
import { Source, SourceLibrary } from '../src/classes.ts';
|
||||
import type { SourceRaw } from '../src/classes.ts';
|
||||
import { pronouns, pronounLibrary } from '../src/data.ts';
|
||||
|
||||
interface Refs {
|
||||
form: InstanceType<typeof SourceSubmitForm> | undefined;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
async setup() {
|
||||
definePageMeta({
|
||||
translatedPaths: (config) => translatedPathByConfigModule(config.sources),
|
||||
});
|
||||
|
||||
const { $translator: translator } = useNuxtApp();
|
||||
const config = useConfig();
|
||||
useSimpleHead({
|
||||
title: translator.translate('sources.headerLonger'),
|
||||
description: translator.translate('sources.subheader'),
|
||||
}, translator);
|
||||
|
||||
const filter = useFilterWithCategory();
|
||||
|
||||
const { data: sources } = await useFetch<SourceRaw[]>('/api/sources', { lazy: true });
|
||||
const sourceLibrary = computed(() => {
|
||||
if (sources.value === null) {
|
||||
return undefined;
|
||||
}
|
||||
return new SourceLibrary(config, sources.value);
|
||||
});
|
||||
|
||||
return {
|
||||
config,
|
||||
filter,
|
||||
sources,
|
||||
sourceLibrary,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pronouns,
|
||||
pronounLibrary,
|
||||
tocShown: false,
|
||||
sourceTypes: Source.TYPES,
|
||||
glue: ` ${this.$t('pronouns.or')} `,
|
||||
submitShown: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
$tRefs(): Refs {
|
||||
return this.$refs as unknown as Refs;
|
||||
},
|
||||
tocPronounGroups() {
|
||||
const pronounGroups = this.pronounLibrary.split((pronoun) => {
|
||||
if (this.sourceLibrary === undefined) {
|
||||
return false;
|
||||
}
|
||||
return this.sourceLibrary.getForPronoun(pronoun.canonicalName).length > 0;
|
||||
}, false);
|
||||
return [...pronounGroups].filter(([_, groupPronouns]) => groupPronouns.length > 0);
|
||||
},
|
||||
categories() {
|
||||
return Object.entries(Source.TYPES)
|
||||
.filter(([type]) => type)
|
||||
.map(([type, icon]) => ({
|
||||
key: type,
|
||||
text: this.$t(`sources.type.${type}`),
|
||||
icon,
|
||||
}));
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toId(str: string): string {
|
||||
return str.replace(/\//g, '-').replace(/&/g, '_');
|
||||
},
|
||||
edit(source: Source) {
|
||||
this.submitShown = true;
|
||||
this.$nextTick(() => {
|
||||
this.$tRefs.form?.edit(source);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -273,6 +273,11 @@ export class Source {
|
||||
icon(): string {
|
||||
return Source.TYPES[this.type];
|
||||
}
|
||||
|
||||
matches(filter: Filter) {
|
||||
return (!filter.text || !!this.index?.includes(filter.text.toLowerCase())) &&
|
||||
(!filter.category || this.type === filter.category);
|
||||
}
|
||||
}
|
||||
|
||||
export class SourceLibrary {
|
||||
|
Loading…
x
Reference in New Issue
Block a user