mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-22 12:03:25 -04:00
268 lines
9.3 KiB
Vue
268 lines
9.3 KiB
Vue
<template>
|
|
<div v-if="requiresLogin && !testerPasswordValid" class="body">
|
|
<div class="container">
|
|
<div class="alert alert-warning m-3 text-center">
|
|
<Icon v="exclamation-triangle" />
|
|
This is a test server
|
|
</div>
|
|
<div class="m-3">
|
|
<div class="input-group py-1">
|
|
<input v-model="testerPassword" class="form-control" type="password" placeholder="Password" @keydown.enter.prevent="checkTesterPassword">
|
|
<button type="button" class="btn btn-primary btn-sm border" @click.prevent="checkTesterPassword">
|
|
<Icon v="sign-in" />
|
|
Sign in
|
|
</button>
|
|
</div>
|
|
<p v-if="testerPasswordCookie && !testerPasswordValid" class="small text-danger">
|
|
<Icon v="exclamation-triangle" />
|
|
Password invalid
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else class="body">
|
|
<div class="flex-grow-1 vh">
|
|
<Header />
|
|
<Nuxt />
|
|
<TranslationMode />
|
|
<ScrollButton />
|
|
</div>
|
|
<Footer />
|
|
<DialogueBox ref="dialogue" />
|
|
<Lightbox />
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import Vue from 'vue';
|
|
import type { AxiosRequestConfig } from 'axios';
|
|
import dark from '../plugins/dark.ts';
|
|
import sorter from 'avris-sorter';
|
|
import { sleep } from '../src/helpers.ts';
|
|
import md5 from 'js-md5';
|
|
import { longtimeCookieSetting } from '../src/cookieSettings.ts';
|
|
import { LoadScriptError } from '../src/errors.ts';
|
|
import type { default as dialogueBox, DialogueMessage } from '../components/DialogueBox.vue';
|
|
import type { Color } from '../src/bootstrap.ts';
|
|
|
|
type DialogueBox = InstanceType<typeof dialogueBox>;
|
|
|
|
// no need to be super secure, just a sign that the page is not public
|
|
const TESTER_PASSWORD_COOKIE_KEY = 'tester-password';
|
|
const TESTER_PASSWORD_HASH = '82feeb96d60170e714df8fb062301e90';
|
|
|
|
declare global {
|
|
interface Window {
|
|
dataLayer: unknown[];
|
|
}
|
|
}
|
|
|
|
declare module 'vue/types/vue' {
|
|
interface Vue {
|
|
$alert(message: string | DialogueMessage, color?: Color): Promise<string | string[] | undefined>;
|
|
$confirm(message?: string | DialogueMessage, color?: Color): Promise<string | string[] | undefined>;
|
|
$editor(
|
|
value: string | string[],
|
|
message?: string | DialogueMessage,
|
|
color?: Color,
|
|
): Promise<string | string[] | undefined>;
|
|
$alertRaw(message: string | DialogueMessage, color?: Color): Promise<string | string[] | undefined>;
|
|
|
|
$post<T = unknown>(url: string, data?: unknown, options?: AxiosRequestConfig, timeout?: number): Promise<T>;
|
|
}
|
|
}
|
|
|
|
export default dark.extend({
|
|
data(): { testerPassword: string, testerPasswordCookie: string } {
|
|
return {
|
|
testerPassword: '',
|
|
testerPasswordCookie: this.$cookies.get(TESTER_PASSWORD_COOKIE_KEY),
|
|
};
|
|
},
|
|
computed: {
|
|
adsEnabled(): boolean {
|
|
if (this.$isGranted()) {
|
|
const adsVisible = parseInt(localStorage.getItem('adsVisible') || '0') === 1;
|
|
if (!adsVisible) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return !!this.$config.ads?.enabled && process.env.NODE_ENV !== 'development';
|
|
},
|
|
requiresLogin(): boolean {
|
|
return process.env.NODE_ENV === 'test' ||
|
|
this.$config.locale !== '_' && !this.$locales[this.$config.locale]?.published;
|
|
},
|
|
testerPasswordValid(): boolean {
|
|
return !!this.testerPasswordCookie && md5(this.testerPasswordCookie) === TESTER_PASSWORD_HASH;
|
|
},
|
|
dialogue(): DialogueBox {
|
|
return this.$refs.dialogue as DialogueBox;
|
|
},
|
|
},
|
|
mounted() {
|
|
Vue.prototype.$alert = (
|
|
message: string | DialogueMessage,
|
|
color: Color = 'primary',
|
|
): Promise<string | string[] | undefined> => {
|
|
return new Promise((resolve, reject) => {
|
|
this.dialogue.show(false, message, color, undefined, undefined, resolve, reject);
|
|
});
|
|
};
|
|
Vue.prototype.$confirm = (
|
|
message: string | DialogueMessage = '',
|
|
color: Color = 'primary',
|
|
): Promise<string | string[] | undefined> => {
|
|
return new Promise((resolve, reject) => {
|
|
this.dialogue.show(true, message, color, undefined, undefined, resolve, reject);
|
|
});
|
|
};
|
|
Vue.prototype.$editor = (
|
|
value: string | string[],
|
|
message: string | DialogueMessage = '',
|
|
color: Color = 'primary',
|
|
): Promise<string | string[] | undefined> => {
|
|
return new Promise((resolve, reject) => {
|
|
this.dialogue.show(false, message, color, value, 'lg', resolve, reject);
|
|
});
|
|
};
|
|
Vue.prototype.$alertRaw = (
|
|
message: string | DialogueMessage,
|
|
color: Color = 'primary',
|
|
): Promise<string | string[] | undefined> => {
|
|
return new Promise((resolve, reject) => {
|
|
this.dialogue.show(
|
|
false,
|
|
`<pre class="text-start"><code>${message}</code></pre>`,
|
|
color,
|
|
undefined,
|
|
'lg',
|
|
resolve,
|
|
reject,
|
|
);
|
|
});
|
|
};
|
|
Vue.prototype.$post = <T = unknown>(
|
|
url: string,
|
|
data: unknown = undefined,
|
|
options: AxiosRequestConfig = {},
|
|
timeout = 30000,
|
|
): Promise<T> => {
|
|
return new Promise((resolve, reject) => {
|
|
this.$axios.$post(url, data, { ...options, timeout })
|
|
.then((data) => resolve(data))
|
|
.catch(async (error) => {
|
|
let errorMessage = this.$t('error.generic');
|
|
if (typeof error.response?.data?.error === 'string') {
|
|
errorMessage = this.$t(error.response?.data?.error);
|
|
// in case no translatable key was provided
|
|
if (errorMessage === undefined) {
|
|
errorMessage = error.response?.data?.error;
|
|
}
|
|
}
|
|
await this.$alert(errorMessage, 'danger');
|
|
reject(new Error(`POST to ${url} failed: ${error.response?.data?.error || 'unknown error'}`));
|
|
});
|
|
});
|
|
};
|
|
|
|
sorter();
|
|
|
|
this.confirmAge();
|
|
|
|
this.loadAds();
|
|
this.loadGTM();
|
|
|
|
let needsRefresh = false;
|
|
const bc = new BroadcastChannel('account_switch');
|
|
bc.onmessage = (ev): void => {
|
|
if (ev.data !== this.$user()?.username) {
|
|
needsRefresh = true;
|
|
if (document.hasFocus()) {
|
|
needsRefresh = false;
|
|
window.location.reload();
|
|
}
|
|
}
|
|
};
|
|
window.onfocus = (): void => {
|
|
if (needsRefresh) {
|
|
needsRefresh = false;
|
|
window.location.reload();
|
|
}
|
|
};
|
|
},
|
|
methods: {
|
|
async confirmAge(): Promise<void> {
|
|
if (!this.$te('footer.ageLimit') || localStorage.getItem('ageConfirmed')) {
|
|
return;
|
|
}
|
|
|
|
while (this.$refs.dialogue === undefined) {
|
|
await sleep(100);
|
|
}
|
|
await this.$alert(this.$t('footer.ageLimit'));
|
|
|
|
localStorage.setItem('ageConfirmed', '1');
|
|
},
|
|
async loadAds(): Promise<void> {
|
|
if (!this.adsEnabled) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await this.$loadScript('publift', 'https://cdn.fuseplatform.net/publift/tags/2/3329/fuse.js');
|
|
} catch (error) {
|
|
if (error instanceof LoadScriptError) {
|
|
return;
|
|
}
|
|
throw error;
|
|
}
|
|
},
|
|
async loadGTM(): Promise<void> {
|
|
if (!this.adsEnabled) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await this.$loadScript('gtm', 'https://www.googletagmanager.com/gtag/js?id=G-TDJEP12Q3M');
|
|
} catch (error) {
|
|
if (error instanceof LoadScriptError) {
|
|
return;
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
window.dataLayer = window.dataLayer || [];
|
|
function gtag(..._args: unknown[]): void {
|
|
window.dataLayer.push(arguments);
|
|
}
|
|
gtag('js', new Date());
|
|
gtag('config', 'G-TDJEP12Q3M');
|
|
},
|
|
checkTesterPassword(): void {
|
|
this.$cookies.set(TESTER_PASSWORD_COOKIE_KEY, this.testerPassword, longtimeCookieSetting);
|
|
this.testerPasswordCookie = this.testerPassword;
|
|
},
|
|
},
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
@import "assets/variables";
|
|
@import "~avris-sorter/dist/Sorter.min.css";
|
|
|
|
@include media-breakpoint-up('lg', $grid-breakpoints) {
|
|
.body {
|
|
margin-top: $header-margin;
|
|
}
|
|
.sticky-top {
|
|
top: $header-height - 1px;
|
|
}
|
|
}
|
|
|
|
.vh {
|
|
min-height: calc(100vh - #{$header-height});
|
|
}
|
|
</style>
|