mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-08-05 03:57:03 -04:00
249 lines
9.7 KiB
Vue
249 lines
9.7 KiB
Vue
<script setup lang="ts">
|
||
import type SourceSubmitForm from '~/components/SourceSubmitForm.vue';
|
||
import { Source, SourceLibrary } from '~/src/classes.ts';
|
||
import { loadPronounLibrary } from '~/src/data.ts';
|
||
import { changeSourceInjectionKey } from '~/src/injectionKeys.ts';
|
||
|
||
definePageMeta({
|
||
translatedPaths: (config) => translatedPathByConfigModule(config.sources),
|
||
});
|
||
|
||
const { $translator: translator } = useNuxtApp();
|
||
const config = useConfig();
|
||
|
||
const pronounLibrary = await loadPronounLibrary(config);
|
||
const pronouns = pronounLibrary.pronouns;
|
||
|
||
useSimpleHead({
|
||
title: translator.translate('sources.headerLonger'),
|
||
description: translator.translate('sources.subheader'),
|
||
}, translator);
|
||
|
||
const filter = useFilterWithCategory();
|
||
|
||
const { data: rawSources } = await useFetch('/api/sources', { lazy: true });
|
||
const sourceLibrary = computed(() => {
|
||
if (rawSources.value === null) {
|
||
return undefined;
|
||
}
|
||
return new SourceLibrary(config, rawSources.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 dialogue = useDialogue();
|
||
|
||
provide(changeSourceInjectionKey, {
|
||
async approve(source: Source) {
|
||
await dialogue.postWithAlertOnError(`/api/sources/approve/${source.id}`);
|
||
rawSources.value = (rawSources.value ?? []).map((sourceRaw) => {
|
||
if (sourceRaw.id !== source.id) {
|
||
return sourceRaw;
|
||
}
|
||
return { ...sourceRaw, approved: true, base_id: null };
|
||
});
|
||
},
|
||
async hide(source: Source) {
|
||
await dialogue.postWithAlertOnError(`/api/sources/hide/${source.id}`);
|
||
rawSources.value = (rawSources.value ?? []).map((sourceRaw) => {
|
||
if (sourceRaw.id !== source.id) {
|
||
return sourceRaw;
|
||
}
|
||
return { ...sourceRaw, approved: false };
|
||
});
|
||
},
|
||
async remove(source: Source) {
|
||
await dialogue.confirm(translator.translate('crud.removeConfirm'), 'danger');
|
||
|
||
await dialogue.postWithAlertOnError(`/api/sources/remove/${source.id}`);
|
||
rawSources.value = (rawSources.value ?? []).filter((sourceRaw) => sourceRaw.id !== source.id);
|
||
},
|
||
edit(source: Source) {
|
||
submitShown.value = true;
|
||
nextTick(() => {
|
||
form.value?.edit(source);
|
||
});
|
||
},
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<Page v-if="config.sources.enabled">
|
||
<h2>
|
||
<Icon v="books" />
|
||
<T>sources.headerLonger</T>
|
||
</h2>
|
||
|
||
<p v-if="$t('sources.subheader')">
|
||
<em><T>sources.subheader</T></em>
|
||
</p>
|
||
|
||
<section>
|
||
<Share :title="$t('sources.headerLonger')" />
|
||
</section>
|
||
|
||
<AdPlaceholder :phkey="['content-0', 'content-mobile-0']" />
|
||
|
||
<SourcesChart :sources="rawSources" label="all pronouns" />
|
||
|
||
<Loading :value="sourceLibrary" size="5rem">
|
||
<template v-if="sourceLibrary !== undefined">
|
||
<section v-show="config.sources.submit">
|
||
<SourceSubmitForm v-show="submitShown" ref="form" />
|
||
<button
|
||
v-show="!submitShown"
|
||
type="button"
|
||
class="btn btn-success w-100"
|
||
@click="submitShown = true"
|
||
>
|
||
<Icon v="plus-circle" />
|
||
<T>sources.submit.header</T>
|
||
</button>
|
||
</section>
|
||
|
||
<section>
|
||
<button
|
||
v-if="!tocShown"
|
||
type="button"
|
||
class="btn btn-outline-primary w-100"
|
||
@click="tocShown = true"
|
||
>
|
||
<Icon v="list" />
|
||
<T>sources.toc</T>
|
||
</button>
|
||
<ul v-if="tocShown" class="list-group">
|
||
<li v-for="[group, groupPronouns] in tocPronounGroups" class="list-group-item">
|
||
<p class="h5">
|
||
{{ group.name }}
|
||
</p>
|
||
<div v-if="group.description" class="small my-1">
|
||
<Icon v="info-circle" />
|
||
<em v-html="group.description"></em>
|
||
</div>
|
||
<ul class="list-unstyled">
|
||
<template v-for="pronoun in groupPronouns">
|
||
<li
|
||
v-if="config.sources.mergePronouns?.[pronoun.canonicalName] === undefined"
|
||
:key="pronoun.canonicalName"
|
||
>
|
||
<a :href="`#${toId(pronoun.name(glue))}`">
|
||
<strong>{{ pronoun.name(glue) }}</strong>
|
||
–
|
||
<small>{{ pronoun.description }}</small>
|
||
</a>
|
||
<NormativeBadge v-if="pronoun.normative" />
|
||
</li>
|
||
</template>
|
||
</ul>
|
||
</li>
|
||
<li v-if="sourceLibrary.multiple.length" class="list-group-item">
|
||
<p class="h5 mb-0">
|
||
<a :href="`#${$t('pronouns.alt.raw')}`">
|
||
<strong><T>pronouns.alt.header</T></strong>
|
||
</a>
|
||
</p>
|
||
</li>
|
||
<li v-if="sourceLibrary.getForPronoun('', pronounLibrary)" class="list-group-item">
|
||
<p class="h5 mb-0">
|
||
<a :href="`#${$t('pronouns.othersRaw')}`">
|
||
<strong><T>pronouns.others</T></strong>
|
||
</a>
|
||
</p>
|
||
</li>
|
||
</ul>
|
||
</section>
|
||
|
||
<ModerationSection
|
||
v-model="filter.moderation"
|
||
kind="sources"
|
||
:moderation-filters="['unapproved', 'no key']"
|
||
:entries="sourceLibrary.sources"
|
||
/>
|
||
|
||
<ModerationRules type="rulesSources" emphasise />
|
||
|
||
<FilterBar
|
||
v-model="filter.text"
|
||
v-model:category="filter.category"
|
||
:categories="categories"
|
||
/>
|
||
|
||
<template v-for="pronoun in pronouns">
|
||
<section v-if="config.sources.mergePronouns?.[pronoun.canonicalName] === undefined && sourceLibrary.getForPronoun(pronoun.canonicalName).length">
|
||
<SourceList
|
||
:sources="sourceLibrary.getForPronoun(pronoun.canonicalName)"
|
||
:pronoun="pronoun"
|
||
:filter="filter"
|
||
>
|
||
<h2 :id="toId(pronoun.name(glue))" class="h4">
|
||
<nuxt-link :to="`/${pronoun.canonicalName}`">
|
||
{{ pronoun.description }}
|
||
<small>({{ pronoun.name(glue) }})</small>
|
||
</nuxt-link>
|
||
</h2>
|
||
</SourceList>
|
||
</section>
|
||
</template>
|
||
|
||
<a :id="$t('pronouns.alt.raw')"></a>
|
||
<template v-for="multiple in sourceLibrary.multiple">
|
||
<section v-if="sourceLibrary.getForPronoun(multiple).length">
|
||
<SourceList
|
||
:sources="sourceLibrary.getForPronoun(multiple)"
|
||
:filter="filter"
|
||
>
|
||
<h2 :id="toId(multiple)" class="h4">
|
||
<nuxt-link :to="`/${multiple}`">
|
||
<T>pronouns.alt.header</T>
|
||
<small>({{ multiple.replace(/&/g, glue) }})</small>
|
||
</nuxt-link>
|
||
</h2>
|
||
</SourceList>
|
||
</section>
|
||
</template>
|
||
|
||
<section v-if="sourceLibrary.getForPronoun('', pronounLibrary)">
|
||
<SourceList
|
||
:sources="sourceLibrary.getForPronoun('', pronounLibrary)"
|
||
:filter="filter"
|
||
>
|
||
<h2 :id="$t('pronouns.othersRaw')" class="h4">
|
||
<T>pronouns.others</T>
|
||
</h2>
|
||
</SourceList>
|
||
</section>
|
||
</template>
|
||
</Loading>
|
||
|
||
<AdPlaceholder :phkey="['content-1', 'content-mobile-1']" />
|
||
</Page>
|
||
</template>
|