127127 </template >
128128 <v-list-item-title >删除</v-list-item-title >
129129 </v-list-item >
130- <!-- 手动触发社交同步 -->
131- <v-list-item :disabled =" actionLoading" @click =" manualSyncPost" >
132- <template #prepend >
133- <v-icon size =" 18" >mdi-share-all-outline</v-icon >
134- </template >
135- <v-list-item-title >同步到社交平台</v-list-item-title >
136- </v-list-item >
137130 <!-- 复制链接 -->
138131 <v-list-item @click =" copyLink" >
139132 <template #prepend >
140133 <v-icon size =" 18" >mdi-link-variant</v-icon >
141134 </template >
142135 <v-list-item-title >复制链接</v-list-item-title >
143136 </v-list-item >
137+ <!-- 联邦社交数据 -->
138+ <v-list-item
139+ @click =" federationDialog = true"
140+ >
141+ <template #prepend >
142+ <v-icon size =" 18" >mdi-access-point-network</v-icon >
143+ </template >
144+ <v-list-item-title >联邦社交数据</v-list-item-title >
145+ </v-list-item >
144146 </v-list >
145147 </v-menu >
146148 </div >
435437 </v-carousel >
436438 </div >
437439 </v-dialog >
440+
441+ <!-- Federation Data Dialog -->
442+ <v-dialog v-model =" federationDialog" max-width =" 500px" >
443+ <v-card class =" post-dialog-card" >
444+ <div class =" post-dialog-header" >
445+ <span class =" text-h6" >联邦社交数据</span >
446+ <v-spacer ></v-spacer >
447+ <v-btn icon =" mdi-close" variant =" text" size =" small" @click =" federationDialog = false" ></v-btn >
448+ </div >
449+ <v-card-text class =" pa-4" >
450+ <v-list density =" compact" v-if =" post.platform_refs" >
451+ <v-list-item v-if =" post.platform_refs.twitter" >
452+ <template v-slot :prepend ><v-icon color =" blue" >mdi-twitter</v-icon ></template >
453+ <v-list-item-title >Twitter</v-list-item-title >
454+ <v-list-item-subtitle >
455+ <a v-if =" post.platform_refs.twitter.url" :href =" post.platform_refs.twitter.url" target =" _blank" >
456+ {{ post.platform_refs.twitter.id || 'Link' }}
457+ </a >
458+ <span v-else >{{ post.platform_refs.twitter.id }}</span >
459+ <span v-if =" post.platform_refs.twitter.kind" class =" ml-2 text-caption" >({{ post.platform_refs.twitter.kind }})</span >
460+ </v-list-item-subtitle >
461+ </v-list-item >
462+
463+ <v-list-item v-if =" post.platform_refs.bluesky" >
464+ <template v-slot :prepend ><v-icon color =" light-blue" >mdi-weather-cloudy</v-icon ></template >
465+ <v-list-item-title >Bluesky</v-list-item-title >
466+ <v-list-item-subtitle >
467+ <a v-if =" getBskyUrl(post.platform_refs.bluesky)" :href =" getBskyUrl(post.platform_refs.bluesky)" target =" _blank" rel =" noopener" >
468+ 在 Bluesky 上查看
469+ </a >
470+ <div v-if =" post.platform_refs.bluesky.uri" class =" text-caption text-truncate" style =" max-width : 300px ;" >URI: {{ post.platform_refs.bluesky.uri }}</div >
471+ <div v-if =" post.platform_refs.bluesky.cid" class =" text-caption text-truncate" style =" max-width : 300px ;" >CID: {{ post.platform_refs.bluesky.cid }}</div >
472+ </v-list-item-subtitle >
473+ </v-list-item >
474+
475+ <v-list-item v-if =" post.platform_refs.activitypub" >
476+ <template v-slot :prepend ><v-icon color =" purple" >mdi-earth</v-icon ></template >
477+ <v-list-item-title >ActivityPub</v-list-item-title >
478+ <v-list-item-subtitle >
479+ <a v-if =" post.platform_refs.activitypub.url" :href =" post.platform_refs.activitypub.url" target =" _blank" >
480+ {{ post.platform_refs.activitypub.id || 'View Note' }}
481+ </a >
482+ <span v-else >{{ post.platform_refs.activitypub.id }}</span >
483+ </v-list-item-subtitle >
484+ </v-list-item >
485+ </v-list >
486+ <div v-else class =" text-center pa-4 text-medium-emphasis" >
487+ 无同步数据
488+ </div >
489+ </v-card-text >
490+
491+ <v-divider ></v-divider >
492+
493+ <v-card-actions v-if =" canDelete" >
494+ <v-btn
495+ variant =" text"
496+ color =" primary"
497+ :loading =" actionLoading"
498+ prepend-icon =" mdi-share-all-outline"
499+ @click =" manualSyncPost"
500+ >
501+ 全平台同步
502+ </v-btn >
503+ <v-spacer ></v-spacer >
504+ <v-btn
505+ variant =" text"
506+ color =" secondary"
507+ :loading =" actionLoading"
508+ prepend-icon =" mdi-earth"
509+ @click =" pushToFederation"
510+ >
511+ 推送到联邦
512+ </v-btn >
513+ </v-card-actions >
514+ </v-card >
515+ </v-dialog >
438516</template >
439517
440518<script setup>
441519import { computed , ref , watch , nextTick } from " vue" ;
442- import { useRouter } from " vue-router" ;
520+ import { useRouter , useRoute } from " vue-router" ;
443521import { localuser } from " @/services/localAccount" ;
444522import { getS3staticurl } from " @/services/projectService" ;
445523import PostsService from " @/services/postsService" ;
524+ import federationService from " @/services/federationService" ;
446525import { showSnackbar } from " @/composables/useNotifications" ;
447526import { useDeleteConfirm } from " @/composables/useDeleteConfirm" ;
448527import axios from " @/axios/axios" ;
@@ -471,10 +550,12 @@ const props = defineProps({
471550const emit = defineEmits ([" deleted" , " created" , " updated" ]);
472551
473552const router = useRouter ();
553+ const route = useRoute ();
474554
475555// Dialog states
476556const replyDialog = ref (false );
477557const quoteDialog = ref (false );
558+ const federationDialog = ref (false );
478559const actionLoading = ref (false );
479560
480561const mediaViewerOpen = ref (false );
@@ -519,6 +600,11 @@ const authorAvatar = computed(() => {
519600 return localuser .getUserAvatar (avatar);
520601});
521602
603+ // Check if current view is detail view for this post
604+ const isDetailView = computed (() => {
605+ return String (route .params .id ) === String (postId .value );
606+ });
607+
522608// Time
523609const createdAt = computed (
524610 () =>
@@ -1112,6 +1198,7 @@ const copyLink = async () => {
11121198};
11131199
11141200const handleDeleteClick = () => {
1201+ // Trigger rebuild
11151202 showDeleteConfirm(
11161203 async () => {
11171204 await PostsService.remove(postId.value);
@@ -1128,18 +1215,52 @@ const handleDeleteClick = () => {
11281215 );
11291216};
11301217
1218+ const getBskyUrl = (bluesky) => {
1219+ if (!bluesky) return null;
1220+ if (bluesky.url) return bluesky.url;
1221+ // Parse AT URI: at://did:plc:xxx/app.bsky.feed.post/rkey
1222+ const uri = bluesky.uri;
1223+ if (!uri) return null;
1224+ const match = uri.match(/^at:\/\/ ([^/]+)\/ [^/]+\/ ([^/]+)$/);
1225+ if (match) {
1226+ return ` https: // bsky.app/profile/${match[1]}/post/${match[2]}`;
1227+ }
1228+ return null;
1229+ };
1230+
11311231const manualSyncPost = async () => {
1132- if (!requireLogin("手动同步")) return;
1232+ if (!canDelete.value) return;
1233+ if (!confirm("确定要重新同步此帖子到所有关联的社交平台吗?")) return;
1234+
1235+ actionLoading.value = true;
1236+ try {
1237+ const res = await federationService.userResyncPost(postId.value);
1238+ if (res.data?.status === "success" || res.data?.status === "ok") {
1239+ showSnackbar("同步任务已提交", "success");
1240+ } else {
1241+ showSnackbar(res.data?.message || "同步失败", "error");
1242+ }
1243+ } catch (e) {
1244+ showSnackbar(e?.message || "同步失败", "error");
1245+ } finally {
1246+ actionLoading.value = false;
1247+ }
1248+ };
1249+
1250+ const pushToFederation = async () => {
1251+ if (!canDelete.value) return;
1252+ if (!confirm("确定要推送到 ActivityPub 联邦网络吗?")) return;
1253+
11331254 actionLoading.value = true;
11341255 try {
1135- const result = await PostsService.syncToSocial (postId.value);
1136- if (result ?.status === "success") {
1137- showSnackbar("已提交同步任务 ", "success");
1256+ const res = await federationService.userPushPostToFederation (postId.value);
1257+ if (res.data ?.status === "success" || res.data?.status === "ok ") {
1258+ showSnackbar("推送任务已提交 ", "success");
11381259 } else {
1139- showSnackbar(result ?.message || "提交同步任务失败 ", "error");
1260+ showSnackbar(res.data ?.message || "推送失败 ", "error");
11401261 }
11411262 } catch (e) {
1142- showSnackbar(e?.message || "提交同步任务失败 ", "error");
1263+ showSnackbar(e?.message || "推送失败 ", "error");
11431264 } finally {
11441265 actionLoading.value = false;
11451266 }
0 commit comments