@@ -331,7 +333,7 @@ function toneClass(tone: CardTone): string {
class="media-search"
>
-
+
@@ -375,7 +377,7 @@ function toneClass(tone: CardTone): string {
- {{ t("media.card.multiAccount", { count: card.accountCount }) }}
+ {{ t("media.card.multiAccount", { count: card.accountCount }) }}
@@ -596,7 +598,7 @@ function toneClass(tone: CardTone): string {
top: 4px;
right: -8px;
background: #f5f5f5;
- color: #bfbfbf;
+ color: f3f#737373;
font-size: 10px;
padding: 2px 6px;
border-radius: 4px;
@@ -716,12 +718,12 @@ function toneClass(tone: CardTone): string {
.media-card__identity-text p {
margin: 2px 0 0;
- color: #bfbfbf;
+ color: #737373;
font-size: 11px;
}
.more-icon {
- color: #bfbfbf;
+ color: #737373;
font-size: 18px;
cursor: pointer;
padding: 0 4px;
@@ -750,7 +752,7 @@ function toneClass(tone: CardTone): string {
.media-card__account-unbound {
font-size: 13px;
- color: #bfbfbf;
+ color: #737373;
}
.media-card__footer {
diff --git a/apps/admin-web/src/views/WorkspaceView.vue b/apps/admin-web/src/views/WorkspaceView.vue
index d6a91f3..3f275f9 100644
--- a/apps/admin-web/src/views/WorkspaceView.vue
+++ b/apps/admin-web/src/views/WorkspaceView.vue
@@ -501,7 +501,7 @@ function refreshDashboard(): void {
align-items: center;
gap: 6px;
}
-.muted { color: #bfbfbf; font-size: 11px; }
+.muted { color: #737373; font-size: 11px; }
.muted-dot { color: #d9d9d9; font-size: 10px; }
/* Status Badges */
diff --git a/apps/browser-extension/entrypoints/background.ts b/apps/browser-extension/entrypoints/background.ts
index 2bf7de2..0b1cc03 100644
--- a/apps/browser-extension/entrypoints/background.ts
+++ b/apps/browser-extension/entrypoints/background.ts
@@ -4,6 +4,16 @@ import { defineBackground } from "wxt/utils/define-background";
import { handlePublisherAction } from "../src/runtime";
import { getExtensionState } from "../src/storage";
+type BackgroundSuccessResponse = {
+ ok: true;
+ data: Awaited
>;
+};
+
+type BackgroundErrorResponse = {
+ ok: false;
+ error: string;
+};
+
export default defineBackground(() => {
void getExtensionState();
@@ -21,11 +31,25 @@ export default defineBackground(() => {
await getExtensionState();
});
- browser.runtime.onMessage.addListener((message) => {
+ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if (!message || message.type !== "geo.publisher.request") {
return undefined;
}
- return handlePublisherAction(message.action, message.payload);
+ void handlePublisherAction(message.action, message.payload)
+ .then((data) => {
+ sendResponse({
+ ok: true,
+ data,
+ } satisfies BackgroundSuccessResponse);
+ })
+ .catch((error: unknown) => {
+ sendResponse({
+ ok: false,
+ error: error instanceof Error ? error.message : "publisher_plugin_unknown_error",
+ } satisfies BackgroundErrorResponse);
+ });
+
+ return true;
});
});
diff --git a/apps/browser-extension/entrypoints/content.ts b/apps/browser-extension/entrypoints/content.ts
index 10a5b6c..5b153ac 100644
--- a/apps/browser-extension/entrypoints/content.ts
+++ b/apps/browser-extension/entrypoints/content.ts
@@ -9,6 +9,20 @@ type PublisherRequestMessage = {
payload?: unknown;
};
+type BackgroundSuccessResponse = {
+ ok: true;
+ data: unknown;
+};
+
+type BackgroundErrorResponse = {
+ ok: false;
+ error?: string;
+};
+
+function isBackgroundResponse(value: unknown): value is BackgroundSuccessResponse | BackgroundErrorResponse {
+ return typeof value === "object" && value !== null && "ok" in value;
+}
+
export default defineContentScript({
matches: ["http://*/*", "https://*/*"],
main() {
@@ -23,12 +37,25 @@ export default defineContentScript({
const { requestId, action, payload } = event.data;
try {
- const data = await browser.runtime.sendMessage({
+ const response = await browser.runtime.sendMessage({
type: "geo.publisher.request",
action,
payload,
});
+ const data = isBackgroundResponse(response)
+ ? (() => {
+ if (!response.ok) {
+ throw new Error(response.error || "publisher_plugin_unknown_error");
+ }
+ return response.data;
+ })()
+ : response;
+
+ if (typeof data === "undefined") {
+ throw new Error("publisher_plugin_empty_response");
+ }
+
window.postMessage(
{
source: "geo-extension",
diff --git a/apps/browser-extension/entrypoints/popup/App.vue b/apps/browser-extension/entrypoints/popup/App.vue
index be0b90f..ce4763b 100644
--- a/apps/browser-extension/entrypoints/popup/App.vue
+++ b/apps/browser-extension/entrypoints/popup/App.vue
@@ -2,14 +2,22 @@
import { computed, onMounted, ref } from "vue";
import { browser } from "wxt/browser";
-import { getExtensionState, resetPlatformStates, updatePlatformState } from "../../src/storage";
+import { normalizeRemoteUrl } from "../../src/adapters/common";
+import { getExtensionState } from "../../src/storage";
import type { StoredPlatformState } from "../../src/platforms";
+type BackgroundSuccessResponse = {
+ ok: true;
+ data: unknown;
+};
+
+type BackgroundErrorResponse = {
+ ok: false;
+ error?: string;
+};
+
const loading = ref(true);
const refreshing = ref(false);
-const resetting = ref(false);
-const savingId = ref(null);
-const expandedId = ref(null);
const installationKey = ref("");
const installationId = ref(null);
@@ -33,6 +41,10 @@ function clonePlatforms(items: StoredPlatformState[]): StoredPlatformState[] {
return items.map((platform) => ({ ...platform }));
}
+function isBackgroundResponse(value: unknown): value is BackgroundSuccessResponse | BackgroundErrorResponse {
+ return typeof value === "object" && value !== null && "ok" in value;
+}
+
async function loadState(): Promise {
loading.value = true;
try {
@@ -64,8 +76,9 @@ function uidFor(platform: StoredPlatformState): string {
}
function avatarFor(platform: StoredPlatformState): string | null {
- if (platform.avatar_url?.trim()) {
- return platform.avatar_url.trim();
+ const normalizedAvatar = normalizeRemoteUrl(platform.avatar_url);
+ if (normalizedAvatar) {
+ return normalizedAvatar;
}
if (platform.connected) {
return `https://api.dicebear.com/9.x/initials/svg?seed=${encodeURIComponent(titleFor(platform))}`;
@@ -73,13 +86,6 @@ function avatarFor(platform: StoredPlatformState): string | null {
return null;
}
-function statusLabel(platform: StoredPlatformState): string {
- if (refreshing.value || loading.value) {
- return "检测中...";
- }
- return platform.connected ? "已登录" : "未登录";
-}
-
function statusTone(platform: StoredPlatformState): "success" | "danger" | "pending" {
if (refreshing.value || loading.value) {
return "pending";
@@ -87,36 +93,18 @@ function statusTone(platform: StoredPlatformState): "success" | "danger" | "pend
return platform.connected ? "success" : "danger";
}
-function fillDemo(platform: StoredPlatformState): void {
- platform.connected = true;
- platform.nickname = platform.nickname?.trim() || `${platform.platform_name} Demo`;
- platform.platform_uid = (platform.platform_uid != null ? String(platform.platform_uid).trim() : "") || `${platform.platform_id}-demo`;
- platform.avatar_url =
- platform.avatar_url?.trim() ||
- `https://api.dicebear.com/9.x/initials/svg?seed=${encodeURIComponent(platform.nickname || platform.platform_name)}`;
- platform.message = null;
-}
-
-function toggleExpanded(platformId: string): void {
- expandedId.value = expandedId.value === platformId ? null : platformId;
-}
-
-async function savePlatform(platform: StoredPlatformState): Promise {
- savingId.value = platform.platform_id;
- try {
- const state = await updatePlatformState(platform.platform_id, {
- connected: platform.connected,
- nickname: platform.nickname?.trim() || null,
- platform_uid: platform.platform_uid != null ? String(platform.platform_uid).trim() || null : null,
- avatar_url: platform.avatar_url?.trim() || null,
- message: platform.connected ? null : "Sign in required",
- });
- installationId.value = state.plugin_installation_id;
- apiBaseUrl.value = state.api_base_url;
- platforms.value = clonePlatforms(state.platforms);
- } finally {
- savingId.value = null;
+function loginButtonLabel(platform: StoredPlatformState): string {
+ if (refreshing.value || loading.value) {
+ return "检测中...";
}
+ return "未登录";
+}
+
+async function openLoginPage(platform: StoredPlatformState): Promise {
+ if (!platform.login_url || refreshing.value || loading.value) {
+ return;
+ }
+ await browser.tabs.create({ url: platform.login_url });
}
async function simulateDetect(): Promise {
@@ -126,10 +114,13 @@ async function simulateDetect(): Promise {
async function refreshDetectedPlatforms(): Promise {
refreshing.value = true;
try {
- await browser.runtime.sendMessage({
+ const response = await browser.runtime.sendMessage({
type: "geo.publisher.request",
action: "detectPlatforms",
});
+ if (isBackgroundResponse(response) && !response.ok) {
+ throw new Error(response.error || "publisher_plugin_unknown_error");
+ }
const state = await getExtensionState();
installationId.value = state.plugin_installation_id;
apiBaseUrl.value = state.api_base_url;
@@ -138,17 +129,6 @@ async function refreshDetectedPlatforms(): Promise {
refreshing.value = false;
}
}
-
-async function handleReset(): Promise {
- resetting.value = true;
- try {
- const state = await resetPlatformStates();
- platforms.value = clonePlatforms(state.platforms);
- expandedId.value = null;
- } finally {
- resetting.value = false;
- }
-}
@@ -169,9 +149,8 @@ async function handleReset(): Promise {
v-for="platform in platforms"
:key="platform.platform_id"
class="platform-card"
- :class="[`is-${statusTone(platform)}`, { 'is-expanded': expandedId === platform.platform_id }]"
+ :class="[`is-${statusTone(platform)}`]"
:style="{ '--platform-accent': platform.accent_color }"
- @click="toggleExpanded(platform.platform_id)"
>
@@ -203,47 +182,17 @@ async function handleReset(): Promise {