feat: ✨ Donation pop-up with optional admin link
Co-authored-by: Syuilo <syuilotan@yahoo.co.jp>
This commit is contained in:
parent
a8382fd007
commit
04224bfc66
@ -1118,6 +1118,7 @@ enableIdenticonGeneration: "Enable Identicon generation"
|
|||||||
showPopup: "Notify users with popup"
|
showPopup: "Notify users with popup"
|
||||||
showWithSparkles: "Show with sparkles"
|
showWithSparkles: "Show with sparkles"
|
||||||
youHaveUnreadAnnouncements: "You have unread announcements"
|
youHaveUnreadAnnouncements: "You have unread announcements"
|
||||||
|
donationLink: "Link to donation page"
|
||||||
|
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "Reduces the effort of server moderation through automatically recognizing
|
description: "Reduces the effort of server moderation through automatically recognizing
|
||||||
@ -1216,6 +1217,10 @@ _aboutMisskey:
|
|||||||
source: "Source code"
|
source: "Source code"
|
||||||
translation: "Translate Calckey"
|
translation: "Translate Calckey"
|
||||||
donate: "Donate to Calckey"
|
donate: "Donate to Calckey"
|
||||||
|
donateTitle: "Enjoying Calckey?"
|
||||||
|
pleaseDonateToCalckey: "Please consider donating to Calckey to support its development."
|
||||||
|
pleaseDonateToHost: "Please also consider donating to your honme server, {host}, to help support its operation costs."
|
||||||
|
donateHost: "Donate to {host}"
|
||||||
morePatrons: "We also appreciate the support of many other helpers not listed here.
|
morePatrons: "We also appreciate the support of many other helpers not listed here.
|
||||||
Thank you! 🥰"
|
Thank you! 🥰"
|
||||||
patrons: "Calckey patrons"
|
patrons: "Calckey patrons"
|
||||||
|
15
packages/backend/migration/1689136347561-donation-link.js
Normal file
15
packages/backend/migration/1689136347561-donation-link.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export class DonationLink1689136347561 {
|
||||||
|
name = "DonationLink1689136347561";
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "meta" ADD "donationLink" character varying(256)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "meta" DROP COLUMN "DonationLink1689136347561"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -556,4 +556,10 @@ export class Meta {
|
|||||||
default: true,
|
default: true,
|
||||||
})
|
})
|
||||||
public enableIdenticonGeneration: boolean;
|
public enableIdenticonGeneration: boolean;
|
||||||
|
|
||||||
|
@Column("varchar", {
|
||||||
|
length: 256,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public donationLink: string | null;
|
||||||
}
|
}
|
||||||
|
@ -491,6 +491,11 @@ export const meta = {
|
|||||||
optional: false,
|
optional: false,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
},
|
},
|
||||||
|
donationLink: {
|
||||||
|
type: "string",
|
||||||
|
optional: true,
|
||||||
|
nullable: true,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
@ -604,5 +609,6 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||||||
experimentalFeatures: instance.experimentalFeatures,
|
experimentalFeatures: instance.experimentalFeatures,
|
||||||
enableServerMachineStats: instance.enableServerMachineStats,
|
enableServerMachineStats: instance.enableServerMachineStats,
|
||||||
enableIdenticonGeneration: instance.enableIdenticonGeneration,
|
enableIdenticonGeneration: instance.enableIdenticonGeneration,
|
||||||
|
donationLink: instance.donationLink,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
131
packages/client/src/components/MkDonation.vue
Normal file
131
packages/client/src/components/MkDonation.vue
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_panel _shadow" :class="$style.root">
|
||||||
|
<div :class="$style.icon">
|
||||||
|
<i class="ph-hand-heart ph-bold ph-6x" />
|
||||||
|
</div>
|
||||||
|
<div :class="$style.main">
|
||||||
|
<div :class="$style.title">
|
||||||
|
{{ i18n.ts._aboutMisskey.donateTitle }}
|
||||||
|
</div>
|
||||||
|
<div :class="$style.text">
|
||||||
|
{{ i18n.ts._aboutMisskey.pleaseDonateToCalckey }}
|
||||||
|
<p v-if="instance.donationLink">
|
||||||
|
{{
|
||||||
|
i18n.t("_aboutMisskey.pleaseDonateToHost", {
|
||||||
|
host: hostname,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="_flexList">
|
||||||
|
<MkButton primary @click="openCalckeyDonation">{{
|
||||||
|
i18n.ts._aboutMisskey.donate
|
||||||
|
}}</MkButton>
|
||||||
|
<MkButton v-if="instance.donationLink" primary @click="openExternalDonation">{{
|
||||||
|
i18n.t("_aboutMisskey.donateHost", {
|
||||||
|
host: hostname,
|
||||||
|
})
|
||||||
|
}}</MkButton>
|
||||||
|
</div>
|
||||||
|
<div class="_flexList">
|
||||||
|
<MkButton @click="close">{{ i18n.ts.remindMeLater }}</MkButton>
|
||||||
|
<MkButton @click="neverShow">{{ i18n.ts.neverShow }}</MkButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="_button" :class="$style.close" @click="close">
|
||||||
|
<i class="ph-x ph-bold ph-lg"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import MkButton from "@/components/MkButton.vue";
|
||||||
|
import { host } from "@/config";
|
||||||
|
import { i18n } from "@/i18n";
|
||||||
|
import * as os from "@/os";
|
||||||
|
import { instance } from "@/instance";
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: "closed"): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const hostname = instance.name?.length <= 20 ? instance.name : host;
|
||||||
|
|
||||||
|
const zIndex = os.claimZIndex("low");
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
localStorage.setItem("latestDonationInfoShownAt", Date.now().toString());
|
||||||
|
emit("closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
function neverShow() {
|
||||||
|
localStorage.setItem("neverShowDonationInfo", "true");
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCalckeyDonation() {
|
||||||
|
window.open("https://opencollective.com/calckey", "_blank");
|
||||||
|
}
|
||||||
|
|
||||||
|
function openExternalDonation() {
|
||||||
|
let link = instance.donationLink;
|
||||||
|
if (!/^https?:\/\//i.test(link)) {
|
||||||
|
link = `http://${link}`;
|
||||||
|
}
|
||||||
|
window.open(link, "_blank");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.root {
|
||||||
|
position: fixed;
|
||||||
|
z-index: v-bind(zIndex);
|
||||||
|
bottom: var(--margin);
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: calc(100% - (var(--margin) * 2));
|
||||||
|
max-width: 500px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 25px;
|
||||||
|
width: 100px;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
.icon {
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 450px) {
|
||||||
|
.icon {
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
padding: 25px 25px 25px 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
margin: 0.7em 0 1em 0;
|
||||||
|
}
|
||||||
|
</style>
|
@ -450,6 +450,29 @@ function checkForSplash() {
|
|||||||
}
|
}
|
||||||
localStorage.setItem("lastUsed", Date.now().toString());
|
localStorage.setItem("lastUsed", Date.now().toString());
|
||||||
|
|
||||||
|
const latestDonationInfoShownAt = localStorage.getItem(
|
||||||
|
"latestDonationInfoShownAt",
|
||||||
|
);
|
||||||
|
const neverShowDonationInfo = localStorage.getItem("neverShowDonationInfo");
|
||||||
|
if (
|
||||||
|
neverShowDonationInfo !== "true" &&
|
||||||
|
new Date($i.createdAt).getTime() < Date.now() - 1000 * 60 * 60 * 24 * 3 &&
|
||||||
|
!location.pathname.startsWith("/miauth")
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
latestDonationInfoShownAt == null ||
|
||||||
|
new Date(latestDonationInfoShownAt).getTime() <
|
||||||
|
Date.now() - 1000 * 60 * 60 * 24 * 30
|
||||||
|
) {
|
||||||
|
popup(
|
||||||
|
defineAsyncComponent(() => import("@/components/MkDonation.vue")),
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
"closed",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ("Notification" in window) {
|
if ("Notification" in window) {
|
||||||
// 許可を得ていなかったらリクエスト
|
// 許可を得ていなかったらリクエスト
|
||||||
if (Notification.permission === "default") {
|
if (Notification.permission === "default") {
|
||||||
|
@ -53,6 +53,20 @@
|
|||||||
i18n.ts.maintainerEmail
|
i18n.ts.maintainerEmail
|
||||||
}}</template>
|
}}</template>
|
||||||
</FormInput>
|
</FormInput>
|
||||||
|
|
||||||
|
<FormInput
|
||||||
|
v-model="donationLink"
|
||||||
|
class="_formBlock"
|
||||||
|
>
|
||||||
|
<template #prefix
|
||||||
|
><i
|
||||||
|
class="ph-hand-heart ph-bold ph-lg"
|
||||||
|
></i
|
||||||
|
></template>
|
||||||
|
<template #label>{{
|
||||||
|
i18n.ts.donationLink
|
||||||
|
}}</template>
|
||||||
|
</FormInput>
|
||||||
</FormSplit>
|
</FormSplit>
|
||||||
|
|
||||||
<FormTextarea v-model="pinnedUsers" class="_formBlock">
|
<FormTextarea v-model="pinnedUsers" class="_formBlock">
|
||||||
@ -435,6 +449,7 @@ let description: string | null = $ref(null);
|
|||||||
let tosUrl: string | null = $ref(null);
|
let tosUrl: string | null = $ref(null);
|
||||||
let maintainerName: string | null = $ref(null);
|
let maintainerName: string | null = $ref(null);
|
||||||
let maintainerEmail: string | null = $ref(null);
|
let maintainerEmail: string | null = $ref(null);
|
||||||
|
let donationLink: string | null = $ref(null);
|
||||||
let iconUrl: string | null = $ref(null);
|
let iconUrl: string | null = $ref(null);
|
||||||
let bannerUrl: string | null = $ref(null);
|
let bannerUrl: string | null = $ref(null);
|
||||||
let logoImageUrl: string | null = $ref(null);
|
let logoImageUrl: string | null = $ref(null);
|
||||||
@ -481,6 +496,7 @@ async function init() {
|
|||||||
defaultDarkTheme = meta.defaultDarkTheme;
|
defaultDarkTheme = meta.defaultDarkTheme;
|
||||||
maintainerName = meta.maintainerName;
|
maintainerName = meta.maintainerName;
|
||||||
maintainerEmail = meta.maintainerEmail;
|
maintainerEmail = meta.maintainerEmail;
|
||||||
|
donationLink = meta.donationLink;
|
||||||
enableLocalTimeline = !meta.disableLocalTimeline;
|
enableLocalTimeline = !meta.disableLocalTimeline;
|
||||||
enableGlobalTimeline = !meta.disableGlobalTimeline;
|
enableGlobalTimeline = !meta.disableGlobalTimeline;
|
||||||
enableRecommendedTimeline = !meta.disableRecommendedTimeline;
|
enableRecommendedTimeline = !meta.disableRecommendedTimeline;
|
||||||
@ -527,6 +543,7 @@ function save() {
|
|||||||
defaultDarkTheme: defaultDarkTheme === "" ? null : defaultDarkTheme,
|
defaultDarkTheme: defaultDarkTheme === "" ? null : defaultDarkTheme,
|
||||||
maintainerName,
|
maintainerName,
|
||||||
maintainerEmail,
|
maintainerEmail,
|
||||||
|
donationLink,
|
||||||
disableLocalTimeline: !enableLocalTimeline,
|
disableLocalTimeline: !enableLocalTimeline,
|
||||||
disableGlobalTimeline: !enableGlobalTimeline,
|
disableGlobalTimeline: !enableGlobalTimeline,
|
||||||
disableRecommendedTimeline: !enableRecommendedTimeline,
|
disableRecommendedTimeline: !enableRecommendedTimeline,
|
||||||
|
@ -82,7 +82,7 @@
|
|||||||
<p>
|
<p>
|
||||||
{{ `${i18n.ts.lastUsedDate}: ${key.lastUsed}` }}
|
{{ `${i18n.ts.lastUsedDate}: ${key.lastUsed}` }}
|
||||||
</p>
|
</p>
|
||||||
<div class="_buttons _flexList">
|
<div class="_flexList">
|
||||||
<MkButton @click="renameKey(key)"
|
<MkButton @click="renameKey(key)"
|
||||||
><i
|
><i
|
||||||
class="ph-pencil-line ph-bold ph-lg"
|
class="ph-pencil-line ph-bold ph-lg"
|
||||||
|
Loading…
Reference in New Issue
Block a user