Remove support for ads

This commit is contained in:
Laura Hausmann 2023-07-26 23:02:37 +02:00
parent a3fb1f19e0
commit 5a2ca61f26
Signed by: zotan
GPG key ID: D044E84C5BE01605
33 changed files with 76 additions and 794 deletions

View file

@ -119,11 +119,11 @@
- [DragonflyDB](https://dragonflydb.io/) support as a Redis alternative
- Link verification
- Importing posts from other Misskey/Mastodon/Pleroma/Akkoma instances
- Removed ability to serve ads
## Implemented (remote)
- MissV: [fix Misskey Forkbomb](https://code.vtopia.live/Vtopia/MissV/commit/40b23c070bd4adbb3188c73546c6c625138fb3c1)
- [Make showing ads optional](https://github.com/misskey-dev/misskey/pull/8996)
- [Tapping avatar in mobile opens account modal](https://github.com/misskey-dev/misskey/pull/9056)
- [OAuth bearer token authentication](https://github.com/misskey-dev/misskey/pull/9021)
- [Styled Repair Tools](https://github.com/misskey-dev/misskey/pull/8956)

View file

@ -3,6 +3,7 @@ pub use sea_orm_migration::prelude::*;
mod m20230531_180824_drop_reversi;
mod m20230627_185451_index_note_url;
mod m20230709_000510_move_antenna_to_cache;
mod m20230726_213530_drop_ads;
pub struct Migrator;
@ -13,6 +14,7 @@ impl MigratorTrait for Migrator {
Box::new(m20230531_180824_drop_reversi::Migration),
Box::new(m20230627_185451_index_note_url::Migration),
Box::new(m20230709_000510_move_antenna_to_cache::Migration),
Box::new(m20230726_213530_drop_ads::Migration),
]
}
}

View file

@ -0,0 +1,69 @@
use sea_orm::entity::prelude::*;
use sea_orm::Schema;
use sea_orm_migration::{
prelude::*,
sea_orm::{DbBackend, Statement},
};
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
if manager.get_database_backend() == DbBackend::Sqlite {
return Ok(());
}
let db = manager.get_connection();
db.query_one(Statement::from_string(
DbBackend::Postgres,
Table::drop()
.table(Entity)
.if_exists()
.to_string(PostgresQueryBuilder),
))
.await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
if manager.get_database_backend() == DbBackend::Sqlite {
return Ok(());
}
let db = manager.get_connection();
let builder = db.get_database_backend();
let schema = Schema::new(builder);
db.execute(builder.build(&schema.create_table_from_entity(Entity)))
.await?;
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
#[sea_orm(table_name = "ad")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
#[sea_orm(column_name = "createdAt")]
pub created_at: DateTimeWithTimeZone,
#[sea_orm(column_name = "expiresAt")]
pub expires_at: DateTimeWithTimeZone,
pub place: String,
pub priority: String,
pub url: String,
#[sea_orm(column_name = "imageUrl")]
pub image_url: String,
pub memo: String,
pub ratio: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -4,7 +4,6 @@ pub mod prelude;
pub mod abuse_user_report;
pub mod access_token;
pub mod ad;
pub mod announcement;
pub mod announcement_read;
pub mod antenna;

View file

@ -1,26 +0,0 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
#[sea_orm(table_name = "ad")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
#[sea_orm(column_name = "createdAt")]
pub created_at: DateTimeWithTimeZone,
#[sea_orm(column_name = "expiresAt")]
pub expires_at: DateTimeWithTimeZone,
pub place: String,
pub priority: String,
pub url: String,
#[sea_orm(column_name = "imageUrl")]
pub image_url: String,
pub memo: String,
pub ratio: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -2,7 +2,6 @@
pub use super::abuse_user_report::Entity as AbuseUserReport;
pub use super::access_token::Entity as AccessToken;
pub use super::ad::Entity as Ad;
pub use super::announcement::Entity as Announcement;
pub use super::announcement_read::Entity as AnnouncementRead;
pub use super::antenna::Entity as Antenna;

View file

@ -66,7 +66,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";
import { Webhook } from "@/models/entities/webhook.js";
@ -175,7 +174,6 @@ export const entities = [
ChannelFollowing,
ChannelNotePining,
RegistryItem,
Ad,
PasswordResetRequest,
UserPending,
Webhook,

View file

@ -1,65 +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;
}
}
}

View file

@ -60,7 +60,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";
@ -131,5 +130,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);

View file

@ -5,10 +5,6 @@ import * as ep___admin_abuseUserReports from "./endpoints/admin/abuse-user-repor
import * as ep___admin_accounts_create from "./endpoints/admin/accounts/create.js";
import * as ep___admin_accounts_delete from "./endpoints/admin/accounts/delete.js";
import * as ep___admin_accounts_hosted from "./endpoints/admin/accounts/hosted.js";
import * as ep___admin_ad_create from "./endpoints/admin/ad/create.js";
import * as ep___admin_ad_delete from "./endpoints/admin/ad/delete.js";
import * as ep___admin_ad_list from "./endpoints/admin/ad/list.js";
import * as ep___admin_ad_update from "./endpoints/admin/ad/update.js";
import * as ep___admin_announcements_create from "./endpoints/admin/announcements/create.js";
import * as ep___admin_announcements_delete from "./endpoints/admin/announcements/delete.js";
import * as ep___admin_announcements_list from "./endpoints/admin/announcements/list.js";
@ -351,10 +347,6 @@ const eps = [
["admin/accounts/create", ep___admin_accounts_create],
["admin/accounts/delete", ep___admin_accounts_delete],
["admin/accounts/hosted", ep___admin_accounts_hosted],
["admin/ad/create", ep___admin_ad_create],
["admin/ad/delete", ep___admin_ad_delete],
["admin/ad/list", ep___admin_ad_list],
["admin/ad/update", ep___admin_ad_update],
["admin/announcements/create", ep___admin_announcements_create],
["admin/announcements/delete", ep___admin_announcements_delete],
["admin/announcements/list", ep___admin_announcements_list],

View file

@ -1,46 +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;
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,
});
});

