feat: Donation pop-up with optional admin link

Co-authored-by: Syuilo <syuilotan@yahoo.co.jp>
This commit is contained in:
ThatOneCalculator 2023-07-11 23:23:44 -07:00
parent a8382fd007
commit 04224bfc66
No known key found for this signature in database
GPG key ID: 8703CACD01000000
8 changed files with 204 additions and 1 deletions

View file

@ -1118,6 +1118,7 @@ enableIdenticonGeneration: "Enable Identicon generation"
showPopup: "Notify users with popup"
showWithSparkles: "Show with sparkles"
youHaveUnreadAnnouncements: "You have unread announcements"
donationLink: "Link to donation page"
_sensitiveMediaDetection:
description: "Reduces the effort of server moderation through automatically recognizing
@ -1216,6 +1217,10 @@ _aboutMisskey:
source: "Source code"
translation: "Translate 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.
Thank you! 🥰"
patrons: "Calckey patrons"

View 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"`,
);
}
}

View file

@ -556,4 +556,10 @@ export class Meta {
default: true,
})
public enableIdenticonGeneration: boolean;
@Column("varchar", {
length: 256,
nullable: true,
})
public donationLink: string | null;
}

View file

@ -491,6 +491,11 @@ export const meta = {
optional: false,
nullable: false,
},
donationLink: {
type: "string",
optional: true,
nullable: true,
}
},
},
} as const;
@ -604,5 +609,6 @@ export default define(meta, paramDef, async (ps, me) => {
experimentalFeatures: instance.experimentalFeatures,
enableServerMachineStats: instance.enableServerMachineStats,
enableIdenticonGeneration: instance.enableIdenticonGeneration,
donationLink: instance.donationLink,
};
});

View 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>

View file

@ -450,6 +450,29 @@ function checkForSplash() {
}
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.permission === "default") {

View file

@ -53,6 +53,20 @@
i18n.ts.maintainerEmail
}}</template>
</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>
<FormTextarea v-model="pinnedUsers" class="_formBlock">
@ -435,6 +449,7 @@ let description: string | null = $ref(null);
let tosUrl: string | null = $ref(null);
let maintainerName: string | null = $ref(null);
let maintainerEmail: string | null = $ref(null);
let donationLink: string | null = $ref(null);
let iconUrl: string | null = $ref(null);
let bannerUrl: string | null = $ref(null);
let logoImageUrl: string | null = $ref(null);
@ -481,6 +496,7 @@ async function init() {
defaultDarkTheme = meta.defaultDarkTheme;
maintainerName = meta.maintainerName;
maintainerEmail = meta.maintainerEmail;
donationLink = meta.donationLink;
enableLocalTimeline = !meta.disableLocalTimeline;
enableGlobalTimeline = !meta.disableGlobalTimeline;
enableRecommendedTimeline = !meta.disableRecommendedTimeline;
@ -527,6 +543,7 @@ function save() {
defaultDarkTheme: defaultDarkTheme === "" ? null : defaultDarkTheme,
maintainerName,
maintainerEmail,
donationLink,
disableLocalTimeline: !enableLocalTimeline,
disableGlobalTimeline: !enableGlobalTimeline,
disableRecommendedTimeline: !enableRecommendedTimeline,

View file

@ -82,7 +82,7 @@
<p>
{{ `${i18n.ts.lastUsedDate}: ${key.lastUsed}` }}
</p>
<div class="_buttons _flexList">
<div class="_flexList">
<MkButton @click="renameKey(key)"
><i
class="ph-pencil-line ph-bold ph-lg"