@pie-players/pie-players-shared 0.3.47 → 0.3.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/dist/i18n/simple-i18n.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/instrumentation/debug-panel-stream.js.map +1 -1
- package/dist/instrumentation/providers/CompositeInstrumentationProvider.js.map +1 -1
- package/dist/instrumentation/providers/DebugPanelInstrumentationProvider.js +2 -1
- package/dist/instrumentation/providers/DebugPanelInstrumentationProvider.js.map +1 -1
- package/dist/loaders/ElementLoader.js +2 -1
- package/dist/loaders/ElementLoader.js.map +1 -1
- package/dist/loaders/element-loader.js +28 -4
- package/dist/loaders/element-loader.js.map +1 -1
- package/dist/loaders/esm-adapter.d.ts +22 -5
- package/dist/loaders/esm-adapter.js +320 -60
- package/dist/loaders/esm-adapter.js.map +1 -1
- package/dist/loaders/iife-adapter.js +1 -2
- package/dist/loaders/iife-adapter.js.map +1 -1
- package/dist/loaders/index.d.ts +1 -1
- package/dist/loaders/index.js.map +1 -1
- package/dist/pie/authoring.js +4 -2
- package/dist/pie/authoring.js.map +1 -1
- package/dist/pie/config.js +4 -1
- package/dist/pie/config.js.map +1 -1
- package/dist/pie/configure-initialization.js.map +1 -1
- package/dist/pie/correct-response-env.js.map +1 -1
- package/dist/pie/index.d.ts +1 -1
- package/dist/pie/index.js +1 -1
- package/dist/pie/index.js.map +1 -1
- package/dist/pie/initialization.js +2 -1
- package/dist/pie/initialization.js.map +1 -1
- package/dist/pie/instrumentation-event-bridge.js.map +1 -1
- package/dist/pie/instrumentation-event-map.js.map +1 -1
- package/dist/pie/instrumentation-provider-resolution.js.map +1 -1
- package/dist/pie/item-controller-storage.js.map +1 -1
- package/dist/pie/item-controller.d.ts +15 -0
- package/dist/pie/item-controller.js +22 -2
- package/dist/pie/item-controller.js.map +1 -1
- package/dist/pie/item-session-contract.d.ts +1 -0
- package/dist/pie/item-session-contract.js +28 -13
- package/dist/pie/item-session-contract.js.map +1 -1
- package/dist/pie/math-rendering.js.map +1 -1
- package/dist/pie/overrides.js +2 -1
- package/dist/pie/overrides.js.map +1 -1
- package/dist/pie/resource-monitor.js.map +1 -1
- package/dist/pie/stage-tracker.js.map +1 -1
- package/dist/pie/types.d.ts +7 -0
- package/dist/pie/types.js.map +1 -1
- package/dist/pie/updates.d.ts +3 -3
- package/dist/pie/updates.js +48 -25
- package/dist/pie/updates.js.map +1 -1
- package/dist/security/index.d.ts +1 -0
- package/dist/security/index.js +1 -0
- package/dist/security/index.js.map +1 -1
- package/dist/security/sanitize-item-markup.js.map +1 -1
- package/dist/security/wrap-model-rich-content.d.ts +1 -0
- package/dist/security/wrap-model-rich-content.js +41 -0
- package/dist/security/wrap-model-rich-content.js.map +1 -0
- package/dist/security/wrap-overwide-images.js +1 -2
- package/dist/security/wrap-overwide-images.js.map +1 -1
- package/dist/security/wrap-overwide-tables.js +1 -2
- package/dist/security/wrap-overwide-tables.js.map +1 -1
- package/dist/server/npm-registry.js.map +1 -1
- package/dist/types/index.d.ts +15 -3
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
package/dist/pie/updates.d.ts
CHANGED
|
@@ -8,12 +8,12 @@ import type { UpdatePieElementOptions } from "./types.js";
|
|
|
8
8
|
/**
|
|
9
9
|
* Update a PIE element by ref (direct Element reference)
|
|
10
10
|
*/
|
|
11
|
-
export declare const updatePieElementWithRef: (el: Element, opts: UpdatePieElementOptions) => void
|
|
11
|
+
export declare const updatePieElementWithRef: (el: Element, opts: UpdatePieElementOptions) => Promise<void>;
|
|
12
12
|
/**
|
|
13
13
|
* Update a PIE element by name (finds all matching elements in DOM)
|
|
14
14
|
*/
|
|
15
|
-
export declare const updatePieElement: (elName: string, opts: UpdatePieElementOptions) => void
|
|
15
|
+
export declare const updatePieElement: (elName: string, opts: UpdatePieElementOptions) => Promise<void>;
|
|
16
16
|
/**
|
|
17
17
|
* Update all PIE elements in a config
|
|
18
18
|
*/
|
|
19
|
-
export declare const updatePieElements: (config: ConfigEntity, session: any[], env: Env, container?: Element | Document) => void
|
|
19
|
+
export declare const updatePieElements: (config: ConfigEntity, session: any[], env: Env, container?: Element | Document, onElementSessionUpdate?: UpdatePieElementOptions["onElementSessionUpdate"]) => Promise<void>;
|
package/dist/pie/updates.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Functions for updating PIE elements with new models, sessions, and env.
|
|
5
5
|
*/
|
|
6
6
|
import { mergeObjectsIgnoringNullUndefined } from "../object/index.js";
|
|
7
|
+
import { wrapModelRichContent } from "../security/wrap-model-rich-content.js";
|
|
7
8
|
import { createPieLogger, isGlobalDebugEnabled } from "./logger.js";
|
|
8
9
|
import { findPieController } from "./scoring.js";
|
|
9
10
|
import { defaultPieElementOptions } from "./types.js";
|
|
@@ -58,17 +59,23 @@ const emitControllerError = (pieElement, detail) => {
|
|
|
58
59
|
* Helper function to apply controller to element
|
|
59
60
|
* Extracted to eliminate duplication and ensure consistent controller invocation
|
|
60
61
|
*/
|
|
61
|
-
const applyControllerToElement = async (element, model, elementSession, controller, env, logPrefix) => {
|
|
62
|
+
const applyControllerToElement = async (element, model, elementSession, controller, env, logPrefix, onElementSessionUpdate) => {
|
|
62
63
|
logger.debug(`${logPrefix} Using controller, env:`, env);
|
|
63
64
|
logger.debug(`${logPrefix} Model before filter:`, {
|
|
64
65
|
id: model.id,
|
|
65
66
|
element: model.element,
|
|
66
67
|
hasCorrectResponse: "correctResponse" in model,
|
|
67
68
|
});
|
|
68
|
-
// Create updateSession callback for controller to save
|
|
69
|
+
// Create updateSession callback for controller to save derived state (e.g.
|
|
70
|
+
// shuffle order). Mutate the in-flight element session so this render uses it,
|
|
71
|
+
// AND propagate the write to the authoritative session via the host callback
|
|
72
|
+
// so subsequent renders reuse it instead of regenerating it. The write-back is
|
|
73
|
+
// keyed by the canonical model id/element (the entry findOrAddSession resolved),
|
|
74
|
+
// which also tolerates controllers that pass an undefined id/element.
|
|
69
75
|
const updateSession = (id, _elementName, properties) => {
|
|
70
76
|
logger.debug(`${logPrefix} updateSession called for ${id} with:`, properties);
|
|
71
77
|
Object.assign(elementSession, properties);
|
|
78
|
+
onElementSessionUpdate?.(model.id, model.element, properties);
|
|
72
79
|
return Promise.resolve();
|
|
73
80
|
};
|
|
74
81
|
try {
|
|
@@ -86,14 +93,15 @@ const applyControllerToElement = async (element, model, elementSession, controll
|
|
|
86
93
|
element: model.element,
|
|
87
94
|
...controllerResultObject,
|
|
88
95
|
};
|
|
96
|
+
const wrappedModel = wrapModelRichContent(filteredModel);
|
|
89
97
|
logger.debug(`${logPrefix} ✅ Controller filtered model:`, {
|
|
90
|
-
id:
|
|
91
|
-
element:
|
|
92
|
-
hasCorrectResponse: "correctResponse" in
|
|
98
|
+
id: wrappedModel.id,
|
|
99
|
+
element: wrappedModel.element,
|
|
100
|
+
hasCorrectResponse: "correctResponse" in wrappedModel,
|
|
93
101
|
mode: env.mode,
|
|
94
102
|
role: env.role,
|
|
95
103
|
});
|
|
96
|
-
element.model =
|
|
104
|
+
element.model = wrappedModel;
|
|
97
105
|
element.session = elementSession;
|
|
98
106
|
}
|
|
99
107
|
catch (err) {
|
|
@@ -102,7 +110,7 @@ const applyControllerToElement = async (element, model, elementSession, controll
|
|
|
102
110
|
}
|
|
103
111
|
};
|
|
104
112
|
const resolveAndValidateUpdateOptions = (opts) => {
|
|
105
|
-
const { config, session, env, eventListeners, invokeControllerForModel } = mergeObjectsIgnoringNullUndefined(defaultPieElementOptions, opts);
|
|
113
|
+
const { config, session, env, eventListeners, invokeControllerForModel, onElementSessionUpdate, } = mergeObjectsIgnoringNullUndefined(defaultPieElementOptions, opts);
|
|
106
114
|
if (!env) {
|
|
107
115
|
throw new Error("env is required");
|
|
108
116
|
}
|
|
@@ -112,10 +120,17 @@ const resolveAndValidateUpdateOptions = (opts) => {
|
|
|
112
120
|
if (!config) {
|
|
113
121
|
throw new Error("config is required");
|
|
114
122
|
}
|
|
115
|
-
return {
|
|
123
|
+
return {
|
|
124
|
+
config,
|
|
125
|
+
session,
|
|
126
|
+
env,
|
|
127
|
+
eventListeners,
|
|
128
|
+
invokeControllerForModel,
|
|
129
|
+
onElementSessionUpdate,
|
|
130
|
+
};
|
|
116
131
|
};
|
|
117
|
-
const updateSinglePieElement = (pieElement, controllerLookupTag, options, logContext) => {
|
|
118
|
-
const { config, session, env, eventListeners, invokeControllerForModel } = options;
|
|
132
|
+
const updateSinglePieElement = async (pieElement, controllerLookupTag, options, logContext) => {
|
|
133
|
+
const { config, session, env, eventListeners, invokeControllerForModel, onElementSessionUpdate, } = options;
|
|
119
134
|
const model = config.models?.find((m) => m.id === pieElement.id);
|
|
120
135
|
if (!model) {
|
|
121
136
|
logger.error(`${logContext} Model not found for`, controllerLookupTag);
|
|
@@ -132,7 +147,7 @@ const updateSinglePieElement = (pieElement, controllerLookupTag, options, logCon
|
|
|
132
147
|
const controller = findPieController(controllerLookupTag);
|
|
133
148
|
if (!controller) {
|
|
134
149
|
logger.debug(`${logContext} ℹ️ No controller for ${controllerLookupTag}, using server-processed model`);
|
|
135
|
-
pieElement.model = model;
|
|
150
|
+
pieElement.model = wrapModelRichContent(model);
|
|
136
151
|
pieElement.session = elementSession;
|
|
137
152
|
return;
|
|
138
153
|
}
|
|
@@ -141,7 +156,14 @@ const updateSinglePieElement = (pieElement, controllerLookupTag, options, logCon
|
|
|
141
156
|
role: env.role,
|
|
142
157
|
hasCorrectResponse: "correctResponse" in model,
|
|
143
158
|
});
|
|
144
|
-
|
|
159
|
+
// Await the controller so any updateSession write-back completes before the
|
|
160
|
+
// caller computes/emits the session signature; otherwise a late, async
|
|
161
|
+
// shuffle write lands after the cycle that read the session and the order
|
|
162
|
+
// never round-trips (PIE-631).
|
|
163
|
+
try {
|
|
164
|
+
await applyControllerToElement(pieElement, model, elementSession, controller, env, `${logContext}(${controllerLookupTag}#${pieElement.id})`, onElementSessionUpdate);
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
145
167
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
146
168
|
const isContractError = errorMessage.includes("Controller contract mismatch");
|
|
147
169
|
logger.error(`${logContext} Controller failed for ${controllerLookupTag}#${pieElement.id}:`, err);
|
|
@@ -156,13 +178,13 @@ const updateSinglePieElement = (pieElement, controllerLookupTag, options, logCon
|
|
|
156
178
|
cause: errorMessage,
|
|
157
179
|
});
|
|
158
180
|
// Fall back to raw model on controller error
|
|
159
|
-
pieElement.model = model;
|
|
181
|
+
pieElement.model = wrapModelRichContent(model);
|
|
160
182
|
pieElement.session = elementSession;
|
|
161
|
-
}
|
|
183
|
+
}
|
|
162
184
|
}
|
|
163
185
|
else {
|
|
164
186
|
logger.debug(`${logContext} Direct model assignment for ${controllerLookupTag}#${pieElement.id} (no controller invocation requested)`);
|
|
165
|
-
pieElement.model = model;
|
|
187
|
+
pieElement.model = wrapModelRichContent(model);
|
|
166
188
|
pieElement.session = elementSession;
|
|
167
189
|
}
|
|
168
190
|
};
|
|
@@ -173,7 +195,7 @@ export const updatePieElementWithRef = (el, opts) => {
|
|
|
173
195
|
const options = resolveAndValidateUpdateOptions(opts);
|
|
174
196
|
const pieElement = el;
|
|
175
197
|
const elName = pieElement.tagName.toLowerCase();
|
|
176
|
-
updateSinglePieElement(pieElement, elName, options, "[updatePieElementWithRef]");
|
|
198
|
+
return updateSinglePieElement(pieElement, elName, options, "[updatePieElementWithRef]");
|
|
177
199
|
};
|
|
178
200
|
/**
|
|
179
201
|
* Update a PIE element by name (finds all matching elements in DOM)
|
|
@@ -186,20 +208,21 @@ export const updatePieElement = (elName, opts) => {
|
|
|
186
208
|
const pieElements = searchRoot.querySelectorAll(elName);
|
|
187
209
|
if (!pieElements || pieElements.length === 0) {
|
|
188
210
|
logger.debug(`no elements found for ${elName}`);
|
|
189
|
-
return;
|
|
211
|
+
return Promise.resolve();
|
|
190
212
|
}
|
|
191
|
-
|
|
192
|
-
const pieElement = el;
|
|
193
|
-
updateSinglePieElement(pieElement, elName, options, "[updatePieElement]");
|
|
194
|
-
});
|
|
213
|
+
return Promise.all(Array.from(pieElements, (el) => updateSinglePieElement(el, elName, options, "[updatePieElement]"))).then(() => undefined);
|
|
195
214
|
};
|
|
196
215
|
/**
|
|
197
216
|
* Update all PIE elements in a config
|
|
198
217
|
*/
|
|
199
|
-
export const updatePieElements = (config, session, env, container) => {
|
|
218
|
+
export const updatePieElements = (config, session, env, container, onElementSessionUpdate) => {
|
|
200
219
|
logger.debug("[updatePieElements] Updating all elements with env:", env);
|
|
201
|
-
Object.entries(config.elements).
|
|
202
|
-
|
|
203
|
-
|
|
220
|
+
return Promise.all(Object.entries(config.elements).map(([elName, _pkg]) => updatePieElement(elName, {
|
|
221
|
+
config,
|
|
222
|
+
session,
|
|
223
|
+
env,
|
|
224
|
+
container,
|
|
225
|
+
onElementSessionUpdate,
|
|
226
|
+
}))).then(() => undefined);
|
|
204
227
|
};
|
|
205
228
|
//# sourceMappingURL=updates.js.map
|
package/dist/pie/updates.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"updates.js","sourceRoot":"","sources":["../../src/pie/updates.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * PIE Updates Module\n *\n * Functions for updating PIE elements with new models, sessions, and env.\n */\n\nimport { mergeObjectsIgnoringNullUndefined } from \"../object/index.js\";\nimport type { ConfigEntity, Env, PieModel } from \"../types/index.js\";\nimport { createPieLogger, isGlobalDebugEnabled } from \"./logger.js\";\nimport { findPieController } from \"./scoring.js\";\nimport type { PieElement, UpdatePieElementOptions } from \"./types.js\";\nimport { defaultPieElementOptions } from \"./types.js\";\nimport { findOrAddSession } from \"./utils.js\";\n\n// Create module-level logger (respects global debug flag - pass function for dynamic checking)\nconst logger = createPieLogger(\"pie-updates\", () => isGlobalDebugEnabled());\n\ntype ControllerErrorDetail = {\n\tcode: \"PIE_CONTROLLER_CONTRACT_ERROR\" | \"PIE_CONTROLLER_RUNTIME_ERROR\";\n\tmessage: string;\n\telementName: string;\n\telementId: string;\n\tcontrollerShape?: string;\n\tcause?: string;\n};\n\nconst describeControllerShape = (controller: unknown): string => {\n\tif (!controller) return \"missing\";\n\tif (typeof controller === \"function\") return \"function\";\n\tif (typeof controller !== \"object\") return typeof controller;\n\tconst keys = Object.keys(controller as Record<string, unknown>);\n\tconst defaultValue = (controller as Record<string, unknown>).default;\n\tconst defaultKeys =\n\t\tdefaultValue && typeof defaultValue === \"object\"\n\t\t\t? Object.keys(defaultValue as Record<string, unknown>)\n\t\t\t: [];\n\treturn defaultKeys.length > 0\n\t\t? `object(keys=[${keys.join(\",\")}],defaultKeys=[${defaultKeys.join(\",\")}])`\n\t\t: `object(keys=[${keys.join(\",\")}])`;\n};\n\nconst resolveControllerModelFunction = (\n\tcontroller: unknown,\n): ((model: PieModel, session: any, env: Env, updateSession: any) => unknown) | null => {\n\tif (!controller) return null;\n\tif (typeof controller === \"function\") {\n\t\treturn controller as (model: PieModel, session: any, env: Env, updateSession: any) => unknown;\n\t}\n\tif (typeof controller !== \"object\") return null;\n\tconst direct = (controller as Record<string, unknown>).model;\n\tif (typeof direct === \"function\") {\n\t\treturn (model, sessionData, env, updateSession) =>\n\t\t\t(direct as Function).call(controller, model, sessionData, env, updateSession);\n\t}\n\tconst fromDefault = (controller as Record<string, unknown>).default;\n\tif (fromDefault && typeof fromDefault === \"object\") {\n\t\tconst nested = (fromDefault as Record<string, unknown>).model;\n\t\tif (typeof nested === \"function\") {\n\t\t\treturn (model, sessionData, env, updateSession) =>\n\t\t\t\t(nested as Function).call(fromDefault, model, sessionData, env, updateSession);\n\t\t}\n\t}\n\treturn null;\n};\n\nconst emitControllerError = (\n\tpieElement: PieElement,\n\tdetail: ControllerErrorDetail,\n): void => {\n\tpieElement.dispatchEvent(\n\t\tnew CustomEvent(\"pie-controller-error\", {\n\t\t\tdetail,\n\t\t\tbubbles: true,\n\t\t\tcomposed: true,\n\t\t}),\n\t);\n};\n\n/**\n * Helper function to apply controller to element\n * Extracted to eliminate duplication and ensure consistent controller invocation\n */\nconst applyControllerToElement = async (\n\telement: PieElement,\n\tmodel: PieModel,\n\telementSession: any,\n\tcontroller: unknown,\n\tenv: Env,\n\tlogPrefix: string,\n): Promise<void> => {\n\tlogger.debug(`${logPrefix} Using controller, env:`, env);\n\tlogger.debug(`${logPrefix} Model before filter:`, {\n\t\tid: model.id,\n\t\telement: model.element,\n\t\thasCorrectResponse: \"correctResponse\" in model,\n\t});\n\n\t// Create updateSession callback for controller to save shuffle order\n\tconst updateSession = (id: string, _elementName: string, properties: any) => {\n\t\tlogger.debug(\n\t\t\t`${logPrefix} updateSession called for ${id} with:`,\n\t\t\tproperties,\n\t\t);\n\t\tObject.assign(elementSession, properties);\n\t\treturn Promise.resolve();\n\t};\n\n\ttry {\n\t\tconst modelFunction = resolveControllerModelFunction(controller);\n\t\tif (!modelFunction) {\n\t\t\tthrow new Error(\n\t\t\t\t`Controller contract mismatch: expected a model() function but received ${describeControllerShape(\n\t\t\t\t\tcontroller,\n\t\t\t\t)}`,\n\t\t\t);\n\t\t}\n\t\tconst controllerResult = await modelFunction(model, elementSession, env, updateSession);\n\n\t\t// Merge controller result with id and element (like server-side PieControllerExecutor does)\n\t\tconst controllerResultObject =\n\t\t\tcontrollerResult && typeof controllerResult === \"object\"\n\t\t\t\t? (controllerResult as Record<string, unknown>)\n\t\t\t\t: {};\n\t\tconst filteredModel = {\n\t\t\tid: model.id,\n\t\t\telement: model.element,\n\t\t\t...controllerResultObject,\n\t\t};\n\n\t\tlogger.debug(`${logPrefix} ✅ Controller filtered model:`, {\n\t\t\tid: filteredModel.id,\n\t\t\telement: filteredModel.element,\n\t\t\thasCorrectResponse: \"correctResponse\" in filteredModel,\n\t\t\tmode: env.mode,\n\t\t\trole: env.role,\n\t\t});\n\n\t\telement.model = filteredModel;\n\t\telement.session = elementSession;\n\t} catch (err) {\n\t\tlogger.error(`${logPrefix} ❌ Controller error:`, err);\n\t\tthrow err; // Re-throw - controller errors are fatal\n\t}\n};\n\ntype ResolvedUpdateOptions = Pick<\n\tUpdatePieElementOptions,\n\t\"config\" | \"session\" | \"env\" | \"eventListeners\" | \"invokeControllerForModel\"\n>;\n\nconst resolveAndValidateUpdateOptions = (\n\topts: UpdatePieElementOptions,\n): ResolvedUpdateOptions => {\n\tconst { config, session, env, eventListeners, invokeControllerForModel } =\n\t\tmergeObjectsIgnoringNullUndefined(defaultPieElementOptions, opts);\n\tif (!env) {\n\t\tthrow new Error(\"env is required\");\n\t}\n\tif (!session) {\n\t\tthrow new Error(\"session is required\");\n\t}\n\tif (!config) {\n\t\tthrow new Error(\"config is required\");\n\t}\n\treturn { config, session, env, eventListeners, invokeControllerForModel };\n};\n\nconst updateSinglePieElement = (\n\tpieElement: PieElement,\n\tcontrollerLookupTag: string,\n\toptions: ResolvedUpdateOptions,\n\tlogContext: string,\n): void => {\n\tconst { config, session, env, eventListeners, invokeControllerForModel } =\n\t\toptions;\n\tconst model = config.models?.find((m) => m.id === pieElement.id) as\n\t\t| PieModel\n\t\t| undefined;\n\tif (!model) {\n\t\tlogger.error(`${logContext} Model not found for`, controllerLookupTag);\n\t\tthrow new Error(`model not found for ${controllerLookupTag}`);\n\t}\n\n\tconst elementSession = findOrAddSession(session, model.id, model.element);\n\n\t// Always attach event listeners (don't skip them for no-controller case)\n\tif (eventListeners) {\n\t\tObject.entries(eventListeners).forEach(([evt, fn]) => {\n\t\t\tpieElement.addEventListener(evt as any, fn);\n\t\t});\n\t}\n\n\tif (env && invokeControllerForModel) {\n\t\tconst controller = findPieController(controllerLookupTag);\n\t\tif (!controller) {\n\t\t\tlogger.debug(\n\t\t\t\t`${logContext} ℹ️ No controller for ${controllerLookupTag}, using server-processed model`,\n\t\t\t);\n\t\t\tpieElement.model = model;\n\t\t\tpieElement.session = elementSession;\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug(\n\t\t\t`${logContext} Invoking controller for ${controllerLookupTag}#${pieElement.id}`,\n\t\t\t{\n\t\t\t\tmode: env.mode,\n\t\t\t\trole: env.role,\n\t\t\t\thasCorrectResponse: \"correctResponse\" in model,\n\t\t\t},\n\t\t);\n\n\t\tapplyControllerToElement(\n\t\t\tpieElement,\n\t\t\tmodel,\n\t\t\telementSession,\n\t\t\tcontroller,\n\t\t\tenv,\n\t\t\t`${logContext}(${controllerLookupTag}#${pieElement.id})`,\n\t\t).catch((err) => {\n\t\t\tconst errorMessage = err instanceof Error ? err.message : String(err);\n\t\t\tconst isContractError = errorMessage.includes(\n\t\t\t\t\"Controller contract mismatch\",\n\t\t\t);\n\t\t\tlogger.error(\n\t\t\t\t`${logContext} Controller failed for ${controllerLookupTag}#${pieElement.id}:`,\n\t\t\t\terr,\n\t\t\t);\n\t\t\temitControllerError(pieElement, {\n\t\t\t\tcode: isContractError\n\t\t\t\t\t? \"PIE_CONTROLLER_CONTRACT_ERROR\"\n\t\t\t\t\t: \"PIE_CONTROLLER_RUNTIME_ERROR\",\n\t\t\t\tmessage: `${controllerLookupTag} controller failed while applying model for ${pieElement.id}. ${errorMessage}`,\n\t\t\t\telementName: controllerLookupTag,\n\t\t\t\telementId: pieElement.id,\n\t\t\t\tcontrollerShape: describeControllerShape(controller),\n\t\t\t\tcause: errorMessage,\n\t\t\t});\n\t\t\t// Fall back to raw model on controller error\n\t\t\tpieElement.model = model;\n\t\t\tpieElement.session = elementSession;\n\t\t});\n\t} else {\n\t\tlogger.debug(\n\t\t\t`${logContext} Direct model assignment for ${controllerLookupTag}#${pieElement.id} (no controller invocation requested)`,\n\t\t);\n\t\tpieElement.model = model;\n\t\tpieElement.session = elementSession;\n\t}\n};\n\n/**\n * Update a PIE element by ref (direct Element reference)\n */\nexport const updatePieElementWithRef = (\n\tel: Element,\n\topts: UpdatePieElementOptions,\n): void => {\n\tconst options = resolveAndValidateUpdateOptions(opts);\n\tconst pieElement = el as PieElement;\n\tconst elName = pieElement.tagName.toLowerCase();\n\tupdateSinglePieElement(\n\t\tpieElement,\n\t\telName,\n\t\toptions,\n\t\t\"[updatePieElementWithRef]\",\n\t);\n};\n\n/**\n * Update a PIE element by name (finds all matching elements in DOM)\n */\nexport const updatePieElement = (\n\telName: string,\n\topts: UpdatePieElementOptions,\n): void => {\n\tconst options = resolveAndValidateUpdateOptions(opts);\n\tconst { container } = mergeObjectsIgnoringNullUndefined(\n\t\tdefaultPieElementOptions,\n\t\topts,\n\t);\n\t// Use container for scoped query or fallback to document for global query\n\tconst searchRoot = container || document;\n\tconst pieElements = searchRoot.querySelectorAll(elName);\n\tif (!pieElements || pieElements.length === 0) {\n\t\tlogger.debug(`no elements found for ${elName}`);\n\t\treturn;\n\t}\n\tpieElements.forEach((el) => {\n\t\tconst pieElement = el as PieElement;\n\t\tupdateSinglePieElement(pieElement, elName, options, \"[updatePieElement]\");\n\t});\n};\n\n/**\n * Update all PIE elements in a config\n */\nexport const updatePieElements = (\n\tconfig: ConfigEntity,\n\tsession: any[],\n\tenv: Env,\n\tcontainer?: Element | Document,\n): void => {\n\tlogger.debug(\"[updatePieElements] Updating all elements with env:\", env);\n\tObject.entries(config.elements).forEach(([elName, _pkg]) => {\n\t\tupdatePieElement(elName, { config, session, env, container });\n\t});\n};\n"]}
|
|
1
|
+
{"version":3,"file":"updates.js","sourceRoot":"","sources":["../../src/pie/updates.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * PIE Updates Module\n *\n * Functions for updating PIE elements with new models, sessions, and env.\n */\n\nimport { mergeObjectsIgnoringNullUndefined } from \"../object/index.js\";\nimport { wrapModelRichContent } from \"../security/wrap-model-rich-content.js\";\nimport type { ConfigEntity, Env, PieModel } from \"../types/index.js\";\nimport { createPieLogger, isGlobalDebugEnabled } from \"./logger.js\";\nimport { findPieController } from \"./scoring.js\";\nimport type { PieElement, UpdatePieElementOptions } from \"./types.js\";\nimport { defaultPieElementOptions } from \"./types.js\";\nimport { findOrAddSession } from \"./utils.js\";\n\n// Create module-level logger (respects global debug flag - pass function for dynamic checking)\nconst logger = createPieLogger(\"pie-updates\", () => isGlobalDebugEnabled());\n\ntype ControllerErrorDetail = {\n\tcode: \"PIE_CONTROLLER_CONTRACT_ERROR\" | \"PIE_CONTROLLER_RUNTIME_ERROR\";\n\tmessage: string;\n\telementName: string;\n\telementId: string;\n\tcontrollerShape?: string;\n\tcause?: string;\n};\n\nconst describeControllerShape = (controller: unknown): string => {\n\tif (!controller) return \"missing\";\n\tif (typeof controller === \"function\") return \"function\";\n\tif (typeof controller !== \"object\") return typeof controller;\n\tconst keys = Object.keys(controller as Record<string, unknown>);\n\tconst defaultValue = (controller as Record<string, unknown>).default;\n\tconst defaultKeys =\n\t\tdefaultValue && typeof defaultValue === \"object\"\n\t\t\t? Object.keys(defaultValue as Record<string, unknown>)\n\t\t\t: [];\n\treturn defaultKeys.length > 0\n\t\t? `object(keys=[${keys.join(\",\")}],defaultKeys=[${defaultKeys.join(\",\")}])`\n\t\t: `object(keys=[${keys.join(\",\")}])`;\n};\n\nconst resolveControllerModelFunction = (\n\tcontroller: unknown,\n):\n\t| ((model: PieModel, session: any, env: Env, updateSession: any) => unknown)\n\t| null => {\n\tif (!controller) return null;\n\tif (typeof controller === \"function\") {\n\t\treturn controller as (\n\t\t\tmodel: PieModel,\n\t\t\tsession: any,\n\t\t\tenv: Env,\n\t\t\tupdateSession: any,\n\t\t) => unknown;\n\t}\n\tif (typeof controller !== \"object\") return null;\n\tconst direct = (controller as Record<string, unknown>).model;\n\tif (typeof direct === \"function\") {\n\t\treturn (model, sessionData, env, updateSession) =>\n\t\t\t(direct as Function).call(\n\t\t\t\tcontroller,\n\t\t\t\tmodel,\n\t\t\t\tsessionData,\n\t\t\t\tenv,\n\t\t\t\tupdateSession,\n\t\t\t);\n\t}\n\tconst fromDefault = (controller as Record<string, unknown>).default;\n\tif (fromDefault && typeof fromDefault === \"object\") {\n\t\tconst nested = (fromDefault as Record<string, unknown>).model;\n\t\tif (typeof nested === \"function\") {\n\t\t\treturn (model, sessionData, env, updateSession) =>\n\t\t\t\t(nested as Function).call(\n\t\t\t\t\tfromDefault,\n\t\t\t\t\tmodel,\n\t\t\t\t\tsessionData,\n\t\t\t\t\tenv,\n\t\t\t\t\tupdateSession,\n\t\t\t\t);\n\t\t}\n\t}\n\treturn null;\n};\n\nconst emitControllerError = (\n\tpieElement: PieElement,\n\tdetail: ControllerErrorDetail,\n): void => {\n\tpieElement.dispatchEvent(\n\t\tnew CustomEvent(\"pie-controller-error\", {\n\t\t\tdetail,\n\t\t\tbubbles: true,\n\t\t\tcomposed: true,\n\t\t}),\n\t);\n};\n\n/**\n * Helper function to apply controller to element\n * Extracted to eliminate duplication and ensure consistent controller invocation\n */\nconst applyControllerToElement = async (\n\telement: PieElement,\n\tmodel: PieModel,\n\telementSession: any,\n\tcontroller: unknown,\n\tenv: Env,\n\tlogPrefix: string,\n\tonElementSessionUpdate?: (\n\t\telementId: string,\n\t\telementName: string,\n\t\tproperties: Record<string, unknown>,\n\t) => void,\n): Promise<void> => {\n\tlogger.debug(`${logPrefix} Using controller, env:`, env);\n\tlogger.debug(`${logPrefix} Model before filter:`, {\n\t\tid: model.id,\n\t\telement: model.element,\n\t\thasCorrectResponse: \"correctResponse\" in model,\n\t});\n\n\t// Create updateSession callback for controller to save derived state (e.g.\n\t// shuffle order). Mutate the in-flight element session so this render uses it,\n\t// AND propagate the write to the authoritative session via the host callback\n\t// so subsequent renders reuse it instead of regenerating it. The write-back is\n\t// keyed by the canonical model id/element (the entry findOrAddSession resolved),\n\t// which also tolerates controllers that pass an undefined id/element.\n\tconst updateSession = (id: string, _elementName: string, properties: any) => {\n\t\tlogger.debug(\n\t\t\t`${logPrefix} updateSession called for ${id} with:`,\n\t\t\tproperties,\n\t\t);\n\t\tObject.assign(elementSession, properties);\n\t\tonElementSessionUpdate?.(model.id, model.element, properties);\n\t\treturn Promise.resolve();\n\t};\n\n\ttry {\n\t\tconst modelFunction = resolveControllerModelFunction(controller);\n\t\tif (!modelFunction) {\n\t\t\tthrow new Error(\n\t\t\t\t`Controller contract mismatch: expected a model() function but received ${describeControllerShape(\n\t\t\t\t\tcontroller,\n\t\t\t\t)}`,\n\t\t\t);\n\t\t}\n\t\tconst controllerResult = await modelFunction(\n\t\t\tmodel,\n\t\t\telementSession,\n\t\t\tenv,\n\t\t\tupdateSession,\n\t\t);\n\n\t\t// Merge controller result with id and element (like server-side PieControllerExecutor does)\n\t\tconst controllerResultObject =\n\t\t\tcontrollerResult && typeof controllerResult === \"object\"\n\t\t\t\t? (controllerResult as Record<string, unknown>)\n\t\t\t\t: {};\n\t\tconst filteredModel = {\n\t\t\tid: model.id,\n\t\t\telement: model.element,\n\t\t\t...controllerResultObject,\n\t\t};\n\t\tconst wrappedModel = wrapModelRichContent(filteredModel);\n\n\t\tlogger.debug(`${logPrefix} ✅ Controller filtered model:`, {\n\t\t\tid: wrappedModel.id,\n\t\t\telement: wrappedModel.element,\n\t\t\thasCorrectResponse: \"correctResponse\" in wrappedModel,\n\t\t\tmode: env.mode,\n\t\t\trole: env.role,\n\t\t});\n\n\t\telement.model = wrappedModel;\n\t\telement.session = elementSession;\n\t} catch (err) {\n\t\tlogger.error(`${logPrefix} ❌ Controller error:`, err);\n\t\tthrow err; // Re-throw - controller errors are fatal\n\t}\n};\n\ntype ResolvedUpdateOptions = Pick<\n\tUpdatePieElementOptions,\n\t| \"config\"\n\t| \"session\"\n\t| \"env\"\n\t| \"eventListeners\"\n\t| \"invokeControllerForModel\"\n\t| \"onElementSessionUpdate\"\n>;\n\nconst resolveAndValidateUpdateOptions = (\n\topts: UpdatePieElementOptions,\n): ResolvedUpdateOptions => {\n\tconst {\n\t\tconfig,\n\t\tsession,\n\t\tenv,\n\t\teventListeners,\n\t\tinvokeControllerForModel,\n\t\tonElementSessionUpdate,\n\t} = mergeObjectsIgnoringNullUndefined(defaultPieElementOptions, opts);\n\tif (!env) {\n\t\tthrow new Error(\"env is required\");\n\t}\n\tif (!session) {\n\t\tthrow new Error(\"session is required\");\n\t}\n\tif (!config) {\n\t\tthrow new Error(\"config is required\");\n\t}\n\treturn {\n\t\tconfig,\n\t\tsession,\n\t\tenv,\n\t\teventListeners,\n\t\tinvokeControllerForModel,\n\t\tonElementSessionUpdate,\n\t};\n};\n\nconst updateSinglePieElement = async (\n\tpieElement: PieElement,\n\tcontrollerLookupTag: string,\n\toptions: ResolvedUpdateOptions,\n\tlogContext: string,\n): Promise<void> => {\n\tconst {\n\t\tconfig,\n\t\tsession,\n\t\tenv,\n\t\teventListeners,\n\t\tinvokeControllerForModel,\n\t\tonElementSessionUpdate,\n\t} = options;\n\tconst model = config.models?.find((m) => m.id === pieElement.id) as\n\t\t| PieModel\n\t\t| undefined;\n\tif (!model) {\n\t\tlogger.error(`${logContext} Model not found for`, controllerLookupTag);\n\t\tthrow new Error(`model not found for ${controllerLookupTag}`);\n\t}\n\n\tconst elementSession = findOrAddSession(session, model.id, model.element);\n\n\t// Always attach event listeners (don't skip them for no-controller case)\n\tif (eventListeners) {\n\t\tObject.entries(eventListeners).forEach(([evt, fn]) => {\n\t\t\tpieElement.addEventListener(evt as any, fn);\n\t\t});\n\t}\n\n\tif (env && invokeControllerForModel) {\n\t\tconst controller = findPieController(controllerLookupTag);\n\t\tif (!controller) {\n\t\t\tlogger.debug(\n\t\t\t\t`${logContext} ℹ️ No controller for ${controllerLookupTag}, using server-processed model`,\n\t\t\t);\n\t\t\tpieElement.model = wrapModelRichContent(model);\n\t\t\tpieElement.session = elementSession;\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug(\n\t\t\t`${logContext} Invoking controller for ${controllerLookupTag}#${pieElement.id}`,\n\t\t\t{\n\t\t\t\tmode: env.mode,\n\t\t\t\trole: env.role,\n\t\t\t\thasCorrectResponse: \"correctResponse\" in model,\n\t\t\t},\n\t\t);\n\n\t\t// Await the controller so any updateSession write-back completes before the\n\t\t// caller computes/emits the session signature; otherwise a late, async\n\t\t// shuffle write lands after the cycle that read the session and the order\n\t\t// never round-trips (PIE-631).\n\t\ttry {\n\t\t\tawait applyControllerToElement(\n\t\t\t\tpieElement,\n\t\t\t\tmodel,\n\t\t\t\telementSession,\n\t\t\t\tcontroller,\n\t\t\t\tenv,\n\t\t\t\t`${logContext}(${controllerLookupTag}#${pieElement.id})`,\n\t\t\t\tonElementSessionUpdate,\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tconst errorMessage = err instanceof Error ? err.message : String(err);\n\t\t\tconst isContractError = errorMessage.includes(\n\t\t\t\t\"Controller contract mismatch\",\n\t\t\t);\n\t\t\tlogger.error(\n\t\t\t\t`${logContext} Controller failed for ${controllerLookupTag}#${pieElement.id}:`,\n\t\t\t\terr,\n\t\t\t);\n\t\t\temitControllerError(pieElement, {\n\t\t\t\tcode: isContractError\n\t\t\t\t\t? \"PIE_CONTROLLER_CONTRACT_ERROR\"\n\t\t\t\t\t: \"PIE_CONTROLLER_RUNTIME_ERROR\",\n\t\t\t\tmessage: `${controllerLookupTag} controller failed while applying model for ${pieElement.id}. ${errorMessage}`,\n\t\t\t\telementName: controllerLookupTag,\n\t\t\t\telementId: pieElement.id,\n\t\t\t\tcontrollerShape: describeControllerShape(controller),\n\t\t\t\tcause: errorMessage,\n\t\t\t});\n\t\t\t// Fall back to raw model on controller error\n\t\t\tpieElement.model = wrapModelRichContent(model);\n\t\t\tpieElement.session = elementSession;\n\t\t}\n\t} else {\n\t\tlogger.debug(\n\t\t\t`${logContext} Direct model assignment for ${controllerLookupTag}#${pieElement.id} (no controller invocation requested)`,\n\t\t);\n\t\tpieElement.model = wrapModelRichContent(model);\n\t\tpieElement.session = elementSession;\n\t}\n};\n\n/**\n * Update a PIE element by ref (direct Element reference)\n */\nexport const updatePieElementWithRef = (\n\tel: Element,\n\topts: UpdatePieElementOptions,\n): Promise<void> => {\n\tconst options = resolveAndValidateUpdateOptions(opts);\n\tconst pieElement = el as PieElement;\n\tconst elName = pieElement.tagName.toLowerCase();\n\treturn updateSinglePieElement(\n\t\tpieElement,\n\t\telName,\n\t\toptions,\n\t\t\"[updatePieElementWithRef]\",\n\t);\n};\n\n/**\n * Update a PIE element by name (finds all matching elements in DOM)\n */\nexport const updatePieElement = (\n\telName: string,\n\topts: UpdatePieElementOptions,\n): Promise<void> => {\n\tconst options = resolveAndValidateUpdateOptions(opts);\n\tconst { container } = mergeObjectsIgnoringNullUndefined(\n\t\tdefaultPieElementOptions,\n\t\topts,\n\t);\n\t// Use container for scoped query or fallback to document for global query\n\tconst searchRoot = container || document;\n\tconst pieElements = searchRoot.querySelectorAll(elName);\n\tif (!pieElements || pieElements.length === 0) {\n\t\tlogger.debug(`no elements found for ${elName}`);\n\t\treturn Promise.resolve();\n\t}\n\treturn Promise.all(\n\t\tArray.from(pieElements, (el) =>\n\t\t\tupdateSinglePieElement(\n\t\t\t\tel as PieElement,\n\t\t\t\telName,\n\t\t\t\toptions,\n\t\t\t\t\"[updatePieElement]\",\n\t\t\t),\n\t\t),\n\t).then(() => undefined);\n};\n\n/**\n * Update all PIE elements in a config\n */\nexport const updatePieElements = (\n\tconfig: ConfigEntity,\n\tsession: any[],\n\tenv: Env,\n\tcontainer?: Element | Document,\n\tonElementSessionUpdate?: UpdatePieElementOptions[\"onElementSessionUpdate\"],\n): Promise<void> => {\n\tlogger.debug(\"[updatePieElements] Updating all elements with env:\", env);\n\treturn Promise.all(\n\t\tObject.entries(config.elements).map(([elName, _pkg]) =>\n\t\t\tupdatePieElement(elName, {\n\t\t\t\tconfig,\n\t\t\t\tsession,\n\t\t\t\tenv,\n\t\t\t\tcontainer,\n\t\t\t\tonElementSessionUpdate,\n\t\t\t}),\n\t\t),\n\t).then(() => undefined);\n};\n"]}
|
package/dist/security/index.d.ts
CHANGED
|
@@ -2,4 +2,5 @@ export { buildAuthoringAllowList, createDefaultItemMarkupSanitizer, resetPurifie
|
|
|
2
2
|
export { parseAllowedStyleOrigins, validateExternalStyleUrl, type StyleUrlValidationError, type StyleUrlValidationOk, type StyleUrlValidationOptions, type StyleUrlValidationResult, } from "./validate-style-url.js";
|
|
3
3
|
export { resetSvgSanitizerForTesting, sanitizeSvgIcon, } from "./sanitize-svg-icon.js";
|
|
4
4
|
export { wrapOverwideImages } from "./wrap-overwide-images.js";
|
|
5
|
+
export { wrapModelRichContent } from "./wrap-model-rich-content.js";
|
|
5
6
|
export { wrapOverwideTables } from "./wrap-overwide-tables.js";
|
package/dist/security/index.js
CHANGED
|
@@ -2,5 +2,6 @@ export { buildAuthoringAllowList, createDefaultItemMarkupSanitizer, resetPurifie
|
|
|
2
2
|
export { parseAllowedStyleOrigins, validateExternalStyleUrl, } from "./validate-style-url.js";
|
|
3
3
|
export { resetSvgSanitizerForTesting, sanitizeSvgIcon, } from "./sanitize-svg-icon.js";
|
|
4
4
|
export { wrapOverwideImages } from "./wrap-overwide-images.js";
|
|
5
|
+
export { wrapModelRichContent } from "./wrap-model-rich-content.js";
|
|
5
6
|
export { wrapOverwideTables } from "./wrap-overwide-tables.js";
|
|
6
7
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/security/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,uBAAuB,EACvB,gCAAgC,EAChC,uBAAuB,EACvB,kBAAkB,GAGlB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACN,wBAAwB,EACxB,wBAAwB,GAKxB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACN,2BAA2B,EAC3B,eAAe,GACf,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC","sourcesContent":["export {\n\tbuildAuthoringAllowList,\n\tcreateDefaultItemMarkupSanitizer,\n\tresetPurifierForTesting,\n\tsanitizeItemMarkup,\n\ttype ItemMarkupSanitizer,\n\ttype SanitizeItemMarkupOptions,\n} from \"./sanitize-item-markup.js\";\nexport {\n\tparseAllowedStyleOrigins,\n\tvalidateExternalStyleUrl,\n\ttype StyleUrlValidationError,\n\ttype StyleUrlValidationOk,\n\ttype StyleUrlValidationOptions,\n\ttype StyleUrlValidationResult,\n} from \"./validate-style-url.js\";\nexport {\n\tresetSvgSanitizerForTesting,\n\tsanitizeSvgIcon,\n} from \"./sanitize-svg-icon.js\";\nexport { wrapOverwideImages } from \"./wrap-overwide-images.js\";\nexport { wrapOverwideTables } from \"./wrap-overwide-tables.js\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/security/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,uBAAuB,EACvB,gCAAgC,EAChC,uBAAuB,EACvB,kBAAkB,GAGlB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACN,wBAAwB,EACxB,wBAAwB,GAKxB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACN,2BAA2B,EAC3B,eAAe,GACf,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC","sourcesContent":["export {\n\tbuildAuthoringAllowList,\n\tcreateDefaultItemMarkupSanitizer,\n\tresetPurifierForTesting,\n\tsanitizeItemMarkup,\n\ttype ItemMarkupSanitizer,\n\ttype SanitizeItemMarkupOptions,\n} from \"./sanitize-item-markup.js\";\nexport {\n\tparseAllowedStyleOrigins,\n\tvalidateExternalStyleUrl,\n\ttype StyleUrlValidationError,\n\ttype StyleUrlValidationOk,\n\ttype StyleUrlValidationOptions,\n\ttype StyleUrlValidationResult,\n} from \"./validate-style-url.js\";\nexport {\n\tresetSvgSanitizerForTesting,\n\tsanitizeSvgIcon,\n} from \"./sanitize-svg-icon.js\";\nexport { wrapOverwideImages } from \"./wrap-overwide-images.js\";\nexport { wrapModelRichContent } from \"./wrap-model-rich-content.js\";\nexport { wrapOverwideTables } from \"./wrap-overwide-tables.js\";\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitize-item-markup.js","sourceRoot":"","sources":["../../src/security/sanitize-item-markup.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Default sanitizer for PIE item / passage markup.\n *\n * Used by `PieItemPlayer.svelte` to strip scripts, event-handler attributes,\n * and unknown tags before injecting authored markup via `{@html}`. Hosts can\n * opt out with the `trust-markup` attribute on the `<pie-*-player>` element,\n * or supply their own sanitizer function if they need a stricter / looser\n * allow-list.\n */\n\nimport DOMPurify from \"dompurify\";\n\nimport { wrapOverwideImages } from \"./wrap-overwide-images.js\";\nimport { wrapOverwideTables } from \"./wrap-overwide-tables.js\";\n\nexport type ItemMarkupSanitizer = (markup: string) => string;\n\nexport interface SanitizeItemMarkupOptions {\n\t/**\n\t * Extra custom-element tag names that should survive sanitization in\n\t * addition to the default `pie-*` allow-list. Useful for authoring-mode\n\t * tags that rewrite to `pie-*-config` or host-registered extensions.\n\t */\n\tallowedCustomElements?: string[];\n}\n\n// Attributes every PIE element / wrapper is allowed to carry.\nconst BASE_ALLOWED_ATTRS = [\n\t\"slot\",\n\t\"role\",\n\t\"tabindex\",\n\t\"id\",\n\t\"class\",\n\t\"style\",\n\t\"href\",\n\t\"src\",\n\t\"alt\",\n\t\"title\",\n\t\"hidden\",\n\t\"disabled\",\n\t\"lang\",\n\t\"dir\",\n];\n\nconst BASE_URI_SAFE_ATTRS = [\"pie-id\"];\n\nconst FORBIDDEN_TAGS = [\n\t\"script\",\n\t\"iframe\",\n\t\"object\",\n\t\"embed\",\n\t\"base\",\n\t\"form\",\n\t\"meta\",\n\t\"link\",\n\t// <foreignObject> inside an <svg> is a well-known escape hatch back\n\t// into HTML context; match the SVG-icon sanitizer and forbid it here\n\t// so both sanitizers agree on the surface.\n\t\"foreignobject\",\n];\n\n// DOMPurify already strips `on*` handlers via its default block-list;\n// these entries guarantee they stay stripped even if a consumer tweaks\n// defaults, and they cover the common SVG / math sinks.\nconst FORBIDDEN_ATTRS = [\n\t\"onerror\",\n\t\"onload\",\n\t\"onclick\",\n\t\"onmouseover\",\n\t\"onmouseout\",\n\t\"onmouseenter\",\n\t\"onmouseleave\",\n\t\"onfocus\",\n\t\"onblur\",\n\t\"onkeydown\",\n\t\"onkeyup\",\n\t\"onkeypress\",\n\t\"onsubmit\",\n\t\"onchange\",\n\t\"onbeforeunload\",\n\t\"formaction\",\n\t\"xlink:href\",\n];\n\n// Any tag that looks like a custom element (contains a hyphen) is permitted\n// provided it starts with `pie-` or is explicitly named in\n// `allowedCustomElements`. This intentionally keeps third-party unknown\n// custom elements out unless the host opts in.\nconst PIE_CUSTOM_ELEMENT_REGEX = /^pie-[a-z0-9-]+$/i;\n\n// Attribute names that custom elements are allowed to declare. We stay\n// permissive for the PIE element contract (`model-*`, `session-*`, ...) and\n// the standard `data-*` / `aria-*` families.\nconst CUSTOM_ELEMENT_ATTR_REGEX =\n\t/^(id|class|style|slot|role|tabindex|hidden|disabled|lang|dir|data-[\\w-]+|aria-[\\w-]+|pie-[\\w-]+|model-[\\w-]+|session-[\\w-]+|config-[\\w-]+|context-[\\w-]+)$/i;\n\ninterface DOMPurifyInstance {\n\tsanitize: (\n\t\tsource: string,\n\t\tconfig?: Record<string, unknown>,\n\t) => string | Node | DocumentFragment;\n}\n\nlet purifierInstance: DOMPurifyInstance | null = null;\n\nfunction resolvePurifier(): DOMPurifyInstance | null {\n\tif (purifierInstance) return purifierInstance;\n\tif (typeof window === \"undefined\" || !window.document) return null;\n\t// DOMPurify's default export is both the instance and the factory.\n\t// Calling it with a window binds the instance to that document.\n\tconst factory = DOMPurify as unknown as (\n\t\twin: Window & typeof globalThis,\n\t) => DOMPurifyInstance;\n\tpurifierInstance =\n\t\ttypeof factory === \"function\"\n\t\t\t? factory(window as Window & typeof globalThis)\n\t\t\t: (DOMPurify as unknown as DOMPurifyInstance);\n\treturn purifierInstance;\n}\n\n/**\n * Sanitize raw item/passage markup before it is injected into the DOM.\n *\n * - Strips `<script>`, event-handler attributes, unknown protocols and\n * a standard set of dangerous tags (`iframe`, `object`, `embed`, `base`,\n * `form`, `meta`, `link`).\n * - Preserves PIE custom elements (`pie-*`) and any extra tags listed in\n * `allowedCustomElements`.\n * - During SSR (no `window`) returns an empty string so untrusted markup\n * never reaches the prerender output; the live renderer will re-run the\n * sanitizer on hydrate.\n */\nexport function sanitizeItemMarkup(\n\tmarkup: string,\n\toptions: SanitizeItemMarkupOptions = {},\n): string {\n\tif (!markup) return \"\";\n\tconst purifier = resolvePurifier();\n\tif (!purifier) return \"\";\n\n\tconst allowedCustomElements = (options.allowedCustomElements ?? []).map(\n\t\t(name) => name.toLowerCase(),\n\t);\n\tconst explicitCustomElementSet = new Set(allowedCustomElements);\n\n\tconst result = purifier.sanitize(markup, {\n\t\tADD_TAGS: allowedCustomElements,\n\t\tADD_ATTR: BASE_ALLOWED_ATTRS,\n\t\tADD_URI_SAFE_ATTR: BASE_URI_SAFE_ATTRS,\n\t\tFORBID_TAGS: FORBIDDEN_TAGS,\n\t\tFORBID_ATTR: FORBIDDEN_ATTRS,\n\t\tALLOW_UNKNOWN_PROTOCOLS: false,\n\t\tSANITIZE_DOM: true,\n\t\t// pie-item contract compatibility: PIE models are matched to DOM\n\t\t// elements via strict `id` equality (see `updateSinglePieElement`\n\t\t// in players-shared/src/pie/updates.ts). `SANITIZE_NAMED_PROPS`\n\t\t// would prefix every `id`/`name` with `user-content-`, which silently\n\t\t// breaks model lookup for every item. `SANITIZE_DOM: true` above\n\t\t// still provides the core DOM-clobbering defenses we rely on.\n\t\tSANITIZE_NAMED_PROPS: false,\n\t\tWHOLE_DOCUMENT: false,\n\t\tALLOW_DATA_ATTR: true,\n\t\tALLOW_ARIA_ATTR: true,\n\t\tCUSTOM_ELEMENT_HANDLING: {\n\t\t\ttagNameCheck: (tagName: string) => {\n\t\t\t\tconst lower = tagName.toLowerCase();\n\t\t\t\treturn (\n\t\t\t\t\tPIE_CUSTOM_ELEMENT_REGEX.test(lower) ||\n\t\t\t\t\texplicitCustomElementSet.has(lower)\n\t\t\t\t);\n\t\t\t},\n\t\t\tattributeNameCheck: (attrName: string) =>\n\t\t\t\tCUSTOM_ELEMENT_ATTR_REGEX.test(attrName),\n\t\t\tallowCustomizedBuiltInElements: false,\n\t\t},\n\t\tRETURN_TRUSTED_TYPE: false,\n\t});\n\n\tconst sanitized =\n\t\ttypeof result === \"string\" ? result : String(result ?? \"\");\n\t// PIE-94: wrap overwide authored images in a horizontal-scroll container\n\t// so they don't get clipped by ancestor `overflow-x: hidden` regions in\n\t// the section player (and match WCAG 1.4.10 Reflow at 400% zoom).\n\t// Tables get the same treatment so wide data grids reflow into a\n\t// scrollable region instead of forcing the page itself to scroll.\n\treturn wrapOverwideTables(wrapOverwideImages(sanitized));\n}\n\n/**\n * Build the default `ItemMarkupSanitizer` used by the players. The returned\n * function is stable for a given set of allowed custom elements so callers\n * can safely use reference equality when deciding whether to re-sanitize.\n */\nexport function createDefaultItemMarkupSanitizer(\n\toptions: SanitizeItemMarkupOptions = {},\n): ItemMarkupSanitizer {\n\tconst { allowedCustomElements } = options;\n\treturn (markup: string) =>\n\t\tsanitizeItemMarkup(markup, { allowedCustomElements });\n}\n\n/**\n * Derive the authoring-mode allow-list (`pie-*-config`) from a set of PIE\n * element tag names. Used by `transformMarkupForAuthoring` so the sanitizer\n * keeps the rewritten `-config` tags instead of stripping them.\n */\nexport function buildAuthoringAllowList(\n\telementTagNames: Iterable<string>,\n): string[] {\n\tconst out = new Set<string>();\n\tfor (const tag of elementTagNames) {\n\t\tif (!tag) continue;\n\t\tconst lower = tag.toLowerCase();\n\t\tout.add(lower);\n\t\tout.add(`${lower}-config`);\n\t}\n\treturn [...out];\n}\n\n/** Reset the memoised DOMPurify instance. Only intended for tests. */\nexport function resetPurifierForTesting() {\n\tpurifierInstance = null;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"sanitize-item-markup.js","sourceRoot":"","sources":["../../src/security/sanitize-item-markup.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Default sanitizer for PIE item / passage markup.\n *\n * Used by `PieItemPlayer.svelte` to strip scripts, event-handler attributes,\n * and unknown tags before injecting authored markup via `{@html}`. Hosts can\n * opt out with the `trust-markup` attribute on the `<pie-*-player>` element,\n * or supply their own sanitizer function if they need a stricter / looser\n * allow-list.\n */\n\nimport DOMPurify from \"dompurify\";\n\nimport { wrapOverwideImages } from \"./wrap-overwide-images.js\";\nimport { wrapOverwideTables } from \"./wrap-overwide-tables.js\";\n\nexport type ItemMarkupSanitizer = (markup: string) => string;\n\nexport interface SanitizeItemMarkupOptions {\n\t/**\n\t * Extra custom-element tag names that should survive sanitization in\n\t * addition to the default `pie-*` allow-list. Useful for authoring-mode\n\t * tags that rewrite to `pie-*-config` or host-registered extensions.\n\t */\n\tallowedCustomElements?: string[];\n}\n\n// Attributes every PIE element / wrapper is allowed to carry.\nconst BASE_ALLOWED_ATTRS = [\n\t\"slot\",\n\t\"role\",\n\t\"tabindex\",\n\t\"id\",\n\t\"class\",\n\t\"style\",\n\t\"href\",\n\t\"src\",\n\t\"alt\",\n\t\"title\",\n\t\"hidden\",\n\t\"disabled\",\n\t\"lang\",\n\t\"dir\",\n];\n\nconst BASE_URI_SAFE_ATTRS = [\"pie-id\"];\n\nconst FORBIDDEN_TAGS = [\n\t\"script\",\n\t\"iframe\",\n\t\"object\",\n\t\"embed\",\n\t\"base\",\n\t\"form\",\n\t\"meta\",\n\t\"link\",\n\t// <foreignObject> inside an <svg> is a well-known escape hatch back\n\t// into HTML context; match the SVG-icon sanitizer and forbid it here\n\t// so both sanitizers agree on the surface.\n\t\"foreignobject\",\n];\n\n// DOMPurify already strips `on*` handlers via its default block-list;\n// these entries guarantee they stay stripped even if a consumer tweaks\n// defaults, and they cover the common SVG / math sinks.\nconst FORBIDDEN_ATTRS = [\n\t\"onerror\",\n\t\"onload\",\n\t\"onclick\",\n\t\"onmouseover\",\n\t\"onmouseout\",\n\t\"onmouseenter\",\n\t\"onmouseleave\",\n\t\"onfocus\",\n\t\"onblur\",\n\t\"onkeydown\",\n\t\"onkeyup\",\n\t\"onkeypress\",\n\t\"onsubmit\",\n\t\"onchange\",\n\t\"onbeforeunload\",\n\t\"formaction\",\n\t\"xlink:href\",\n];\n\n// Any tag that looks like a custom element (contains a hyphen) is permitted\n// provided it starts with `pie-` or is explicitly named in\n// `allowedCustomElements`. This intentionally keeps third-party unknown\n// custom elements out unless the host opts in.\nconst PIE_CUSTOM_ELEMENT_REGEX = /^pie-[a-z0-9-]+$/i;\n\n// Attribute names that custom elements are allowed to declare. We stay\n// permissive for the PIE element contract (`model-*`, `session-*`, ...) and\n// the standard `data-*` / `aria-*` families.\nconst CUSTOM_ELEMENT_ATTR_REGEX =\n\t/^(id|class|style|slot|role|tabindex|hidden|disabled|lang|dir|data-[\\w-]+|aria-[\\w-]+|pie-[\\w-]+|model-[\\w-]+|session-[\\w-]+|config-[\\w-]+|context-[\\w-]+)$/i;\n\ninterface DOMPurifyInstance {\n\tsanitize: (\n\t\tsource: string,\n\t\tconfig?: Record<string, unknown>,\n\t) => string | Node | DocumentFragment;\n}\n\nlet purifierInstance: DOMPurifyInstance | null = null;\n\nfunction resolvePurifier(): DOMPurifyInstance | null {\n\tif (purifierInstance) return purifierInstance;\n\tif (typeof window === \"undefined\" || !window.document) return null;\n\t// DOMPurify's default export is both the instance and the factory.\n\t// Calling it with a window binds the instance to that document.\n\tconst factory = DOMPurify as unknown as (\n\t\twin: Window & typeof globalThis,\n\t) => DOMPurifyInstance;\n\tpurifierInstance =\n\t\ttypeof factory === \"function\"\n\t\t\t? factory(window as Window & typeof globalThis)\n\t\t\t: (DOMPurify as unknown as DOMPurifyInstance);\n\treturn purifierInstance;\n}\n\n/**\n * Sanitize raw item/passage markup before it is injected into the DOM.\n *\n * - Strips `<script>`, event-handler attributes, unknown protocols and\n * a standard set of dangerous tags (`iframe`, `object`, `embed`, `base`,\n * `form`, `meta`, `link`).\n * - Preserves PIE custom elements (`pie-*`) and any extra tags listed in\n * `allowedCustomElements`.\n * - During SSR (no `window`) returns an empty string so untrusted markup\n * never reaches the prerender output; the live renderer will re-run the\n * sanitizer on hydrate.\n */\nexport function sanitizeItemMarkup(\n\tmarkup: string,\n\toptions: SanitizeItemMarkupOptions = {},\n): string {\n\tif (!markup) return \"\";\n\tconst purifier = resolvePurifier();\n\tif (!purifier) return \"\";\n\n\tconst allowedCustomElements = (options.allowedCustomElements ?? []).map(\n\t\t(name) => name.toLowerCase(),\n\t);\n\tconst explicitCustomElementSet = new Set(allowedCustomElements);\n\n\tconst result = purifier.sanitize(markup, {\n\t\tADD_TAGS: allowedCustomElements,\n\t\tADD_ATTR: BASE_ALLOWED_ATTRS,\n\t\tADD_URI_SAFE_ATTR: BASE_URI_SAFE_ATTRS,\n\t\tFORBID_TAGS: FORBIDDEN_TAGS,\n\t\tFORBID_ATTR: FORBIDDEN_ATTRS,\n\t\tALLOW_UNKNOWN_PROTOCOLS: false,\n\t\tSANITIZE_DOM: true,\n\t\t// pie-item contract compatibility: PIE models are matched to DOM\n\t\t// elements via strict `id` equality (see `updateSinglePieElement`\n\t\t// in players-shared/src/pie/updates.ts). `SANITIZE_NAMED_PROPS`\n\t\t// would prefix every `id`/`name` with `user-content-`, which silently\n\t\t// breaks model lookup for every item. `SANITIZE_DOM: true` above\n\t\t// still provides the core DOM-clobbering defenses we rely on.\n\t\tSANITIZE_NAMED_PROPS: false,\n\t\tWHOLE_DOCUMENT: false,\n\t\tALLOW_DATA_ATTR: true,\n\t\tALLOW_ARIA_ATTR: true,\n\t\tCUSTOM_ELEMENT_HANDLING: {\n\t\t\ttagNameCheck: (tagName: string) => {\n\t\t\t\tconst lower = tagName.toLowerCase();\n\t\t\t\treturn (\n\t\t\t\t\tPIE_CUSTOM_ELEMENT_REGEX.test(lower) ||\n\t\t\t\t\texplicitCustomElementSet.has(lower)\n\t\t\t\t);\n\t\t\t},\n\t\t\tattributeNameCheck: (attrName: string) =>\n\t\t\t\tCUSTOM_ELEMENT_ATTR_REGEX.test(attrName),\n\t\t\tallowCustomizedBuiltInElements: false,\n\t\t},\n\t\tRETURN_TRUSTED_TYPE: false,\n\t});\n\n\tconst sanitized = typeof result === \"string\" ? result : String(result ?? \"\");\n\t// PIE-94: wrap overwide authored images in a horizontal-scroll container\n\t// so they don't get clipped by ancestor `overflow-x: hidden` regions in\n\t// the section player (and match WCAG 1.4.10 Reflow at 400% zoom).\n\t// Tables get the same treatment so wide data grids reflow into a\n\t// scrollable region instead of forcing the page itself to scroll.\n\treturn wrapOverwideTables(wrapOverwideImages(sanitized));\n}\n\n/**\n * Build the default `ItemMarkupSanitizer` used by the players. The returned\n * function is stable for a given set of allowed custom elements so callers\n * can safely use reference equality when deciding whether to re-sanitize.\n */\nexport function createDefaultItemMarkupSanitizer(\n\toptions: SanitizeItemMarkupOptions = {},\n): ItemMarkupSanitizer {\n\tconst { allowedCustomElements } = options;\n\treturn (markup: string) =>\n\t\tsanitizeItemMarkup(markup, { allowedCustomElements });\n}\n\n/**\n * Derive the authoring-mode allow-list (`pie-*-config`) from a set of PIE\n * element tag names. Used by `transformMarkupForAuthoring` so the sanitizer\n * keeps the rewritten `-config` tags instead of stripping them.\n */\nexport function buildAuthoringAllowList(\n\telementTagNames: Iterable<string>,\n): string[] {\n\tconst out = new Set<string>();\n\tfor (const tag of elementTagNames) {\n\t\tif (!tag) continue;\n\t\tconst lower = tag.toLowerCase();\n\t\tout.add(lower);\n\t\tout.add(`${lower}-config`);\n\t}\n\treturn [...out];\n}\n\n/** Reset the memoised DOMPurify instance. Only intended for tests. */\nexport function resetPurifierForTesting() {\n\tpurifierInstance = null;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function wrapModelRichContent<T>(model: T): T;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { wrapOverwideImages } from "./wrap-overwide-images.js";
|
|
2
|
+
import { wrapOverwideTables } from "./wrap-overwide-tables.js";
|
|
3
|
+
const RICH_CONTENT_TAG_REGEX = /<(?:img|table)\b/i;
|
|
4
|
+
const SKIPPED_KEYS = new Set(["accessibilityCatalogs"]);
|
|
5
|
+
function maybeWrapRichContent(value) {
|
|
6
|
+
if (!RICH_CONTENT_TAG_REGEX.test(value))
|
|
7
|
+
return value;
|
|
8
|
+
return wrapOverwideTables(wrapOverwideImages(value));
|
|
9
|
+
}
|
|
10
|
+
function wrapValue(value, parentKey) {
|
|
11
|
+
if (parentKey && SKIPPED_KEYS.has(parentKey))
|
|
12
|
+
return value;
|
|
13
|
+
if (typeof value === "string")
|
|
14
|
+
return maybeWrapRichContent(value);
|
|
15
|
+
if (!value || typeof value !== "object")
|
|
16
|
+
return value;
|
|
17
|
+
if (Array.isArray(value)) {
|
|
18
|
+
let changed = false;
|
|
19
|
+
const next = value.map((entry) => {
|
|
20
|
+
const wrapped = wrapValue(entry);
|
|
21
|
+
if (wrapped !== entry)
|
|
22
|
+
changed = true;
|
|
23
|
+
return wrapped;
|
|
24
|
+
});
|
|
25
|
+
return changed ? next : value;
|
|
26
|
+
}
|
|
27
|
+
const record = value;
|
|
28
|
+
let changed = false;
|
|
29
|
+
const next = {};
|
|
30
|
+
for (const [key, entry] of Object.entries(record)) {
|
|
31
|
+
const wrapped = wrapValue(entry, key);
|
|
32
|
+
if (wrapped !== entry)
|
|
33
|
+
changed = true;
|
|
34
|
+
next[key] = wrapped;
|
|
35
|
+
}
|
|
36
|
+
return changed ? next : value;
|
|
37
|
+
}
|
|
38
|
+
export function wrapModelRichContent(model) {
|
|
39
|
+
return wrapValue(model);
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=wrap-model-rich-content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wrap-model-rich-content.js","sourceRoot":"","sources":["../../src/security/wrap-model-rich-content.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AACnD,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC;AAExD,SAAS,oBAAoB,CAAC,KAAa;IAC1C,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACtD,OAAO,kBAAkB,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,SAAS,CAAC,KAAc,EAAE,SAAkB;IACpD,IAAI,SAAS,IAAI,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3D,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAClE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAEtD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YAChC,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;YACjC,IAAI,OAAO,KAAK,KAAK;gBAAE,OAAO,GAAG,IAAI,CAAC;YACtC,OAAO,OAAO,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,OAAO,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;IAC/B,CAAC;IAED,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,IAAI,GAA4B,EAAE,CAAC;IACzC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACnD,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACtC,IAAI,OAAO,KAAK,KAAK;YAAE,OAAO,GAAG,IAAI,CAAC;QACtC,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;IACrB,CAAC;IACD,OAAO,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAI,KAAQ;IAC/C,OAAO,SAAS,CAAC,KAAK,CAAM,CAAC;AAC9B,CAAC","sourcesContent":["import { wrapOverwideImages } from \"./wrap-overwide-images.js\";\nimport { wrapOverwideTables } from \"./wrap-overwide-tables.js\";\n\nconst RICH_CONTENT_TAG_REGEX = /<(?:img|table)\\b/i;\nconst SKIPPED_KEYS = new Set([\"accessibilityCatalogs\"]);\n\nfunction maybeWrapRichContent(value: string): string {\n\tif (!RICH_CONTENT_TAG_REGEX.test(value)) return value;\n\treturn wrapOverwideTables(wrapOverwideImages(value));\n}\n\nfunction wrapValue(value: unknown, parentKey?: string): unknown {\n\tif (parentKey && SKIPPED_KEYS.has(parentKey)) return value;\n\tif (typeof value === \"string\") return maybeWrapRichContent(value);\n\tif (!value || typeof value !== \"object\") return value;\n\n\tif (Array.isArray(value)) {\n\t\tlet changed = false;\n\t\tconst next = value.map((entry) => {\n\t\t\tconst wrapped = wrapValue(entry);\n\t\t\tif (wrapped !== entry) changed = true;\n\t\t\treturn wrapped;\n\t\t});\n\t\treturn changed ? next : value;\n\t}\n\n\tconst record = value as Record<string, unknown>;\n\tlet changed = false;\n\tconst next: Record<string, unknown> = {};\n\tfor (const [key, entry] of Object.entries(record)) {\n\t\tconst wrapped = wrapValue(entry, key);\n\t\tif (wrapped !== entry) changed = true;\n\t\tnext[key] = wrapped;\n\t}\n\treturn changed ? next : value;\n}\n\nexport function wrapModelRichContent<T>(model: T): T {\n\treturn wrapValue(model) as T;\n}\n"]}
|
|
@@ -71,8 +71,7 @@ export function wrapOverwideImages(markup) {
|
|
|
71
71
|
if (!parent)
|
|
72
72
|
continue;
|
|
73
73
|
// Idempotency — already wrapped.
|
|
74
|
-
if (parent.classList &&
|
|
75
|
-
parent.classList.contains(SCROLL_WRAPPER_CLASS)) {
|
|
74
|
+
if (parent.classList && parent.classList.contains(SCROLL_WRAPPER_CLASS)) {
|
|
76
75
|
continue;
|
|
77
76
|
}
|
|
78
77
|
// Leave PIE custom-element internals alone.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wrap-overwide-images.js","sourceRoot":"","sources":["../../src/security/wrap-overwide-images.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,4BAA4B,GAAG,QAAQ,CAAC;AAC9C,MAAM,oBAAoB,GAAG,kBAAkB,CAAC;AAEhD,SAAS,wBAAwB,
|
|
1
|
+
{"version":3,"file":"wrap-overwide-images.js","sourceRoot":"","sources":["../../src/security/wrap-overwide-images.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,4BAA4B,GAAG,QAAQ,CAAC;AAC9C,MAAM,oBAAoB,GAAG,kBAAkB,CAAC;AAEhD,SAAS,wBAAwB,CAAC,KAAc,EAAE,IAAa;IAC9D,IAAI,QAAQ,GAAmB,KAAK,CAAC,aAAa,CAAC;IACnD,OAAO,QAAQ,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtC,IAAI,4BAA4B,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YACzD,OAAO,IAAI,CAAC;QACb,CAAC;QACD,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC;IACnC,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACrC,MAAM,GAAG,GAAG,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACtC,OAAO,OAAO,CAAC,CAAC,CAAC,qBAAqB,OAAO,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC;AACtE,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAChD,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAEvB,0EAA0E;IAC1E,iEAAiE;IACjE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IAE3C,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,CAAC,MAAM,CAAC,QAAQ;QAAE,OAAO,MAAM,CAAC;IAErE,MAAM,UAAU,GACf,OAAO,SAAS,KAAK,WAAW;QAC/B,CAAC,CAAC,SAAS;QACX,CAAC,CAAE,MAAsD,CAAC,SAAS,CAAC;IACtE,IAAI,CAAC,UAAU;QAAE,OAAO,MAAM,CAAC;IAE/B,MAAM,GAAG,GAAG,IAAI,UAAU,EAAE,CAAC,eAAe,CAC3C,8BAA8B,MAAM,gBAAgB,EACpD,WAAW,CACX,CAAC;IACF,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;IACtB,IAAI,CAAC,IAAI;QAAE,OAAO,MAAM,CAAC;IAEzB,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC;IACxD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC;IAEvC,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,KAAK,CAAC,aAAa,CAAC;QACnC,IAAI,CAAC,MAAM;YAAE,SAAS;QAEtB,iCAAiC;QACjC,IAAI,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAAC;YACzE,SAAS;QACV,CAAC;QAED,4CAA4C;QAC5C,IAAI,wBAAwB,CAAC,KAAK,EAAE,IAAI,CAAC;YAAE,SAAS;QAEpD,MAAM,OAAO,GAAG,GAAG,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAC1C,OAAO,CAAC,SAAS,GAAG,oBAAoB,CAAC;QACzC,OAAO,CAAC,YAAY,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QACtC,OAAO,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACvC,OAAO,CAAC,YAAY,CAAC,YAAY,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAE1D,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACpC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAC3B,OAAO,GAAG,IAAI,CAAC;IAChB,CAAC;IAED,OAAO,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;AAC1C,CAAC","sourcesContent":["/**\n * Wraps authored `<img>` elements with a horizontally scrollable container so\n * images that are wider than their column surface a scrollbar instead of being\n * clipped by ancestor `overflow-x: hidden` regions (PIE-94).\n *\n * The wrapper is rendered as\n * `<span class=\"pie-image-scroll\" tabindex=\"0\" role=\"region\" aria-label=\"...\">`\n * and receives the accompanying CSS from `@pie-players/pie-theme`. The CSS uses\n * `overflow-x: auto` so small images stay visually unchanged: a scrollbar only\n * appears when the image's intrinsic width exceeds the wrapper's available\n * space (including at higher browser-zoom levels, which is the original driver\n * for this change — WCAG 1.4.10 Reflow at 400% zoom).\n *\n * This helper runs as a post-sanitization step inside `sanitizeItemMarkup`, so\n * every host that renders authored markup through the shared\n * `pie-item-player` (including the section player) benefits uniformly.\n */\n\nconst PIE_CUSTOM_ELEMENT_TAG_REGEX = /^pie-/i;\nconst SCROLL_WRAPPER_CLASS = \"pie-image-scroll\";\n\nfunction isInsidePieCustomElement(image: Element, root: Element): boolean {\n\tlet ancestor: Element | null = image.parentElement;\n\twhile (ancestor && ancestor !== root) {\n\t\tif (PIE_CUSTOM_ELEMENT_TAG_REGEX.test(ancestor.tagName)) {\n\t\t\treturn true;\n\t\t}\n\t\tancestor = ancestor.parentElement;\n\t}\n\treturn false;\n}\n\nfunction buildAriaLabel(image: Element): string {\n\tconst alt = image.getAttribute(\"alt\");\n\tconst trimmed = alt ? alt.trim() : \"\";\n\treturn trimmed ? `Scrollable image: ${trimmed}` : \"Scrollable image\";\n}\n\n/**\n * Wrap `<img>` elements in `markup` with a horizontal-scroll container.\n *\n * - No-ops on empty input.\n * - No-ops during SSR (no `window` / `DOMParser`) — the markup is returned\n * unchanged; the browser re-run on hydrate will perform the wrap.\n * - Idempotent: images whose direct parent already carries the\n * `pie-image-scroll` class are left alone.\n * - Leaves images inside PIE custom elements (`<pie-*>`) alone. Those are\n * rendered by the element's own template / shadow DOM and should not be\n * restructured by the authored-markup pipeline.\n */\nexport function wrapOverwideImages(markup: string): string {\n\tif (!markup) return \"\";\n\n\t// Fast path: avoid the DOM round-trip entirely when the markup carries no\n\t// images. Keeps the sanitize pipeline cheap for the common case.\n\tif (!/<img\\b/i.test(markup)) return markup;\n\n\tif (typeof window === \"undefined\" || !window.document) return markup;\n\n\tconst ParserCtor =\n\t\ttypeof DOMParser !== \"undefined\"\n\t\t\t? DOMParser\n\t\t\t: (window as unknown as { DOMParser?: typeof DOMParser }).DOMParser;\n\tif (!ParserCtor) return markup;\n\n\tconst doc = new ParserCtor().parseFromString(\n\t\t`<!DOCTYPE html><html><body>${markup}</body></html>`,\n\t\t\"text/html\",\n\t);\n\tconst body = doc.body;\n\tif (!body) return markup;\n\n\tconst images = Array.from(body.querySelectorAll(\"img\"));\n\tif (images.length === 0) return markup;\n\n\tlet mutated = false;\n\tfor (const image of images) {\n\t\tconst parent = image.parentElement;\n\t\tif (!parent) continue;\n\n\t\t// Idempotency — already wrapped.\n\t\tif (parent.classList && parent.classList.contains(SCROLL_WRAPPER_CLASS)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Leave PIE custom-element internals alone.\n\t\tif (isInsidePieCustomElement(image, body)) continue;\n\n\t\tconst wrapper = doc.createElement(\"span\");\n\t\twrapper.className = SCROLL_WRAPPER_CLASS;\n\t\twrapper.setAttribute(\"tabindex\", \"0\");\n\t\twrapper.setAttribute(\"role\", \"region\");\n\t\twrapper.setAttribute(\"aria-label\", buildAriaLabel(image));\n\n\t\tparent.insertBefore(wrapper, image);\n\t\twrapper.appendChild(image);\n\t\tmutated = true;\n\t}\n\n\treturn mutated ? body.innerHTML : markup;\n}\n"]}
|
|
@@ -84,8 +84,7 @@ export function wrapOverwideTables(markup) {
|
|
|
84
84
|
if (!parent)
|
|
85
85
|
continue;
|
|
86
86
|
// Idempotency — already wrapped.
|
|
87
|
-
if (parent.classList &&
|
|
88
|
-
parent.classList.contains(SCROLL_WRAPPER_CLASS)) {
|
|
87
|
+
if (parent.classList && parent.classList.contains(SCROLL_WRAPPER_CLASS)) {
|
|
89
88
|
continue;
|
|
90
89
|
}
|
|
91
90
|
// Leave PIE custom-element internals alone.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wrap-overwide-tables.js","sourceRoot":"","sources":["../../src/security/wrap-overwide-tables.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"wrap-overwide-tables.js","sourceRoot":"","sources":["../../src/security/wrap-overwide-tables.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Wraps authored `<table>` elements with a horizontally scrollable container so\n * tables that are wider than their column surface a scrollbar instead of being\n * clipped by ancestor `overflow-x: hidden` regions.\n *\n * The wrapper is rendered as\n * `<div class=\"pie-table-scroll\" tabindex=\"0\" role=\"region\" aria-label=\"...\">`\n * and receives the accompanying CSS from `@pie-players/pie-theme`. The CSS uses\n * `overflow-x: auto` so narrow tables stay visually unchanged: a scrollbar only\n * appears when the table's intrinsic width exceeds the wrapper's available\n * space (including at higher browser-zoom levels — WCAG 1.4.10 Reflow at 400%\n * zoom is the same driver as for `wrapOverwideImages`).\n *\n * This helper runs as a post-sanitization step inside `sanitizeItemMarkup`, so\n * every host that renders authored markup through the shared\n * `pie-item-player` (including the section player) benefits uniformly.\n */\n\nconst PIE_CUSTOM_ELEMENT_TAG_REGEX = /^pie-/i;\nconst SCROLL_WRAPPER_CLASS = \"pie-table-scroll\";\n\nfunction isInsidePieCustomElement(table: Element, root: Element): boolean {\n\tlet ancestor: Element | null = table.parentElement;\n\twhile (ancestor && ancestor !== root) {\n\t\tif (PIE_CUSTOM_ELEMENT_TAG_REGEX.test(ancestor.tagName)) {\n\t\t\treturn true;\n\t\t}\n\t\tancestor = ancestor.parentElement;\n\t}\n\treturn false;\n}\n\nfunction buildAriaLabel(table: Element): string {\n\t// Authors commonly label tables via <caption>, aria-label, or aria-labelledby.\n\t// Prefer the most explicit signal and fall back to the generic label so\n\t// every wrapper still announces itself as a region.\n\tconst ariaLabel = table.getAttribute(\"aria-label\");\n\tif (ariaLabel?.trim()) {\n\t\treturn `Scrollable table: ${ariaLabel.trim()}`;\n\t}\n\tconst labelledBy = table.getAttribute(\"aria-labelledby\");\n\tif (labelledBy?.trim()) {\n\t\tconst ownerDocument = table.ownerDocument;\n\t\tconst ids = labelledBy.trim().split(/\\s+/);\n\t\tconst labels: string[] = [];\n\t\tfor (const id of ids) {\n\t\t\tconst labelEl = ownerDocument?.getElementById(id);\n\t\t\tconst text = labelEl?.textContent?.trim();\n\t\t\tif (text) labels.push(text);\n\t\t}\n\t\tif (labels.length > 0) {\n\t\t\treturn `Scrollable table: ${labels.join(\" \")}`;\n\t\t}\n\t}\n\tconst caption = table.querySelector(\"caption\");\n\tconst captionText = caption?.textContent?.trim();\n\tif (captionText) {\n\t\treturn `Scrollable table: ${captionText}`;\n\t}\n\treturn \"Scrollable table\";\n}\n\nexport function wrapOverwideTables(markup: string): string {\n\tif (!markup) return \"\";\n\n\t// Fast path: avoid the DOM round-trip entirely when the markup carries no\n\t// tables. Keeps the sanitize pipeline cheap for the common case.\n\tif (!/<table\\b/i.test(markup)) return markup;\n\n\tif (typeof window === \"undefined\" || !window.document) return markup;\n\n\tconst ParserCtor =\n\t\ttypeof DOMParser !== \"undefined\"\n\t\t\t? DOMParser\n\t\t\t: (window as unknown as { DOMParser?: typeof DOMParser }).DOMParser;\n\tif (!ParserCtor) return markup;\n\n\tconst doc = new ParserCtor().parseFromString(\n\t\t`<!DOCTYPE html><html><body>${markup}</body></html>`,\n\t\t\"text/html\",\n\t);\n\tconst body = doc.body;\n\tif (!body) return markup;\n\n\tconst tables = Array.from(body.querySelectorAll(\"table\"));\n\tif (tables.length === 0) return markup;\n\n\tlet mutated = false;\n\tfor (const table of tables) {\n\t\tconst parent = table.parentElement;\n\t\tif (!parent) continue;\n\n\t\t// Idempotency — already wrapped.\n\t\tif (parent.classList && parent.classList.contains(SCROLL_WRAPPER_CLASS)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Leave PIE custom-element internals alone.\n\t\tif (isInsidePieCustomElement(table, body)) continue;\n\n\t\tconst wrapper = doc.createElement(\"div\");\n\t\twrapper.className = SCROLL_WRAPPER_CLASS;\n\t\twrapper.setAttribute(\"tabindex\", \"0\");\n\t\twrapper.setAttribute(\"role\", \"region\");\n\t\twrapper.setAttribute(\"aria-label\", buildAriaLabel(table));\n\n\t\tparent.insertBefore(wrapper, table);\n\t\twrapper.appendChild(table);\n\t\tmutated = true;\n\t}\n\n\treturn mutated ? body.innerHTML : markup;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"npm-registry.js","sourceRoot":"","sources":["../../src/server/npm-registry.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"npm-registry.js","sourceRoot":"","sources":["../../src/server/npm-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import semver from \"semver\";\n\nconst NPM_REGISTRY_URL = \"https://registry.npmjs.org\";\n\nif (typeof window !== \"undefined\") {\n\tthrow new Error(\n\t\t\"[npm-registry] @pie-players/pie-players-shared/server/npm-registry is server-only and cannot be imported in browser code\",\n\t);\n}\n\nconst DEFAULT_FILTER = (v: string) =>\n\tv.indexOf(\"print\") === -1 &&\n\tv.indexOf(\"ps\") === -1 &&\n\tv.indexOf(\"strikethrough\") === -1 &&\n\tv.indexOf(\"alpha\") === -1 &&\n\tv.indexOf(\"bm\") === -1;\n\ntype NpmFetch = (\n\tinput: RequestInfo | URL,\n\tinit?: RequestInit,\n) => Promise<Response>;\n\n/**\n * Server-only helper for npm package version lookups.\n * Do not import this module from browser-executed code.\n */\nexport const getNpmPackageVersions = async (\n\telement: string,\n\tfetch: NpmFetch,\n\tfilter: (v: string) => boolean = DEFAULT_FILTER,\n\tlimit?: number,\n\tsearchTerm = \"\",\n): Promise<string[]> => {\n\tif (typeof window !== \"undefined\") {\n\t\tthrow new Error(\n\t\t\t\"[npm-registry] server-only module imported in browser context\",\n\t\t);\n\t}\n\n\ttry {\n\t\tconst response = await fetch(`${NPM_REGISTRY_URL}/${element}`);\n\t\tif (!response.ok) {\n\t\t\tconsole.warn(\n\t\t\t\t`[npm-registry] npm status ${response.status} for package ${element}`,\n\t\t\t);\n\t\t\treturn searchTerm ? [] : [\"latest\"];\n\t\t}\n\n\t\tconst json = await response.json();\n\t\tconst versions = json?.versions as Record<\n\t\t\tstring,\n\t\t\t{ deprecated?: string }\n\t\t> | null;\n\t\tif (!versions || typeof versions !== \"object\") {\n\t\t\tconsole.warn(\n\t\t\t\t`[npm-registry] response for ${element} missing 'versions' property`,\n\t\t\t);\n\t\t\treturn searchTerm ? [] : [\"latest\"];\n\t\t}\n\n\t\tlet sorted = Object.keys(versions)\n\t\t\t.filter(filter)\n\t\t\t.filter((v) => !versions[v]?.deprecated)\n\t\t\t.filter((v) => !/^11\\.0\\.\\d+-esm\\.\\d+/.test(v))\n\t\t\t.filter(\n\t\t\t\t(v) =>\n\t\t\t\t\t!searchTerm || v.toLowerCase().includes(searchTerm.toLowerCase()),\n\t\t\t)\n\t\t\t.sort((a, b) => {\n\t\t\t\tconst verA = semver.valid(semver.clean(a));\n\t\t\t\tconst verB = semver.valid(semver.clean(b));\n\t\t\t\tif (!verA && !verB) return 0;\n\t\t\t\tif (!verA) return 1;\n\t\t\t\tif (!verB) return -1;\n\t\t\t\treturn semver.rcompare(verA, verB);\n\t\t\t});\n\n\t\tif (typeof limit === \"number\" && limit > 0) {\n\t\t\tsorted = sorted.slice(0, limit);\n\t\t}\n\n\t\tif (!searchTerm) {\n\t\t\tsorted.unshift(\"latest\");\n\t\t}\n\n\t\treturn sorted;\n\t} catch (error) {\n\t\tconsole.warn(`[npm-registry] failed package lookup for ${element}:`, error);\n\t\treturn searchTerm ? [] : [\"latest\"];\n\t}\n};\n"]}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -481,10 +481,14 @@ export type BundleInfo = {
|
|
|
481
481
|
url: string;
|
|
482
482
|
hash: string;
|
|
483
483
|
};
|
|
484
|
-
|
|
484
|
+
export type ItemSession = {
|
|
485
|
+
id: string;
|
|
486
|
+
data: any[];
|
|
487
|
+
};
|
|
488
|
+
export interface PieDefaultModel {
|
|
485
489
|
[x: string]: any;
|
|
486
490
|
}
|
|
487
|
-
interface PieContent {
|
|
491
|
+
export interface PieContent {
|
|
488
492
|
id: string;
|
|
489
493
|
/**
|
|
490
494
|
* Set of elements to include in the pie, provided in the format `{'element-name': 'mpm-package-name'}`
|
|
@@ -494,11 +498,19 @@ interface PieContent {
|
|
|
494
498
|
models: PieModel[];
|
|
495
499
|
markup: string;
|
|
496
500
|
bundle?: BundleInfo;
|
|
501
|
+
resources?: ConfigResource;
|
|
497
502
|
defaultExtraModels?: {
|
|
498
503
|
[key: string]: PieDefaultModel;
|
|
499
504
|
};
|
|
500
505
|
}
|
|
501
|
-
interface
|
|
506
|
+
export interface ConfigResource {
|
|
507
|
+
stylesheets?: {
|
|
508
|
+
url: string;
|
|
509
|
+
}[];
|
|
510
|
+
containerClass?: string;
|
|
511
|
+
passageContainerClass?: string;
|
|
512
|
+
}
|
|
513
|
+
export interface AdvancedItemConfig {
|
|
502
514
|
id: string;
|
|
503
515
|
pie: PieContent;
|
|
504
516
|
passage?: PieContent;
|