devup_editor_html/lib.rs
1// The clipboard port mirrors the React `clipboardHtml.ts` module
2// structure deliberately; several pedantic lints fire on idiomatic
3// patterns (boolean-heavy mark sets, identical match arms for text
4// node dispatch, explicit closures that document intent better than
5// bare fn references, etc.). We allow them at the crate level so the
6// port stays side-by-side comparable with the upstream TS.
7//
8// This list has been aggressively trimmed. Each remaining allow has a
9// justification in this comment block:
10//
11// - `struct_excessive_bools`: `MarkSet` carries 5 booleans for the 5
12// boolean marks. Refactoring into a bitflags struct would obscure the
13// 1:1 correspondence with the TS mark names.
14// - `too_many_lines`: `visit_block` is one long dispatch `match` — each
15// tag handler is short but there are many of them.
16// - `module_name_repetitions`: `HtmlError` alongside the `html` module.
17// - `doc_markdown`: English prose in doc comments contains backtick-free
18// references (Notion, Word) that pedantic lint flags as missing code
19// fences.
20// - `match_same_arms`: `handle_dispatch` has multiple arms that return
21// the same paragraph fallback for distinct tag names — explicit for
22// readability.
23// - `similar_names`: `open_idx` / `close_idx` are the established naming
24// from the TS source; renaming loses parity.
25// - `missing_errors_doc` / `missing_panics_doc`: all public errors are
26// documented on the `HtmlError` enum; per-fn boilerplate is noise.
27// - `must_use_candidate`: every public function here has semantics where
28// ignoring the return would be ambiguous (debug logging, side-channel
29// validation in tests). `#[must_use]` is applied selectively where it
30// adds value.
31#![allow(
32 clippy::struct_excessive_bools,
33 clippy::too_many_lines,
34 clippy::module_name_repetitions,
35 clippy::doc_markdown,
36 clippy::match_same_arms,
37 clippy::similar_names,
38 clippy::missing_errors_doc,
39 clippy::missing_panics_doc,
40 clippy::must_use_candidate
41)]
42
43//! HTML ↔ [`devup_editor_core::Document`] conversion for the devup editor.
44//!
45//! Implements [`DocumentExport`] and [`DocumentImport`] from
46//! `devup-editor-core`, plus a clipboard-oriented API
47//! ([`blocks_to_html`] / [`html_to_copied_blocks`]) that mirrors the
48//! behaviour of the React TypeScript `clipboardHtml.ts` exactly so
49//! clipboard interop with Word / Notion / Google Docs keeps working.
50//!
51//! ## Structural support
52//!
53//! Export and import both cover:
54//! - Headings (h1–h6)
55//! - Paragraphs (p)
56//! - Quotes (blockquote)
57//! - Pre / code (language class preserved)
58//! - Unordered & ordered lists (with nested indent tracking)
59//! - Todo lists (via `<ul data-devup-type="todo">` marker OR
60//! `<input type="checkbox">` heuristic OR Notion `<ul class="to-do-list">`)
61//! - Dividers (hr)
62//! - Toggle blocks: `<details><summary>`, Notion `<ul class="toggle">`,
63//! plus Notion v3's `<li>`-with-multiple-block-children heuristic
64//! - Tables: `colspan`, `rowspan`, `<colgroup>` widths, cell / row /
65//! table styling via `data-devup-props` marker and inline `style=""`
66//!
67//! Marks on export wrap text in `<strong>`, `<em>`, `<u>`, `<s>`,
68//! `<code>`, `<a href="…" rel="noopener noreferrer">`, and
69//! `<span style="color:…">` / `<span style="background-color:…">` for
70//! color / highlight. Unknown mark types survive via a
71//! `<span data-mark="type">` fallback so copy never silently drops text.
72//!
73//! ## Clipboard-specific features
74//!
75//! The [`clipboard`](crate::clipboard) module provides:
76//! - [`encode_props`] / [`decode_props`] — base64+JSON round-trip marker
77//! (`data-devup-props="…"`) for lossless devup→devup copy/paste of
78//! table prop maps.
79//! - [`clean_html`] — strips Word / HWP preprocessing artifacts
80//! (`<!--StartFragment-->`, `<o:p>` tags) before parsing.
81//!
82//! See [`CopiedBlocks`] for the clipboard subtree shape used by the
83//! React layer.
84
85mod clipboard;
86mod export;
87mod import;
88mod slice;
89
90pub use clipboard::{
91 CopiedBlocks, clean_html, decode_props, encode_props, looks_like_xml, strip_xml_prolog,
92};
93pub use export::{Html, blocks_to_html, copied_blocks_to_html, document_to_copied_blocks};
94pub use import::html_to_copied_blocks;
95pub use slice::slice_content;
96
97/// Test-only re-export of the internal `is_safe_href` helper so the
98/// parity integration test (`tests/href_parity.rs`) can reach it. Not
99/// part of the stable public API — production callers should use the
100/// link-aware serialisation via `blocks_to_html` instead.
101#[doc(hidden)]
102pub fn __test_is_safe_href(href: &str) -> bool {
103 export::is_safe_href(href)
104}
105
106use thiserror::Error;
107
108#[derive(Debug, Error)]
109pub enum HtmlError {
110 #[error("html parse error: {0}")]
111 Parse(String),
112 #[error("html serialize error: {0}")]
113 Serialize(String),
114}