View file

@ -1,34 +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;
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);
});

View file

@ -1,32 +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;
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;
});

View file

@ -1,58 +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;
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),
});
});

View file

@ -136,35 +136,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,

View file

@ -2,7 +2,7 @@ import JSON5 from "json5";
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, MAX_CAPTION_TEXT_LENGTH } from "@/const.js";
import define from "../define.js";
@ -228,35 +228,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,
@ -423,12 +394,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,
@ -475,16 +440,7 @@ export default define(meta, paramDef, async (ps, me) => {
defaultDarkTheme: instance.defaultDarkTheme
? JSON.stringify(JSON5.parse(instance.defaultDarkTheme))
: null,
ads:
instance.privateMode && !me
? []
: ads.map((ad) => ({
id: ad.id,
url: ad.url,
place: ad.place,
ratio: ad.ratio,
imageUrl: ad.imageUrl,
})),
enableEmail: instance.enableEmail,
enableTwitterIntegration: instance.enableTwitterIntegration,

View file

@ -1,7 +1,6 @@
<script lang="ts">
import type { PropType } from "vue";
import { TransitionGroup, defineComponent, h } from "vue";
import MkAd from "@/components/global/MkAd.vue";
import { i18n } from "@/i18n";
import { defaultStore } from "@/store";
@ -27,12 +26,7 @@ export default defineComponent({
type: Boolean,
required: false,
default: false,
},
ad: {
type: Boolean,
required: false,
default: false,
},
}
},
setup(props, { slots, expose }) {
@ -91,18 +85,7 @@ export default defineComponent({
return [el, separator];
} else {
if (props.ad && item._shouldInsertAd_) {
return [
h(MkAd, {
class: "a", // advertise()
key: item.id + ":ad",
prefer: ["inline", "inline-big"],
}),
el,
];
} else {
return el;
}
return el;
}
});

View file

@ -20,7 +20,6 @@
:direction="pagination.reversed ? 'up' : 'down'"
:reversed="pagination.reversed"
:no-gap="noGap"
:ad="true"
class="notes"
>
<XNote

View file

@ -1,222 +0,0 @@
<template>
<div
v-for="chosenItem in chosen"
v-if="chosen && chosen.length > 0 && defaultStore.state.showAds"
class="qiivuoyo"
>
<div v-if="!showMenu" class="main" :class="chosenItem.place">
<a :href="chosenItem.url" target="_blank">
<img :src="chosenItem.imageUrl" />
</a>
</div>
</div>
<div v-else-if="chosen && defaultStore.state.showAds" class="qiivuoyo">
<div v-if="!showMenu" class="main" :class="chosen.place">
<a :href="chosen.url" target="_blank">
<img :src="chosen.imageUrl" />
<button class="_button menu" @click.prevent.stop="toggleMenu">
<span class="ph-info ph-bold ph-lg info-circle"></span>
</button>
</a>
</div>
<div v-else class="menu">
<div class="body">
<div>Ads by {{ host }}</div>
<!--<MkButton class="button" primary>{{ i18n.ts._ad.like }}</MkButton>-->
<MkButton
v-if="chosen.ratio !== 0"
class="button"
@click="reduceFrequency"
>{{ i18n.ts._ad.reduceFrequencyOfThisAd }}</MkButton
>
<button class="_textButton" @click="toggleMenu">
{{ i18n.ts._ad.back }}
</button>
</div>
</div>
</div>
<div v-else></div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { instance } from "@/instance";
import { host } from "@/config";
import MkButton from "@/components/MkButton.vue";
import { defaultStore } from "@/store";
import * as os from "@/os";
import { i18n } from "@/i18n";
type Ad = (typeof instance)["ads"][number];
const props = defineProps<{
prefer: string[];
specify?: Ad;
}>();
const showMenu = ref(false);
const toggleMenu = (): void => {
showMenu.value = !showMenu.value;
};
const choseAd = (): Ad | null => {
if (props.specify) {
return props.specify;
}
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);
const widgetAds = ads.filter((ad) => ad.place === "widget");
ads = ads.filter((ad) => ad.ratio !== 0);
if (widgetAds.length !== 0) {
return widgetAds;
} else 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());
function reduceFrequency(): void {
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;
}
</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;
border-radius: 5px;
}
> .menu {
position: absolute;
top: 1px;
right: 1px;
> .info-circle {
border: 3px solid var(--panel);
border-radius: 50%;
background: var(--panel);
}
}
}
&.widget {
> a,
> a > img {
max-width: min(300px, 100%);
max-height: 300px;
}
}
&.inline {
padding: 8px;
> a,
> a > img {
max-width: min(600px, 100%);
max-height: 80px;
}
}
&.inline-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/RouterView.vue";
import MkLoading from "./global/MkLoading.vue";
import MkError from "./global/MkError.vue";
import MkAd from "./global/MkAd.vue";
import MkPageHeader from "./global/MkPageHeader.vue";
import MkSpacer from "./global/MkSpacer.vue";
import MkStickyContainer from "./global/MkStickyContainer.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;

