mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-24 05:05:20 -04:00
249 lines
10 KiB
Vue
249 lines
10 KiB
Vue
<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>
|
|
|
|
<section class="sticky-top">
|
|
<div class="input-group mb-3 bg-white">
|
|
<span class="input-group-text">
|
|
<Icon v="filter" />
|
|
</span>
|
|
<input ref="filter" v-model="filter" class="form-control border-primary" :placeholder="$t('crud.filterLong')">
|
|
<button v-if="filter" class="btn btn-outline-danger" @click="filter = ''; $refs.filter.focus()">
|
|
<Icon v="times" />
|
|
</button>
|
|
<button class="btn btn-success" @click="$refs.form.$el.scrollIntoView({ block: 'center' })">
|
|
<Icon v="plus-circle" />
|
|
<T>nouns.submit.action</T>
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<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']">
|
|
<Name :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>
|
|
</Name>
|
|
|
|
<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><Name :name="names[name.base]" /></template>
|
|
<template #after><Name :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" />
|
|
|
|
<section class="px-3">
|
|
<NameSubmitForm ref="form" style="scroll-padding-top: 2rem;" />
|
|
</section>
|
|
</div>
|
|
</Page>
|
|
</template>
|
|
|
|
<script>
|
|
import { defineComponent } from 'vue';
|
|
import { useNuxtApp } from 'nuxt/app';
|
|
import { buildDict } from '../src/helpers.ts';
|
|
import { Name } from '../src/classes.ts';
|
|
import useConfig from '../composables/useConfig.ts';
|
|
import useDialogue from '../composables/useDialogue.ts';
|
|
import useHash from '../composables/useHash.ts';
|
|
import useSimpleHead from '../composables/useSimpleHead.ts';
|
|
|
|
export default defineComponent({
|
|
setup() {
|
|
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,
|
|
});
|
|
|
|
return {
|
|
config: useConfig(),
|
|
dialogue: useDialogue(),
|
|
handleHash,
|
|
setHash,
|
|
namesAsyncData,
|
|
};
|
|
},
|
|
data() {
|
|
return {
|
|
filter: '',
|
|
};
|
|
},
|
|
computed: {
|
|
names() {
|
|
if (this.namesAsyncData.status.value !== 'success') {
|
|
return {};
|
|
}
|
|
return this.namesAsyncData.data.value;
|
|
},
|
|
},
|
|
watch: {
|
|
filter() {
|
|
this.setHash('', this.filter);
|
|
},
|
|
},
|
|
async mounted() {
|
|
this.handleHash('', (filter) => {
|
|
this.filter = filter;
|
|
if (filter) {
|
|
this.$refs.filter.focus();
|
|
this.$refs.filter.scrollIntoView();
|
|
setTimeout((_) => {
|
|
this.$refs.filter.scrollIntoView();
|
|
}, 1000);
|
|
}
|
|
});
|
|
},
|
|
methods: {
|
|
visibleNames() {
|
|
return Object.values(this.names).filter((n) => n.matches(this.filter));
|
|
},
|
|
edit(name) {
|
|
this.$refs.form.edit(name);
|
|
},
|
|
async approve(name) {
|
|
await this.dialogue.postWithAlertOnError(`/api/names/approve/${name.id}`);
|
|
if (name.base) {
|
|
delete this.names[name.base];
|
|
}
|
|
name.approved = true;
|
|
name.base = null;
|
|
},
|
|
async hide(name) {
|
|
await this.dialogue.postWithAlertOnError(`/api/names/hide/${name.id}`);
|
|
name.approved = false;
|
|
},
|
|
async remove(name) {
|
|
await this.dialogue.confirm(this.$t('crud.removeConfirm'), 'danger');
|
|
|
|
await this.dialogue.postWithAlertOnError(`/api/names/remove/${name.id}`);
|
|
delete this.names[name.id];
|
|
},
|
|
|
|
// those must be methods, not computed, because when modified, they don't get updated in the view for some reason
|
|
namesCountApproved() {
|
|
return Object.values(this.names).filter((n) => n.approved).length;
|
|
},
|
|
namesCountPending() {
|
|
return Object.values(this.names).filter((n) => !n.approved).length;
|
|
},
|
|
},
|
|
});
|
|
</script>
|
|
|
|
<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>
|