feat: follow-me
This commit is contained in:
parent
af109b45ef
commit
22b52ac3d3
5 changed files with 107 additions and 3 deletions
|
@ -1011,6 +1011,8 @@ isSystemAccount: "This account is created and automatically operated by the syst
|
||||||
Please do not moderate, edit, delete, or otherwise tamper with this account, or
|
Please do not moderate, edit, delete, or otherwise tamper with this account, or
|
||||||
it may break your server."
|
it may break your server."
|
||||||
typeToConfirm: "Please enter {x} to confirm"
|
typeToConfirm: "Please enter {x} to confirm"
|
||||||
|
useThisAccountConfirm: "Do you want to continue with this account?"
|
||||||
|
inputAccountId: "Please input your account (e.g., @firefish@info.firefish.dev )"
|
||||||
deleteAccount: "Delete account"
|
deleteAccount: "Delete account"
|
||||||
document: "Documentation"
|
document: "Documentation"
|
||||||
numberOfPageCache: "Number of cached pages"
|
numberOfPageCache: "Number of cached pages"
|
||||||
|
@ -1158,6 +1160,8 @@ confirm: "Confirm"
|
||||||
importZip: "Import ZIP"
|
importZip: "Import ZIP"
|
||||||
exportZip: "Export ZIP"
|
exportZip: "Export ZIP"
|
||||||
getQrCode: "Get QR code"
|
getQrCode: "Get QR code"
|
||||||
|
remoteFollow: "Remote Follow"
|
||||||
|
remoteFollowUrl: "Remote Follow URL"
|
||||||
emojiPackCreator: "Emoji pack creator"
|
emojiPackCreator: "Emoji pack creator"
|
||||||
indexable: "Indexable"
|
indexable: "Indexable"
|
||||||
indexableDescription: "Allow built-in search to show your public posts"
|
indexableDescription: "Allow built-in search to show your public posts"
|
||||||
|
|
|
@ -879,6 +879,8 @@ driveCapOverrideCaption: "输入 0 或以下的值将容量重置为默认值。
|
||||||
requireAdminForView: "您需要使用管理员账号登录才能查看。"
|
requireAdminForView: "您需要使用管理员账号登录才能查看。"
|
||||||
isSystemAccount: "该账号由系统自动创建。请不要修改、编辑、删除或以其它方式篡改这个账号,否则可能会破坏您的服务器。"
|
isSystemAccount: "该账号由系统自动创建。请不要修改、编辑、删除或以其它方式篡改这个账号,否则可能会破坏您的服务器。"
|
||||||
typeToConfirm: "输入 {x} 以确认操作"
|
typeToConfirm: "输入 {x} 以确认操作"
|
||||||
|
useThisAccountConfirm: "您想使用此帐户继续执行此操作吗?"
|
||||||
|
inputAccountId: "请输入您的帐户(例如 @firefish@info.firefish.dev )"
|
||||||
deleteAccount: "删除账号"
|
deleteAccount: "删除账号"
|
||||||
document: "文档"
|
document: "文档"
|
||||||
numberOfPageCache: "缓存页数"
|
numberOfPageCache: "缓存页数"
|
||||||
|
@ -1975,6 +1977,8 @@ confirm: 确认
|
||||||
importZip: 导入 ZIP
|
importZip: 导入 ZIP
|
||||||
exportZip: 导出 ZIP
|
exportZip: 导出 ZIP
|
||||||
getQrCode: "获取二维码"
|
getQrCode: "获取二维码"
|
||||||
|
remoteFollow: "远程关注"
|
||||||
|
remoteFollowUrl: "远程关注 URL"
|
||||||
emojiPackCreator: 表情包创建工具
|
emojiPackCreator: 表情包创建工具
|
||||||
objectStorageS3ForcePathStyleDesc: 打开此选项可构建格式为 "s3.amazonaws.com/<bucket>/" 而非 "<bucket>.s3.amazonaws.com"
|
objectStorageS3ForcePathStyleDesc: 打开此选项可构建格式为 "s3.amazonaws.com/<bucket>/" 而非 "<bucket>.s3.amazonaws.com"
|
||||||
的端点 URL。
|
的端点 URL。
|
||||||
|
|
78
packages/client/src/pages/follow-me.vue
Normal file
78
packages/client/src/pages/follow-me.vue
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
<template>
|
||||||
|
<div class="mk-follow-page"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { acct } from "firefish-js";
|
||||||
|
import * as os from "@/os";
|
||||||
|
import { i18n } from "@/i18n";
|
||||||
|
import { host as hostRaw } from "@/config";
|
||||||
|
import { isSignedIn, me } from "@/me";
|
||||||
|
import { waiting } from "@/os";
|
||||||
|
|
||||||
|
const acctUri = new URL(location.href).searchParams.get("acct");
|
||||||
|
if (acctUri == null) {
|
||||||
|
throw new Error("acct required");
|
||||||
|
}
|
||||||
|
|
||||||
|
let useThisAccount = isSignedIn(me) ? true : false;
|
||||||
|
|
||||||
|
// If the user is already logged in, ask whether to follow using the current account.
|
||||||
|
if (useThisAccount) {
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: "question",
|
||||||
|
text: i18n.ts.useThisAccountConfirm,
|
||||||
|
});
|
||||||
|
if (!canceled) {
|
||||||
|
waiting();
|
||||||
|
window.location.href = `/authorize-follow?acct=${acctUri}`;
|
||||||
|
} else {
|
||||||
|
useThisAccount = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!useThisAccount) {
|
||||||
|
// Ask the user what the account ID is
|
||||||
|
const remoteAccountId = await os.inputText({
|
||||||
|
text: i18n.ts.inputAccountId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// If the user do not want enter uri, the user will be redirected to the user page.
|
||||||
|
if (!remoteAccountId.result) {
|
||||||
|
waiting();
|
||||||
|
window.location.href = `/@${acctUri}`;
|
||||||
|
} else {
|
||||||
|
const remoteAcctInfo = acct.parse(remoteAccountId.result);
|
||||||
|
|
||||||
|
// If the user on this server, redirect directly
|
||||||
|
if (remoteAcctInfo.host === hostRaw || remoteAcctInfo.host === null) {
|
||||||
|
waiting();
|
||||||
|
window.location.href = `/authorize-follow?acct=${acctUri}`;
|
||||||
|
} else {
|
||||||
|
waiting();
|
||||||
|
// If not, find the interaction url through webfinger interface
|
||||||
|
fetch(
|
||||||
|
`https://${remoteAcctInfo.host}/.well-known/webfinger?resource=${remoteAcctInfo.username}@${remoteAcctInfo.host}`,
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
const subscribeUri = data.links.find(
|
||||||
|
(link) => link.rel === "http://ostatus.org/schema/1.0/subscribe",
|
||||||
|
).template;
|
||||||
|
window.location.href = subscribeUri.replace(
|
||||||
|
"{uri}",
|
||||||
|
acctUri.includes("@") ? acctUri : `${acctUri}@${hostRaw}`,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
// TODO: It would be better to provide more information, but the priority of
|
||||||
|
// waiting component is too high and the pop-up window will be blocked.
|
||||||
|
window.location.href = `/@${acctUri}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -325,6 +325,10 @@ export const routes: RouteDef[] = [
|
||||||
component: page(() => import("./pages/follow.vue")),
|
component: page(() => import("./pages/follow.vue")),
|
||||||
loginRequired: true,
|
loginRequired: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/follow-me",
|
||||||
|
component: page(() => import("./pages/follow-me.vue")),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/authorize_interaction",
|
path: "/authorize_interaction",
|
||||||
component: page(() => import("./pages/authorize_interaction.vue")),
|
component: page(() => import("./pages/authorize_interaction.vue")),
|
||||||
|
|
|
@ -281,6 +281,13 @@ export function getUserMenu(user, router: Router = mainRouter) {
|
||||||
copyToClipboard(`https://${host}/@${user.username}.json`);
|
copyToClipboard(`https://${host}/@${user.username}.json`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: `${icon("ph-hand-waving")}`,
|
||||||
|
text: i18n.ts.remoteFollowUrl,
|
||||||
|
action: () => {
|
||||||
|
copyToClipboard(`https://${host}/follow-me?acct=${user.username}`);
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -290,13 +297,20 @@ export function getUserMenu(user, router: Router = mainRouter) {
|
||||||
os.post({ specified: user });
|
os.post({ specified: user });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
!isSignedIn(me)
|
||||||
|
? {
|
||||||
|
icon: `${icon("ph-hand-waving")}`,
|
||||||
|
text: i18n.ts.remoteFollow,
|
||||||
|
action: () => {
|
||||||
|
router.push(`/follow-me?acct=${user.username}`);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
{
|
{
|
||||||
icon: "ph-qr-code ph-bold ph-lg",
|
icon: "ph-qr-code ph-bold ph-lg",
|
||||||
text: i18n.ts.getQrCode,
|
text: i18n.ts.getQrCode,
|
||||||
action: () => {
|
action: () => {
|
||||||
os.displayQrCode(
|
os.displayQrCode(`https://${host}/follow-me?acct=${user.username}`);
|
||||||
`https://${host}/authorize-follow?acct=${user.username}`,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
isSignedIn(me) && me.id !== user.id
|
isSignedIn(me) && me.id !== user.id
|
||||||
|
|
Loading…
Reference in a new issue