Spaces:
Running
Running
| /** | |
| * Shared Morphus conversion pipeline. | |
| * Reused by the CLI and the local HTTP bridge for the Figma plugin. | |
| */ | |
| import { extractFromFile, extractFromHtml } from '../core/extractor.js'; | |
| import { resolveFonts } from '../figma/font-resolver.js'; | |
| import { sortByZIndex } from '../figma/z-index-sorter.js'; | |
| import { buildFigmaTree } from '../figma/mapper.js'; | |
| const DEFAULT_VIEWPORT = { width: 1440, height: 900 }; | |
| /** | |
| * @param {string} inputPath | |
| * @param {ConvertOptions} options | |
| */ | |
| export async function convertHtmlFile(inputPath, options = {}) { | |
| const viewport = normalizeViewport(options.viewport); | |
| return convertWithExtractor({ | |
| extractor: () => extractFromFile(inputPath, viewport), | |
| source: inputPath, | |
| viewport, | |
| onProgress: options.onProgress, | |
| }); | |
| } | |
| /** | |
| * @param {string} html | |
| * @param {ConvertHtmlOptions} options | |
| */ | |
| export async function convertHtmlString(html, options = {}) { | |
| const viewport = normalizeViewport(options.viewport); | |
| return convertWithExtractor({ | |
| extractor: () => extractFromHtml(html, { | |
| ...viewport, | |
| baseUrl: options.baseUrl ?? null, | |
| }), | |
| source: options.sourceName ?? 'inline.html', | |
| viewport, | |
| baseUrl: options.baseUrl ?? null, | |
| onProgress: options.onProgress, | |
| }); | |
| } | |
| async function convertWithExtractor({ extractor, source, viewport, baseUrl = null, onProgress = null }) { | |
| progress(onProgress, 5, 'Extracting page...'); | |
| const { domTree, title } = await extractor(); | |
| progress(onProgress, 78, 'Resolving fonts...'); | |
| const fontMap = await resolveFonts(domTree); | |
| progress(onProgress, 86, 'Building Figma tree...'); | |
| const sorted = sortByZIndex(domTree); | |
| const figmaTree = buildFigmaTree(sorted, { fontMap }); | |
| const documentTitle = normalizeDocumentTitle(title); | |
| progress(onProgress, 90, 'Snapshot ready. Sending to Figma...'); | |
| return { | |
| version: '0.1.0', | |
| meta: { | |
| source, | |
| ...(documentTitle ? { title: documentTitle } : {}), | |
| viewport, | |
| ...(baseUrl ? { baseUrl } : {}), | |
| }, | |
| warnings: [], | |
| figmaTree, | |
| }; | |
| } | |
| function normalizeDocumentTitle(title) { | |
| return String(title || '').replace(/\s+/g, ' ').trim(); | |
| } | |
| function progress(onProgress, percent, message) { | |
| if (typeof onProgress === 'function') { | |
| onProgress(percent, message); | |
| } | |
| } | |
| function normalizeViewport(viewport = {}) { | |
| const width = Number.parseInt(viewport.width ?? DEFAULT_VIEWPORT.width, 10); | |
| const height = Number.parseInt(viewport.height ?? DEFAULT_VIEWPORT.height, 10); | |
| return { | |
| width: Number.isFinite(width) ? width : DEFAULT_VIEWPORT.width, | |
| height: Number.isFinite(height) ? height : DEFAULT_VIEWPORT.height, | |
| }; | |
| } | |
| /** | |
| * @typedef {{ viewport?: { width?: number, height?: number } }} ConvertOptions | |
| * @typedef {ConvertOptions & { sourceName?: string, baseUrl?: string | null }} ConvertHtmlOptions | |
| */ | |