Files
photo-live/packages/embed-loader/src/params.js
T

80 lines
2.8 KiB
JavaScript
Raw Normal View History

// packages/embed-loader/src/params.js
// 纯函数参数模块:loader(esbuild 打包内联)与 vitest 单测共用的唯一参数源。
// 取值域与缺省严格对齐契约 EmbedSlotSettings(packages/contracts/openapi.yaml),
// 非法/缺省一律静默回退——loader 对宿主零报错(A11「不破坏宿主」)。
export const POSITION_VALUES = ['left', 'right'];
export const SIZE_VALUES = ['sm', 'md'];
export const LANG_VALUES = ['auto', 'zh-CN', 'en'];
export const DEFAULTS = {
position: 'right',
size: 'md',
intervalMs: 5000,
lang: 'auto',
collapsedDefault: false,
};
/** size → 宿主侧 wrapper 宽度(px)。sm 为窄屏降级档。 */
export const SIZE_WIDTH = { sm: 280, md: 340 };
export const INTERVAL_MIN = 3000;
export const INTERVAL_MAX = 30000;
/** resize 消息高度夹取范围(px),越界/非数值返回 null 表示忽略。 */
export const HEIGHT_MIN = 60;
export const HEIGHT_MAX = 640;
function oneOf(allowed, value, fallback) {
return allowed.indexOf(value) >= 0 ? value : fallback;
}
/** 轮播间隔(ms):解析失败取缺省,超界夹取到 [3000, 30000]。 */
export function normalizeInterval(value) {
const n = parseInt(value, 10);
if (isNaN(n)) return DEFAULTS.intervalMs;
return Math.min(INTERVAL_MAX, Math.max(INTERVAL_MIN, n));
}
/**
* 解析宿主 script 标签 data-* 配置(dataset 形如 {slot, lang, position, interval})。
* opts.narrow=true(viewport <480px)时强制 sm 尺寸 + 默认收起(计划评审缺口:移动端可用)。
* data-slot 缺失/空白 → slot 为 null,调用方须静默 no-op。
*/
export function parseEmbedConfig(dataset, opts) {
const ds = dataset || {};
const slot = typeof ds.slot === 'string' ? ds.slot.replace(/^\s+|\s+$/g, '') : '';
const narrow = !!(opts && opts.narrow);
return {
slot: slot || null,
lang: oneOf(LANG_VALUES, ds.lang, DEFAULTS.lang),
position: oneOf(POSITION_VALUES, ds.position, DEFAULTS.position),
intervalMs: normalizeInterval(ds.interval),
size: narrow ? 'sm' : oneOf(SIZE_VALUES, ds.size, DEFAULTS.size),
collapsedDefault: narrow ? true : DEFAULTS.collapsedDefault,
};
}
/** iframe src:{origin}/embed/{slot}?lang=&pos=&interval=(槽 token 编码防注入)。 */
export function buildEmbedUrl(origin, slot, config) {
return (
origin +
'/embed/' +
encodeURIComponent(slot) +
'?lang=' +
encodeURIComponent(config.lang) +
'&pos=' +
encodeURIComponent(config.position) +
'&interval=' +
String(config.intervalMs)
);
}
/** resize 高度夹取:非有限数值/缺失 → null(忽略该消息)。 */
export function clampHeight(value) {
if (value === null || value === undefined) return null;
const n = Number(value);
if (isNaN(n) || !isFinite(n)) return null;
return Math.min(HEIGHT_MAX, Math.max(HEIGHT_MIN, Math.round(n)));
}