View file

@ -218,12 +218,6 @@ const menuDef = $computed(() => [
to: "/admin/announcements",
active: currentPage?.route.name === "announcements",
},
{
icon: "ph-money ph-bold ph-lg",
text: i18n.ts.ads,
to: "/admin/ads",
active: currentPage?.route.name === "ads",
},
{
icon: "ph-warning-circle ph-bold ph-lg",
text: i18n.ts.abuseReports,

View file

@ -1,158 +0,0 @@
<template>
<MkStickyContainer>
<template #header
><MkPageHeader
:actions="headerActions"
:tabs="headerTabs"
:display-back-button="true"
/></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="widget">widget</option>
<option value="inline">inline</option>
<option value="inline-big">inline-big</option>
</FormRadios>
<FormSplit>
<MkInput
:disabled="ad.place === 'widget'"
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="ph-floppy-disk-back ph-bold ph-lg"></i>
{{ i18n.ts.save }}</MkButton
>
<MkButton
class="button"
inline
danger
@click="remove(ad)"
><i class="ph-trash ph-bold ph-lg"></i>
{{ i18n.ts.remove }}</MkButton
>
</div>
</div>
</div>
</MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import {} from "vue";
import MkButton from "@/components/MkButton.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";
import { formatDateTimeString } from "@/scripts/format-time-string";
let ads: any[] = $ref([]);
os.api("admin/ad/list").then((adsResponse) => {
ads = adsResponse;
// The date format should be changed to yyyy-MM-dd in order to be properly displayed
for (let i in ads) {
ads[i].expiresAt = ads[i].expiresAt.substr(0, 10);
}
});
function add() {
const tomorrow = formatDateTimeString(
new Date(new Date().setDate(new Date().getDate() + 1)),
"yyyy-MM-dd",
);
ads.unshift({
id: null,
memo: "",
place: "widget",
priority: "middle",
ratio: 1,
url: "",
imageUrl: null,
expiresAt: tomorrow,
});
}
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: "ph-plus ph-bold ph-lg",
text: i18n.ts.add,
handler: add,
},
]);
const headerTabs = $computed(() => []);
definePageMetadata({
title: i18n.ts.ads,
icon: "ph-money ph-bold ph-lg",
});
</script>
<style lang="scss" scoped>
.uqshojas {
> .ad {
padding: 32px;
&:not(:last-child) {
margin-bottom: var(--margin);
}
}
}
</style>

