feat(viewer): UI-02 heart particles, interaction notices, share and download
Task 5 (V05 §5.4/§6): decorative heart particles on the lightbox stage (local like clicks + others' WS bursts for the current photo), compact cross-photo interaction notices above the action bar (click to locate), navigator.share-first share orchestration with clipboard fallback for album and photo deep links (lang param), and download failures attributed to the photo that triggered them. Self-echo dedup stays in the realtime router (own batches never produce particles/notices); particles cap at 12 with visibility/unmount cleanup and are suppressed under reduced motion; notices cap at 3 with ~4s auto-dismiss and throttled SR summary. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
// apps/viewer/src/support/share.ts —— UI-02 §5.4 分享编排(Task 5 / V05):
|
||||
// navigator.share 优先(成功后由原生分享面板收尾,应用内不再重复反馈;用户
|
||||
// 取消 AbortError 静默——不显示错误);API 不可用或其他失败回退剪贴板
|
||||
// (copyText 已含 execCommand 兜底),复制失败由调用方展示可选中文本。
|
||||
// 始终分享观众端路由(相册 /a/:token、照片 /a/:token/p/:photoId?lang=…),
|
||||
// 不分享 OSS 签名 URL(§5.4)。
|
||||
import { copyText } from './clipboard';
|
||||
|
||||
export type ShareOutcome =
|
||||
| 'shared' // navigator.share 成功(原生 UI 已反馈)
|
||||
| 'cancelled' // 用户取消原生分享(AbortError:静默,无错误)
|
||||
| 'copied' // 回退剪贴板成功
|
||||
| 'failed'; // 回退剪贴板失败(调用方展示可选中文本)
|
||||
|
||||
/** navigator.share 是否可用(安全上下文 + 浏览器支持)。 */
|
||||
export function canNativeShare(): boolean {
|
||||
return typeof navigator.share === 'function';
|
||||
}
|
||||
|
||||
/** 原生分享优先,剪贴板兜底(§5.4)。 */
|
||||
export async function shareOrCopy(input: { title: string; url: string }): Promise<ShareOutcome> {
|
||||
if (canNativeShare()) {
|
||||
try {
|
||||
await navigator.share({ title: input.title, url: input.url });
|
||||
return 'shared';
|
||||
} catch (error) {
|
||||
if ((error as { name?: unknown } | null)?.name === 'AbortError') {
|
||||
return 'cancelled';
|
||||
}
|
||||
// 其他失败(数据/权限/非安全上下文等):继续剪贴板回退。
|
||||
}
|
||||
}
|
||||
return (await copyText(input.url)) ? 'copied' : 'failed';
|
||||
}
|
||||
Reference in New Issue
Block a user