5a207b395e
- 按钮文字「分享到微信/其他应用」→「分享到...」(用户需求) - 微信卡片分享(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>
570 lines
22 KiB
JavaScript
570 lines
22 KiB
JavaScript
// packages/embed-loader/src/loader-v2.js
|
||
// PhotoLive 嵌入组件加载器 v2(UI-04 vertical-stream 照片流窗)——对外发布物
|
||
// (esbuild 打包为 dist/v2/loader.js,宿主以 <script src="{EMBED_ORIGIN}/
|
||
// embed/v2/loader.js" data-slot> 引入)。与 v1(src/loader.js)同约束:零依赖、
|
||
// 零全局(仅版本标记属性)、无 console、一切错误静默不抛(A11 不破坏宿主)。
|
||
//
|
||
// v2 与 v1 的宿主侧差异(方案 §2/§7/§8):
|
||
// · 窗口硬性固定 195×280 CSS px(border-box,含 1px 边框);PC/手机同值;
|
||
// 仅在视口不足时按 min(195,可用宽)/min(280,可用高) 收缩,绝不 transform
|
||
// 缩放;resize 消息不改高度(iframe 内容自适应窗口,窗口不迁就内容);
|
||
// · 外壳:圆角 12、边框 1px rgba(255,255,255,0.80)(单圈)、阴影
|
||
// 0 4px 16px rgba(15,23,42,0.16)、底色 #10161E(加载/缺口不露宿主内容);
|
||
// · 默认停靠:桌面 right 16 / 手机 right 12,bottom 24 + safe-area-inset;
|
||
// offsetX 仅在 data-* 与后台都未配置时按视口取默认;
|
||
// · 收起把手:白底胶囊 88–104×44、圆角 22、蓝图标 + 12px「照片/Photos」;
|
||
// · 多槽同侧纵排间距 12;可用高度不足时后续槽收起(空间约束≠用户选择);
|
||
// · 窄屏(<480px)默认收起沿用 v1 策略;展开后两端尺寸一致(195×280)。
|
||
// 收起按钮本体在 iframe 内(viewer EmbedStream 渲染,§2.4),经 collapse 消息
|
||
// 请求宿主收起——宿主与 iframe 各自清理,语义同 v1。
|
||
import {
|
||
parseEmbedConfigV2,
|
||
buildEmbedUrlV2,
|
||
mergeBackendConfigV2,
|
||
parseConfigMessageV2,
|
||
MESSAGE_TYPES,
|
||
V2_WIDTH,
|
||
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 {
|
||
var w = window;
|
||
// v2 独立标记:同页引入 v1+v2(迁移期)互不干扰。
|
||
if (w.__photoliveEmbedV2) return;
|
||
w.__photoliveEmbedV2 = '1';
|
||
|
||
var COLLAPSE_KEY = 'pl-embed-collapsed:'; // 与 v1 共用(同槽同会话记忆一致)
|
||
var STACK_GAP = 12; // §8.1 多槽同侧纵排间距
|
||
var HANDLE_MIN = 44; // 收起把手高/最小宽(§2.4)
|
||
// 收缩下限:低于此尺寸无法保证照片窗可用 → 改收起入口(§2.1「小到无法
|
||
// 操作时改成收起入口,而不是缩成不可点」)。
|
||
var MIN_OPERABLE_WIDTH = 150;
|
||
var MIN_OPERABLE_HEIGHT = 200;
|
||
|
||
function readCollapsed(slot) {
|
||
try {
|
||
return w.sessionStorage.getItem(COLLAPSE_KEY + slot) === '1';
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
function writeCollapsed(slot, collapsed) {
|
||
try {
|
||
if (collapsed) w.sessionStorage.setItem(COLLAPSE_KEY + slot, '1');
|
||
else w.sessionStorage.removeItem(COLLAPSE_KEY + slot);
|
||
} catch {
|
||
/* storage 异常:静默放弃记忆 */
|
||
}
|
||
}
|
||
function hasChoice(slot) {
|
||
try {
|
||
return w.sessionStorage.getItem(COLLAPSE_KEY + slot) !== null;
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
function selfScript() {
|
||
if (document.currentScript && document.currentScript.src) return document.currentScript;
|
||
var all = document.querySelectorAll('script[src*="/embed/v2/loader.js"]');
|
||
return all.length ? all[all.length - 1] : null;
|
||
}
|
||
function originOf(el) {
|
||
try {
|
||
return new URL(el.src, w.location.href).origin;
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
var instances = [];
|
||
var seenSlots = {};
|
||
var relayoutFrame = null;
|
||
|
||
function isZh(lang) {
|
||
if (lang === 'zh-CN') return true;
|
||
if (lang === 'auto') {
|
||
try {
|
||
return /^zh/i.test(w.navigator.language || '');
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 收起把手(§2.4;效果示例 .handle 精确值):100×44 白底胶囊、圆角 22、
|
||
// 1px #e3eaf2 边、蓝图标 16 + 12px 文案;全部 inline style(零宿主污染)。
|
||
function createHandle(inst) {
|
||
var h = document.createElement('div');
|
||
h.setAttribute('role', 'button');
|
||
h.setAttribute('tabindex', '0');
|
||
h.setAttribute('aria-label', isZh(inst.config.lang) ? '展开照片窗' : 'Show photo window');
|
||
var s = h.style;
|
||
s.position = 'fixed';
|
||
s.height = HANDLE_MIN + 'px';
|
||
s.width = '100px';
|
||
s.boxSizing = 'border-box';
|
||
s.borderRadius = '22px';
|
||
s.border = '1px solid #e3eaf2';
|
||
s.background = '#ffffff';
|
||
s.boxShadow = '0 4px 16px rgba(15,23,42,0.125)';
|
||
s.zIndex = String(inst.config.zIndex);
|
||
s.cursor = 'pointer';
|
||
s.display = 'flex';
|
||
s.alignItems = 'center';
|
||
s.justifyContent = 'center';
|
||
s.gap = '8px';
|
||
s.font = '400 12px/1 system-ui, -apple-system, "Segoe UI", "PingFang SC", sans-serif';
|
||
s.color = '#32618e';
|
||
s.whiteSpace = 'nowrap';
|
||
h.innerHTML =
|
||
'<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false" style="color:#1a73e8">' +
|
||
'<path fill="currentColor" d="M9 2 7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2h-3.17L15 2H9zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"/></svg>' +
|
||
'<span>' + (isZh(inst.config.lang) ? '照片' : 'Photos') + '</span>';
|
||
function activate(ev) {
|
||
if (ev.type === 'keydown') {
|
||
var k = ev.key;
|
||
if (k !== 'Enter' && k !== ' ' && k !== 'Spacebar') return;
|
||
ev.preventDefault();
|
||
}
|
||
inst.userChoice = true;
|
||
expandInstance(inst);
|
||
try {
|
||
if (inst.frame.contentWindow) {
|
||
inst.frame.contentWindow.postMessage(
|
||
{ photoliveEmbed: true, type: 'expand' },
|
||
inst.frameOrigin
|
||
);
|
||
}
|
||
} catch {
|
||
/* 静默 */
|
||
}
|
||
}
|
||
h.addEventListener('click', activate);
|
||
h.addEventListener('keydown', activate);
|
||
return h;
|
||
}
|
||
|
||
/** 展开态盒高(收缩后可能 < 280);收起态计把手 44(堆叠依据)。 */
|
||
function effectiveHeight(inst) {
|
||
return inst.collapsed ? HANDLE_MIN : inst.height;
|
||
}
|
||
|
||
/** 默认水平偏移:未配置时桌面 16 / ≤599px 视口 12(§2.1;效果示例
|
||
* media(max-width:599px) 档)。data-* 显式或后台配置到达后
|
||
* (offsetConfigured)按配置值。 */
|
||
function effectiveOffsetX(inst) {
|
||
if (inst.config.explicit.offsetX || inst.offsetConfigured) return inst.config.offsetX;
|
||
return w.innerWidth <= 599 ? 12 : 16;
|
||
}
|
||
|
||
/** wrapper 几何:W=min(195,可用宽)、H=min(280,可用高);不足操作下限时
|
||
* 由调用方收起(空间约束)。 */
|
||
function measureBox(inst) {
|
||
var offsetX = effectiveOffsetX(inst);
|
||
var availW = w.innerWidth - offsetX - 8;
|
||
var availH = w.innerHeight - inst.config.offsetBottom - 8;
|
||
return {
|
||
offsetX: offsetX,
|
||
width: Math.max(120, Math.min(V2_WIDTH, availW)),
|
||
height: Math.max(160, Math.min(V2_HEIGHT, availH)),
|
||
operable: availW >= MIN_OPERABLE_WIDTH && availH >= MIN_OPERABLE_HEIGHT,
|
||
};
|
||
}
|
||
|
||
function applyWrapperBox(inst) {
|
||
var box = measureBox(inst);
|
||
inst.height = box.height;
|
||
var side = inst.config.position === 'left' ? 'left' : 'right';
|
||
var other = side === 'left' ? 'right' : 'left';
|
||
var ws = inst.wrapper.style;
|
||
if (!inst.collapsed) {
|
||
ws.display = 'block';
|
||
ws.width = box.width + 'px';
|
||
ws.height = box.height + 'px';
|
||
}
|
||
ws.zIndex = String(inst.config.zIndex);
|
||
ws[side] = box.offsetX + 'px';
|
||
ws[other] = '';
|
||
// 底部偏移 + 安全区(§2.1):不支持 env() 的旧浏览器整条声明失效回退
|
||
// 上一行设置的裸像素值(先写裸值再写 calc,双保险)。
|
||
ws.bottom = inst.config.offsetBottom + 'px';
|
||
ws.bottom =
|
||
'calc(' + inst.config.offsetBottom + 'px + env(safe-area-inset-bottom, 0px))';
|
||
if (inst.handle) {
|
||
var hs = inst.handle.style;
|
||
hs.zIndex = String(inst.config.zIndex);
|
||
hs[side] = box.offsetX + 'px';
|
||
hs[other] = '';
|
||
}
|
||
return box;
|
||
}
|
||
|
||
// 同侧多槽纵排(§8.1):登记顺序自下而上,间距 12;可用高度不足时后续
|
||
// 槽收起(不写用户偏好)。
|
||
function relayout() {
|
||
var sides = { left: [], right: [] };
|
||
for (var i = 0; i < instances.length; i++) {
|
||
var inst = instances[i];
|
||
sides[inst.config.position === 'left' ? 'left' : 'right'].push(inst);
|
||
}
|
||
var keys = ['left', 'right'];
|
||
for (var s = 0; s < keys.length; s++) {
|
||
var list = sides[keys[s]];
|
||
var cursorBottom = 0;
|
||
for (var j = 0; j < list.length; j++) {
|
||
var it = list[j];
|
||
var bottom = j === 0 ? it.config.offsetBottom : cursorBottom + effectiveHeight(list[j - 1]) + STACK_GAP;
|
||
if (j > 0 && bottom + V2_HEIGHT > w.innerHeight) setCollapsed(it, false);
|
||
it.wrapper.style.bottom = bottom + 'px';
|
||
if (it.handle) it.handle.style.bottom = bottom + 'px';
|
||
cursorBottom = bottom;
|
||
}
|
||
}
|
||
}
|
||
|
||
function scheduleRelayout() {
|
||
if (relayoutFrame !== null || typeof w.requestAnimationFrame !== 'function') {
|
||
relayout();
|
||
return;
|
||
}
|
||
relayoutFrame = w.requestAnimationFrame(function () {
|
||
relayoutFrame = null;
|
||
relayout();
|
||
});
|
||
}
|
||
|
||
/** 收起核心(不触发重排调度——relayout 空间不足收起时避免再排)。 */
|
||
function setCollapsed(inst, persist) {
|
||
if (inst.collapsed) return;
|
||
inst.collapsed = true;
|
||
inst.wrapper.style.display = 'none';
|
||
if (!inst.handle) {
|
||
inst.handle = createHandle(inst);
|
||
document.body.appendChild(inst.handle);
|
||
// 首次创建即定位(side/offsetX/zIndex)——漏掉会使 fixed 把手失去
|
||
// 水平定位而跑向左侧(v1 同位有此调用;2026-09-23 服务器实测发现)。
|
||
applyWrapperBox(inst);
|
||
}
|
||
inst.handle.style.display = 'flex';
|
||
if (persist !== false) writeCollapsed(inst.config.slot, true);
|
||
}
|
||
|
||
function collapseInstance(inst, persist) {
|
||
setCollapsed(inst, persist);
|
||
scheduleRelayout();
|
||
}
|
||
|
||
function expandInstance(inst) {
|
||
if (!inst.collapsed) return;
|
||
inst.collapsed = false;
|
||
applyWrapperBox(inst);
|
||
if (inst.handle) inst.handle.style.display = 'none';
|
||
writeCollapsed(inst.config.slot, false);
|
||
scheduleRelayout();
|
||
}
|
||
|
||
/** 服务端终止展示(停用/解绑/404/410):移除照片窗与把手,不留空壳。 */
|
||
function destroyInstance(inst) {
|
||
if (inst.destroyed) return;
|
||
inst.destroyed = true;
|
||
if (inst.handle && inst.handle.parentNode) inst.handle.parentNode.removeChild(inst.handle);
|
||
if (inst.wrapper.parentNode) inst.wrapper.parentNode.removeChild(inst.wrapper);
|
||
try {
|
||
w.sessionStorage.removeItem(COLLAPSE_KEY + inst.config.slot);
|
||
} catch {
|
||
/* 静默 */
|
||
}
|
||
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) {
|
||
inst.backendVersion = version;
|
||
inst.offsetConfigured = true; // 后台 settings 恒为全集 → offsetX 已「配置」
|
||
inst.config = mergeBackendConfigV2(inst.config, settings);
|
||
applyWrapperBox(inst);
|
||
if (!inst.userChoice && !hasChoice(inst.config.slot)) {
|
||
if (settings.collapsedDefault) collapseInstance(inst, false);
|
||
else if (!spaceConstrained(inst)) expandInstance(inst);
|
||
}
|
||
scheduleRelayout();
|
||
}
|
||
|
||
/** 当前视口是否空间不足(不足时默认收起而非塞下不可用的窗口)。 */
|
||
function spaceConstrained(inst) {
|
||
return !measureBox(inst).operable;
|
||
}
|
||
|
||
function createInstance(origin, config) {
|
||
var wrapper = document.createElement('div');
|
||
var ws = wrapper.style;
|
||
ws.position = 'fixed';
|
||
ws.width = V2_WIDTH + 'px';
|
||
ws.height = V2_HEIGHT + 'px';
|
||
ws.borderRadius = '12px';
|
||
ws.border = '1px solid rgba(255,255,255,0.80)';
|
||
ws.boxShadow = '0 4px 16px rgba(15,23,42,0.16)';
|
||
ws.background = '#10161E';
|
||
ws.overflow = 'hidden';
|
||
// 敌意宿主全局 CSS 反制(EMB-A12:*{box-sizing:content-box!important} 等
|
||
// 会翻转 195×280 盒模型/给 iframe 加边框)——几何关键属性以 inline
|
||
// important 压制宿主样式表 important(inline important 优先级最高)。
|
||
ws.setProperty('box-sizing', 'border-box', 'important');
|
||
|
||
var frame = document.createElement('iframe');
|
||
// sandbox 同 v1(含 allow-same-origin:iframe 保有项目域源,白名单按
|
||
// 源+source 双重严格校验;跨域隔离与链接新页安全不变)。
|
||
frame.setAttribute(
|
||
'sandbox',
|
||
'allow-scripts allow-popups allow-popups-to-escape-sandbox allow-same-origin'
|
||
);
|
||
frame.setAttribute('src', buildEmbedUrlV2(origin, config.slot, config));
|
||
frame.setAttribute('referrerpolicy', 'no-referrer-when-downgrade');
|
||
frame.setAttribute('title', 'PhotoLive photos');
|
||
var fs = frame.style;
|
||
fs.width = '100%';
|
||
fs.height = '100%';
|
||
fs.border = '0';
|
||
fs.display = 'block';
|
||
fs.setProperty('border', '0', 'important');
|
||
fs.setProperty('width', '100%', 'important');
|
||
fs.setProperty('height', '100%', 'important');
|
||
fs.setProperty('display', 'block', 'important');
|
||
wrapper.appendChild(frame);
|
||
document.body.appendChild(wrapper);
|
||
|
||
var inst = {
|
||
config: config,
|
||
wrapper: wrapper,
|
||
frame: frame,
|
||
frameOrigin: originOf(frame) || origin,
|
||
height: V2_HEIGHT,
|
||
collapsed: false,
|
||
narrow: config.narrow,
|
||
handle: null,
|
||
userChoice: false,
|
||
backendVersion: 0,
|
||
offsetConfigured: config.explicit.offsetX,
|
||
destroyed: false,
|
||
};
|
||
instances.push(inst);
|
||
var box = applyWrapperBox(inst);
|
||
// 收起记忆 / 默认(窄屏或空间不足默认收起;用户选择优先,不反复弹出)。
|
||
if (hasChoice(config.slot)) {
|
||
if (readCollapsed(config.slot)) collapseInstance(inst, false);
|
||
} else if (inst.narrow || !box.operable) {
|
||
collapseInstance(inst, false);
|
||
}
|
||
return inst;
|
||
}
|
||
|
||
// postMessage 白名单:同 v1 双重严格校验(event.source 命中已登记 iframe
|
||
// 且 event.origin 严格等于该 iframe 源)。v2 忽略 resize(窗口固定)。
|
||
function onMessage(event) {
|
||
try {
|
||
var data = event.data;
|
||
if (!data || data.photoliveEmbed !== true) return;
|
||
var type = data.type;
|
||
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;
|
||
if (event.origin !== inst.frameOrigin) return;
|
||
if (type === 'collapse') {
|
||
inst.userChoice = true;
|
||
collapseInstance(inst, true);
|
||
} else if (type === 'expand') {
|
||
inst.userChoice = true;
|
||
expandInstance(inst);
|
||
} else if (type === 'config') {
|
||
var parsed = parseConfigMessageV2(data, inst.backendVersion);
|
||
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;
|
||
}
|
||
} catch {
|
||
/* 静默 */
|
||
}
|
||
}
|
||
|
||
function isNarrow() {
|
||
try {
|
||
return !!w.matchMedia && w.matchMedia('(max-width: 479px)').matches;
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
function onViewportChange() {
|
||
var narrow = isNarrow();
|
||
for (var i = 0; i < instances.length; i++) {
|
||
var inst = instances[i];
|
||
if (inst.narrow !== narrow) {
|
||
inst.narrow = narrow;
|
||
if (narrow && !inst.userChoice && !hasChoice(inst.config.slot)) {
|
||
collapseInstance(inst, false);
|
||
}
|
||
}
|
||
applyWrapperBox(inst);
|
||
}
|
||
scheduleRelayout();
|
||
}
|
||
|
||
function init() {
|
||
var self = selfScript();
|
||
if (!self) return;
|
||
var embedOrigin = originOf(self);
|
||
if (!embedOrigin) return;
|
||
var narrow = isNarrow();
|
||
var tags = document.querySelectorAll('script[src*="/embed/v2/loader.js"]');
|
||
for (var i = 0; i < tags.length; i++) {
|
||
var config = parseEmbedConfigV2(tags[i].dataset, { narrow: narrow });
|
||
if (!config.slot) continue;
|
||
if (seenSlots[config.slot]) continue;
|
||
seenSlots[config.slot] = true;
|
||
createInstance(embedOrigin, config);
|
||
}
|
||
w.addEventListener('message', onMessage);
|
||
w.addEventListener('resize', onViewportChange);
|
||
w.addEventListener('orientationchange', onViewportChange);
|
||
}
|
||
|
||
if (document.body) {
|
||
init();
|
||
} else {
|
||
document.addEventListener('DOMContentLoaded', init);
|
||
}
|
||
} catch {
|
||
/* 静默:任何异常不得破坏宿主页 */
|
||
}
|
||
})();
|