80 lines
2.8 KiB
JavaScript
80 lines
2.8 KiB
JavaScript
|
|
// 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)));
|
|||
|
|
}
|