howdy/ui/rich_text/document

A rich text document: what howdy/ui/rich_text edits, as Gleam values.

The editor posts its document as JSON. Decoding that JSON is also the safety check: anything that is not a paragraph, heading, quote, list, code block, divider, mention or attachment, or a link that is not http, https, mailto, tel or a path on your site, is refused. Store the document with to_string, and show it with view, which renders through Lustre, so its text is always escaped.

use body <- result.try(document.decode(form.value(fields, "body")))
use person <- list.each(document.mentions(body))
// Check `person.id` is someone this user may mention, then tell them.
save(document.to_string(body))

// Later, drawing mentions from what is true now, not when it was saved:
document.view_with(
  body,
  mention: fn(mention) {
    document.mention_view(mention, href: Some("/people/" <> mention.id))
  },
  attachment: fn(file) {
    document.attachment_view(file, url: Some(files.url(file.key)))
  },
)

Mentions and attachments keep a label, a name and a size from the moment they were added. Like Rails’ Action Text, render them from your own records when you show the document, so a renamed person or a deleted file shows as it is now.

Types

A file stored under key, as your upload endpoint named it. The name, type and size are what the browser said when it was added.

pub type Attachment {
  Attachment(
    key: String,
    name: String,
    content_type: String,
    size: Int,
  )
}

Constructors

  • Attachment(
      key: String,
      name: String,
      content_type: String,
      size: Int,
    )
pub type Block {
  Paragraph(content: List(Inline))
  Heading(level: Int, content: List(Inline))
  Quote(blocks: List(Block))
  BulletList(items: List(List(Block)))
  NumberedList(start: Int, items: List(List(Block)))
  CodeBlock(code: String)
  Divider
  Attached(attachment: Attachment)
}

Constructors

  • Paragraph(content: List(Inline))
  • Heading(level: Int, content: List(Inline))

    A heading from 1, the largest, to 6.

  • Quote(blocks: List(Block))
  • BulletList(items: List(List(Block)))

    A list of items, each made of blocks. An item starts with a paragraph and may hold more lists.

  • NumberedList(start: Int, items: List(List(Block)))
  • CodeBlock(code: String)
  • Divider

    A horizontal rule.

  • Attached(attachment: Attachment)

    A file, such as an image, sent to storage as the document was written.

A whole document: its blocks, in order.

pub type Document {
  Document(blocks: List(Block))
}

Constructors

  • Document(blocks: List(Block))
pub type Inline {
  Text(text: String, marks: List(Mark))
  Mentioned(mention: Mention)
  LineBreak
}

Constructors

  • Text(text: String, marks: List(Mark))
  • Mentioned(mention: Mention)
  • LineBreak

    A line break within a paragraph, from Shift+Enter.

pub type Mark {
  Bold
  Italic
  Strike
  Code
  Link(href: String)
}

Constructors

  • Bold
  • Italic
  • Strike
  • Code
  • Link(href: String)

Someone or something mentioned, such as @Ada Lovelace or #42. kind and id are what your search endpoint gave the editor; label is the text it showed then.

pub type Mention {
  Mention(
    trigger: String,
    kind: String,
    id: String,
    label: String,
  )
}

Constructors

  • Mention(trigger: String, kind: String, id: String, label: String)

Values

pub fn attachment_view(
  attachment: Attachment,
  url url: option.Option(String),
) -> element.Element(msg)

An attachment as a picture, for an image with a url, or otherwise as a card with its name and size, which links to the file if there is a url.

pub fn attachments(document: Document) -> List(Attachment)

Every attached file, in order. Check each key is one your app handed to this user before you keep it.

pub fn classes() -> List(css.Class)

Every class this module uses, for howdy/ui/export.

pub fn decode(json_text: String) -> Result(Document, String)

Read a document from the JSON the editor posts, refusing anything it would not have made. The error says what was wrong, for logs; it is not for people.

pub fn decoder() -> decode.Decoder(Document)

The decoder decode uses, for a document inside other JSON.

pub fn document_class() -> css.Class

What a document adds to prose.

pub fn elements(
  document: Document,
  mention mention: fn(Mention) -> element.Element(msg),
  attachment attachment: fn(Attachment) -> element.Element(msg),
) -> List(element.Element(msg))

A document’s elements, without the prose around them, for a container of your own.

pub fn empty() -> Document

A document with nothing in it.

pub fn figure_class() -> css.Class
pub fn file_link_class() -> css.Class
pub fn from_text(text: String) -> Document

A document from plain text: a paragraph for each run of lines separated by a blank line, keeping single line breaks. For bringing existing text columns into rich text.

pub fn is_empty(document: Document) -> Bool

Whether a document has nothing worth keeping: no text but spaces, no mentions, attachments or dividers. A required editor refuses to submit such a document; check with this on the server too.

pub fn is_safe_link(href: String) -> Bool

Whether a link may go in a document: an http, https, mailto or tel address, or a path, query or fragment on the same site. Anything else, such as javascript:, is refused, as are spaces and control characters, which browsers strip from a scheme before reading it.

pub fn mention_class() -> css.Class
pub fn mention_view(
  mention: Mention,
  href href: option.Option(String),
) -> element.Element(msg)

A mention as a chip, such as @Ada Lovelace, linking to href if given.

pub fn mentions(document: Document) -> List(Mention)

Everyone and everything mentioned, in the order they first appear, each once. Check each against your own records before acting on it: the document came from a browser.

pub fn to_json(document: Document) -> json.Json

A document as JSON, in the shape the editor reads and posts.

pub fn to_plain_text(document: Document) -> String

The text of a document, for search, previews, notifications and email: paragraphs separated by blank lines, list items marked - or 1., quotes with >, mentions as @Ada Lovelace and attachments as their names in brackets.

pub fn to_string(document: Document) -> String

A document as a JSON string, for storing.

pub fn view(document: Document) -> element.Element(msg)

A document as prose, with mentions as chips and attachments as cards without links. Use view_with to link them to your own pages and files.

pub fn view_with(
  document: Document,
  mention mention: fn(Mention) -> element.Element(msg),
  attachment attachment: fn(Attachment) -> element.Element(msg),
) -> element.Element(msg)

A document as prose, drawing each mention and attachment with the functions given, such as from your current records.

✨ Search Document