remove ads #5
38 changed files with 17 additions and 678 deletions
|
@ -734,7 +734,6 @@ gallery: "المعرض"
|
|||
recentPosts: "المشاركات الحديثة"
|
||||
popularPosts: "المشاركات المتداولة"
|
||||
shareWithNote: "شاركه في ملاحظة"
|
||||
ads: "الإعلانات"
|
||||
expiration: "ينتهي استطلاع الرأي في"
|
||||
memo: "تذكير"
|
||||
priority: "الأولوية"
|
||||
|
|
|
@ -765,7 +765,6 @@ gallery: "গ্যালারী"
|
|||
recentPosts: "নতুন পোস্ট"
|
||||
popularPosts: "জনপ্রিয় পোস্ট"
|
||||
shareWithNote: "নোটের মাধ্যমে শেয়ার করুন"
|
||||
ads: "বিজ্ঞাপন"
|
||||
expiration: "নির্দিষ্ট সময়সীমা"
|
||||
memo: "মেমো"
|
||||
priority: "অগ্রাধিকার"
|
||||
|
|
|
@ -766,7 +766,6 @@ gallery: "Galerie"
|
|||
recentPosts: "Neue Beiträge"
|
||||
popularPosts: "Beliebte Beiträge"
|
||||
shareWithNote: "Mit Notiz teilen"
|
||||
ads: "Werbung"
|
||||
expiration: "Frist"
|
||||
memo: "Merkzettel"
|
||||
priority: "Priorität"
|
||||
|
|
|
@ -766,7 +766,6 @@ gallery: "Gallery"
|
|||
recentPosts: "Recent posts"
|
||||
popularPosts: "Popular posts"
|
||||
shareWithNote: "Share with note"
|
||||
ads: "Advertisements"
|
||||
expiration: "Deadline"
|
||||
memo: "Memo"
|
||||
priority: "Priority"
|
||||
|
|
|
@ -761,7 +761,6 @@ gallery: "Galería"
|
|||
recentPosts: "Posts recientes"
|
||||
popularPosts: "Más vistos"
|
||||
shareWithNote: "Compartir con una nota"
|
||||
ads: "Anuncios"
|
||||
expiration: "Termina el"
|
||||
memo: "Notas"
|
||||
priority: "Prioridad"
|
||||
|
|
|
@ -760,7 +760,6 @@ gallery: "Galerie"
|
|||
recentPosts: "Les plus récentes"
|
||||
popularPosts: "Les plus consultées"
|
||||
shareWithNote: "Partager dans une note"
|
||||
ads: "Publicité"
|
||||
expiration: "Échéance"
|
||||
memo: "Pense-bête"
|
||||
priority: "Priorité"
|
||||
|
|
|
@ -765,7 +765,6 @@ gallery: "Galeri"
|
|||
recentPosts: "Postingan terbaru"
|
||||
popularPosts: "Postingan populer"
|
||||
shareWithNote: "Bagikan dengan catatan"
|
||||
ads: "Iklan"
|
||||
expiration: "Batas akhir"
|
||||
memo: "Memo"
|
||||
priority: "Prioritas"
|
||||
|
|
|
@ -753,7 +753,6 @@ gallery: "Galleria"
|
|||
recentPosts: "Le più recenti"
|
||||
popularPosts: "Le più visualizzate"
|
||||
shareWithNote: "Condividere in nota"
|
||||
ads: "Pubblicità"
|
||||
expiration: "Scadenza"
|
||||
memo: "Promemoria"
|
||||
priority: "Priorità"
|
||||
|
|
|
@ -768,7 +768,6 @@ gallery: "ギャラリー"
|
|||
recentPosts: "最近の投稿"
|
||||
popularPosts: "人気の投稿"
|
||||
shareWithNote: "ノートで共有"
|
||||
ads: "広告"
|
||||
expiration: "期限"
|
||||
memo: "メモ"
|
||||
priority: "優先度"
|
||||
|
|
|
@ -645,7 +645,6 @@ goBack: "戻る"
|
|||
info: "情報"
|
||||
user: "ユーザー"
|
||||
administration: "管理"
|
||||
ads: "広告"
|
||||
expiration: "期限"
|
||||
memo: "メモ"
|
||||
high: "高い"
|
||||
|
|
|
@ -765,7 +765,6 @@ gallery: "갤러리"
|
|||
recentPosts: "최근 포스트"
|
||||
popularPosts: "인기 포스트"
|
||||
shareWithNote: "노트로 공유"
|
||||
ads: "광고"
|
||||
expiration: "기한"
|
||||
memo: "메모"
|
||||
priority: "우선순위"
|
||||
|
|
|
@ -739,7 +739,6 @@ gallery: "Galeria"
|
|||
recentPosts: "Ostatnie wpisy"
|
||||
popularPosts: "Popularne wpisy"
|
||||
shareWithNote: "Udostępnij z wpisem"
|
||||
ads: "Reklamy"
|
||||
expiration: "Ankieta kończy się"
|
||||
memo: "Notatki"
|
||||
priority: "Priorytet"
|
||||
|
|
|
@ -763,7 +763,6 @@ gallery: "Галерея"
|
|||
recentPosts: "Недавние публикации"
|
||||
popularPosts: "Популярные публикации"
|
||||
shareWithNote: "Поделиться заметкой"
|
||||
ads: "Реклама"
|
||||
expiration: "Опрос длится"
|
||||
memo: "Памятка"
|
||||
priority: "Приоритет"
|
||||
|
|
|
@ -764,7 +764,6 @@ gallery: "Galéria"
|
|||
recentPosts: "Najnovšie príspevky"
|
||||
popularPosts: "Populárne príspevky"
|
||||
shareWithNote: "Zdieľať s poznámkou"
|
||||
ads: "Reklamy"
|
||||
expiration: "Ukončiť hlasovanie"
|
||||
memo: "Memo"
|
||||
priority: "Priorita"
|
||||
|
|
|
@ -765,7 +765,6 @@ gallery: "Thư viện ảnh"
|
|||
recentPosts: "Tút gần đây"
|
||||
popularPosts: "Tút được xem nhiều nhất"
|
||||
shareWithNote: "Chia sẻ kèm với tút"
|
||||
ads: "Quảng cáo"
|
||||
expiration: "Thời hạn"
|
||||
memo: "Lưu ý"
|
||||
priority: "Ưu tiên"
|
||||
|
|
|
@ -765,7 +765,6 @@ gallery: "图库"
|
|||
recentPosts: "最新发布"
|
||||
popularPosts: "热门投稿"
|
||||
shareWithNote: "在帖子中分享"
|
||||
ads: "广告"
|
||||
expiration: "截止时间"
|
||||
memo: "便笺"
|
||||
priority: "优先级"
|
||||
|
|
|
@ -765,7 +765,6 @@ gallery: "相簿"
|
|||
recentPosts: "最新貼文"
|
||||
popularPosts: "熱門的貼文"
|
||||
shareWithNote: "在貼文中分享"
|
||||
ads: "廣告"
|
||||
expiration: "期限"
|
||||
memo: "備忘錄"
|
||||
priority: "優先級"
|
||||
|
|
12
packages/backend/migration/1657570176749-remove-ads.ts
Normal file
12
packages/backend/migration/1657570176749-remove-ads.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
export class removeAds1657570176749 {
|
||||
name = 'removeAds1657570176749'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`DROP TABLE "ad"`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`CREATE TABLE public.ad ("id" character varying(32) NOT NULL, "createdAt" timestamp with time zone NOT NULL, "expiresAt" timestamp with time zone NOT NULL, "place" character varying(32) NOT NULL, "priority" character varying(32) NOT NULL, "url" character varying(1024) NOT NULL, "imageUrl" character varying(1024) NOT NULL, "memo" character varying(8192) NOT NULL, "ratio" integer DEFAULT 1 NOT NULL)`);
|
||||
}
|
||||
|
||||
}
|
|
@ -65,7 +65,6 @@ import { Channel } from '@/models/entities/channel.js';
|
|||
import { ChannelFollowing } from '@/models/entities/channel-following.js';
|
||||
import { ChannelNotePining } from '@/models/entities/channel-note-pining.js';
|
||||
import { RegistryItem } from '@/models/entities/registry-item.js';
|
||||
import { Ad } from '@/models/entities/ad.js';
|
||||
import { PasswordResetRequest } from '@/models/entities/password-reset-request.js';
|
||||
import { UserPending } from '@/models/entities/user-pending.js';
|
||||
|
||||
|
@ -168,7 +167,6 @@ export const entities = [
|
|||
ChannelFollowing,
|
||||
ChannelNotePining,
|
||||
RegistryItem,
|
||||
Ad,
|
||||
PasswordResetRequest,
|
||||
UserPending,
|
||||
Webhook,
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
import { Entity, Index, Column, PrimaryColumn } from 'typeorm';
|
||||
import { id } from '../id.js';
|
||||
|
||||
@Entity()
|
||||
export class Ad {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the Ad.',
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The expired date of the Ad.',
|
||||
})
|
||||
public expiresAt: Date;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 32, nullable: false,
|
||||
})
|
||||
public place: string;
|
||||
|
||||
// 今は使われていないが将来的に活用される可能性はある
|
||||
@Column('varchar', {
|
||||
length: 32, nullable: false,
|
||||
})
|
||||
public priority: string;
|
||||
|
||||
@Column('integer', {
|
||||
default: 1, nullable: false,
|
||||
})
|
||||
public ratio: number;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024, nullable: false,
|
||||
})
|
||||
public url: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024, nullable: false,
|
||||
})
|
||||
public imageUrl: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 8192, nullable: false,
|
||||
})
|
||||
public memo: string;
|
||||
|
||||
constructor(data: Partial<Ad>) {
|
||||
if (data == null) return;
|
||||
|
||||
for (const [k, v] of Object.entries(data)) {
|
||||
(this as any)[k] = v;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -59,7 +59,6 @@ import { MutedNote } from './entities/muted-note.js';
|
|||
import { ChannelFollowing } from './entities/channel-following.js';
|
||||
import { ChannelNotePining } from './entities/channel-note-pining.js';
|
||||
import { RegistryItem } from './entities/registry-item.js';
|
||||
import { Ad } from './entities/ad.js';
|
||||
import { PasswordResetRequest } from './entities/password-reset-request.js';
|
||||
import { UserPending } from './entities/user-pending.js';
|
||||
import { InstanceRepository } from './repositories/instance.js';
|
||||
|
@ -126,5 +125,4 @@ export const ChannelFollowings = db.getRepository(ChannelFollowing);
|
|||
export const ChannelNotePinings = db.getRepository(ChannelNotePining);
|
||||
export const RegistryItems = db.getRepository(RegistryItem);
|
||||
export const Webhooks = db.getRepository(Webhook);
|
||||
export const Ads = db.getRepository(Ad);
|
||||
export const PasswordResetRequests = db.getRepository(PasswordResetRequest);
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
import define from '../../../define.js';
|
||||
import { Ads } from '@/models/index.js';
|
||||
import { genId } from '@/misc/gen-id.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
url: { type: 'string', minLength: 1 },
|
||||
memo: { type: 'string' },
|
||||
place: { type: 'string' },
|
||||
priority: { type: 'string' },
|
||||
ratio: { type: 'integer' },
|
||||
expiresAt: { type: 'integer' },
|
||||
imageUrl: { type: 'string', minLength: 1 },
|
||||
},
|
||||
required: ['url', 'memo', 'place', 'priority', 'ratio', 'expiresAt', 'imageUrl'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps) => {
|
||||
await Ads.insert({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
expiresAt: new Date(ps.expiresAt),
|
||||
url: ps.url,
|
||||
imageUrl: ps.imageUrl,
|
||||
priority: ps.priority,
|
||||
ratio: ps.ratio,
|
||||
place: ps.place,
|
||||
memo: ps.memo,
|
||||
});
|
||||
});
|
|
@ -1,35 +0,0 @@
|
|||
import define from '../../../define.js';
|
||||
import { Ads } from '@/models/index.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
|
||||
errors: {
|
||||
noSuchAd: {
|
||||
message: 'No such ad.',
|
||||
code: 'NO_SUCH_AD',
|
||||
id: 'ccac9863-3a03-416e-b899-8a64041118b1',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['id'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps, me) => {
|
||||
const ad = await Ads.findOneBy({ id: ps.id });
|
||||
|
||||
if (ad == null) throw new ApiError(meta.errors.noSuchAd);
|
||||
|
||||
await Ads.delete(ad.id);
|
||||
});
|
|
@ -1,30 +0,0 @@
|
|||
import define from '../../../define.js';
|
||||
import { Ads } from '@/models/index.js';
|
||||
import { makePaginationQuery } from '../../../common/make-pagination-query.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps) => {
|
||||
const query = makePaginationQuery(Ads.createQueryBuilder('ad'), ps.sinceId, ps.untilId)
|
||||
.andWhere('ad.expiresAt > :now', { now: new Date() });
|
||||
|
||||
const ads = await query.take(ps.limit).getMany();
|
||||
|
||||
return ads;
|
||||
});
|
|
@ -1,50 +0,0 @@
|
|||
import define from '../../../define.js';
|
||||
import { Ads } from '@/models/index.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
|
||||
errors: {
|
||||
noSuchAd: {
|
||||
message: 'No such ad.',
|
||||
code: 'NO_SUCH_AD',
|
||||
id: 'b7aa1727-1354-47bc-a182-3a9c3973d300',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', format: 'misskey:id' },
|
||||
memo: { type: 'string' },
|
||||
url: { type: 'string', minLength: 1 },
|
||||
imageUrl: { type: 'string', minLength: 1 },
|
||||
place: { type: 'string' },
|
||||
priority: { type: 'string' },
|
||||
ratio: { type: 'integer' },
|
||||
expiresAt: { type: 'integer' },
|
||||
},
|
||||
required: ['id', 'memo', 'url', 'imageUrl', 'place', 'priority', 'ratio', 'expiresAt'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps, me) => {
|
||||
const ad = await Ads.findOneBy({ id: ps.id });
|
||||
|
||||
if (ad == null) throw new ApiError(meta.errors.noSuchAd);
|
||||
|
||||
await Ads.update(ad.id, {
|
||||
url: ps.url,
|
||||
place: ps.place,
|
||||
priority: ps.priority,
|
||||
ratio: ps.ratio,
|
||||
memo: ps.memo,
|
||||
imageUrl: ps.imageUrl,
|
||||
expiresAt: new Date(ps.expiresAt),
|
||||
});
|
||||
});
|
|
@ -97,30 +97,6 @@ export const meta = {
|
|||
},
|
||||
},
|
||||
},
|
||||
ads: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
place: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'url',
|
||||
},
|
||||
imageUrl: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'url',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
enableEmail: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { IsNull, MoreThan } from 'typeorm';
|
||||
import config from '@/config/index.js';
|
||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||
import { Ads, Emojis, Users } from '@/models/index.js';
|
||||
import { Emojis, Users } from '@/models/index.js';
|
||||
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||
import define from '../define.js';
|
||||
|
||||
|
@ -168,30 +168,6 @@ export const meta = {
|
|||
},
|
||||
},
|
||||
},
|
||||
ads: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
place: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'url',
|
||||
},
|
||||
imageUrl: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'url',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
requireSetup: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
|
@ -310,12 +286,6 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
},
|
||||
});
|
||||
|
||||
const ads = await Ads.find({
|
||||
where: {
|
||||
expiresAt: MoreThan(new Date()),
|
||||
},
|
||||
});
|
||||
|
||||
const response: any = {
|
||||
maintainerName: instance.maintainerName,
|
||||
maintainerEmail: instance.maintainerEmail,
|
||||
|
@ -349,13 +319,6 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
emojis: await Emojis.packMany(emojis),
|
||||
defaultLightTheme: instance.defaultLightTheme,
|
||||
defaultDarkTheme: instance.defaultDarkTheme,
|
||||
ads: ads.map(ad => ({
|
||||
id: ad.id,
|
||||
url: ad.url,
|
||||
place: ad.place,
|
||||
ratio: ad.ratio,
|
||||
imageUrl: ad.imageUrl,
|
||||
})),
|
||||
enableEmail: instance.enableEmail,
|
||||
|
||||
enableTwitterIntegration: instance.enableTwitterIntegration,
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, h, PropType, TransitionGroup } from 'vue';
|
||||
import MkAd from '@/components/global/ad.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
items: {
|
||||
type: Array as PropType<{ id: string; createdAt: string; _shouldInsertAd_: boolean; }[]>,
|
||||
type: Array as PropType<{ id: string; createdAt: string; }[]>,
|
||||
required: true,
|
||||
},
|
||||
direction: {
|
||||
|
@ -25,11 +24,6 @@ export default defineComponent({
|
|||
required: false,
|
||||
default: false
|
||||
},
|
||||
ad: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
|
||||
setup(props, { slots, expose }) {
|
||||
|
@ -77,17 +71,9 @@ export default defineComponent({
|
|||
]));
|
||||
|
||||
return [el, separator];
|
||||
} else {
|
||||
if (props.ad && item._shouldInsertAd_) {
|
||||
return [h(MkAd, {
|
||||
class: 'a', // advertiseの意(ブロッカー対策)
|
||||
key: item.id + ':ad',
|
||||
prefer: ['horizontal', 'horizontal-big'],
|
||||
}), el];
|
||||
} else {
|
||||
return el;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return () => h(
|
||||
|
|
|
@ -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>
|
|
@ -13,7 +13,6 @@ import I18n from './global/i18n';
|
|||
import RouterView from './global/router-view.vue';
|
||||
import MkLoading from './global/loading.vue';
|
||||
import MkError from './global/error.vue';
|
||||
import MkAd from './global/ad.vue';
|
||||
import MkPageHeader from './global/page-header.vue';
|
||||
import MkSpacer from './global/spacer.vue';
|
||||
import MkStickyContainer from './global/sticky-container.vue';
|
||||
|
@ -32,7 +31,6 @@ export default function(app: App) {
|
|||
app.component('MkUrl', MkUrl);
|
||||
app.component('MkLoading', MkLoading);
|
||||
app.component('MkError', MkError);
|
||||
app.component('MkAd', MkAd);
|
||||
app.component('MkPageHeader', MkPageHeader);
|
||||
app.component('MkSpacer', MkSpacer);
|
||||
app.component('MkStickyContainer', MkStickyContainer);
|
||||
|
@ -53,7 +51,6 @@ declare module '@vue/runtime-core' {
|
|||
MkUrl: typeof MkUrl;
|
||||
MkLoading: typeof MkLoading;
|
||||
MkError: typeof MkError;
|
||||
MkAd: typeof MkAd;
|
||||
MkPageHeader: typeof MkPageHeader;
|
||||
MkSpacer: typeof MkSpacer;
|
||||
MkStickyContainer: typeof MkStickyContainer;
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<template #default="{ items: notes }">
|
||||
<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"/>
|
||||
</XList>
|
||||
</div>
|
||||
|
|
|
@ -93,14 +93,6 @@ const init = async (): Promise<void> => {
|
|||
...params,
|
||||
limit: props.pagination.noPaging ? (props.pagination.limit || 10) : (props.pagination.limit || 10) + 1,
|
||||
}).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))) {
|
||||
res.pop();
|
||||
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,
|
||||
}),
|
||||
}).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) {
|
||||
res.pop();
|
||||
items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);
|
||||
|
|
|
@ -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>
|
|
@ -126,11 +126,6 @@ const menuDef = $computed(() => [{
|
|||
text: i18n.ts.announcements,
|
||||
to: '/admin/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',
|
||||
text: i18n.ts.abuseReports,
|
||||
|
@ -205,7 +200,6 @@ const component = $computed(() => {
|
|||
case 'queue': return defineAsyncComponent(() => import('./queue.vue'));
|
||||
case 'files': return defineAsyncComponent(() => import('./files.vue'));
|
||||
case 'announcements': return defineAsyncComponent(() => import('./announcements.vue'));
|
||||
case 'ads': return defineAsyncComponent(() => import('./ads.vue'));
|
||||
case 'database': return defineAsyncComponent(() => import('./database.vue'));
|
||||
case 'abuses': return defineAsyncComponent(() => import('./abuses.vue'));
|
||||
case 'settings': return defineAsyncComponent(() => import('./settings.vue'));
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</template>
|
||||
|
||||
<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"/>
|
||||
</XList>
|
||||
</template>
|
||||
|
|
|
@ -47,7 +47,6 @@
|
|||
<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>
|
||||
<MkAd :prefer="['horizontal', 'horizontal-big']"/>
|
||||
<MkContainer :max-height="300" :foldable="true" class="other">
|
||||
<template #header><i class="fas fa-clock"></i> {{ $ts.recentPosts }}</template>
|
||||
<MkPagination v-slot="{items}" :pagination="otherPostsPagination">
|
||||
|
|
|
@ -59,10 +59,6 @@ export const defaultStore = markRaw(new Storage('base', {
|
|||
where: 'account',
|
||||
default: [],
|
||||
},
|
||||
mutedAds: {
|
||||
where: 'account',
|
||||
default: [] as string[],
|
||||
},
|
||||
|
||||
menu: {
|
||||
where: 'deviceAccount',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<template>
|
||||
<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"/>
|
||||
<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-else class="_textButton edit" style="font-size: 0.9em;" @click="editMode = true"><i class="fas fa-pencil-alt"></i> {{ $ts.editWidgets }}</button>
|
||||
|
|
Loading…
Reference in a new issue