mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-08-04 03:27:05 -04:00
224 lines
8.6 KiB
Vue
224 lines
8.6 KiB
Vue
<script setup lang="ts">
|
|
import { useNuxtApp } from 'nuxt/app';
|
|
|
|
import type NameSubmitForm from '~/components/NameSubmitForm.vue';
|
|
import useConfig from '~/composables/useConfig.ts';
|
|
import useDialogue from '~/composables/useDialogue.ts';
|
|
import useHash from '~/composables/useHash.ts';
|
|
import useSimpleHead from '~/composables/useSimpleHead.ts';
|
|
import { Name } from '~/src/classes.ts';
|
|
import { buildDict } from '~/src/helpers.ts';
|
|
|
|
definePageMeta({
|
|
translatedPaths: (config) => translatedPathByConfigModule(config.names),
|
|
});
|
|
|
|
const { $translator: translator } = useNuxtApp();
|
|
useSimpleHead({
|
|
title: translator.translate('names.headerLong'),
|
|
description: translator.translate('names.description'),
|
|
}, translator);
|
|
const { handleHash, setHash } = useHash();
|
|
|
|
const namesAsyncData = useAsyncData(async () => {
|
|
const namesRaw = await $fetch('/api/names');
|
|
|
|
return buildDict(function* () {
|
|
const sorted = namesRaw.sort((a, b) => {
|
|
if (a.approved && !b.approved) {
|
|
return 1;
|
|
}
|
|
if (!a.approved && b.approved) {
|
|
return -1;
|
|
}
|
|
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
|
});
|
|
for (const n of sorted) {
|
|
yield [n.id, new Name(n)];
|
|
}
|
|
});
|
|
}, {
|
|
server: false,
|
|
});
|
|
|
|
const config = useConfig();
|
|
|
|
const filter = ref('');
|
|
watch(filter, () => {
|
|
setHash('', filter.value);
|
|
});
|
|
|
|
const names = computed(() => {
|
|
if (namesAsyncData.status.value !== 'success') {
|
|
return {};
|
|
}
|
|
return namesAsyncData.data.value!;
|
|
});
|
|
|
|
const filterBar = useTemplateRef('filterBar');
|
|
onMounted(async () => {
|
|
handleHash('', (hash) => {
|
|
filter.value = hash;
|
|
if (hash) {
|
|
filterBar.value?.focus();
|
|
}
|
|
});
|
|
});
|
|
|
|
const visibleNames = computed(() => {
|
|
return Object.values(names.value).filter((n) => n.matches(filter.value));
|
|
});
|
|
|
|
const form = useTemplateRef<InstanceType<typeof NameSubmitForm>>('form');
|
|
const edit = (name: Name) => {
|
|
form.value?.edit(name);
|
|
};
|
|
|
|
const dialogue = useDialogue();
|
|
const approve = async (name: Name) => {
|
|
await dialogue.postWithAlertOnError(`/api/names/approve/${name.id}`);
|
|
if (name.base) {
|
|
delete names.value[name.base];
|
|
}
|
|
name.approved = true;
|
|
name.base = null;
|
|
};
|
|
const hide = async (name: Name) => {
|
|
await dialogue.postWithAlertOnError(`/api/names/hide/${name.id}`);
|
|
name.approved = false;
|
|
};
|
|
const remove = async (name: Name) => {
|
|
await dialogue.confirm(translator.translate('crud.removeConfirm'), 'danger');
|
|
|
|
await dialogue.postWithAlertOnError(`/api/names/remove/${name.id}`);
|
|
delete names.value[name.id];
|
|
};
|
|
|
|
// those must be methods, not computed, because when modified, they don't get updated in the view for some reason
|
|
const namesCountApproved = () => {
|
|
return Object.values(names.value).filter((n) => n.approved).length;
|
|
};
|
|
const namesCountPending = () => {
|
|
return Object.values(names.value).filter((n) => !n.approved).length;
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<Page>
|
|
<NotFound v-if="!config.names || !config.names.enabled || !config.names.published && !$isGranted('names')" />
|
|
<div v-else>
|
|
<CommunityNav />
|
|
|
|
<h2>
|
|
<Icon v="signature" />
|
|
<T>names.headerLong</T>
|
|
</h2>
|
|
|
|
<section>
|
|
<T>names.intro</T>
|
|
<NamesLinks />
|
|
</section>
|
|
|
|
<section v-if="$isGranted('names')" class="px-3">
|
|
<div class="alert alert-info">
|
|
<strong>{{ namesCountApproved() }}</strong> <T>nouns.approved</T>,
|
|
<strong>{{ namesCountPending() }}</strong> <T>nouns.pending</T>.
|
|
</div>
|
|
</section>
|
|
|
|
<FilterBar
|
|
ref="filterBar"
|
|
v-model="filter"
|
|
submit-button
|
|
@submit-clicked="form?.focus()"
|
|
/>
|
|
|
|
<section>
|
|
<Loading :value="namesAsyncData.data.value">
|
|
<ul class="list-group small">
|
|
<template v-if="visibleNames.length">
|
|
<li v-for="name in visibleNames" :class="['list-group-item', name.approved ? '' : 'marked']">
|
|
<NamesEntry :name="name">
|
|
<ul class="list-inline small">
|
|
<template v-if="$isGranted('names')">
|
|
<li v-if="name.author" class="list-inline-item small">
|
|
<nuxt-link :to="`/@${name.author}`" class="btn btn-outline-dark btn-sm">
|
|
<Icon v="user" />
|
|
<span class="btn-label">
|
|
<T>crud.author</T><T>quotation.colon</T>
|
|
@{{ name.author }}
|
|
</span>
|
|
</nuxt-link>
|
|
</li>
|
|
<li v-if="!name.approved" class="list-inline-item">
|
|
<button class="btn btn-success btn-sm" @click="approve(name)">
|
|
<Icon v="check" />
|
|
<span class="btn-label"><T>crud.approve</T></span>
|
|
</button>
|
|
</li>
|
|
<li v-else class="list-inline-item" @click="hide(name)">
|
|
<button class="btn btn-outline-secondary btn-sm">
|
|
<Icon v="times" />
|
|
<span class="btn-label"><T>crud.hide</T></span>
|
|
</button>
|
|
</li>
|
|
<li class="list-inline-item">
|
|
<button class="btn btn-outline-danger btn-sm" @click="remove(name)">
|
|
<Icon v="trash" />
|
|
<span class="btn-label"><T>crud.remove</T></span>
|
|
</button>
|
|
</li>
|
|
</template>
|
|
<li class="list-inline-item">
|
|
<button class="btn btn-outline-primary btn-sm" @click="edit(name)">
|
|
<Icon v="pen" />
|
|
<span class="btn-label">
|
|
<T v-if="$isGranted('names')">crud.edit</T>
|
|
<T v-else>nouns.edit</T>
|
|
</span>
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
</NamesEntry>
|
|
|
|
<small v-if="name.base && names[name.base]">
|
|
<hr>
|
|
<p><strong><T>nouns.edited</T><T>quotation.colon</T></strong></p>
|
|
<Diff switchable>
|
|
<template #before><NamesEntry :name="names[name.base]" /></template>
|
|
<template #after><NamesEntry :name="name" /></template>
|
|
</Diff>
|
|
</small>
|
|
</li>
|
|
</template>
|
|
<template v-else>
|
|
<li class="list-group-item text-center">
|
|
<Icon v="search" />
|
|
<T>names.empty</T>
|
|
</li>
|
|
</template>
|
|
</ul>
|
|
</Loading>
|
|
</section>
|
|
|
|
<Separator icon="plus" />
|
|
|
|
<NameSubmitForm ref="form" />
|
|
</div>
|
|
</Page>
|
|
</template>
|
|
|
|
<style lang="scss">
|
|
@import "assets/variables";
|
|
|
|
@include media-breakpoint-up('md') {
|
|
.w-md-50 {
|
|
width: 50%;
|
|
}
|
|
}
|
|
|
|
.list-group-item.marked {
|
|
border-inline-start: 3px solid $primary;
|
|
}
|
|
</style>
|