Frontend: Pagination by time and removed ads
ci/woodpecker/push/ociImagePush Pipeline was successful
Details
ci/woodpecker/push/ociImagePush Pipeline was successful
Details
This commit is contained in:
parent
6b96f60b43
commit
6003f5e404
|
@ -947,9 +947,6 @@ export type Endpoints = {
|
||||||
// pinned-users
|
// pinned-users
|
||||||
"pinned-users": { req: TODO; res: TODO };
|
"pinned-users": { req: TODO; res: TODO };
|
||||||
|
|
||||||
// promo
|
|
||||||
"promo/read": { req: TODO; res: TODO };
|
|
||||||
|
|
||||||
// request-reset-password
|
// request-reset-password
|
||||||
"request-reset-password": {
|
"request-reset-password": {
|
||||||
req: { username: string; email: string };
|
req: { username: string; email: string };
|
||||||
|
|
|
@ -25,14 +25,6 @@
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div class="line"></div>
|
<div class="line"></div>
|
||||||
<div v-if="appearNote._prId_" class="info">
|
|
||||||
<i class="ph-megaphone-simple-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts.promotion
|
|
||||||
}}<button class="_textButton hide" @click.stop="readPromo()">
|
|
||||||
{{ i18n.ts.hideThisNote }}
|
|
||||||
<i class="ph-x ph-bold ph-lg"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div v-if="appearNote._featuredId_" class="info">
|
<div v-if="appearNote._featuredId_" class="info">
|
||||||
<i class="ph-lightning ph-bold ph-lg"></i>
|
<i class="ph-lightning ph-bold ph-lg"></i>
|
||||||
{{ i18n.ts.featured }}
|
{{ i18n.ts.featured }}
|
||||||
|
@ -517,13 +509,6 @@ function noteClick(e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function readPromo() {
|
|
||||||
os.api("promo/read", {
|
|
||||||
noteId: appearNote.id,
|
|
||||||
});
|
|
||||||
isDeleted.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let postIsExpanded = ref(false);
|
let postIsExpanded = ref(false);
|
||||||
|
|
||||||
function setPostExpanded(val: boolean) {
|
function setPostExpanded(val: boolean) {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, h, PropType, TransitionGroup } from "vue";
|
import { defineComponent, h, PropType, TransitionGroup } from "vue";
|
||||||
import MkAd from "@/components/global/MkAd.vue";
|
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
|
|
||||||
|
@ -12,7 +11,6 @@ export default defineComponent({
|
||||||
id: string;
|
id: string;
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
created_at?: string;
|
created_at?: string;
|
||||||
_shouldInsertAd_: boolean;
|
|
||||||
}[]
|
}[]
|
||||||
>,
|
>,
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -102,20 +100,9 @@ export default defineComponent({
|
||||||
);
|
);
|
||||||
|
|
||||||
return [el, separator];
|
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;
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return () =>
|
return () =>
|
||||||
|
|
|
@ -67,22 +67,21 @@ import {
|
||||||
computed,
|
computed,
|
||||||
ComputedRef,
|
ComputedRef,
|
||||||
isRef,
|
isRef,
|
||||||
markRaw,
|
|
||||||
onActivated,
|
onActivated,
|
||||||
onDeactivated,
|
onDeactivated,
|
||||||
Ref,
|
|
||||||
ref,
|
ref,
|
||||||
watch,
|
watch,
|
||||||
} from "vue";
|
} from "vue";
|
||||||
import * as misskey from "calckey-js";
|
import * as misskey from "calckey-js";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import {
|
import {
|
||||||
onScrollTop,
|
|
||||||
isTopVisible,
|
|
||||||
getScrollPosition,
|
|
||||||
getScrollContainer,
|
getScrollContainer,
|
||||||
|
getScrollPosition,
|
||||||
|
isTopVisible,
|
||||||
|
onScrollTop,
|
||||||
} from "@/scripts/scroll";
|
} from "@/scripts/scroll";
|
||||||
import MkButton from "@/components/MkButton.vue";
|
import MkButton from "@/components/MkButton.vue";
|
||||||
|
import { magTransProperty } from "@/scripts-mag/mag-util";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
|
||||||
export type Paging<
|
export type Paging<
|
||||||
|
@ -90,9 +89,11 @@ export type Paging<
|
||||||
> = {
|
> = {
|
||||||
endpoint: E;
|
endpoint: E;
|
||||||
limit: number;
|
limit: number;
|
||||||
params?:
|
params?: (
|
||||||
| misskey.Endpoints[E]["req"]
|
| misskey.Endpoints[E]["req"]
|
||||||
| ComputedRef<misskey.Endpoints[E]["req"]>;
|
| ComputedRef<misskey.Endpoints[E]["req"]>
|
||||||
|
) &
|
||||||
|
Record<string, unknown>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 検索APIのような、ページング不可なエンドポイントを利用する場合
|
* 検索APIのような、ページング不可なエンドポイントを利用する場合
|
||||||
|
@ -125,7 +126,7 @@ const emit = defineEmits<{
|
||||||
(ev: "queue", count: number): void;
|
(ev: "queue", count: number): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
type Item = { id: string; [another: string]: unknown };
|
type Item = { id: string; createdAt?: string; created_at?: string } & any;
|
||||||
|
|
||||||
const rootEl = ref<HTMLElement>();
|
const rootEl = ref<HTMLElement>();
|
||||||
const items = ref<Item[]>([]);
|
const items = ref<Item[]>([]);
|
||||||
|
@ -156,14 +157,6 @@ const init = async (): Promise<void> => {
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
(res) => {
|
(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 (
|
if (
|
||||||
!props.pagination.noPaging &&
|
!props.pagination.noPaging &&
|
||||||
res.length > (props.pagination.limit || 10)
|
res.length > (props.pagination.limit || 10)
|
||||||
|
@ -195,7 +188,7 @@ const reload = (): void => {
|
||||||
init();
|
init();
|
||||||
};
|
};
|
||||||
|
|
||||||
const refresh = async (): void => {
|
const refresh = async (): Promise<void> => {
|
||||||
const params = props.pagination.params
|
const params = props.pagination.params
|
||||||
? isRef(props.pagination.params)
|
? isRef(props.pagination.params)
|
||||||
? props.pagination.params.value
|
? props.pagination.params.value
|
||||||
|
@ -258,22 +251,28 @@ const fetchMore = async (): Promise<void> => {
|
||||||
}
|
}
|
||||||
: props.pagination.reversed
|
: props.pagination.reversed
|
||||||
? {
|
? {
|
||||||
|
sinceDate: new Date(
|
||||||
|
magTransProperty(
|
||||||
|
items.value[0],
|
||||||
|
"createdAt",
|
||||||
|
"created_at"
|
||||||
|
)
|
||||||
|
).getTime(),
|
||||||
sinceId: items.value[0].id,
|
sinceId: items.value[0].id,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
|
untilDate: new Date(
|
||||||
|
magTransProperty(
|
||||||
|
items.value[items.value.length - 1],
|
||||||
|
"createdAt",
|
||||||
|
"created_at"
|
||||||
|
)
|
||||||
|
).getTime(),
|
||||||
untilId: items.value[items.value.length - 1].id,
|
untilId: items.value[items.value.length - 1].id,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
(res) => {
|
(res) => {
|
||||||
for (let i = 0; i < res.length; i++) {
|
|
||||||
const item = res[i];
|
|
||||||
if (props.pagination.reversed) {
|
|
||||||
if (i === res.length - 9) item._shouldInsertAd_ = true;
|
|
||||||
} else {
|
|
||||||
if (i === 10) item._shouldInsertAd_ = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (res.length > SECOND_FETCH_LIMIT) {
|
if (res.length > SECOND_FETCH_LIMIT) {
|
||||||
res.pop();
|
res.pop();
|
||||||
items.value = props.pagination.reversed
|
items.value = props.pagination.reversed
|
||||||
|
@ -319,10 +318,24 @@ const fetchMoreAhead = async (): Promise<void> => {
|
||||||
}
|
}
|
||||||
: props.pagination.reversed
|
: props.pagination.reversed
|
||||||
? {
|
? {
|
||||||
|
untilDate: new Date(
|
||||||
|
magTransProperty(
|
||||||
|
items.value[0],
|
||||||
|
"createdAt",
|
||||||
|
"created_at"
|
||||||
|
)
|
||||||
|
),
|
||||||
untilId: items.value[0].id,
|
untilId: items.value[0].id,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
sinceId: items.value[items.value.length - 1].id,
|
sinceDate: new Date(
|
||||||
|
magTransProperty(
|
||||||
|
items.value[items.value.length - 1],
|
||||||
|
"createdAt",
|
||||||
|
"created_at"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
sinceId: items.value[items.value.length - 1].createdAt,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
|
|
|
@ -1,222 +0,0 @@
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
v-if="chosen && chosen.length > 0 && defaultStore.state.showAds"
|
|
||||||
v-for="chosenItem in chosen"
|
|
||||||
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>
|
|
|
@ -57,7 +57,6 @@ import I18n from "./components/global/i18n";
|
||||||
import RouterView from "./components/global/RouterView.vue";
|
import RouterView from "./components/global/RouterView.vue";
|
||||||
import MkLoading from "./components/global/MkLoading.vue";
|
import MkLoading from "./components/global/MkLoading.vue";
|
||||||
import MkError from "./components/global/MkError.vue";
|
import MkError from "./components/global/MkError.vue";
|
||||||
import MkAd from "./components/global/MkAd.vue";
|
|
||||||
import MkPageHeader from "./components/global/MkPageHeader.vue";
|
import MkPageHeader from "./components/global/MkPageHeader.vue";
|
||||||
import MkSpacer from "./components/global/MkSpacer.vue";
|
import MkSpacer from "./components/global/MkSpacer.vue";
|
||||||
import MkStickyContainer from "./components/global/MkStickyContainer.vue";
|
import MkStickyContainer from "./components/global/MkStickyContainer.vue";
|
||||||
|
@ -76,7 +75,6 @@ function globalComponents(app: App) {
|
||||||
app.component("MkUrl", MkUrl);
|
app.component("MkUrl", MkUrl);
|
||||||
app.component("MkLoading", MkLoading);
|
app.component("MkLoading", MkLoading);
|
||||||
app.component("MkError", MkError);
|
app.component("MkError", MkError);
|
||||||
app.component("MkAd", MkAd);
|
|
||||||
app.component("MkPageHeader", MkPageHeader);
|
app.component("MkPageHeader", MkPageHeader);
|
||||||
app.component("MkSpacer", MkSpacer);
|
app.component("MkSpacer", MkSpacer);
|
||||||
app.component("MkStickyContainer", MkStickyContainer);
|
app.component("MkStickyContainer", MkStickyContainer);
|
||||||
|
@ -98,7 +96,6 @@ declare module "@vue/runtime-core" {
|
||||||
MkUrl: typeof MkUrl;
|
MkUrl: typeof MkUrl;
|
||||||
MkLoading: typeof MkLoading;
|
MkLoading: typeof MkLoading;
|
||||||
MkError: typeof MkError;
|
MkError: typeof MkError;
|
||||||
MkAd: typeof MkAd;
|
|
||||||
MkPageHeader: typeof MkPageHeader;
|
MkPageHeader: typeof MkPageHeader;
|
||||||
MkSpacer: typeof MkSpacer;
|
MkSpacer: typeof MkSpacer;
|
||||||
MkStickyContainer: typeof MkStickyContainer;
|
MkStickyContainer: typeof MkStickyContainer;
|
||||||
|
|
|
@ -185,12 +185,6 @@ const menuDef = $computed(() => [
|
||||||
to: "/admin/announcements",
|
to: "/admin/announcements",
|
||||||
active: currentPage?.route.name === "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",
|
icon: "ph-warning-circle ph-bold ph-lg",
|
||||||
text: i18n.ts.abuseReports,
|
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>
|
|
|
@ -114,7 +114,6 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MkAd :prefer="['inline', 'inline-big']" />
|
|
||||||
<MkContainer
|
<MkContainer
|
||||||
:max-height="300"
|
:max-height="300"
|
||||||
:foldable="true"
|
:foldable="true"
|
||||||
|
|
|
@ -171,7 +171,6 @@
|
||||||
</template>
|
</template>
|
||||||
</div> -->
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
<MkAd :prefer="['inline', 'inline-big']" />
|
|
||||||
<MkContainer
|
<MkContainer
|
||||||
:max-height="300"
|
:max-height="300"
|
||||||
:foldable="true"
|
:foldable="true"
|
||||||
|
|
|
@ -146,9 +146,6 @@
|
||||||
|
|
||||||
<FormSection>
|
<FormSection>
|
||||||
<template #label>{{ i18n.ts.appearance }}</template>
|
<template #label>{{ i18n.ts.appearance }}</template>
|
||||||
<FormSwitch v-model="showAds" class="_formBlock">{{
|
|
||||||
i18n.ts.showAds
|
|
||||||
}}</FormSwitch>
|
|
||||||
<FormSwitch v-model="useBlurEffect" class="_formBlock">{{
|
<FormSwitch v-model="useBlurEffect" class="_formBlock">{{
|
||||||
i18n.ts.useBlurEffect
|
i18n.ts.useBlurEffect
|
||||||
}}</FormSwitch>
|
}}</FormSwitch>
|
||||||
|
|
|
@ -491,11 +491,6 @@ export const routes = [
|
||||||
() => import("./pages/admin/announcements.vue")
|
() => import("./pages/admin/announcements.vue")
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/ads",
|
|
||||||
name: "ads",
|
|
||||||
component: page(() => import("./pages/admin/promotions.vue")),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/database",
|
path: "/database",
|
||||||
name: "database",
|
name: "database",
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
>{{ column.name }}</template
|
>{{ column.name }}</template
|
||||||
>
|
>
|
||||||
<div class="wtdtxvec">
|
<div class="wtdtxvec">
|
||||||
<MkAd class="a" :prefer="['widget']" />
|
|
||||||
<div
|
<div
|
||||||
v-if="!(column.widgets && column.widgets.length > 0) && !edit"
|
v-if="!(column.widgets && column.widgets.length > 0) && !edit"
|
||||||
class="intro"
|
class="intro"
|
||||||
|
@ -32,7 +31,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {} from "vue";
|
|
||||||
import XColumn from "./column.vue";
|
import XColumn from "./column.vue";
|
||||||
import {
|
import {
|
||||||
addColumnWidget,
|
addColumnWidget,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<aside class="widgets" :aria-label="i18n.ts._deck._columns.widgets">
|
<aside class="widgets" :aria-label="i18n.ts._deck._columns.widgets">
|
||||||
<MkAd class="a" :prefer="['widget']" />
|
|
||||||
<XWidgets
|
<XWidgets
|
||||||
:edit="editMode"
|
:edit="editMode"
|
||||||
:widgets="defaultStore.reactiveState.widgets.value"
|
:widgets="defaultStore.reactiveState.widgets.value"
|
||||||
|
|
Loading…
Reference in New Issue