feat(app,viewer): WeChat card share + 'Share to...' label + site og tags

- 按钮文字「分享到微信/其他应用」→「分享到...」(用户需求)
- 微信卡片分享(fluwx 6):WECHAT_APPID 经 --dart-define 注入;已配置且
  装微信 → WXWebpage 卡片(标题=相册名、描述=日期·地点、缩略图=封面压缩
  240px PNG ≤32KB、点击进观众链接);未配置/未装 → 回退系统分享面板
- viewer index.html 加站点级 og 标签(微信/QQ 抓取链接预览用;每相册动态
  og 需服务端注入,登记后续增强)
- 397/397 全绿、analyze 0

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-23 16:05:53 +08:00
parent 77b1519209
commit 5a207b395e
20 changed files with 288 additions and 22 deletions
+118 -1
View File
@@ -27,6 +27,9 @@ import {
V2_HEIGHT,
} from './params.js';
// v2 上行白名单 = v1 白名单 + open-photo(宿主内详情弹框,用户决策 2026-09-23)。
var V2_MESSAGE_TYPES = MESSAGE_TYPES.concat(['open-photo']);
(function () {
'use strict';
try {
@@ -281,10 +284,121 @@ import {
var i = instances.indexOf(inst);
if (i >= 0) instances.splice(i, 1);
delete seenSlots[inst.config.slot];
closePhotoModal();
if (instances.length === 0) w.removeEventListener('message', onMessage);
scheduleRelayout();
}
// ── 照片详情宿主内弹框(用户决策 2026-09-23):点击滚动照片 → 宿主页
// 覆盖层 + 居中卡片内嵌 viewer 深链 iframe(不新开标签)。安全:URL
// 必须与组件 iframe 同源(frameOrigin)才打开——iframe 被攻破也无法
// 诱导宿主内嵌任意第三方页面;Esc/遮罩/× 关闭;单实例(重复点击换 src)。
var photoModal = null;
function closePhotoModal() {
if (photoModal === null) return;
if (photoModal.parentNode) photoModal.parentNode.removeChild(photoModal);
photoModal = null;
w.removeEventListener('keydown', onModalKeydown);
}
function onModalKeydown(e) {
if (e.key === 'Escape') closePhotoModal();
}
function openPhotoModal(inst, rawUrl) {
if (typeof rawUrl !== 'string' || rawUrl === '') return;
var target;
try {
target = new URL(rawUrl);
} catch {
return;
}
if (target.origin !== inst.frameOrigin) return; // 同源校验(防任意嵌入)
if (photoModal === null) {
photoModal = document.createElement('div');
var os = photoModal.style;
os.position = 'fixed';
os.inset = '0';
os.zIndex = String(inst.config.zIndex + 1);
os.background = 'rgba(9, 14, 20, 0.6)';
os.display = 'flex';
os.alignItems = 'center';
os.justifyContent = 'center';
os.padding = '16px';
photoModal.addEventListener('pointerdown', function (e) {
if (e.target === photoModal) closePhotoModal();
});
var card = document.createElement('div');
var cs = card.style;
cs.position = 'relative';
cs.width = 'min(92vw, 420px)';
cs.height = 'min(86vh, 640px)';
cs.borderRadius = '12px';
cs.border = '1px solid rgba(255,255,255,0.80)';
cs.background = '#10161e';
cs.boxShadow = '0 12px 40px rgba(15,23,42,0.35)';
cs.overflow = 'hidden';
var detailFrame = document.createElement('iframe');
detailFrame.setAttribute(
'sandbox',
'allow-scripts allow-popups allow-popups-to-escape-sandbox allow-same-origin'
);
detailFrame.setAttribute('referrerpolicy', 'no-referrer-when-downgrade');
detailFrame.setAttribute('title', 'PhotoLive photo detail');
var dfs = detailFrame.style;
dfs.width = '100%';
dfs.height = '100%';
dfs.border = '0';
dfs.display = 'block';
card.appendChild(detailFrame);
var closeBtn = document.createElement('button');
closeBtn.type = 'button';
closeBtn.setAttribute('aria-label', isZh(inst.config.lang) ? '关闭照片详情' : 'Close photo detail');
var bs = closeBtn.style;
bs.position = 'absolute';
bs.top = '6px';
bs.right = '6px';
bs.width = '44px';
bs.height = '44px';
bs.border = '0';
bs.background = 'transparent';
bs.color = '#647084';
bs.cursor = 'pointer';
bs.padding = '0';
var dot = document.createElement('span');
var dss = dot.style;
dss.position = 'absolute';
dss.top = '6px';
dss.right = '6px';
dss.width = '24px';
dss.height = '24px';
dss.borderRadius = '50%';
dss.background = 'rgba(255,255,255,0.82)';
dss.display = 'flex';
dss.alignItems = 'center';
dss.justifyContent = 'center';
dot.textContent = '×';
dot.style.fontSize = '19px';
dot.style.lineHeight = '1';
closeBtn.appendChild(dot);
closeBtn.addEventListener('click', closePhotoModal);
card.appendChild(closeBtn);
photoModal.appendChild(card);
document.body.appendChild(photoModal);
w.addEventListener('keydown', onModalKeydown);
photoModal.__frame = detailFrame;
// 焦点移入弹框关闭钮:键盘事件落到宿主文档(Esc 可关);否则焦点
// 残留在小组件 iframe 内,宿主 keydown 收不到。
closeBtn.focus();
}
photoModal.__frame.setAttribute('src', target.href);
}
/** 后台设置应用(config 消息已过 parseConfigMessageV2 版本闸):非 data-*
* 显式字段立即生效;collapsedDefault 仅在无用户选择时应用。 */
function applyConfig(inst, version, settings) {
@@ -374,7 +488,7 @@ import {
var data = event.data;
if (!data || data.photoliveEmbed !== true) return;
var type = data.type;
if (MESSAGE_TYPES.indexOf(type) < 0) return;
if (V2_MESSAGE_TYPES.indexOf(type) < 0) return;
for (var i = 0; i < instances.length; i++) {
var inst = instances[i];
if (!inst.frame.contentWindow || event.source !== inst.frame.contentWindow) continue;
@@ -390,6 +504,9 @@ import {
if (parsed !== null) applyConfig(inst, parsed.configVersion, parsed.settings);
} else if (type === 'destroy') {
destroyInstance(inst);
closePhotoModal();
} else if (type === 'open-photo') {
openPhotoModal(inst, data.url);
}
// 'resize':v2 窗口固定,显式忽略(§7.3)。
return;