@ohos-ports/slides-grab 1.5.0-beta.0

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.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README-ko.md +292 -0
  3. package/README.md +301 -0
  4. package/bin/ohos-postinstall.cjs +118 -0
  5. package/bin/ppt-agent.js +620 -0
  6. package/convert.cjs +20 -0
  7. package/native/@img/sharp-libvips-openharmony-arm64/lib/glib-2.0/include/glibconfig.h +220 -0
  8. package/native/@img/sharp-libvips-openharmony-arm64/lib/index.js +1 -0
  9. package/native/@img/sharp-libvips-openharmony-arm64/lib/libexpat.so +0 -0
  10. package/native/@img/sharp-libvips-openharmony-arm64/lib/libffi.so +0 -0
  11. package/native/@img/sharp-libvips-openharmony-arm64/lib/libjpeg.so +0 -0
  12. package/native/@img/sharp-libvips-openharmony-arm64/lib/libpcre2-8.so.0 +0 -0
  13. package/native/@img/sharp-libvips-openharmony-arm64/lib/libvips-cpp.so.42.20.3 +0 -0
  14. package/native/@img/sharp-libvips-openharmony-arm64/lib/libvips.so.42.20.3 +0 -0
  15. package/native/@img/sharp-libvips-openharmony-arm64/package.json +13 -0
  16. package/native/@img/sharp-openharmony-arm64/lib/sharp-openharmony-arm64.node +0 -0
  17. package/native/@img/sharp-openharmony-arm64/package.json +16 -0
  18. package/package.json +95 -0
  19. package/runtimes/claude-code/agents/design-critic-agent.md +23 -0
  20. package/runtimes/codex/agents/slides-grab-design-critic.md +22 -0
  21. package/scripts/build-viewer.js +444 -0
  22. package/scripts/design-gate.js +258 -0
  23. package/scripts/download-video.js +213 -0
  24. package/scripts/editor-server.js +1167 -0
  25. package/scripts/figma-export.js +169 -0
  26. package/scripts/generate-image.js +116 -0
  27. package/scripts/generate-images.js +164 -0
  28. package/scripts/html2pdf.js +822 -0
  29. package/scripts/html2png.js +246 -0
  30. package/scripts/html2pptx.js +162 -0
  31. package/scripts/import-template.js +86 -0
  32. package/scripts/install-runtime.js +216 -0
  33. package/scripts/render-tldraw.js +44 -0
  34. package/scripts/validate-slides.js +221 -0
  35. package/skills/slides-grab/SKILL.md +65 -0
  36. package/skills/slides-grab/references/presentation-workflow-reference.md +61 -0
  37. package/skills/slides-grab-card-news/SKILL.md +35 -0
  38. package/skills/slides-grab-design/SKILL.md +88 -0
  39. package/skills/slides-grab-design/references/beautiful-slide-defaults.md +88 -0
  40. package/skills/slides-grab-design/references/design-gate.md +349 -0
  41. package/skills/slides-grab-design/references/design-rules.md +76 -0
  42. package/skills/slides-grab-design/references/design-system-full.md +568 -0
  43. package/skills/slides-grab-design/references/detailed-design-rules.md +70 -0
  44. package/skills/slides-grab-export/SKILL.md +55 -0
  45. package/skills/slides-grab-export/references/export-rules.md +27 -0
  46. package/skills/slides-grab-export/references/html2pptx.md +627 -0
  47. package/skills/slides-grab-export/references/ooxml.md +427 -0
  48. package/skills/slides-grab-export/references/pptx-skill-reference.md +189 -0
  49. package/skills/slides-grab-html/SKILL.md +67 -0
  50. package/skills/slides-grab-image/SKILL.md +82 -0
  51. package/skills/slides-grab-plan/SKILL.md +68 -0
  52. package/skills/slides-grab-plan/references/design-md-to-slides-conversion.md +135 -0
  53. package/skills/slides-grab-plan/references/outline-format.md +47 -0
  54. package/skills/slides-grab-plan/references/plan-workflow-reference.md +140 -0
  55. package/src/codex-imagen.js +182 -0
  56. package/src/design-diversity-data.js +6932 -0
  57. package/src/design-gate-report.js +244 -0
  58. package/src/design-gate-state.js +329 -0
  59. package/src/design-import.js +164 -0
  60. package/src/design-md-parser.js +415 -0
  61. package/src/design-styles-data.js +1928 -0
  62. package/src/design-styles.js +209 -0
  63. package/src/editor/codex-edit.js +584 -0
  64. package/src/editor/edit-subprocess.js +170 -0
  65. package/src/editor/editor-codex-prompt.md +50 -0
  66. package/src/editor/editor.html +1785 -0
  67. package/src/editor/js/editor-bbox.js +332 -0
  68. package/src/editor/js/editor-chat.js +56 -0
  69. package/src/editor/js/editor-direct-edit.js +238 -0
  70. package/src/editor/js/editor-dom.js +59 -0
  71. package/src/editor/js/editor-init.js +405 -0
  72. package/src/editor/js/editor-navigation.js +54 -0
  73. package/src/editor/js/editor-select.js +558 -0
  74. package/src/editor/js/editor-send.js +175 -0
  75. package/src/editor/js/editor-sse.js +163 -0
  76. package/src/editor/js/editor-state.js +41 -0
  77. package/src/editor/js/editor-type.js +71 -0
  78. package/src/editor/js/editor-utils.js +167 -0
  79. package/src/editor/js/model-registry.js +37 -0
  80. package/src/editor/screenshot.js +82 -0
  81. package/src/export-resolution.cjs +68 -0
  82. package/src/figma.js +71 -0
  83. package/src/html2pptx-scale.cjs +120 -0
  84. package/src/html2pptx.cjs +1262 -0
  85. package/src/image-contract.js +329 -0
  86. package/src/image-native.js +468 -0
  87. package/src/nano-banana.js +841 -0
  88. package/src/ohos-browser-bridge.cjs +272 -0
  89. package/src/pptx-raster-export.cjs +299 -0
  90. package/src/resolve.js +110 -0
  91. package/src/slide-mode.cjs +72 -0
  92. package/src/template-fidelity.js +267 -0
  93. package/src/template-import.js +505 -0
  94. package/src/template-layout.js +261 -0
  95. package/src/template-pack.js +255 -0
  96. package/src/tldraw/render.js +473 -0
  97. package/src/validation/cli.js +120 -0
  98. package/src/validation/core.js +1023 -0
  99. package/templates/chart.html +121 -0
  100. package/templates/closing.html +54 -0
  101. package/templates/content.html +50 -0
  102. package/templates/contents.html +60 -0
  103. package/templates/cover.html +64 -0
  104. package/templates/custom/.gitkeep +0 -0
  105. package/templates/custom/README.md +7 -0
  106. package/templates/design-styles/README.md +20 -0
  107. package/templates/design-styles/preview.html +4438 -0
  108. package/templates/diagram-tldraw.html +56 -0
  109. package/templates/diagram.html +98 -0
  110. package/templates/quote.html +31 -0
  111. package/templates/section-divider.html +43 -0
  112. package/templates/split-layout.html +43 -0
  113. package/templates/statistics.html +55 -0
  114. package/templates/team.html +49 -0
  115. package/templates/timeline.html +59 -0
