diff --git a/README.md b/README.md index 22784439..1a98eb7d 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,12 @@ High-performance HTML → Markdown conversion powered by Rust. Shipping as a Rus [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/Goldziher/html-to-markdown/blob/main/LICENSE) [![Discord](https://img.shields.io/badge/Discord-Join%20our%20community-7289da)](https://discord.gg/pXxagNK2zN) +## 🎮 Live Demo + +**Try it now:** [https://goldziher.github.io/html-to-markdown/](https://goldziher.github.io/html-to-markdown/) + +Experience the power of WebAssembly-based HTML to Markdown conversion directly in your browser! + ## Documentation - **JavaScript/TypeScript guides**: diff --git a/Taskfile.yaml b/Taskfile.yaml index 506cb081..576869a8 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -107,6 +107,17 @@ tasks: - task: build:wasm - task: build:ts + build:demo: + desc: "Build and update the GitHub Pages demo" + cmds: + - ./scripts/build-demo.sh + + serve:demo: + desc: "Serve the GitHub Pages demo locally" + dir: docs + cmds: + - python3 -m http.server 8000 + build:wheel-prep: desc: "Prepare CLI binary for wheel packaging" cmds: diff --git a/biome.json b/biome.json index 6f013084..19fe5273 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.2.5/schema.json", + "$schema": "https://biomejs.dev/schemas/2.2.6/schema.json", "vcs": { "enabled": true, "clientKind": "git", @@ -15,7 +15,9 @@ "**/html-to-markdown-node/index.js", "**/html-to-markdown-node/index.d.ts", "**/html-to-markdown-wasm/**/*.js", - "**/html-to-markdown-wasm/**/*.d.ts" + "**/html-to-markdown-wasm/**/*.d.ts", + "docs/html_to_markdown_wasm.js", + "docs/html_to_markdown_wasm_bg.wasm" ], "linter": { "enabled": false diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..fd1bfd2e --- /dev/null +++ b/docs/README.md @@ -0,0 +1,59 @@ +# GitHub Pages Demo + +This directory contains the live demo of the HTML to Markdown converter, powered by WebAssembly. + +## 🌐 Live Demo + +Visit the live demo at: **** + +## 🚀 Running Locally + +To test the demo locally: + +```bash +# Option 1: Using task +task serve:demo + +# Option 2: Using Python +cd docs +python3 -m http.server 8000 + +# Option 3: Using Node.js +npx http-server docs -p 8000 +``` + +Then open in your browser. + +## 🔧 Building the WASM Files + +When you update the Rust code and need to rebuild: + +```bash +# Option 1: Using go-task (recommended) +go-task build:demo + +# Option 2: Using the script +./scripts/build-demo.sh + +# Option 3: Manual +cd crates/html-to-markdown-wasm +wasm-pack build --target web --out-dir dist-web +cd ../.. +cp crates/html-to-markdown-wasm/dist-web/html_to_markdown_wasm.js docs/ +cp crates/html-to-markdown-wasm/dist-web/html_to_markdown_wasm_bg.wasm docs/ +``` + +Then commit and push to deploy: + +```bash +git add docs/ +git commit -m "Update demo" +git push +``` + +## 📝 Notes + +- The WASM binary is ~2.6MB (optimized with `wasm-opt`) +- First load may take a moment to download and initialize WASM +- All conversion happens client-side - no data is sent to any server +- Must be served over HTTP/HTTPS (not `file://`) due to WASM/CORS requirements diff --git a/docs/html_to_markdown_wasm.js b/docs/html_to_markdown_wasm.js new file mode 100644 index 00000000..2541b4e4 --- /dev/null +++ b/docs/html_to_markdown_wasm.js @@ -0,0 +1,1035 @@ +let wasm; + +let cachedUint8ArrayMemory0 = null; + +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +let cachedTextDecoder = new TextDecoder("utf-8", { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +const MAX_SAFARI_DECODE_BYTES = 2146435072; +let numBytesDecoded = 0; +function decodeText(ptr, len) { + numBytesDecoded += len; + if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) { + cachedTextDecoder = new TextDecoder("utf-8", { ignoreBOM: true, fatal: true }); + cachedTextDecoder.decode(); + numBytesDecoded = len; + } + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return decodeText(ptr, len); +} + +const heap = new Array(128).fill(undefined); + +heap.push(undefined, null, true, false); + +let heap_next = heap.length; + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +function getObject(idx) { + return heap[idx]; +} + +let WASM_VECTOR_LEN = 0; + +const cachedTextEncoder = new TextEncoder(); + +if (!("encodeInto" in cachedTextEncoder)) { + cachedTextEncoder.encodeInto = (arg, view) => { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length, + }; + }; +} + +function passStringToWasm0(arg, malloc, realloc) { + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0() + .subarray(ptr, ptr + buf.length) + .set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7f) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = cachedTextEncoder.encodeInto(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +let cachedDataViewMemory0 = null; + +function getDataViewMemory0() { + if ( + cachedDataViewMemory0 === null || + cachedDataViewMemory0.buffer.detached === true || + (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer) + ) { + cachedDataViewMemory0 = new DataView(wasm.memory.buffer); + } + return cachedDataViewMemory0; +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_export_2(addHeapObject(e)); + } +} + +function getArrayU8FromWasm0(ptr, len) { + ptr = ptr >>> 0; + return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len); +} + +function dropObject(idx) { + if (idx < 132) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +function isLikeNone(x) { + return x === undefined || x === null; +} + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == "number" || type == "boolean" || val == null) { + return `${val}`; + } + if (type == "string") { + return `"${val}"`; + } + if (type == "symbol") { + const description = val.description; + if (description == null) { + return "Symbol"; + } else { + return `Symbol(${description})`; + } + } + if (type == "function") { + const name = val.name; + if (typeof name == "string" && name.length > 0) { + return `Function(${name})`; + } else { + return "Function"; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = "["; + if (length > 0) { + debug += debugString(val[0]); + } + for (let i = 1; i < length; i++) { + debug += ", " + debugString(val[i]); + } + debug += "]"; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == "Object") { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return "Object(" + JSON.stringify(val) + ")"; + } catch (_) { + return "Object"; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} + +let cachedUint32ArrayMemory0 = null; + +function getUint32ArrayMemory0() { + if (cachedUint32ArrayMemory0 === null || cachedUint32ArrayMemory0.byteLength === 0) { + cachedUint32ArrayMemory0 = new Uint32Array(wasm.memory.buffer); + } + return cachedUint32ArrayMemory0; +} + +function getArrayU32FromWasm0(ptr, len) { + ptr = ptr >>> 0; + return getUint32ArrayMemory0().subarray(ptr / 4, ptr / 4 + len); +} + +function getArrayJsValueFromWasm0(ptr, len) { + ptr = ptr >>> 0; + const mem = getDataViewMemory0(); + const result = []; + for (let i = ptr; i < ptr + 4 * len; i += 4) { + result.push(takeObject(mem.getUint32(i, true))); + } + return result; +} +/** + * Initialize panic hook for better error messages in the browser + */ +export function init() { + wasm.init(); +} + +/** + * Convert HTML to Markdown + * + * # Arguments + * + * * `html` - The HTML string to convert + * * `options` - Optional conversion options (as a JavaScript object) + * + * # Example + * + * ```javascript + * import { convert } from '@html-to-markdown/wasm'; + * + * const html = '

Hello World

'; + * const markdown = convert(html); + * console.log(markdown); // # Hello World + * ``` + * @param {string} html + * @param {any} options + * @returns {string} + */ +export function convert(html, options) { + let deferred3_0; + let deferred3_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0(html, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len0 = WASM_VECTOR_LEN; + wasm.convert(retptr, ptr0, len0, addHeapObject(options)); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); + var ptr2 = r0; + var len2 = r1; + if (r3) { + ptr2 = 0; + len2 = 0; + throw takeObject(r2); + } + deferred3_0 = ptr2; + deferred3_1 = len2; + return getStringFromWasm0(ptr2, len2); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export_3(deferred3_0, deferred3_1, 1); + } +} + +function _assertClass(instance, klass) { + if (!(instance instanceof klass)) { + throw new Error(`expected instance of ${klass.name}`); + } +} +/** + * Convert HTML to Markdown while collecting inline images + * + * # Arguments + * + * * `html` - The HTML string to convert + * * `options` - Optional conversion options (as a JavaScript object) + * * `image_config` - Configuration for inline image extraction + * + * # Example + * + * ```javascript + * import { convertWithInlineImages, WasmInlineImageConfig } from '@html-to-markdown/wasm'; + * + * const html = 'test'; + * const config = new WasmInlineImageConfig(1024 * 1024); + * config.inferDimensions = true; + * + * const result = convertWithInlineImages(html, null, config); + * console.log(result.markdown); + * console.log(result.inlineImages.length); + * ``` + * @param {string} html + * @param {any} options + * @param {WasmInlineImageConfig | null} [image_config] + * @returns {WasmHtmlExtraction} + */ +export function convertWithInlineImages(html, options, image_config) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0(html, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len0 = WASM_VECTOR_LEN; + let ptr1 = 0; + if (!isLikeNone(image_config)) { + _assertClass(image_config, WasmInlineImageConfig); + ptr1 = image_config.__destroy_into_raw(); + } + wasm.convertWithInlineImages(retptr, ptr0, len0, addHeapObject(options), ptr1); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + if (r2) { + throw takeObject(r1); + } + return WasmHtmlExtraction.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +const WasmHtmlExtractionFinalization = + typeof FinalizationRegistry === "undefined" + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry((ptr) => wasm.__wbg_wasmhtmlextraction_free(ptr >>> 0, 1)); +/** + * Result of HTML extraction with inline images + */ +export class WasmHtmlExtraction { + static __wrap(ptr) { + ptr = ptr >>> 0; + const obj = Object.create(WasmHtmlExtraction.prototype); + obj.__wbg_ptr = ptr; + WasmHtmlExtractionFinalization.register(obj, obj.__wbg_ptr, obj); + return obj; + } + + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + WasmHtmlExtractionFinalization.unregister(this); + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_wasmhtmlextraction_free(ptr, 0); + } + /** + * @returns {string} + */ + get markdown() { + let deferred1_0; + let deferred1_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.wasmhtmlextraction_markdown(retptr, this.__wbg_ptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + deferred1_0 = r0; + deferred1_1 = r1; + return getStringFromWasm0(r0, r1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export_3(deferred1_0, deferred1_1, 1); + } + } + /** + * @returns {WasmInlineImage[]} + */ + get inlineImages() { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.wasmhtmlextraction_inlineImages(retptr, this.__wbg_ptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var v1 = getArrayJsValueFromWasm0(r0, r1).slice(); + wasm.__wbindgen_export_3(r0, r1 * 4, 4); + return v1; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @returns {WasmInlineImageWarning[]} + */ + get warnings() { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.wasmhtmlextraction_warnings(retptr, this.__wbg_ptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var v1 = getArrayJsValueFromWasm0(r0, r1).slice(); + wasm.__wbindgen_export_3(r0, r1 * 4, 4); + return v1; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } +} +if (Symbol.dispose) WasmHtmlExtraction.prototype[Symbol.dispose] = WasmHtmlExtraction.prototype.free; + +const WasmInlineImageFinalization = + typeof FinalizationRegistry === "undefined" + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry((ptr) => wasm.__wbg_wasminlineimage_free(ptr >>> 0, 1)); +/** + * Inline image data + */ +export class WasmInlineImage { + static __wrap(ptr) { + ptr = ptr >>> 0; + const obj = Object.create(WasmInlineImage.prototype); + obj.__wbg_ptr = ptr; + WasmInlineImageFinalization.register(obj, obj.__wbg_ptr, obj); + return obj; + } + + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + WasmInlineImageFinalization.unregister(this); + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_wasminlineimage_free(ptr, 0); + } + /** + * @returns {Uint8Array} + */ + get data() { + const ret = wasm.wasminlineimage_data(this.__wbg_ptr); + return takeObject(ret); + } + /** + * @returns {string} + */ + get format() { + let deferred1_0; + let deferred1_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.wasminlineimage_format(retptr, this.__wbg_ptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + deferred1_0 = r0; + deferred1_1 = r1; + return getStringFromWasm0(r0, r1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export_3(deferred1_0, deferred1_1, 1); + } + } + /** + * @returns {string | undefined} + */ + get filename() { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.wasminlineimage_filename(retptr, this.__wbg_ptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + let v1; + if (r0 !== 0) { + v1 = getStringFromWasm0(r0, r1).slice(); + wasm.__wbindgen_export_3(r0, r1 * 1, 1); + } + return v1; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @returns {string | undefined} + */ + get description() { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.wasminlineimage_description(retptr, this.__wbg_ptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + let v1; + if (r0 !== 0) { + v1 = getStringFromWasm0(r0, r1).slice(); + wasm.__wbindgen_export_3(r0, r1 * 1, 1); + } + return v1; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @returns {Uint32Array | undefined} + */ + get dimensions() { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.wasminlineimage_dimensions(retptr, this.__wbg_ptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + let v1; + if (r0 !== 0) { + v1 = getArrayU32FromWasm0(r0, r1).slice(); + wasm.__wbindgen_export_3(r0, r1 * 4, 4); + } + return v1; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @returns {string} + */ + get source() { + let deferred1_0; + let deferred1_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.wasminlineimage_source(retptr, this.__wbg_ptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + deferred1_0 = r0; + deferred1_1 = r1; + return getStringFromWasm0(r0, r1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export_3(deferred1_0, deferred1_1, 1); + } + } + /** + * @returns {any} + */ + get attributes() { + const ret = wasm.wasminlineimage_attributes(this.__wbg_ptr); + return takeObject(ret); + } +} +if (Symbol.dispose) WasmInlineImage.prototype[Symbol.dispose] = WasmInlineImage.prototype.free; + +const WasmInlineImageConfigFinalization = + typeof FinalizationRegistry === "undefined" + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry((ptr) => wasm.__wbg_wasminlineimageconfig_free(ptr >>> 0, 1)); +/** + * Inline image configuration + */ +export class WasmInlineImageConfig { + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + WasmInlineImageConfigFinalization.unregister(this); + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_wasminlineimageconfig_free(ptr, 0); + } + /** + * @param {number | null} [max_decoded_size_bytes] + */ + constructor(max_decoded_size_bytes) { + const ret = wasm.wasminlineimageconfig_new( + !isLikeNone(max_decoded_size_bytes), + isLikeNone(max_decoded_size_bytes) ? 0 : max_decoded_size_bytes, + ); + this.__wbg_ptr = ret >>> 0; + WasmInlineImageConfigFinalization.register(this, this.__wbg_ptr, this); + return this; + } + /** + * @param {string | null} [prefix] + */ + set filenamePrefix(prefix) { + var ptr0 = isLikeNone(prefix) ? 0 : passStringToWasm0(prefix, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + var len0 = WASM_VECTOR_LEN; + wasm.wasminlineimageconfig_set_filenamePrefix(this.__wbg_ptr, ptr0, len0); + } + /** + * @param {boolean} capture + */ + set captureSvg(capture) { + wasm.wasminlineimageconfig_set_captureSvg(this.__wbg_ptr, capture); + } + /** + * @param {boolean} infer + */ + set inferDimensions(infer) { + wasm.wasminlineimageconfig_set_inferDimensions(this.__wbg_ptr, infer); + } +} +if (Symbol.dispose) WasmInlineImageConfig.prototype[Symbol.dispose] = WasmInlineImageConfig.prototype.free; + +const WasmInlineImageWarningFinalization = + typeof FinalizationRegistry === "undefined" + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry((ptr) => wasm.__wbg_wasminlineimagewarning_free(ptr >>> 0, 1)); +/** + * Warning about inline image processing + */ +export class WasmInlineImageWarning { + static __wrap(ptr) { + ptr = ptr >>> 0; + const obj = Object.create(WasmInlineImageWarning.prototype); + obj.__wbg_ptr = ptr; + WasmInlineImageWarningFinalization.register(obj, obj.__wbg_ptr, obj); + return obj; + } + + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + WasmInlineImageWarningFinalization.unregister(this); + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_wasminlineimagewarning_free(ptr, 0); + } + /** + * @returns {number} + */ + get index() { + const ret = wasm.wasminlineimagewarning_index(this.__wbg_ptr); + return ret >>> 0; + } + /** + * @returns {string} + */ + get message() { + let deferred1_0; + let deferred1_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.wasminlineimagewarning_message(retptr, this.__wbg_ptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + deferred1_0 = r0; + deferred1_1 = r1; + return getStringFromWasm0(r0, r1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export_3(deferred1_0, deferred1_1, 1); + } + } +} +if (Symbol.dispose) WasmInlineImageWarning.prototype[Symbol.dispose] = WasmInlineImageWarning.prototype.free; + +const EXPECTED_RESPONSE_TYPES = new Set(["basic", "cors", "default"]); + +async function __wbg_load(module, imports) { + if (typeof Response === "function" && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === "function") { + try { + return await WebAssembly.instantiateStreaming(module, imports); + } catch (e) { + const validResponse = module.ok && EXPECTED_RESPONSE_TYPES.has(module.type); + + if (validResponse && module.headers.get("Content-Type") !== "application/wasm") { + console.warn( + "`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", + e, + ); + } else { + throw e; + } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + } else { + return instance; + } + } +} + +function __wbg_get_imports() { + const imports = {}; + imports.wbg = {}; + imports.wbg.__wbg_Error_e17e777aac105295 = (arg0, arg1) => { + const ret = Error(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_Number_998bea33bd87c3e0 = (arg0) => { + const ret = Number(getObject(arg0)); + return ret; + }; + imports.wbg.__wbg_String_8f0eb39a4a4c2f66 = (arg0, arg1) => { + const ret = String(getObject(arg1)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbg_call_13410aac570ffff7 = () => + handleError((arg0, arg1) => { + const ret = getObject(arg0).call(getObject(arg1)); + return addHeapObject(ret); + }, arguments); + imports.wbg.__wbg_codePointAt_6ee161d941249514 = (arg0, arg1) => { + const ret = getObject(arg0).codePointAt(arg1 >>> 0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_done_75ed0ee6dd243d9d = (arg0) => { + const ret = getObject(arg0).done; + return ret; + }; + imports.wbg.__wbg_entries_2be2f15bd5554996 = (arg0) => { + const ret = Object.entries(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = (arg0, arg1) => { + let deferred0_0; + let deferred0_1; + try { + deferred0_0 = arg0; + deferred0_1 = arg1; + console.error(getStringFromWasm0(arg0, arg1)); + } finally { + wasm.__wbindgen_export_3(deferred0_0, deferred0_1, 1); + } + }; + imports.wbg.__wbg_get_0da715ceaecea5c8 = (arg0, arg1) => { + const ret = getObject(arg0)[arg1 >>> 0]; + return addHeapObject(ret); + }; + imports.wbg.__wbg_get_458e874b43b18b25 = () => + handleError((arg0, arg1) => { + const ret = Reflect.get(getObject(arg0), getObject(arg1)); + return addHeapObject(ret); + }, arguments); + imports.wbg.__wbg_getwithrefkey_1dc361bd10053bfe = (arg0, arg1) => { + const ret = getObject(arg0)[getObject(arg1)]; + return addHeapObject(ret); + }; + imports.wbg.__wbg_instanceof_ArrayBuffer_67f3012529f6a2dd = (arg0) => { + let result; + try { + result = getObject(arg0) instanceof ArrayBuffer; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_instanceof_Uint8Array_9a8378d955933db7 = (arg0) => { + let result; + try { + result = getObject(arg0) instanceof Uint8Array; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_isArray_030cce220591fb41 = (arg0) => { + const ret = Array.isArray(getObject(arg0)); + return ret; + }; + imports.wbg.__wbg_isSafeInteger_1c0d1af5542e102a = (arg0) => { + const ret = Number.isSafeInteger(getObject(arg0)); + return ret; + }; + imports.wbg.__wbg_iterator_f370b34483c71a1c = () => { + const ret = Symbol.iterator; + return addHeapObject(ret); + }; + imports.wbg.__wbg_length_186546c51cd61acd = (arg0) => { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_length_6bb7e81f9d7713e4 = (arg0) => { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_length_9d771c54845e987f = (arg0) => { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_new_19c25a3f2fa63a02 = () => { + const ret = new Object(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_2ff1f68f3676ea53 = () => { + const ret = new Map(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_638ebfaedbf32a5e = (arg0) => { + const ret = new Uint8Array(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = () => { + const ret = new Error(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_newfromslice_074c56947bd43469 = (arg0, arg1) => { + const ret = new Uint8Array(getArrayU8FromWasm0(arg0, arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_next_5b3530e612fde77d = (arg0) => { + const ret = getObject(arg0).next; + return addHeapObject(ret); + }; + imports.wbg.__wbg_next_692e82279131b03c = () => + handleError((arg0) => { + const ret = getObject(arg0).next(); + return addHeapObject(ret); + }, arguments); + imports.wbg.__wbg_prototypesetcall_3d4a26c1ed734349 = (arg0, arg1, arg2) => { + Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), getObject(arg2)); + }; + imports.wbg.__wbg_set_3f1d0b984ed272ed = (arg0, arg1, arg2) => { + getObject(arg0)[takeObject(arg1)] = takeObject(arg2); + }; + imports.wbg.__wbg_set_b7f1cf4fae26fe2a = (arg0, arg1, arg2) => { + const ret = getObject(arg0).set(getObject(arg1), getObject(arg2)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = (arg0, arg1) => { + const ret = getObject(arg1).stack; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbg_value_dd9372230531eade = (arg0) => { + const ret = getObject(arg0).value; + return addHeapObject(ret); + }; + imports.wbg.__wbg_wasminlineimage_new = (arg0) => { + const ret = WasmInlineImage.__wrap(arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_wasminlineimagewarning_new = (arg0) => { + const ret = WasmInlineImageWarning.__wrap(arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_wbindgenbigintgetasi64_ac743ece6ab9bba1 = (arg0, arg1) => { + const v = getObject(arg1); + const ret = typeof v === "bigint" ? v : undefined; + getDataViewMemory0().setBigInt64(arg0 + 8 * 1, isLikeNone(ret) ? BigInt(0) : ret, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true); + }; + imports.wbg.__wbg_wbindgenbooleanget_3fe6f642c7d97746 = (arg0) => { + const v = getObject(arg0); + const ret = typeof v === "boolean" ? v : undefined; + return isLikeNone(ret) ? 0xffffff : ret ? 1 : 0; + }; + imports.wbg.__wbg_wbindgendebugstring_99ef257a3ddda34d = (arg0, arg1) => { + const ret = debugString(getObject(arg1)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbg_wbindgenin_d7a1ee10933d2d55 = (arg0, arg1) => { + const ret = getObject(arg0) in getObject(arg1); + return ret; + }; + imports.wbg.__wbg_wbindgenisbigint_ecb90cc08a5a9154 = (arg0) => { + const ret = typeof getObject(arg0) === "bigint"; + return ret; + }; + imports.wbg.__wbg_wbindgenisfunction_8cee7dce3725ae74 = (arg0) => { + const ret = typeof getObject(arg0) === "function"; + return ret; + }; + imports.wbg.__wbg_wbindgenisnull_f3037694abe4d97a = (arg0) => { + const ret = getObject(arg0) === null; + return ret; + }; + imports.wbg.__wbg_wbindgenisobject_307a53c6bd97fbf8 = (arg0) => { + const val = getObject(arg0); + const ret = typeof val === "object" && val !== null; + return ret; + }; + imports.wbg.__wbg_wbindgenisstring_d4fa939789f003b0 = (arg0) => { + const ret = typeof getObject(arg0) === "string"; + return ret; + }; + imports.wbg.__wbg_wbindgenisundefined_c4b71d073b92f3c5 = (arg0) => { + const ret = getObject(arg0) === undefined; + return ret; + }; + imports.wbg.__wbg_wbindgenjsvaleq_e6f2ad59ccae1b58 = (arg0, arg1) => { + const ret = getObject(arg0) === getObject(arg1); + return ret; + }; + imports.wbg.__wbg_wbindgenjsvallooseeq_9bec8c9be826bed1 = (arg0, arg1) => { + const ret = getObject(arg0) == getObject(arg1); + return ret; + }; + imports.wbg.__wbg_wbindgennumberget_f74b4c7525ac05cb = (arg0, arg1) => { + const obj = getObject(arg1); + const ret = typeof obj === "number" ? obj : undefined; + getDataViewMemory0().setFloat64(arg0 + 8 * 1, isLikeNone(ret) ? 0 : ret, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true); + }; + imports.wbg.__wbg_wbindgenstringget_0f16a6ddddef376f = (arg0, arg1) => { + const obj = getObject(arg1); + const ret = typeof obj === "string" ? obj : undefined; + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + var len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbg_wbindgenthrow_451ec1a8469d7eb6 = (arg0, arg1) => { + throw new Error(getStringFromWasm0(arg0, arg1)); + }; + imports.wbg.__wbindgen_cast_2241b6af4c4b2941 = (arg0, arg1) => { + // Cast intrinsic for `Ref(String) -> Externref`. + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_cast_4625c577ab2ec9ee = (arg0) => { + // Cast intrinsic for `U64 -> Externref`. + const ret = BigInt.asUintN(64, arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_object_clone_ref = (arg0) => { + const ret = getObject(arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_object_drop_ref = (arg0) => { + takeObject(arg0); + }; + + return imports; +} + +function __wbg_init_memory(imports, memory) {} + +function __wbg_finalize_init(instance, module) { + wasm = instance.exports; + __wbg_init.__wbindgen_wasm_module = module; + cachedDataViewMemory0 = null; + cachedUint32ArrayMemory0 = null; + cachedUint8ArrayMemory0 = null; + + wasm.__wbindgen_start(); + return wasm; +} + +function initSync(module) { + if (wasm !== undefined) return wasm; + + if (typeof module !== "undefined") { + if (Object.getPrototypeOf(module) === Object.prototype) { + ({ module } = module); + } else { + console.warn("using deprecated parameters for `initSync()`; pass a single object instead"); + } + } + + const imports = __wbg_get_imports(); + + __wbg_init_memory(imports); + + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + + const instance = new WebAssembly.Instance(module, imports); + + return __wbg_finalize_init(instance, module); +} + +async function __wbg_init(module_or_path) { + if (wasm !== undefined) return wasm; + + if (typeof module_or_path !== "undefined") { + if (Object.getPrototypeOf(module_or_path) === Object.prototype) { + ({ module_or_path } = module_or_path); + } else { + console.warn("using deprecated parameters for the initialization function; pass a single object instead"); + } + } + + if (typeof module_or_path === "undefined") { + module_or_path = new URL("html_to_markdown_wasm_bg.wasm", import.meta.url); + } + const imports = __wbg_get_imports(); + + if ( + typeof module_or_path === "string" || + (typeof Request === "function" && module_or_path instanceof Request) || + (typeof URL === "function" && module_or_path instanceof URL) + ) { + module_or_path = fetch(module_or_path); + } + + __wbg_init_memory(imports); + + const { instance, module } = await __wbg_load(await module_or_path, imports); + + return __wbg_finalize_init(instance, module); +} + +export { initSync }; +export default __wbg_init; diff --git a/docs/html_to_markdown_wasm_bg.wasm b/docs/html_to_markdown_wasm_bg.wasm new file mode 100644 index 00000000..560d2cb0 Binary files /dev/null and b/docs/html_to_markdown_wasm_bg.wasm differ diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 00000000..c99849a7 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,113 @@ + + + + + + HTML to Markdown Converter - Live Demo + + + + +
+
+

🚀 HTML to Markdown Converter

+

High-performance conversion powered by Rust & WebAssembly

+ +
+ +
+
+
+
+

HTML Input

+ +
+ +
+ +
+ +
+
+ +
+
+

Markdown Output

+ +
+

+                
+
+
+ +
+

+ Built with ❤️ using Rust and + WebAssembly +

+

+ Loading WASM module... +

+
+
+ + + + diff --git a/docs/script.js b/docs/script.js new file mode 100644 index 00000000..edce1d35 --- /dev/null +++ b/docs/script.js @@ -0,0 +1,139 @@ +import init, { convert } from "./html_to_markdown_wasm.js"; + +// State +let wasmInitialized = false; + +// DOM Elements +const htmlInput = document.getElementById("htmlInput"); +const markdownOutput = document.getElementById("markdownOutput"); +const convertBtn = document.getElementById("convertBtn"); +const copyBtn = document.getElementById("copyBtn"); +const clearBtn = document.getElementById("clearBtn"); +const statusEl = document.getElementById("status"); +const wasmStatusEl = document.getElementById("wasmStatus"); + +// Initialize WASM module +async function initWasm() { + try { + await init(); + wasmInitialized = true; + wasmStatusEl.textContent = "✓ WASM module loaded"; + wasmStatusEl.classList.add("loaded"); + statusEl.textContent = "Ready to convert!"; + statusEl.classList.add("success"); + + // Auto-convert the example on load + performConversion(); + } catch (error) { + console.error("Failed to initialize WASM:", error); + wasmStatusEl.textContent = "✗ Failed to load WASM"; + wasmStatusEl.classList.add("error"); + statusEl.textContent = "Error: Failed to load WASM module"; + statusEl.classList.add("error"); + convertBtn.disabled = true; + } +} + +// Perform HTML to Markdown conversion +function performConversion() { + if (!wasmInitialized) { + statusEl.textContent = "WASM module not initialized yet..."; + statusEl.className = "status"; + return; + } + + const html = htmlInput.value.trim(); + + if (!html) { + markdownOutput.textContent = ""; + statusEl.textContent = "Please enter some HTML to convert"; + statusEl.className = "status"; + return; + } + + try { + const startTime = performance.now(); + + // Convert HTML to Markdown + const markdown = convert(html, null); + + const endTime = performance.now(); + const duration = (endTime - startTime).toFixed(2); + + // Display result + markdownOutput.textContent = markdown; + statusEl.textContent = `✓ Converted in ${duration}ms`; + statusEl.className = "status success"; + + // Reset copy button if it was in copied state + copyBtn.classList.remove("copied"); + } catch (error) { + console.error("Conversion error:", error); + markdownOutput.textContent = ""; + statusEl.textContent = `Error: ${error.message}`; + statusEl.className = "status error"; + } +} + +// Copy markdown to clipboard +async function copyToClipboard() { + const markdown = markdownOutput.textContent; + + if (!markdown) { + statusEl.textContent = "Nothing to copy"; + statusEl.className = "status"; + return; + } + + try { + await navigator.clipboard.writeText(markdown); + copyBtn.classList.add("copied"); + statusEl.textContent = "✓ Copied to clipboard!"; + statusEl.className = "status success"; + + // Reset button after 2 seconds + setTimeout(() => { + copyBtn.classList.remove("copied"); + }, 2000); + } catch (error) { + console.error("Failed to copy:", error); + statusEl.textContent = "Failed to copy to clipboard"; + statusEl.className = "status error"; + } +} + +// Clear input +function clearInput() { + htmlInput.value = ""; + markdownOutput.textContent = ""; + statusEl.textContent = "Input cleared"; + statusEl.className = "status"; + htmlInput.focus(); +} + +// Event Listeners +convertBtn.addEventListener("click", performConversion); +copyBtn.addEventListener("click", copyToClipboard); +clearBtn.addEventListener("click", clearInput); + +// Convert on Enter (Ctrl/Cmd + Enter) +htmlInput.addEventListener("keydown", (e) => { + if ((e.ctrlKey || e.metaKey) && e.key === "Enter") { + e.preventDefault(); + performConversion(); + } +}); + +// Auto-convert on input (debounced) +let debounceTimer; +htmlInput.addEventListener("input", () => { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => { + if (wasmInitialized && htmlInput.value.trim()) { + performConversion(); + } + }, 500); +}); + +// Initialize on page load +initWasm(); diff --git a/docs/style.css b/docs/style.css new file mode 100644 index 00000000..8a26b8d2 --- /dev/null +++ b/docs/style.css @@ -0,0 +1,363 @@ +:root { + --primary-color: #2563eb; + --primary-hover: #1d4ed8; + --secondary-color: #64748b; + --success-color: #10b981; + --background: #ffffff; + --surface: #f8fafc; + --border: #e2e8f0; + --text-primary: #0f172a; + --text-secondary: #475569; + --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + padding: 2rem 1rem; + color: var(--text-primary); +} + +.container { + max-width: 1400px; + margin: 0 auto; + background: var(--background); + border-radius: 1rem; + box-shadow: var(--shadow-lg); + overflow: hidden; +} + +header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 3rem 2rem; + text-align: center; +} + +header h1 { + font-size: 2.5rem; + font-weight: 700; + margin-bottom: 0.5rem; +} + +.subtitle { + font-size: 1.125rem; + opacity: 0.95; + margin-bottom: 1.5rem; +} + +.links { + display: flex; + gap: 1rem; + justify-content: center; + flex-wrap: wrap; +} + +.links a { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + background: rgba(255, 255, 255, 0.2); + color: white; + text-decoration: none; + border-radius: 0.5rem; + font-weight: 500; + transition: all 0.2s; + backdrop-filter: blur(10px); +} + +.links a:hover { + background: rgba(255, 255, 255, 0.3); + transform: translateY(-2px); +} + +main { + padding: 2rem; +} + +.converter { + display: grid; + gap: 1.5rem; +} + +.section-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.section-header h2 { + font-size: 1.25rem; + font-weight: 600; + color: var(--text-primary); +} + +.input-section, +.output-section { + background: var(--surface); + padding: 1.5rem; + border-radius: 0.75rem; + border: 1px solid var(--border); +} + +textarea, +.output { + width: 100%; + min-height: 400px; + padding: 1rem; + border: 1px solid var(--border); + border-radius: 0.5rem; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace; + font-size: 0.875rem; + line-height: 1.6; + resize: vertical; + background: var(--background); + color: var(--text-primary); + transition: border-color 0.2s; +} + +textarea:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); +} + +.output { + overflow-x: auto; + white-space: pre-wrap; + word-wrap: break-word; +} + +.controls { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + padding: 1rem 0; +} + +.primary-btn { + display: inline-flex; + align-items: center; + gap: 0.75rem; + padding: 1rem 2rem; + background: var(--primary-color); + color: white; + border: none; + border-radius: 0.5rem; + font-size: 1.125rem; + font-weight: 600; + cursor: pointer; + transition: all 0.2s; + box-shadow: var(--shadow); +} + +.primary-btn:hover { + background: var(--primary-hover); + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} + +.primary-btn:active { + transform: translateY(0); +} + +.primary-btn:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; +} + +.btn-icon { + font-size: 1.5rem; + transition: transform 0.2s; +} + +.primary-btn:hover .btn-icon { + transform: translateX(4px); +} + +.secondary-btn { + padding: 0.5rem 1rem; + background: var(--background); + color: var(--text-secondary); + border: 1px solid var(--border); + border-radius: 0.375rem; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + position: relative; +} + +.secondary-btn:hover { + background: var(--surface); + border-color: var(--secondary-color); + color: var(--text-primary); +} + +#copyBtn .copied-text { + display: none; +} + +#copyBtn.copied .copy-text { + display: none; +} + +#copyBtn.copied .copied-text { + display: inline; +} + +#copyBtn.copied { + background: var(--success-color); + color: white; + border-color: var(--success-color); +} + +.status { + font-size: 0.875rem; + color: var(--text-secondary); + min-height: 1.5rem; +} + +.status.success { + color: var(--success-color); + font-weight: 500; +} + +.status.error { + color: #ef4444; + font-weight: 500; +} + +footer { + background: var(--surface); + padding: 2rem; + text-align: center; + border-top: 1px solid var(--border); + color: var(--text-secondary); +} + +footer p { + margin-bottom: 0.5rem; +} + +footer a { + color: var(--primary-color); + text-decoration: none; + font-weight: 500; +} + +footer a:hover { + text-decoration: underline; +} + +.version { + font-size: 0.875rem; + opacity: 0.8; +} + +#wasmStatus { + display: inline-block; + padding: 0.25rem 0.75rem; + background: var(--background); + border-radius: 0.25rem; + font-family: 'Monaco', 'Menlo', monospace; + font-size: 0.75rem; +} + +#wasmStatus.loaded { + color: var(--success-color); +} + +#wasmStatus.error { + color: #ef4444; +} + +/* Responsive Design */ +@media (min-width: 768px) { + body { + padding: 3rem 2rem; + } + + header h1 { + font-size: 3rem; + } + + main { + padding: 3rem; + } + + .converter { + grid-template-columns: 1fr 1fr; + grid-template-rows: auto auto; + } + + .input-section { + grid-column: 1; + grid-row: 1; + } + + .controls { + grid-column: 1 / -1; + grid-row: 2; + flex-direction: row; + justify-content: center; + } + + .output-section { + grid-column: 2; + grid-row: 1; + } +} + +/* Loading Animation */ +@keyframes pulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +.primary-btn:disabled { + animation: pulse 1.5s ease-in-out infinite; +} + +/* Scrollbar Styling */ +textarea::-webkit-scrollbar, +.output::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +textarea::-webkit-scrollbar-track, +.output::-webkit-scrollbar-track { + background: var(--surface); + border-radius: 4px; +} + +textarea::-webkit-scrollbar-thumb, +.output::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 4px; +} + +textarea::-webkit-scrollbar-thumb:hover, +.output::-webkit-scrollbar-thumb:hover { + background: var(--secondary-color); +} diff --git a/scripts/build-demo.sh b/scripts/build-demo.sh new file mode 100755 index 00000000..c0e0303d --- /dev/null +++ b/scripts/build-demo.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Script to rebuild and update the GitHub Pages demo + +set -e + +echo "🔨 Building WASM package..." +cd crates/html-to-markdown-wasm +wasm-pack build --target web --out-dir dist-web + +echo "📦 Copying files to docs/..." +cd ../.. +cp crates/html-to-markdown-wasm/dist-web/html_to_markdown_wasm.js docs/ +cp crates/html-to-markdown-wasm/dist-web/html_to_markdown_wasm_bg.wasm docs/ + +echo "✅ Demo updated successfully!" +echo "" +echo "To test locally, run:" +echo " cd docs && python3 -m http.server 8000" +echo "" +echo "Then open http://localhost:8000 in your browser"