@accelflow/space-content 0.1.0-beta.4 → 0.1.0-beta.5
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 +16 -0
- package/package.json +5 -3
- package/render.mjs +12 -3
package/README.md
CHANGED
|
@@ -214,3 +214,19 @@ paragraphs and content outside this wrapper are unchanged. Customize the gap:
|
|
|
214
214
|
```css
|
|
215
215
|
.space-content { --space-content-paragraph-gap: 0.75em; }
|
|
216
216
|
```
|
|
217
|
+
|
|
218
|
+
### Image dimensions
|
|
219
|
+
|
|
220
|
+
`renderArticleBody` retains independently specified `width` and `height` for JSON
|
|
221
|
+
image nodes and raster attachment nodes. Each dimension must be an integer from
|
|
222
|
+
1 through 10000 pixels. Missing dimensions retain the browser's automatic sizing.
|
|
223
|
+
Images keep `max-width:100%` so they fit narrower article containers.
|
|
224
|
+
|
|
225
|
+
For HTML bodies, the sanitizer retains only bounded pixel width/height, image
|
|
226
|
+
`width:100%`, `height:auto`, and `max-width:100%` on the image/figure elements used
|
|
227
|
+
by Space exports. Other inline CSS and executable attributes remain disallowed.
|
|
228
|
+
|
|
229
|
+
Run `npm run verify:local` for the standard local verification. This runs the
|
|
230
|
+
SDK tests, including Build API transfer-to-render image regressions for JSON and
|
|
231
|
+
HTML, then verifies the packed public exports and Vite/Next static builds, and
|
|
232
|
+
audits dependencies. The same image regressions run against the packed SDK.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@accelflow/space-content",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24"
|
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
"scripts": {
|
|
30
30
|
"test": "node --test test/*.test.mjs",
|
|
31
31
|
"prepack": "npm test",
|
|
32
|
-
"verify:package": "node scripts/verify-package.mjs"
|
|
32
|
+
"verify:package": "node scripts/verify-package.mjs",
|
|
33
|
+
"audit:dependencies": "npm audit --audit-level=moderate",
|
|
34
|
+
"verify:local": "npm run verify:package && npm run audit:dependencies"
|
|
33
35
|
},
|
|
34
36
|
"files": [
|
|
35
37
|
"node.mjs",
|
|
@@ -61,7 +63,7 @@
|
|
|
61
63
|
"tag": "beta"
|
|
62
64
|
},
|
|
63
65
|
"dependencies": {
|
|
64
|
-
"sanitize-html": "
|
|
66
|
+
"sanitize-html": "2.18.1"
|
|
65
67
|
},
|
|
66
68
|
"author": {
|
|
67
69
|
"name": "Accelflow Inc.",
|
package/render.mjs
CHANGED
|
@@ -4,6 +4,11 @@ export const escapeHtml = value => String(value ?? '').replace(/[&<>"']/g, c =>
|
|
|
4
4
|
const linkTarget = attrs => attrs?.target === '_blank' ? ' target="_blank" rel="noopener noreferrer"' : '';
|
|
5
5
|
const hash = /^[a-f0-9]{64}$/;
|
|
6
6
|
const raster = new Set(['image/png','image/jpeg','image/gif','image/webp','image/avif']);
|
|
7
|
+
const imagePixels = /^(?:[1-9]\d{0,3}|10000)px$/;
|
|
8
|
+
function imageStyle(attrs) {
|
|
9
|
+
const dimensions=['width','height'].filter(key=>Number.isSafeInteger(attrs[key])&&attrs[key]>0&&attrs[key]<=10000).map(key=>`${key}:${attrs[key]}px`);
|
|
10
|
+
return dimensions.length?` style="max-width:100%;${dimensions.join(';')}"`:'';
|
|
11
|
+
}
|
|
7
12
|
function assetsIndex(assets = []) {
|
|
8
13
|
const ids = new Map(), paths = new Map();
|
|
9
14
|
for (const a of assets) {
|
|
@@ -26,12 +31,16 @@ export function sanitizeArticleHtml(html, {assets = [], pdfMode = 'link'} = {})
|
|
|
26
31
|
allowedTags: [...sanitizeHtml.defaults.allowedTags,'img','details','summary','nav',...(pdfMode==='embed'?['iframe']:[])],
|
|
27
32
|
allowedAttributes: {
|
|
28
33
|
'*':['id','class'], a:['href','title','data-level','download','target','rel'],
|
|
29
|
-
img:['src','alt','title','width','height','loading','referrerpolicy'],
|
|
34
|
+
img:['src','alt','title','width','height','loading','referrerpolicy','style'], figure:['style'],
|
|
30
35
|
nav:['data-table-of-contents','data-max-level','aria-label'], details:['open','data-space-toc'],
|
|
31
36
|
li:['data-level','data-checked'], ol:['start'], td:['colspan','rowspan'], th:['colspan','rowspan','scope'],
|
|
32
37
|
iframe:['src','title','sandbox','loading','referrerpolicy'],
|
|
33
38
|
},
|
|
34
39
|
allowedClasses:{'*':['*']},
|
|
40
|
+
allowedStyles:{
|
|
41
|
+
img:{width:[imagePixels,/^100%$/],height:[imagePixels,/^auto$/],'max-width':[/^100%$/]},
|
|
42
|
+
figure:{width:[imagePixels],'max-width':[/^100%$/]},
|
|
43
|
+
},
|
|
35
44
|
allowedSchemes:['https','http','mailto','tel'], allowedSchemesByTag:{img:['https'],iframe:[]}, allowProtocolRelative:false,
|
|
36
45
|
transformTags: {
|
|
37
46
|
a: (tagName, attrs) => {const {target,rel,...rest}=attrs;return {tagName,attribs:{...rest,...(target==='_blank'&&/^https?:\/\//i.test(safeUrl(attrs.href))?{target:'_blank',rel:'noopener noreferrer'}:{})}};},
|
|
@@ -69,11 +78,11 @@ export function renderArticleBody(body, options = {}) {
|
|
|
69
78
|
for(const mark of Array.isArray(n.marks)?n.marks:[]) {const tag={bold:'strong',italic:'em',underline:'u',strike:'s',code:'code'}[mark?.type];if(tag)html=wrap(tag,html);else if(mark?.type==='link'){const href=safeUrl(mark.attrs?.href);if(href)html=wrap('a',html,` href="${e(href)}"${linkTarget(mark.attrs)}`);}}
|
|
70
79
|
} else if(n.type==='heading') html=wrap('h'+(Number.isInteger(a.level)&&a.level>=1&&a.level<=6?a.level:2),children,` id="${e(anchors.get(n))}"`);
|
|
71
80
|
else if(n.type==='tableOfContents') html=renderTableOfContents(headings,{locale:options.locale??'ja',maxLevel:a.maxLevel===2?2:3});
|
|
72
|
-
else if(n.type==='image') {const src=safeUrl(a.src,true);html=src?`<img src="${e(src)}" alt="${e(a.alt)}" loading="lazy" referrerpolicy="no-referrer">`:'';}
|
|
81
|
+
else if(n.type==='image') {const src=safeUrl(a.src,true);html=src?`<img src="${e(src)}" alt="${e(a.alt)}" loading="lazy" referrerpolicy="no-referrer"${imageStyle(a)}>`:'';}
|
|
73
82
|
else if(n.type==='attachment') {
|
|
74
83
|
const asset=ids.get(a.attachmentId);if(!asset)throw Error('attachment_not_in_manifest');
|
|
75
84
|
const src='/space-assets/'+asset.hash, label=e(asset.filename);
|
|
76
|
-
html=raster.has(asset.contentType)?`<img src="${src}" alt="${label}" loading="lazy">`:`<a href="${src}" download="${label}">${label}</a>`;
|
|
85
|
+
html=raster.has(asset.contentType)?`<img src="${src}" alt="${label}" loading="lazy"${imageStyle(a)}>`:`<a href="${src}" download="${label}">${label}</a>`;
|
|
77
86
|
if(asset.contentType==='application/pdf'&&options.pdfMode==='embed')html=`<iframe class="space-pdf" src="${src}" title="${label}" sandbox="" loading="lazy" referrerpolicy="no-referrer"></iframe>`+html;
|
|
78
87
|
} else if(n.type==='externalLinkCard') {const href=safeUrl(a.href);html=href?wrap('a',e(a.title||href),` href="${e(href)}"${linkTarget(a)}`):e(a.title);}
|
|
79
88
|
else if(n.type==='articleReference') {const target=options.resolveArticle?.(a);const href=safeUrl(target?.href);const label=e(a.labelMode==='custom'?a.customLabel:target?.title??a.fallbackTitle??a.title??'');html=href?wrap('a',label,` href="${e(href)}"`):label;}
|