@@ -0,0 +1,329 @@
1
+ import { dirname, join, resolve } from 'node:path';
2
+
3
+ const ABSOLUTE_FILESYSTEM_PATH_RE = /^(file:\/\/|\/Users\/|\/home\/|\/var\/|\/tmp\/|\/private\/|\/Volumes\/|[A-Za-z]:[\\/]|\\\\)/i;
4
+ const SCHEME_RE = /^[a-z][a-z0-9+\-.]*:/i;
5
+ const CSS_URL_RE = /url\(\s*(['"]?)(.*?)\1\s*\)/gi;
6
+
7
+ export const LOCAL_ASSET_PREFIX = './assets/';
8
+
9
+ const ASSET_CONTRACT_RULES = {
10
+ image: {
11
+ label: 'image',
12
+ remoteCode: 'remote-image-url',
13
+ remoteInsecureCode: 'remote-image-url-insecure',
14
+ absoluteCode: 'absolute-filesystem-image-path',
15
+ rootRelativeCode: 'root-relative-image-path',
16
+ otherSchemeCode: 'unsupported-image-url-scheme',
17
+ noncanonicalCode: 'noncanonical-relative-image-path',
18
+ },
19
+ video: {
20
+ label: 'video',
21
+ remoteCode: 'remote-video-url',
22
+ remoteInsecureCode: 'remote-video-url-insecure',
23
+ absoluteCode: 'absolute-filesystem-video-path',
24
+ rootRelativeCode: 'root-relative-video-path',
25
+ otherSchemeCode: 'unsupported-video-url-scheme',
26
+ noncanonicalCode: 'noncanonical-relative-video-path',
27
+ },
28
+ };
29
+
30
+ export function looksLikeAbsoluteFilesystemPath(value) {
31
+ return ABSOLUTE_FILESYSTEM_PATH_RE.test((value || '').trim());
32
+ }
33
+
34
+ export function extractCssUrls(value) {
35
+ const input = typeof value === 'string' ? value : '';
36
+ const matches = [];
37
+ let match;
38
+ while ((match = CSS_URL_RE.exec(input)) !== null) {
39
+ const candidate = (match[2] || '').trim();
40
+ if (candidate) {
41
+ matches.push(candidate);
42
+ }
43
+ }
44
+ return matches;
45
+ }
46
+
47
+ export function classifyImageSource(source) {
48
+ const value = typeof source === 'string' ? source.trim() : '';
49
+
50
+ if (!value) return { kind: 'empty' };
51
+ if (value.startsWith('data:')) return { kind: 'data-url' };
52
+ if (value.startsWith('https://')) return { kind: 'remote-url' };
53
+ if (value.startsWith('http://')) return { kind: 'remote-url-insecure' };
54
+ if (looksLikeAbsoluteFilesystemPath(value)) return { kind: 'absolute-filesystem-path' };
55
+ if (value.startsWith(LOCAL_ASSET_PREFIX)) return { kind: 'local-asset-path' };
56
+ if (value.startsWith('/')) return { kind: 'root-relative-path' };
57
+ if (SCHEME_RE.test(value)) return { kind: 'other-scheme' };
58
+ return { kind: 'noncanonical-relative-path' };
59
+ }
60
+
61
+ export function resolveSlideSourcePath(slidePath, source) {
62
+ return resolve(dirname(slidePath), source);
63
+ }
64
+
65
+ function injectIntoHead(html, snippet) {
66
+ if (/<head\b[^>]*>/i.test(html)) {
67
+ return html.replace(/<head\b[^>]*>/i, (match) => `${match}\n${snippet}`);
68
+ }
69
+
70
+ if (/<html\b[^>]*>/i.test(html)) {
71
+ return html.replace(/<html\b[^>]*>/i, (match) => `${match}\n<head>\n${snippet}\n</head>`);
72
+ }
73
+
74
+ return `${snippet}\n${html}`;
75
+ }
76
+
77
+ function buildAssetContractReport({ slideFile, sources = [], assetType = 'image' }) {
78
+ const rules = ASSET_CONTRACT_RULES[assetType] || ASSET_CONTRACT_RULES.image;
79
+ const { label } = rules;
80
+ const issues = [];
81
+
82
+ for (const entry of sources) {
83
+ const source = typeof entry?.source === 'string' ? entry.source.trim() : '';
84
+ const classification = classifyImageSource(source);
85
+
86
+ if (classification.kind === 'empty' || classification.kind === 'data-url') {
87
+ continue;
88
+ }
89
+
90
+ if (classification.kind === 'remote-url') {
91
+ issues.push({
92
+ severity: 'critical',
93
+ code: rules.remoteCode,
94
+ message: `Remote ${label} URLs are unsupported in saved slide HTML. Download the ${label} into ./assets/<file> instead.`,
95
+ slide: slideFile,
96
+ ...entry,
97
+ });
98
+ continue;
99
+ }
100
+
101
+ if (classification.kind === 'remote-url-insecure') {
102
+ issues.push({
103
+ severity: 'critical',
104
+ code: rules.remoteInsecureCode,
105
+ message: `Remote http:// ${label} URLs are unsupported in saved slide HTML. Download the ${label} into ./assets/<file> instead.`,
106
+ slide: slideFile,
107
+ ...entry,
108
+ });
109
+ continue;
110
+ }
111
+
112
+ if (classification.kind === 'absolute-filesystem-path') {
113
+ issues.push({
114
+ severity: 'critical',
115
+ code: rules.absoluteCode,
116
+ message: `Absolute filesystem ${label} paths are unsupported. Use ./assets/<file> instead.`,
117
+ slide: slideFile,
118
+ ...entry,
119
+ });
120
+ continue;
121
+ }
122
+
123
+ if (classification.kind === 'root-relative-path') {
124
+ issues.push({
125
+ severity: 'critical',
126
+ code: rules.rootRelativeCode,
127
+ message: `Root-relative ${label} paths are unsupported. Use ./assets/<file> instead.`,
128
+ slide: slideFile,
129
+ ...entry,
130
+ });
131
+ continue;
132
+ }
133
+
134
+ if (classification.kind === 'other-scheme' && rules.otherSchemeCode) {
135
+ issues.push({
136
+ severity: 'critical',
137
+ code: rules.otherSchemeCode,
138
+ message: `Non-file URL schemes for ${label} assets are unsupported in saved slide HTML. Download the ${label} into ./assets/<file> instead.`,
139
+ slide: slideFile,
140
+ ...entry,
141
+ });
142
+ continue;
143
+ }
144
+
145
+ if (classification.kind === 'noncanonical-relative-path') {
146
+ issues.push({
147
+ severity: 'warning',
148
+ code: rules.noncanonicalCode,
149
+ message: `Use ./assets/<file> for portable local ${label} assets.`,
150
+ slide: slideFile,
151
+ ...entry,
152
+ });
153
+ }
154
+ }
155
+
156
+ return issues;
157
+ }
158
+
159
+ export function buildImageContractReport({ slideFile, sources = [] }) {
160
+ return buildAssetContractReport({ slideFile, sources, assetType: 'image' });
161
+ }
162
+
163
+ export function buildVideoContractReport({ slideFile, sources = [] }) {
164
+ return buildAssetContractReport({ slideFile, sources, assetType: 'video' });
165
+ }
166
+
167
+ export function buildSlideRuntimeHtml(html, { baseHref, slideFile }) {
168
+ const snippets = [];
169
+
170
+ if (baseHref && !/<base\b/i.test(html)) {
171
+ snippets.push(`<base data-slides-grab-runtime="base" href="${baseHref}">`);
172
+ }
173
+
174
+ const script = `<script data-slides-grab-runtime="diagnostics">
175
+ (() => {
176
+ const slideFile = ${JSON.stringify(slideFile)};
177
+ const localAssetPrefix = ${JSON.stringify(LOCAL_ASSET_PREFIX)};
178
+ const absolutePathRe = ${ABSOLUTE_FILESYSTEM_PATH_RE.toString()};
179
+ const schemeRe = ${SCHEME_RE.toString()};
180
+ const prefix = '[slides-grab:image]';
181
+
182
+ function describeElement(element) {
183
+ if (!element || element.nodeType !== Node.ELEMENT_NODE) return '';
184
+ if (element === document.body) return 'body';
185
+
186
+ const parts = [];
187
+ let current = element;
188
+ while (current && current.nodeType === Node.ELEMENT_NODE && current !== document.body) {
189
+ let part = current.tagName.toLowerCase();
190
+ if (current.id) {
191
+ part += '#' + current.id;
192
+ parts.unshift(part);
193
+ break;
194
+ }
195
+ if (current.classList.length > 0) {
196
+ part += '.' + Array.from(current.classList).slice(0, 2).join('.');
197
+ }
198
+ parts.unshift(part);
199
+ current = current.parentElement;
200
+ }
201
+ return 'body > ' + parts.join(' > ');
202
+ }
203
+
204
+ function warn(message, detail) {
205
+ console.warn(prefix + ' ' + slideFile + ': ' + message, detail);
206
+ }
207
+
208
+ function fail(message, detail) {
209
+ console.error(prefix + ' ' + slideFile + ': ' + message, detail);
210
+ }
211
+
212
+ function validateAssetSource(kind, source, { allowEmpty = true, onNoncanonical = 'warn' } = {}) {
213
+ const value = (source || '').trim();
214
+ if (!value) {
215
+ return allowEmpty;
216
+ }
217
+ if (value.startsWith('data:')) {
218
+ return true;
219
+ }
220
+ if (value.startsWith('https://')) {
221
+ fail('remote ' + kind + ' URL is unsupported in saved slides; download it into ./assets/<file>', { src: value });
222
+ return false;
223
+ }
224
+ if (value.startsWith('http://')) {
225
+ fail('remote http:// ' + kind + ' URL is unsupported in saved slides; download it into ./assets/<file>', { src: value });
226
+ return false;
227
+ }
228
+ if (absolutePathRe.test(value) || value.startsWith('/')) {
229
+ fail('non-portable ' + kind + ' path is unsupported', { src: value });
230
+ return false;
231
+ }
232
+ if (schemeRe.test(value)) {
233
+ fail('unsupported ' + kind + ' URL scheme in saved slides; download it into ./assets/<file>', { src: value });
234
+ return false;
235
+ }
236
+ if (!value.startsWith(localAssetPrefix)) {
237
+ const report = onNoncanonical === 'fail' ? fail : warn;
238
+ report('noncanonical local ' + kind + ' path should use ./assets/<file>', { src: value });
239
+ }
240
+ return true;
241
+ }
242
+
243
+ function getVideoSources(video) {
244
+ const sources = [];
245
+ const directSrc = (video.getAttribute('src') || '').trim();
246
+ if (directSrc) {
247
+ sources.push(directSrc);
248
+ }
249
+ for (const source of video.querySelectorAll('source[src]')) {
250
+ const src = (source.getAttribute('src') || '').trim();
251
+ if (src) {
252
+ sources.push(src);
253
+ }
254
+ }
255
+ return sources;
256
+ }
257
+
258
+ window.addEventListener('error', (event) => {
259
+ const target = event.target;
260
+ if (target instanceof HTMLImageElement) {
261
+ const src = (target.getAttribute('src') || target.currentSrc || '').trim();
262
+ if (!src || src.startsWith('data:')) return;
263
+ if (src.startsWith(localAssetPrefix)) {
264
+ fail('missing local asset', { src });
265
+ return;
266
+ }
267
+ fail('image failed to load', { src });
268
+ return;
269
+ }
270
+
271
+ if (target instanceof HTMLVideoElement) {
272
+ const sources = getVideoSources(target);
273
+ if (sources.some((src) => src.startsWith(localAssetPrefix))) {
274
+ fail('missing local video asset', { sources });
275
+ return;
276
+ }
277
+ fail('video failed to load', { sources });
278
+ return;
279
+ }
280
+
281
+ if (target instanceof HTMLSourceElement && target.parentElement instanceof HTMLVideoElement) {
282
+ const src = (target.getAttribute('src') || '').trim();
283
+ if (!src) return;
284
+ if (src.startsWith(localAssetPrefix)) {
285
+ fail('missing local video asset', { src });
286
+ return;
287
+ }
288
+ fail('video source failed to load', { src });
289
+ }
290
+ }, true);
291
+
292
+ window.addEventListener('DOMContentLoaded', () => {
293
+ for (const image of document.querySelectorAll('img[src]')) {
294
+ const src = (image.getAttribute('src') || '').trim();
295
+ validateAssetSource('image', src);
296
+ }
297
+
298
+ for (const video of document.querySelectorAll('video')) {
299
+ for (const src of getVideoSources(video)) {
300
+ validateAssetSource('video', src);
301
+ }
302
+
303
+ const poster = (video.getAttribute('poster') || '').trim();
304
+ if (poster) {
305
+ validateAssetSource('image', poster);
306
+ }
307
+ }
308
+
309
+ for (const element of document.body.querySelectorAll('*')) {
310
+ if (element === document.body) continue;
311
+ const backgroundImage = window.getComputedStyle(element).backgroundImage;
312
+ if (!backgroundImage || backgroundImage === 'none' || !backgroundImage.includes('url(')) continue;
313
+ fail('non-body background-image is not supported for slide content', {
314
+ element: describeElement(element),
315
+ backgroundImage,
316
+ });
317
+ }
318
+ });
319
+ })();
320
+ </script>`;
321
+
322
+ snippets.push(script);
323
+
324
+ return injectIntoHead(html, snippets.join('\n'));
325
+ }
326
+
327
+ export function resolveLocalAssetPath(slidePath, source) {
328
+ return join(dirname(slidePath), source.replace(/^\.\//, ''));
329
+ }