mirror of
https://iceshrimp.dev/limepotato/jormungandr-bite.git
synced 2024-11-22 01:47:39 -07:00
Remove support for ads
This commit is contained in:
parent
a3fb1f19e0
commit
5a2ca61f26
33 changed files with 76 additions and 794 deletions
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {}
|
|
@ -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;
|
||||
|
|
|
@ -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 {}
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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),
|
||||
});
|
||||
});
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 }) {
|
||||
|
@ -90,20 +84,9 @@ 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 () =>
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
:direction="pagination.reversed ? 'up' : 'down'"
|
||||
:reversed="pagination.reversed"
|
||||
:no-gap="noGap"
|
||||
:ad="true"
|
||||
class="notes"
|
||||
>
|
||||
<XNote
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
|
@ -20,7 +20,6 @@
|
|||
:items="items"
|
||||
:direction="'down'"
|
||||
:no-gap="false"
|
||||
:ad="false"
|
||||
>
|
||||
<XNote
|
||||
:key="item.id"
|
||||
|
|
|
@ -111,7 +111,6 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<MkAd :prefer="['inline', 'inline-big']" />
|
||||
<MkContainer
|
||||
:max-height="300"
|
||||
:foldable="true"
|
||||
|
|
|
@ -168,7 +168,6 @@
|
|||
</template>
|
||||
</div> -->
|
||||
</div>
|
||||
<MkAd :prefer="['inline', 'inline-big']" />
|
||||
<MkContainer
|
||||
:max-height="300"
|
||||
:foldable="true"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -83,7 +83,6 @@ const defaultStoreSaveKeys: (keyof (typeof defaultStore)["state"])[] = [
|
|||
"overridedDeviceKind",
|
||||
"serverDisconnectedBehavior",
|
||||
"nsfw",
|
||||
"showAds",
|
||||
"animation",
|
||||
"animatedMfm",
|
||||
"loadRawImages",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import {
|
||||
Ad,
|
||||
Announcement,
|
||||
Antenna,
|
||||
App,
|
||||
|
|
|
@ -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 = {
|
||||
|
|
Loading…
Reference in a new issue