mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-10 16:05:32 -04:00
(ts) migrate some components to composition API with typescript
This commit is contained in:
parent
d61f5c306b
commit
9516a93501
@ -219,7 +219,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import forbidden from '../src/forbidden.js';
|
import forbidden from '../src/forbidden.ts';
|
||||||
import { sleep } from '../src/helpers.ts';
|
import { sleep } from '../src/helpers.ts';
|
||||||
import useDialogue from '../composables/useDialogue.ts';
|
import useDialogue from '../composables/useDialogue.ts';
|
||||||
|
|
||||||
|
@ -1,5 +1,57 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Columnist from 'avris-columnist';
|
||||||
|
import type { Post } from '~/server/express/blog.ts';
|
||||||
|
import useConfig from '../composables/useConfig.ts';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
posts: string[] | Post[];
|
||||||
|
details?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { data: postsFull } = useAsyncData(`posts-${JSON.stringify(props.posts)}`, async () => {
|
||||||
|
if (!props.posts.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (typeof props.posts[0] === 'object') {
|
||||||
|
return props.posts;
|
||||||
|
}
|
||||||
|
return await $fetch('/api/blog', {
|
||||||
|
params: {
|
||||||
|
slugs: props.posts,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const config = useConfig();
|
||||||
|
|
||||||
|
const shortcuts: Record<string, string | undefined> = {};
|
||||||
|
if (config.blog && config.blog.shortcuts) {
|
||||||
|
for (const shortcut in config.blog.shortcuts) {
|
||||||
|
if (!config.blog.shortcuts.hasOwnProperty(shortcut)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
shortcuts[config.blog.shortcuts[shortcut]] = shortcut;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateLink = (slug: string): string => {
|
||||||
|
const keepFullPath = config.blog?.keepFullPath || [];
|
||||||
|
return shortcuts[slug] !== undefined && !keepFullPath.includes(slug)
|
||||||
|
? `/${shortcuts[slug]}`
|
||||||
|
: `/${config.links.blogRoute}/${slug}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const entries = useTemplateRef<HTMLDivElement>('entries');
|
||||||
|
onMounted(async () => {
|
||||||
|
if (entries.value) {
|
||||||
|
const columnist = new Columnist(entries.value);
|
||||||
|
columnist.start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="columnist-wall row">
|
<div ref="entries" class="columnist-wall row">
|
||||||
<div v-for="post in postsFull" class="columnist-column col-12 col-sm-6 col-md-4 mb-3">
|
<div v-for="post in postsFull" class="columnist-column col-12 col-sm-6 col-md-4 mb-3">
|
||||||
<div class="card shadow">
|
<div class="card shadow">
|
||||||
<nuxt-link v-if="post.hero" :to="generateLink(post.slug)">
|
<nuxt-link v-if="post.hero" :to="generateLink(post.slug)">
|
||||||
@ -30,64 +82,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import Columnist from 'avris-columnist';
|
|
||||||
import useConfig from '../composables/useConfig.ts';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
posts: { required: true },
|
|
||||||
details: { type: Boolean },
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const { data: postsFull } = useAsyncData(`posts-${JSON.stringify(props.posts)}`, async () => {
|
|
||||||
if (!props.posts.length) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
if (typeof props.posts[0] === 'object') {
|
|
||||||
return props.posts;
|
|
||||||
}
|
|
||||||
return await $fetch('/api/blog', {
|
|
||||||
params: {
|
|
||||||
slugs: props.posts,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
config: useConfig(),
|
|
||||||
postsFull,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
const shortcuts = {};
|
|
||||||
if (this.config.blog && this.config.blog.shortcuts) {
|
|
||||||
for (const shortcut in this.config.blog.shortcuts) {
|
|
||||||
if (!this.config.blog.shortcuts.hasOwnProperty(shortcut)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
shortcuts[this.config.blog.shortcuts[shortcut]] = shortcut;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
shortcuts,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
async mounted() {
|
|
||||||
const columnist = new Columnist(this.$el);
|
|
||||||
columnist.start();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
generateLink(slug) {
|
|
||||||
return this.shortcuts[slug] !== undefined && !(this.config.blog.keepFullPath || []).includes(slug)
|
|
||||||
? `/${this.shortcuts[slug]}`
|
|
||||||
: `/${this.config.links.blogRoute}/${slug}`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.columnist-wall > .columnist-column {
|
.columnist-wall > .columnist-column {
|
||||||
transition: margin-top .2s ease-in-out;
|
transition: margin-top .2s ease-in-out;
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useMainStore } from '../store/index.ts';
|
||||||
|
|
||||||
|
const config = useConfig();
|
||||||
|
|
||||||
|
const store = useMainStore();
|
||||||
|
const darkMode = store.darkMode;
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Submenu
|
<Submenu
|
||||||
v-if="config.community"
|
v-if="config.community"
|
||||||
@ -44,22 +53,3 @@
|
|||||||
}]"
|
}]"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import { mapState } from 'pinia';
|
|
||||||
import useConfig from '../composables/useConfig.ts';
|
|
||||||
import { useMainStore } from '../store/index.ts';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
config: useConfig(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(useMainStore, [
|
|
||||||
'darkMode',
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
@ -185,14 +185,14 @@
|
|||||||
<!--<div class="container">
|
<!--<div class="container">
|
||||||
<SafariWarning dismissable />
|
<SafariWarning dismissable />
|
||||||
</div>-->
|
</div>-->
|
||||||
<div v-if="$user() && $user().bannedReason" class="alert alert-danger mb-0 container">
|
<div v-if="$user() && $user()!.bannedReason" class="alert alert-danger mb-0 container">
|
||||||
<p class="h4 mb-2">
|
<p class="h4 mb-2">
|
||||||
<Icon v="ban" />
|
<Icon v="ban" />
|
||||||
<T>ban.header</T>
|
<T>ban.header</T>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<T>ban.reason</T><T>quotation.colon</T>
|
<T>ban.reason</T><T>quotation.colon</T>
|
||||||
{{ $user().bannedReason }}
|
{{ $user()!.bannedReason }}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<T>ban.termsIntro</T><T>quotation.colon</T>
|
<T>ban.termsIntro</T><T>quotation.colon</T>
|
||||||
@ -200,7 +200,7 @@
|
|||||||
<blockquote class="small">
|
<blockquote class="small">
|
||||||
<T>terms.content.content.violations</T>
|
<T>terms.content.content.violations</T>
|
||||||
<template v-for="(violation, i) in forbidden">
|
<template v-for="(violation, i) in forbidden">
|
||||||
<T :class="[$user().bannedTerms.includes(violation) ? 'fw-bold' : '']">terms.content.content.violationsExamples.{{ violation }}</T><template v-if="i !== forbidden.length - 1">
|
<T :class="[$user()!.bannedTerms?.includes(violation) ? 'fw-bold' : '']">terms.content.content.violationsExamples.{{ violation }}</T><template v-if="i !== forbidden.length - 1">
|
||||||
,
|
,
|
||||||
</template>
|
</template>
|
||||||
</template>.
|
</template>.
|
||||||
@ -218,19 +218,34 @@
|
|||||||
</header>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { mapState } from 'pinia';
|
import { mapState } from 'pinia';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import forbidden from '../src/forbidden.js';
|
import forbidden from '../src/forbidden.ts';
|
||||||
import NounsNav from '../data/nouns/NounsNav.vue';
|
import NounsNav from '../data/nouns/NounsNav.vue';
|
||||||
import useConfig from '../composables/useConfig.ts';
|
import useConfig from '../composables/useConfig.ts';
|
||||||
import { useMainStore } from '../store/index.ts';
|
import { useMainStore } from '../store/index.ts';
|
||||||
|
import type { User } from '../src/user.ts';
|
||||||
|
|
||||||
export default {
|
interface HeaderLink {
|
||||||
|
header?: boolean;
|
||||||
|
link: string;
|
||||||
|
avatar?: User | null;
|
||||||
|
icon: string;
|
||||||
|
text: string;
|
||||||
|
textLong?: string;
|
||||||
|
extra?: (string | null)[];
|
||||||
|
desktop?: boolean;
|
||||||
|
mobile?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
components: { NounsNav },
|
components: { NounsNav },
|
||||||
setup() {
|
setup() {
|
||||||
|
const hoverItem = ref<HeaderLink | null>(null);
|
||||||
return {
|
return {
|
||||||
config: useConfig(),
|
config: useConfig(),
|
||||||
|
hoverItem,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -239,7 +254,6 @@ export default {
|
|||||||
hamburgerShown: false,
|
hamburgerShown: false,
|
||||||
censusDismissed: false,
|
censusDismissed: false,
|
||||||
forbidden,
|
forbidden,
|
||||||
hoverItem: null,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -247,8 +261,8 @@ export default {
|
|||||||
'user',
|
'user',
|
||||||
'darkMode',
|
'darkMode',
|
||||||
]),
|
]),
|
||||||
links() {
|
links(): HeaderLink[] {
|
||||||
const links = [];
|
const links: HeaderLink[] = [];
|
||||||
|
|
||||||
links.push({
|
links.push({
|
||||||
header: true,
|
header: true,
|
||||||
@ -351,14 +365,14 @@ export default {
|
|||||||
this.config.contact && this.config.contact.team && this.config.contact.team.enabled
|
this.config.contact && this.config.contact.team && this.config.contact.team.enabled
|
||||||
) {
|
) {
|
||||||
const extra = [
|
const extra = [
|
||||||
this.config.terminology && this.config.terminology.enabled && this.config.terminology.published ? `/${this.config.terminology.route}` : '',
|
this.config.terminology.enabled && this.config.terminology.published ? `/${this.config.terminology.route}` : '',
|
||||||
this.config.calendar && this.config.calendar.enabled ? `/${this.config.calendar.route}` : '',
|
this.config.calendar?.enabled ? `/${this.config.calendar.route}` : '',
|
||||||
this.config.census && this.config.census.enabled ? `/${this.config.census.route}` : '',
|
this.config.census.enabled ? `/${this.config.census.route}` : '',
|
||||||
this.config.inclusive && this.config.inclusive.enabled ? `/${this.config.inclusive.route}` : '',
|
this.config.inclusive.enabled ? `/${this.config.inclusive.route}` : '',
|
||||||
this.config.names && this.config.names.enabled && this.config.names.published ? `/${this.config.names.route}` : '',
|
this.config.names.enabled && this.config.names.published ? `/${this.config.names.route}` : '',
|
||||||
this.config.people && this.config.people.enabled ? `/${this.config.people.route}` : '',
|
this.config.people.enabled ? `/${this.config.people.route}` : '',
|
||||||
`/${this.config.contact.team.route}`,
|
this.config.contact.enabled ? `/${this.config.contact.team.route}` : '',
|
||||||
this.config.workshops && this.config.workshops.enabled ? `/${this.config.workshops.route}` : '',
|
this.config.workshops?.enabled ? `/${this.config.workshops.route}` : '',
|
||||||
];
|
];
|
||||||
|
|
||||||
if (this.config.community) {
|
if (this.config.community) {
|
||||||
@ -396,13 +410,14 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (this.config.user.enabled) {
|
if (this.config.user.enabled) {
|
||||||
|
const user = this.$user();
|
||||||
links.push({
|
links.push({
|
||||||
link: `/${this.config.user.route}`,
|
link: `/${this.config.user.route}`,
|
||||||
avatar: this.$user(),
|
avatar: this.$user(),
|
||||||
icon: 'user',
|
icon: 'user',
|
||||||
text: this.user ? `@${this.user.username}` : this.$t('user.header'),
|
text: this.user ? `@${this.user.username}` : this.$t('user.header'),
|
||||||
textLong: this.user ? `@${this.user.username}` : this.$t('user.headerLong'),
|
textLong: this.user ? `@${this.user.username}` : this.$t('user.headerLong'),
|
||||||
extra: ['/editor', this.$user() ? `/@${this.$user().username}` : null],
|
extra: ['/editor', user ? `/@${user.username}` : null],
|
||||||
});
|
});
|
||||||
if (this.$isGranted('panel')) {
|
if (this.$isGranted('panel')) {
|
||||||
links.push({
|
links.push({
|
||||||
@ -426,12 +441,12 @@ export default {
|
|||||||
|
|
||||||
return links;
|
return links;
|
||||||
},
|
},
|
||||||
showCensus() {
|
showCensus(): boolean {
|
||||||
if (!process.client) {
|
if (!process.client) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const finished = !!parseInt(window.localStorage.getItem(`census-${this.config.census.edition}-finished`) || 0);
|
const finished = !!parseInt(window.localStorage.getItem(`census-${this.config.census.edition}-finished`) || '0');
|
||||||
const dismissed = !!parseInt(window.localStorage.getItem(`census-${this.config.census.edition}-dismissed`) || 0);
|
const dismissed = !!parseInt(window.localStorage.getItem(`census-${this.config.census.edition}-dismissed`) || '0');
|
||||||
const alreadyIn = this.$route.path === `/${this.config.census.route}`;
|
const alreadyIn = this.$route.path === `/${this.config.census.route}`;
|
||||||
const isHomepage = this.$route.path === '/';
|
const isHomepage = this.$route.path === '/';
|
||||||
if (!this.config.census.enabled || !isHomepage && (finished || dismissed) || this.censusDismissed || alreadyIn) {
|
if (!this.config.census.enabled || !isHomepage && (finished || dismissed) || this.censusDismissed || alreadyIn) {
|
||||||
@ -439,7 +454,7 @@ export default {
|
|||||||
}
|
}
|
||||||
const start = DateTime.fromISO(this.config.census.start).toLocal();
|
const start = DateTime.fromISO(this.config.census.start).toLocal();
|
||||||
const end = DateTime.fromISO(this.config.census.end).toLocal();
|
const end = DateTime.fromISO(this.config.census.end).toLocal();
|
||||||
const now = DateTime.utc().setZone(this.config.format.timezone);
|
const now = DateTime.utc().setZone(this.config.format?.timezone ?? 'utc');
|
||||||
return now >= start && now <= end;
|
return now >= start && now <= end;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -453,29 +468,29 @@ export default {
|
|||||||
document.removeEventListener('scroll', this.updateShown);
|
document.removeEventListener('scroll', this.updateShown);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
isActiveRoute(link) {
|
isActiveRoute(link: HeaderLink) {
|
||||||
return decodeURIComponent(this.$route.path) === link.link ||
|
return decodeURIComponent(this.$route.path) === link.link ||
|
||||||
link.extra && this.$route.name && link.extra.includes(this.$route.name.split(':')[0]) ||
|
link.extra && this.$route.name && link.extra.includes((this.$route.name as string).split(':')[0]) ||
|
||||||
(link.extra || []).includes(decodeURIComponent(this.$route.path)) ||
|
(link.extra || []).includes(decodeURIComponent(this.$route.path)) ||
|
||||||
(link.extra || []).filter((x) => x && (
|
(link.extra || []).filter((x) => x && (
|
||||||
decodeURIComponent(this.$route.path).startsWith(`${x}/`) ||
|
decodeURIComponent(this.$route.path).startsWith(`${x}/`) ||
|
||||||
decodeURIComponent(this.$route.path).startsWith(`${x}:`))).length;
|
decodeURIComponent(this.$route.path).startsWith(`${x}:`))).length;
|
||||||
},
|
},
|
||||||
documentClicked() {
|
documentClicked(): void {
|
||||||
if (this.hamburgerActive) {
|
if (this.hamburgerActive) {
|
||||||
this.hamburgerActive = false;
|
this.hamburgerActive = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateShown() {
|
updateShown(): void {
|
||||||
const st = document.body.scrollTop || document.querySelector('html').scrollTop;
|
const st = document.body.scrollTop || document.querySelector('html')!.scrollTop;
|
||||||
this.hamburgerShown = st > 300;
|
this.hamburgerShown = st > 300;
|
||||||
},
|
},
|
||||||
dismissCensus() {
|
dismissCensus(): void {
|
||||||
window.localStorage.setItem(`census-${this.config.census.edition}-dismissed`, '1');
|
window.localStorage.setItem(`census-${this.config.census.edition}-dismissed`, '1');
|
||||||
this.censusDismissed = true;
|
this.censusDismissed = true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -1,6 +1,15 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useMainStore } from '../store/index.ts';
|
||||||
|
|
||||||
|
const config = useConfig();
|
||||||
|
|
||||||
|
const store = useMainStore();
|
||||||
|
const darkMode = store.darkMode;
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Submenu
|
<Submenu
|
||||||
v-if="config.links.split"
|
v-if="config.links.enabled && config.links.split"
|
||||||
:links="[{
|
:links="[{
|
||||||
name: 'links.links',
|
name: 'links.links',
|
||||||
route: config.links.route,
|
route: config.links.route,
|
||||||
@ -40,22 +49,3 @@
|
|||||||
}]"
|
}]"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import { mapState } from 'pinia';
|
|
||||||
import useConfig from '../composables/useConfig.ts';
|
|
||||||
import { useMainStore } from '../store/index.ts';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
config: useConfig(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(useMainStore, [
|
|
||||||
'darkMode',
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
@ -1,3 +1,15 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
link: string;
|
||||||
|
locale: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const config = useConfig();
|
||||||
|
|
||||||
|
const runtimeConfig = useRuntimeConfig();
|
||||||
|
const homeLink = runtimeConfig.public.homeUrl;
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<nuxt-link v-if="locale === config.locale" :to="link" :class="$attrs.class">
|
<nuxt-link v-if="locale === config.locale" :to="link" :class="$attrs.class">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
@ -9,24 +21,3 @@
|
|||||||
<slot></slot>
|
<slot></slot>
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import useConfig from '../composables/useConfig.ts';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
link: { required: true },
|
|
||||||
locale: { required: true },
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
config: useConfig(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
homeLink: this.$config.public.home,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
@ -1,19 +1,16 @@
|
|||||||
<template>
|
<script setup lang="ts">
|
||||||
<a v-if="isExternal" :href="to" target="_blank" rel="noopener" :class="$attrs.class"><slot></slot></a>
|
import type { RouteLocationRaw } from 'vue-router';
|
||||||
<nuxt-link v-else :to="to">
|
|
||||||
<slot></slot>
|
|
||||||
</nuxt-link>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
const props = defineProps<{
|
||||||
export default {
|
to: RouteLocationRaw,
|
||||||
props: {
|
}>();
|
||||||
to: { required: true, type: String },
|
|
||||||
},
|
const isExternal = computed((): boolean => {
|
||||||
computed: {
|
return typeof props.to === 'string' && (props.to.startsWith('https://') || props.to.startsWith('http://'));
|
||||||
isExternal() {
|
});
|
||||||
return this.to.startsWith('https://') || this.to.startsWith('http://');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<a v-if="isExternal" :href="to as string" target="_blank" rel="noopener" :class="$attrs.class"><slot></slot></a>
|
||||||
|
<nuxt-link v-else :to="to"><slot></slot></nuxt-link>
|
||||||
|
</template>
|
||||||
|
@ -1,3 +1,46 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const { data: stats } = useFetch('/api/admin/stats-public', { lazy: true });
|
||||||
|
|
||||||
|
const runtimeConfig = useRuntimeConfig();
|
||||||
|
const plausibleHost = runtimeConfig.public.plausibleHost;
|
||||||
|
const heartbeatHost = runtimeConfig.public.heartbeatHost;
|
||||||
|
|
||||||
|
const overall = ref(true);
|
||||||
|
|
||||||
|
const activeStats = computed(() => {
|
||||||
|
if (stats.value === null) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return overall
|
||||||
|
? stats.value.overall
|
||||||
|
: stats.value.current || {};
|
||||||
|
});
|
||||||
|
|
||||||
|
const formatNumber = (number: number): string => {
|
||||||
|
if (number > 1000000) {
|
||||||
|
return `${Math.round(10 * number / 1000000) / 10}M`;
|
||||||
|
}
|
||||||
|
if (number > 1000) {
|
||||||
|
return `${Math.round(10 * number / 1000) / 10}k`;
|
||||||
|
}
|
||||||
|
return number.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDuration = (secondsCount: number): string => {
|
||||||
|
const minutes = Math.floor(secondsCount / 60);
|
||||||
|
const seconds = secondsCount % 60;
|
||||||
|
const res = [];
|
||||||
|
if (minutes > 0) {
|
||||||
|
res.push(`${minutes}m`);
|
||||||
|
}
|
||||||
|
if (seconds > 0) {
|
||||||
|
res.push(`${seconds}s`);
|
||||||
|
}
|
||||||
|
return res.join(' ');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="stats !== null">
|
<div v-if="stats !== null">
|
||||||
<p class="h6 mb-2">
|
<p class="h6 mb-2">
|
||||||
@ -13,7 +56,7 @@
|
|||||||
|
|
|
|
||||||
<strong><T>footer.stats.current</T></strong>
|
<strong><T>footer.stats.current</T></strong>
|
||||||
</p>
|
</p>
|
||||||
<ul :key="overall" class="list-unstyled">
|
<ul :key="`${overall}`" class="list-unstyled">
|
||||||
<li v-if="activeStats.cards" class="mb-2">
|
<li v-if="activeStats.cards" class="mb-2">
|
||||||
<Icon v="id-card" />
|
<Icon v="id-card" />
|
||||||
<T>footer.stats.keys.cards</T><T>quotation.colon</T>
|
<T>footer.stats.keys.cards</T><T>quotation.colon</T>
|
||||||
@ -67,55 +110,3 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
setup() {
|
|
||||||
const { data: stats } = useFetch('/api/admin/stats-public', { lazy: true });
|
|
||||||
return {
|
|
||||||
stats,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
plausibleHost: this.$config.public.plausibleHost,
|
|
||||||
heartbeatHost: this.$config.public.heartbeatHost,
|
|
||||||
overall: true,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
activeStats() {
|
|
||||||
if (this.stats === null) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.overall
|
|
||||||
? this.stats.overall
|
|
||||||
: this.stats.current || {};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
formatNumber(number) {
|
|
||||||
if (number > 1000000) {
|
|
||||||
return `${Math.round(10 * number / 1000000) / 10}M`;
|
|
||||||
}
|
|
||||||
if (number > 1000) {
|
|
||||||
return `${Math.round(10 * number / 1000) / 10}k`;
|
|
||||||
}
|
|
||||||
return number;
|
|
||||||
},
|
|
||||||
formatDuration(secondsCount) {
|
|
||||||
const minutes = Math.floor(secondsCount / 60);
|
|
||||||
const seconds = secondsCount % 60;
|
|
||||||
const res = [];
|
|
||||||
if (minutes > 0) {
|
|
||||||
res.push(`${minutes}m`);
|
|
||||||
}
|
|
||||||
if (seconds > 0) {
|
|
||||||
res.push(`${seconds}s`);
|
|
||||||
}
|
|
||||||
return res.join(' ');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
@ -1,3 +1,46 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
interface Link {
|
||||||
|
name: string;
|
||||||
|
route: string;
|
||||||
|
icon: string;
|
||||||
|
iconInverse?: boolean;
|
||||||
|
routesExtra?: string[];
|
||||||
|
condition?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
prefix?: string;
|
||||||
|
links: Link[];
|
||||||
|
extraClass?: string;
|
||||||
|
}>(), {
|
||||||
|
prefix: '',
|
||||||
|
extraClass: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const visibleLinks = computed((): Link[] => {
|
||||||
|
return props.links.filter((l) => l.condition === undefined || l.condition);
|
||||||
|
});
|
||||||
|
|
||||||
|
const buildRoute = (route: string): string => {
|
||||||
|
return `${props.prefix}/${route}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const isActiveRoute = (path: string, routesExtra?: string[]): boolean => {
|
||||||
|
if (routesExtra && route.name && routesExtra.includes((route.name as string).split(':')[0])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let current = decodeURIComponent(route.fullPath).replace(/\/$/, '');
|
||||||
|
if (current.includes('#')) {
|
||||||
|
current = current.substring(0, current.indexOf('#'));
|
||||||
|
}
|
||||||
|
const expected = buildRoute(path).replace(/\/$/, '');
|
||||||
|
|
||||||
|
return current === expected || current.startsWith(`${expected}/`);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section v-if="visibleLinks.length" class="mt-4 mt-lg-0 d-print-none">
|
<section v-if="visibleLinks.length" class="mt-4 mt-lg-0 d-print-none">
|
||||||
<div class="d-none d-md-inline-flex btn-group btn-block mb-2 w-100">
|
<div class="d-none d-md-inline-flex btn-group btn-block mb-2 w-100">
|
||||||
@ -24,36 +67,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
prefix: { default: '' },
|
|
||||||
links: { required: true },
|
|
||||||
extraClass: { default: '' },
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
visibleLinks() {
|
|
||||||
return this.links.filter((l) => l.condition === undefined || l.condition === true);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
buildRoute(route) {
|
|
||||||
return `${this.prefix}/${route}`;
|
|
||||||
},
|
|
||||||
isActiveRoute(route, routesExtra) {
|
|
||||||
if (routesExtra && this.$route.name && routesExtra.includes(this.$route.name.split(':')[0])) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let current = decodeURIComponent(this.$route.fullPath).replace(/\/$/, '');
|
|
||||||
if (current.includes('#')) {
|
|
||||||
current = current.substring(0, current.indexOf('#'));
|
|
||||||
}
|
|
||||||
const expected = this.buildRoute(route).replace(/\/$/, '');
|
|
||||||
|
|
||||||
return current === expected || current.startsWith(`${expected}/`);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
@ -1,3 +1,16 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import forbidden from '../src/forbidden.ts';
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
translatedPaths: (config) => {
|
||||||
|
if (!config.user.enabled) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [`/${encodeURIComponent(config.user.termsRoute)}`];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page>
|
<Page>
|
||||||
<h2>
|
<h2>
|
||||||
@ -49,25 +62,3 @@
|
|||||||
<p><T>terms.content.closing.changes</T></p>
|
<p><T>terms.content.closing.changes</T></p>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import forbidden from '../src/forbidden.js';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
setup() {
|
|
||||||
definePageMeta({
|
|
||||||
translatedPaths: (config) => {
|
|
||||||
if (!config.user.enabled) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return [`/${encodeURIComponent(config.user.termsRoute)}`];
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
forbidden,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
@ -1,3 +1,53 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const config = useConfig();
|
||||||
|
|
||||||
|
interface Link {
|
||||||
|
icon: string;
|
||||||
|
header: string;
|
||||||
|
route: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainLinks: Link[] = [];
|
||||||
|
if (config.pronouns.enabled) {
|
||||||
|
mainLinks.push({
|
||||||
|
icon: 'tags',
|
||||||
|
header: 'pronouns.headerLong',
|
||||||
|
route: config.pronouns.route,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (config.nouns.enabled) {
|
||||||
|
mainLinks.push({
|
||||||
|
icon: 'book',
|
||||||
|
header: 'nouns.headerLong',
|
||||||
|
route: config.nouns.route,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (config.user.enabled) {
|
||||||
|
mainLinks.push({
|
||||||
|
icon: 'id-card',
|
||||||
|
header: 'profile.bannerButton',
|
||||||
|
route: config.user.route,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: latestPosts } = useAsyncData('latest-posts', async () => {
|
||||||
|
if (!config.blog) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return (await $fetch('/api/blog')).slice(0, 9);
|
||||||
|
}, {
|
||||||
|
lazy: true,
|
||||||
|
});
|
||||||
|
const { data: featuredPosts } = useAsyncData('featured-posts', async () => {
|
||||||
|
if (!config.blog?.shortcuts) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return await $fetch('/api/blog?shortcuts');
|
||||||
|
}, {
|
||||||
|
lazy: true,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page>
|
<Page>
|
||||||
<AdPlaceholder :phkey="['header', null]" />
|
<AdPlaceholder :phkey="['header', null]" />
|
||||||
@ -12,8 +62,12 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div v-for="{ icon, header, route, link } in mainLinks" :class="[mainLinks.length > 3 ? 'col-6 col-lg-3' : 'col', 'my-4', 'text-center']">
|
<div
|
||||||
<LocaleLink :link="link || route" :locale="link ? 'external' : config.locale">
|
v-for="{ icon, header, route } in mainLinks"
|
||||||
|
:key="header"
|
||||||
|
:class="[mainLinks.length > 3 ? 'col-6 col-lg-3' : 'col', 'my-4', 'text-center']"
|
||||||
|
>
|
||||||
|
<LocaleLink :link="route" :locale="config.locale">
|
||||||
<p>
|
<p>
|
||||||
<Icon :v="icon" :size="2" class="d-inline-block d-lg-none" />
|
<Icon :v="icon" :size="2" class="d-inline-block d-lg-none" />
|
||||||
<Icon :v="icon" :size="3" class="d-none d-lg-inline-block" />
|
<Icon :v="icon" :size="3" class="d-none d-lg-inline-block" />
|
||||||
@ -115,78 +169,3 @@
|
|||||||
</section>
|
</section>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import { mapState } from 'pinia';
|
|
||||||
import useConfig from '../composables/useConfig.ts';
|
|
||||||
import { useMainStore } from '../store/index.ts';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
setup() {
|
|
||||||
const config = useConfig();
|
|
||||||
|
|
||||||
const { data: latestPosts } = useAsyncData('latest-posts', async () => {
|
|
||||||
if (!config.blog) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return (await $fetch('/api/blog')).slice(0, 9);
|
|
||||||
}, {
|
|
||||||
lazy: true,
|
|
||||||
});
|
|
||||||
const { data: featuredPosts } = useAsyncData('featured-posts', async () => {
|
|
||||||
if (!config.blog?.shortcuts) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return await $fetch('/api/blog?shortcuts');
|
|
||||||
}, {
|
|
||||||
lazy: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
config,
|
|
||||||
latestPosts,
|
|
||||||
featuredPosts,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(useMainStore, [
|
|
||||||
'user',
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
const mainLinks = [];
|
|
||||||
if (this.config.pronouns.enabled) {
|
|
||||||
mainLinks.push({
|
|
||||||
icon: 'tags',
|
|
||||||
header: 'pronouns.headerLong',
|
|
||||||
route: this.config.pronouns.route,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (this.config.nouns.enabled) {
|
|
||||||
mainLinks.push({
|
|
||||||
icon: 'book',
|
|
||||||
header: 'nouns.headerLong',
|
|
||||||
route: this.config.nouns.route,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (this.config.user.enabled) {
|
|
||||||
mainLinks.push({
|
|
||||||
icon: 'id-card',
|
|
||||||
header: 'profile.bannerButton',
|
|
||||||
route: this.config.user.route,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// if (this.config.locale === 'pl') {
|
|
||||||
// mainLinks.push({
|
|
||||||
// icon: 'isjp.svg',
|
|
||||||
// header: 'isjp.homepage',
|
|
||||||
// link: 'https://isjp.pl',
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
return {
|
|
||||||
mainLinks,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
@ -7,6 +7,7 @@ export interface User {
|
|||||||
avatarSource: string;
|
avatarSource: string;
|
||||||
bannedReason: string;
|
bannedReason: string;
|
||||||
usernameNorm: string;
|
usernameNorm: string;
|
||||||
|
bannedTerms?: string;
|
||||||
bannedBy: string;
|
bannedBy: string;
|
||||||
lastActive: number;
|
lastActive: number;
|
||||||
banSnapshot: string;
|
banSnapshot: string;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user