remove ads from client

This commit is contained in:
Johann150 2022-07-11 22:31:39 +02:00
parent 0e3a9d1e0d
commit 79ec9641d2
Signed by untrusted user: Johann150
GPG key ID: 9EE6577A2A06F8F1
10 changed files with 4 additions and 378 deletions

View file

@ -1,13 +1,12 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, h, PropType, TransitionGroup } from 'vue'; import { defineComponent, h, PropType, TransitionGroup } from 'vue';
import MkAd from '@/components/global/ad.vue';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
export default defineComponent({ export default defineComponent({
props: { props: {
items: { items: {
type: Array as PropType<{ id: string; createdAt: string; _shouldInsertAd_: boolean; }[]>, type: Array as PropType<{ id: string; createdAt: string; }[]>,
required: true, required: true,
}, },
direction: { direction: {
@ -25,11 +24,6 @@ export default defineComponent({
required: false, required: false,
default: false default: false
}, },
ad: {
type: Boolean,
required: false,
default: false
},
}, },
setup(props, { slots, expose }) { setup(props, { slots, expose }) {
@ -78,15 +72,7 @@ export default defineComponent({
return [el, separator]; return [el, separator];
} else { } else {
if (props.ad && item._shouldInsertAd_) { return el;
return [h(MkAd, {
class: 'a', // advertise()
key: item.id + ':ad',
prefer: ['horizontal', 'horizontal-big'],
}), el];
} else {
return el;
}
} }
}); });

View file

@ -1,200 +0,0 @@
<template>
<div v-if="ad" class="qiivuoyo">
<div v-if="!showMenu" class="main" :class="ad.place">
<a :href="ad.url" target="_blank">
<img :src="ad.imageUrl">
<button class="_button menu" @click.prevent.stop="toggleMenu"><span class="fas fa-info-circle"></span></button>
</a>
</div>
<div v-else class="menu">
<div class="body">
<div>Ads by {{ host }}</div>
<!--<MkButton class="button" primary>{{ $ts._ad.like }}</MkButton>-->
<MkButton v-if="ad.ratio !== 0" class="button" @click="reduceFrequency">{{ $ts._ad.reduceFrequencyOfThisAd }}</MkButton>
<button class="_textButton" @click="toggleMenu">{{ $ts._ad.back }}</button>
</div>
</div>
</div>
<div v-else></div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { instance } from '@/instance';
import { host } from '@/config';
import MkButton from '@/components/ui/button.vue';
import { defaultStore } from '@/store';
import * as os from '@/os';
export default defineComponent({
components: {
MkButton
},
props: {
prefer: {
type: Array,
required: true
},
specify: {
type: Object,
required: false
},
},
setup(props) {
const showMenu = ref(false);
const toggleMenu = () => {
showMenu.value = !showMenu.value;
};
const choseAd = (): (typeof instance)['ads'][number] | null => {
if (props.specify) {
return props.specify as (typeof instance)['ads'][number];
}
const allAds = instance.ads.map(ad => defaultStore.state.mutedAds.includes(ad.id) ? {
...ad,
ratio: 0
} : ad);
let ads = allAds.filter(ad => props.prefer.includes(ad.place));
if (ads.length === 0) {
ads = allAds.filter(ad => ad.place === 'square');
}
const lowPriorityAds = ads.filter(ad => ad.ratio === 0);
ads = ads.filter(ad => ad.ratio !== 0);
if (ads.length === 0) {
if (lowPriorityAds.length !== 0) {
return lowPriorityAds[Math.floor(Math.random() * lowPriorityAds.length)];
} else {
return null;
}
}
const totalFactor = ads.reduce((a, b) => a + b.ratio, 0);
const r = Math.random() * totalFactor;
let stackedFactor = 0;
for (const ad of ads) {
if (r >= stackedFactor && r <= stackedFactor + ad.ratio) {
return ad;
} else {
stackedFactor += ad.ratio;
}
}
return null;
};
const chosen = ref(choseAd());
const reduceFrequency = () => {
if (chosen.value == null) return;
if (defaultStore.state.mutedAds.includes(chosen.value.id)) return;
defaultStore.push('mutedAds', chosen.value.id);
os.success();
chosen.value = choseAd();
showMenu.value = false;
};
return {
ad: chosen,
showMenu,
toggleMenu,
host,
reduceFrequency,
};
}
});
</script>
<style lang="scss" scoped>
.qiivuoyo {
background-size: auto auto;
background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--ad) 8px, var(--ad) 14px );
> .main {
text-align: center;
> a {
display: inline-block;
position: relative;
vertical-align: bottom;
&:hover {
> img {
filter: contrast(120%);
}
}
> img {
display: block;
object-fit: contain;
margin: auto;
}
> .menu {
position: absolute;
top: 0;
right: 0;
background: var(--panel);
}
}
&.square {
> a ,
> a > img {
max-width: min(300px, 100%);
max-height: 300px;
}
}
&.horizontal {
padding: 8px;
> a ,
> a > img {
max-width: min(600px, 100%);
max-height: 80px;
}
}
&.horizontal-big {
padding: 8px;
> a ,
> a > img {
max-width: min(600px, 100%);
max-height: 250px;
}
}
&.vertical {
> a ,
> a > img {
max-width: min(100px, 100%);
}
}
}
> .menu {
padding: 8px;
text-align: center;
> .body {
padding: 8px;
margin: 0 auto;
max-width: 400px;
border: solid 1px var(--divider);
> .button {
margin: 8px auto;
}
}
}
}
</style>

View file

@ -13,7 +13,6 @@ import I18n from './global/i18n';
import RouterView from './global/router-view.vue'; import RouterView from './global/router-view.vue';
import MkLoading from './global/loading.vue'; import MkLoading from './global/loading.vue';
import MkError from './global/error.vue'; import MkError from './global/error.vue';
import MkAd from './global/ad.vue';
import MkPageHeader from './global/page-header.vue'; import MkPageHeader from './global/page-header.vue';
import MkSpacer from './global/spacer.vue'; import MkSpacer from './global/spacer.vue';
import MkStickyContainer from './global/sticky-container.vue'; import MkStickyContainer from './global/sticky-container.vue';
@ -32,7 +31,6 @@ export default function(app: App) {
app.component('MkUrl', MkUrl); app.component('MkUrl', MkUrl);
app.component('MkLoading', MkLoading); app.component('MkLoading', MkLoading);
app.component('MkError', MkError); app.component('MkError', MkError);
app.component('MkAd', MkAd);
app.component('MkPageHeader', MkPageHeader); app.component('MkPageHeader', MkPageHeader);
app.component('MkSpacer', MkSpacer); app.component('MkSpacer', MkSpacer);
app.component('MkStickyContainer', MkStickyContainer); app.component('MkStickyContainer', MkStickyContainer);
@ -53,7 +51,6 @@ declare module '@vue/runtime-core' {
MkUrl: typeof MkUrl; MkUrl: typeof MkUrl;
MkLoading: typeof MkLoading; MkLoading: typeof MkLoading;
MkError: typeof MkError; MkError: typeof MkError;
MkAd: typeof MkAd;
MkPageHeader: typeof MkPageHeader; MkPageHeader: typeof MkPageHeader;
MkSpacer: typeof MkSpacer; MkSpacer: typeof MkSpacer;
MkStickyContainer: typeof MkStickyContainer; MkStickyContainer: typeof MkStickyContainer;

View file

@ -9,7 +9,7 @@
<template #default="{ items: notes }"> <template #default="{ items: notes }">
<div class="giivymft" :class="{ noGap }"> <div class="giivymft" :class="{ noGap }">
<XList ref="notes" v-slot="{ item: note }" :items="notes" :direction="pagination.reversed ? 'up' : 'down'" :reversed="pagination.reversed" :no-gap="noGap" :ad="true" class="notes"> <XList ref="notes" v-slot="{ item: note }" :items="notes" :direction="pagination.reversed ? 'up' : 'down'" :reversed="pagination.reversed" :no-gap="noGap" class="notes">
<XNote :key="note._featuredId_ || note._prId_ || note.id" class="qtqtichx" :note="note"/> <XNote :key="note._featuredId_ || note._prId_ || note.id" class="qtqtichx" :note="note"/>
</XList> </XList>
</div> </div>

View file

@ -93,14 +93,6 @@ const init = async (): Promise<void> => {
...params, ...params,
limit: props.pagination.noPaging ? (props.pagination.limit || 10) : (props.pagination.limit || 10) + 1, limit: props.pagination.noPaging ? (props.pagination.limit || 10) : (props.pagination.limit || 10) + 1,
}).then(res => { }).then(res => {
for (let i = 0; i < res.length; i++) {
const item = res[i];
if (props.pagination.reversed) {
if (i === res.length - 2) item._shouldInsertAd_ = true;
} else {
if (i === 3) item._shouldInsertAd_ = true;
}
}
if (!props.pagination.noPaging && (res.length > (props.pagination.limit || 10))) { if (!props.pagination.noPaging && (res.length > (props.pagination.limit || 10))) {
res.pop(); res.pop();
items.value = props.pagination.reversed ? [...res].reverse() : res; items.value = props.pagination.reversed ? [...res].reverse() : res;
@ -137,14 +129,6 @@ const fetchMore = async (): Promise<void> => {
untilId: props.pagination.reversed ? items.value[0].id : items.value[items.value.length - 1].id, untilId: props.pagination.reversed ? items.value[0].id : items.value[items.value.length - 1].id,
}), }),
}).then(res => { }).then(res => {
for (let i = 0; i < res.length; i++) {
const item = res[i];
if (props.pagination.reversed) {
if (i === res.length - 9) item._shouldInsertAd_ = true;
} else {
if (i === 10) item._shouldInsertAd_ = true;
}
}
if (res.length > SECOND_FETCH_LIMIT) { if (res.length > SECOND_FETCH_LIMIT) {
res.pop(); res.pop();
items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res); items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);

View file

@ -1,133 +0,0 @@
<template>
<MkStickyContainer>
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :content-max="900">
<div class="uqshojas">
<div v-for="ad in ads" class="_panel _formRoot ad">
<MkAd v-if="ad.url" :specify="ad"/>
<MkInput v-model="ad.url" type="url" class="_formBlock">
<template #label>URL</template>
</MkInput>
<MkInput v-model="ad.imageUrl" class="_formBlock">
<template #label>{{ i18n.ts.imageUrl }}</template>
</MkInput>
<FormRadios v-model="ad.place" class="_formBlock">
<template #label>Form</template>
<option value="square">square</option>
<option value="horizontal">horizontal</option>
<option value="horizontal-big">horizontal-big</option>
</FormRadios>
<!--
<div style="margin: 32px 0;">
{{ i18n.ts.priority }}
<MkRadio v-model="ad.priority" value="high">{{ i18n.ts.high }}</MkRadio>
<MkRadio v-model="ad.priority" value="middle">{{ i18n.ts.middle }}</MkRadio>
<MkRadio v-model="ad.priority" value="low">{{ i18n.ts.low }}</MkRadio>
</div>
-->
<FormSplit>
<MkInput v-model="ad.ratio" type="number">
<template #label>{{ i18n.ts.ratio }}</template>
</MkInput>
<MkInput v-model="ad.expiresAt" type="date">
<template #label>{{ i18n.ts.expiration }}</template>
</MkInput>
</FormSplit>
<MkTextarea v-model="ad.memo" class="_formBlock">
<template #label>{{ i18n.ts.memo }}</template>
</MkTextarea>
<div class="buttons _formBlock">
<MkButton class="button" inline primary style="margin-right: 12px;" @click="save(ad)"><i class="fas fa-save"></i> {{ i18n.ts.save }}</MkButton>
<MkButton class="button" inline danger @click="remove(ad)"><i class="fas fa-trash-alt"></i> {{ i18n.ts.remove }}</MkButton>
</div>
</div>
</div>
</MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import { } from 'vue';
import XHeader from './_header_.vue';
import MkButton from '@/components/ui/button.vue';
import MkInput from '@/components/form/input.vue';
import MkTextarea from '@/components/form/textarea.vue';
import FormRadios from '@/components/form/radios.vue';
import FormSplit from '@/components/form/split.vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
let ads: any[] = $ref([]);
os.api('admin/ad/list').then(adsResponse => {
ads = adsResponse;
});
function add() {
ads.unshift({
id: null,
memo: '',
place: 'square',
priority: 'middle',
ratio: 1,
url: '',
imageUrl: null,
expiresAt: null,
});
}
function remove(ad) {
os.confirm({
type: 'warning',
text: i18n.t('removeAreYouSure', { x: ad.url }),
}).then(({ canceled }) => {
if (canceled) return;
ads = ads.filter(x => x !== ad);
os.apiWithDialog('admin/ad/delete', {
id: ad.id,
});
});
}
function save(ad) {
if (ad.id == null) {
os.apiWithDialog('admin/ad/create', {
...ad,
expiresAt: new Date(ad.expiresAt).getTime(),
});
} else {
os.apiWithDialog('admin/ad/update', {
...ad,
expiresAt: new Date(ad.expiresAt).getTime(),
});
}
}
const headerActions = $computed(() => [{
asFullButton: true,
icon: 'fas fa-plus',
text: i18n.ts.add,
handler: add,
}]);
const headerTabs = $computed(() => []);
definePageMetadata({
title: i18n.ts.ads,
icon: 'fas fa-audio-description',
bg: 'var(--bg)',
});
</script>
<style lang="scss" scoped>
.uqshojas {
> .ad {
padding: 32px;
&:not(:last-child) {
margin-bottom: var(--margin);
}
}
}
</style>

View file

@ -126,11 +126,6 @@ const menuDef = $computed(() => [{
text: i18n.ts.announcements, text: i18n.ts.announcements,
to: '/admin/announcements', to: '/admin/announcements',
active: props.initialPage === 'announcements', active: props.initialPage === 'announcements',
}, {
icon: 'fas fa-audio-description',
text: i18n.ts.ads,
to: '/admin/ads',
active: props.initialPage === 'ads',
}, { }, {
icon: 'fas fa-exclamation-circle', icon: 'fas fa-exclamation-circle',
text: i18n.ts.abuseReports, text: i18n.ts.abuseReports,
@ -205,7 +200,6 @@ const component = $computed(() => {
case 'queue': return defineAsyncComponent(() => import('./queue.vue')); case 'queue': return defineAsyncComponent(() => import('./queue.vue'));
case 'files': return defineAsyncComponent(() => import('./files.vue')); case 'files': return defineAsyncComponent(() => import('./files.vue'));
case 'announcements': return defineAsyncComponent(() => import('./announcements.vue')); case 'announcements': return defineAsyncComponent(() => import('./announcements.vue'));
case 'ads': return defineAsyncComponent(() => import('./ads.vue'));
case 'database': return defineAsyncComponent(() => import('./database.vue')); case 'database': return defineAsyncComponent(() => import('./database.vue'));
case 'abuses': return defineAsyncComponent(() => import('./abuses.vue')); case 'abuses': return defineAsyncComponent(() => import('./abuses.vue'));
case 'settings': return defineAsyncComponent(() => import('./settings.vue')); case 'settings': return defineAsyncComponent(() => import('./settings.vue'));

View file

@ -11,7 +11,7 @@
</template> </template>
<template #default="{ items }"> <template #default="{ items }">
<XList v-slot="{ item }" :items="items" :direction="'down'" :no-gap="false" :ad="false"> <XList v-slot="{ item }" :items="items" :direction="'down'" :no-gap="false">
<XNote :key="item.id" :note="item.note" :class="$style.note"/> <XNote :key="item.id" :note="item.note" :class="$style.note"/>
</XList> </XList>
</template> </template>

View file

@ -47,7 +47,6 @@
<div><i class="far fa-clock"></i> {{ $ts.createdAt }}: <MkTime :time="page.createdAt" mode="detail"/></div> <div><i class="far fa-clock"></i> {{ $ts.createdAt }}: <MkTime :time="page.createdAt" mode="detail"/></div>
<div v-if="page.createdAt != page.updatedAt"><i class="far fa-clock"></i> {{ $ts.updatedAt }}: <MkTime :time="page.updatedAt" mode="detail"/></div> <div v-if="page.createdAt != page.updatedAt"><i class="far fa-clock"></i> {{ $ts.updatedAt }}: <MkTime :time="page.updatedAt" mode="detail"/></div>
</div> </div>
<MkAd :prefer="['horizontal', 'horizontal-big']"/>
<MkContainer :max-height="300" :foldable="true" class="other"> <MkContainer :max-height="300" :foldable="true" class="other">
<template #header><i class="fas fa-clock"></i> {{ $ts.recentPosts }}</template> <template #header><i class="fas fa-clock"></i> {{ $ts.recentPosts }}</template>
<MkPagination v-slot="{items}" :pagination="otherPostsPagination"> <MkPagination v-slot="{items}" :pagination="otherPostsPagination">

View file

@ -1,7 +1,6 @@
<template> <template>
<div class="ddiqwdnk"> <div class="ddiqwdnk">
<XWidgets class="widgets" :edit="editMode" :widgets="$store.reactiveState.widgets.value.filter(w => w.place === place)" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/> <XWidgets class="widgets" :edit="editMode" :widgets="$store.reactiveState.widgets.value.filter(w => w.place === place)" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/>
<MkAd class="a" :prefer="['square']"/>
<button v-if="editMode" class="_textButton edit" style="font-size: 0.9em;" @click="editMode = false"><i class="fas fa-check"></i> {{ $ts.editWidgetsExit }}</button> <button v-if="editMode" class="_textButton edit" style="font-size: 0.9em;" @click="editMode = false"><i class="fas fa-check"></i> {{ $ts.editWidgetsExit }}</button>
<button v-else class="_textButton edit" style="font-size: 0.9em;" @click="editMode = true"><i class="fas fa-pencil-alt"></i> {{ $ts.editWidgets }}</button> <button v-else class="_textButton edit" style="font-size: 0.9em;" @click="editMode = true"><i class="fas fa-pencil-alt"></i> {{ $ts.editWidgets }}</button>