//! The Askama entry point for a described markdown field. //! //! Shape 5 of the conversion plan (wiki `mnw-shape-conversion-plans`), and the //! narrowest of them: one member, [`FieldKind::Rich`], a field whose value is //! markdown. Autosave and section reordering stay hand-written by ruling, so //! what moves here is the editor surface and nothing around it. //! //! Four templates declared a markdown textarea by hand and one of them wrapped //! it in a Write/Preview pair it drew itself. All four call this instead, so //! the box, the pair and the pane come out of the renderer that already ships //! the CSS for them: `makeover_webview::form` emits the group, and //! `static/layout.css` carries its `[data-format="markdown"]` rules because //! `build.rs` generates that file from the same crate. //! //! # Why the id is built out of a prefix rather than taken whole //! //! `Filling::id_for` is `-`, and every one of the four ids these //! templates already had reads that way: `text-body`, `post-body`, //! `new-psec-body`, `edit-psec-body`, `new-section-body`. So the field is named //! `body` on all five, which is what the value is, and the surface supplies the //! prefix, which is what makes it unique in a document holding two of them. //! //! That is not a coincidence being leaned on. It is what keeps the conversion //! from breaking the thing that reaches these controls by id from outside: each //! surface's own script reads its value back with `getElementById`, and the id //! is the same string it was. //! //! The described picker addresses its destination by [`Field::name`] rather //! than by a textarea id, which is what makes [`named`] necessary; see its own //! note. //! //! # Why height is the app's and not the description's //! //! The description says nothing about how tall a control is, deliberately, and //! the four surfaces did not agree anyway: 400px of CSS on the item editor, //! `rows="20"` on the blog editor, `rows="10"` twice in project settings and //! `rows="6"` in the wizard. So the fact stays here, as [`Height`], and lands //! as a class on a wrapper this module owns. Three sizes rather than four //! because the item editor and the blog editor were already the same surface at //! two spellings. use makeover_layout::{Field, FieldKind}; use makeover_webview::{ Emit, form::{Filling, Value, field_html}, }; /// How much room the editor takes before the reader drags it. /// /// A markdown editor is one surface, so the sizes are named for the room they /// are given rather than for the screen that gives it. The classes are matched /// in `css/60-interaction.css`, under `RICH FIELD`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Height { /// A few lines, for an editor behind a disclosure the reader opened to add /// one row. The wizard's Add Section. Compact, /// A screenful, for an editor that is one field among several. Standard, /// The page's main work surface, for a screen that exists to write prose. Tall, } impl Height { /// The wrapper class this size lands as. const fn class(self) -> &'static str { match self { Self::Compact => "rich-field rich-field--compact", Self::Standard => "rich-field rich-field--standard", Self::Tall => "rich-field rich-field--tall", } } } /// The markup, for an Askama template to drop in. /// /// `prefix` scopes the control's `id` and must be unique in the document; /// `label` and `placeholder` are the question and its ghost text; `value` is /// what the field holds now, escaped by the emitter rather than by the caller. /// /// The wrapper is this app's and the group inside it is the renderer's, the /// same division `widgets::carousel` makes for the same reason: a class this /// host prefixes with nothing and a description that has no idea it exists. #[must_use] pub fn html(prefix: &str, label: &str, placeholder: &str, value: &str, height: Height) -> String { named(Some(prefix), "body", label, placeholder, value, height) } /// The same editor, naming the value itself. /// /// The rule above ("the name is what the value is, and the id is what tells two /// editors apart") holds while nothing has to *address* an editor. /// [`Act::fills`](quasi_router::Act::fills) does: a picker names the /// box it deposits into by [`Field::name`], because a name is what identifies a /// field within a description. /// /// So a document holding two editors under one name has one addressable editor /// and one unreachable one. `item_details.html` is the measured case and was /// worse than that: its two section bodies were raw `", Height::Tall, ); assert!(!html.contains("