View file

@ -20,7 +20,6 @@
:items="items"
:direction="'down'"
:no-gap="false"
:ad="false"
>
<XNote
:key="item.id"

View file

@ -111,7 +111,6 @@
/>
</div>
</div>
<MkAd :prefer="['inline', 'inline-big']" />
<MkContainer
:max-height="300"
:foldable="true"

View file

@ -168,7 +168,6 @@
</template>
</div> -->
</div>
<MkAd :prefer="['inline', 'inline-big']" />
<MkContainer
:max-height="300"
:foldable="true"

View file

@ -155,9 +155,6 @@
<FormSection>
<template #label>{{ i18n.ts.appearance }}</template>
<FormSwitch v-model="showAds" class="_formBlock">{{
i18n.ts.showAds
}}</FormSwitch>
<FormSwitch v-model="useBlurEffect" class="_formBlock">{{
i18n.ts.useBlurEffect
}}</FormSwitch>
@ -298,7 +295,6 @@ const useBlurEffect = computed(defaultStore.makeGetterSetter("useBlurEffect"));
const showGapBetweenNotesInTimeline = computed(
defaultStore.makeGetterSetter("showGapBetweenNotesInTimeline"),
);
const showAds = computed(defaultStore.makeGetterSetter("showAds"));
const advancedMfm = computed(defaultStore.makeGetterSetter("advancedMfm"));
const autoplayMfm = computed(
defaultStore.makeGetterSetter(
@ -392,7 +388,6 @@ watch(
showGapBetweenNotesInTimeline,
instanceTicker,
overridedDeviceKind,
showAds,
showUpdates,
swipeOnMobile,
swipeOnDesktop,

View file

@ -83,7 +83,6 @@ const defaultStoreSaveKeys: (keyof (typeof defaultStore)["state"])[] = [
"overridedDeviceKind",
"serverDisconnectedBehavior",
"nsfw",
"showAds",
"animation",
"animatedMfm",
"loadRawImages",

View file

@ -484,11 +484,6 @@ export const routes = [
name: "announcements",
component: page(() => import("./pages/admin/announcements.vue")),
},
{
path: "/ads",
name: "ads",
component: page(() => import("./pages/admin/promotions.vue")),
},
{
path: "/database",
name: "database",

View file

@ -102,14 +102,6 @@ export const defaultStore = markRaw(
where: "account",
default: [],
},
mutedAds: {
where: "account",
default: [] as string[],
},
showAds: {
where: "account",
default: true,
},
menu: {
where: "deviceAccount",
default: menuOptions,

View file

@ -11,7 +11,6 @@
>{{ column.name }}</template
>
<div class="wtdtxvec">
<MkAd class="a" :prefer="['widget']" />
<div
v-if="!(column.widgets && column.widgets.length > 0) && !edit"
class="intro"

View file

@ -1,6 +1,5 @@
<template>
<aside class="widgets" :aria-label="i18n.ts._deck._columns.widgets">
<MkAd class="a" :prefer="['widget']" />
<XWidgets
:edit="editMode"
:widgets="defaultStore.reactiveState.widgets.value"

View file

@ -1,5 +1,4 @@
import {
Ad,
Announcement,
Antenna,
App,

View file

@ -301,13 +301,6 @@ export type LiteInstanceMetadata = {
enableDiscordIntegration: boolean;
enableServiceWorker: boolean;
emojis: CustomEmoji[];
ads: {
id: ID;
ratio: number;
place: string;
url: string;
imageUrl: string;
}[];
};
export type DetailedInstanceMetadata = LiteInstanceMetadata & {
@ -407,8 +400,6 @@ export type AuthSession = {
token: string;
};
export type Ad = TODO;
export type Clip = TODO;
export type NoteFavorite = {