Documentation
¶
Overview ¶
Package richtext provides utilities for converting between Markdown and HTML. It uses glamour for terminal-friendly Markdown rendering.
Index ¶
- Variables
- func AttachmentToHTML(sgid, filename, contentType string) string
- func DetectMIME(path string) string
- func EmbedAttachments(html string, attachments []AttachmentRef) string
- func HTMLToMarkdown(html string) string
- func HasComplexTableHTML(s string) bool
- func Hyperlink(text, url string) string
- func IsHTML(s string) bool
- func IsMarkdown(s string) bool
- func LinkifyMarkdownLinks(text string) string
- func LinkifyURLs(text string) string
- func MarkdownToHTML(md string) string
- func MentionToHTML(sgid, name string) string
- func NormalizeDragPath(raw string) string
- func PlainToHTML(s string) string
- func RenderMarkdown(md string) (string, error)
- func RenderMarkdownWithWidth(md string, width int) (string, error)
- func SanitizeSingleLine(s string) string
- func SanitizeTerminal(s string) string
- func ValidateFile(path string) error
- type AttachmentRef
- type EscapedAt
- type MentionLookupFunc
- type MentionResult
- type ParsedAttachment
- type PersonByIDFunc
- type TrixBreak
Constants ¶
This section is empty.
Variables ¶
var ErrMentionSkip = errors.New("mention skip")
ErrMentionSkip is a sentinel error that lookup functions can return to indicate that a fuzzy @Name mention should be left as plain text instead of failing the entire operation. Use this for recoverable errors like not-found or ambiguous.
var KindEscapedAt = ast.NewNodeKind("EscapedAt")
KindEscapedAt is the node kind for EscapedAt.
var KindTrixBreak = ast.NewNodeKind("TrixBreak")
KindTrixBreak is the node kind for TrixBreak.
Functions ¶
func AttachmentToHTML ¶
AttachmentToHTML builds a <bc-attachment> tag for embedding in Trix-compatible HTML.
func DetectMIME ¶
DetectMIME returns the MIME type for a file path. It uses the extension map first, then falls back to reading file header bytes.
func EmbedAttachments ¶
func EmbedAttachments(html string, attachments []AttachmentRef) string
EmbedAttachments appends <bc-attachment> tags to HTML content. Each attachment is added as a separate block after the main content.
func HTMLToMarkdown ¶
HTMLToMarkdown converts HTML content to Markdown. This is useful for displaying Basecamp's rich text content in the terminal.
func HasComplexTableHTML ¶ added in v0.10.0
HasComplexTableHTML reports whether s contains a table that HTMLToMarkdown cannot round-trip as a GFM pipe table: merged cells (colspan/rowspan), a nested table, block content inside a cell, or the table itself nested in a blockquote or list. The TUI in-place editors use this to gate edits — HTMLToMarkdown still converts such tables for display, best-effort, but an edit-and-resubmit would flatten the structure, so those edits fail closed. Simple grids round-trip cleanly and stay editable.
func Hyperlink ¶ added in v0.3.0
Hyperlink wraps text in an OSC 8 terminal hyperlink sequence. Returns text unchanged when url is empty. The URL is sanitized to strip control characters that could break out of the OSC 8 sequence or inject terminal commands.
func IsHTML ¶
IsHTML attempts to detect if the input string contains HTML. Only returns true for well-formed HTML with common content tags. Does not detect arbitrary tags like <script> to prevent XSS passthrough. Tags inside Markdown code spans (`...`) and fenced code blocks (```) are ignored.
func IsMarkdown ¶
IsMarkdown attempts to detect if the input string is Markdown rather than plain text or HTML. This is a heuristic and may not be 100% accurate.
func LinkifyMarkdownLinks ¶ added in v0.3.0
LinkifyMarkdownLinks converts markdown-style text(url) links to OSC 8 terminal hyperlinks where the link text is clickable. Use this for rendering paths that bypass glamour (e.g., chat).
func LinkifyURLs ¶ added in v0.3.0
LinkifyURLs wraps bare URLs in OSC 8 hyperlink sequences. URLs already inside an OSC 8 sequence are not double-wrapped.
func MarkdownToHTML ¶
MarkdownToHTML converts Markdown text to HTML suitable for Basecamp's rich text fields. It uses goldmark with custom AST transformations for Trix editor compatibility. If the input already appears to be HTML, it is passed through with existing formatting preserved, except that a separator is inserted between directly adjacent paragraph blocks (see insertParagraphSeparators).
func MentionToHTML ¶ added in v0.7.0
MentionToHTML builds a <bc-attachment> mention tag.
func NormalizeDragPath ¶ added in v0.3.0
NormalizeDragPath normalizes a pasted/dragged path into a filesystem path. It handles quoted paths, file:// URLs, shell-escaped characters, and tilde expansion. Only inputs that look like filesystem paths (absolute, ~/, file://, or quoted versions of these) are transformed; other inputs are returned unchanged. Returns empty for empty input.
This targets macOS and Linux terminals only. It compiles on Windows to avoid breaking cross-compilation, but Windows drag-and-drop (e.g. file:// URLs with drive letters) is not supported or tested.
func PlainToHTML ¶ added in v0.8.0
PlainToHTML serializes literal plain text as Basecamp rich text. HTML-special characters are escaped so they render as typed (not interpreted as markup), and the line structure is kept in the one shape Basecamp's editor preserves across an edit: each run of non-blank lines becomes a <p> with single line breaks as <br> between its lines, and each blank line between runs becomes an empty paragraph (paragraphSeparator).
The editor drops a root-level <br> on import and keeps a <br> only when it sits between two text runs inside a block, so neither bare <br> between lines nor <br><br> for a blank line survives the first edit in Basecamp. Leading and trailing blank lines are dropped — they have no paragraphs to separate — matching the Markdown path; a whitespace-only line counts as blank. Windows CRLF and bare CR are normalized to LF first. Use this when the caller wants the text delivered verbatim to an endpoint that always stores rich text.
func RenderMarkdown ¶
RenderMarkdown renders Markdown for terminal display using glamour. It returns styled output suitable for CLI display.
func RenderMarkdownWithWidth ¶
RenderMarkdownWithWidth renders Markdown for terminal display with a custom width.
func SanitizeSingleLine ¶ added in v0.8.0
SanitizeSingleLine sanitizes API-controlled text for a single-line terminal sink. It normalizes CR/CRLF to newlines, strips terminal escape sequences and control characters via SanitizeTerminal, then collapses all remaining whitespace (newlines, tabs, runs of spaces) to single spaces so the value occupies exactly one line. Bare CR between words becomes a space separator rather than gluing words together, and embedded newlines/tabs can no longer break a single-line layout. This mirrors the canonical ordering used by output.sanitizeText with singleLine=true. All-whitespace or all-control input collapses to the empty string.
func SanitizeTerminal ¶ added in v0.8.0
SanitizeTerminal removes terminal escape sequences and control characters from API-controlled text before it reaches a terminal sink.
ansi.Strip alone is not enough: it only removes ESC-prefixed (7-bit) sequences and preserves UTF-8-encoded Unicode C1 controls — U+009B (CSI), U+009D (OSC), U+0090 (DCS), and the rest of U+0080–U+009F — plus DEL and bare C0 bytes like BEL and CR. Terminals that honor 8-bit C1 controls execute those directly, so a name containing U+009B (CSI) followed by "31m" would still restyle or inject after ansi.Strip. After stripping ESC sequences, this drops every remaining C0 control (except newline and tab), DEL, and the C1 range.
func ValidateFile ¶
ValidateFile checks that a path refers to an existing, regular, readable file within the size limit. Returns nil on success.
Types ¶
type AttachmentRef ¶
AttachmentRef holds the metadata needed to embed a <bc-attachment> in HTML.
type EscapedAt ¶ added in v0.8.0
type EscapedAt struct{ ast.BaseInline }
EscapedAt is a custom inline node that renders as literal \@.
type MentionLookupFunc ¶ added in v0.7.0
MentionLookupFunc resolves a name to an attachable SGID and display name.
type MentionResult ¶ added in v0.7.0
MentionResult holds the resolved HTML and any mentions that could not be resolved.
func ResolveMentions ¶ added in v0.7.0
func ResolveMentions(html string, lookup MentionLookupFunc, lookupByID PersonByIDFunc) (MentionResult, error)
ResolveMentions processes mention syntax in HTML in four passes:
- Markdown mention anchors: <a href="mention:SGID">@Name</a> and <a href="person:ID">@Name</a>
- Literal Markdown mention links [@Name](mention:SGID) / [@Name](person:ID) that were never converted to anchors (e.g. embedded inside an author-supplied HTML block)
- Inline @sgid:VALUE syntax
- Fuzzy @Name and @First.Last patterns
Each pass replaces matches with <bc-attachment> tags. Subsequent passes skip regions already converted by earlier passes via the mention exclusion index.
lookupByID may be nil if person:ID syntax is not needed; encountering any person:ID syntax with a nil lookupByID returns an error.
type ParsedAttachment ¶ added in v0.7.0
type ParsedAttachment struct {
SGID string `json:"sgid,omitempty"`
Filename string `json:"filename,omitempty"`
ContentType string `json:"content_type,omitempty"`
Filesize string `json:"filesize,omitempty"`
URL string `json:"url,omitempty"`
Href string `json:"href,omitempty"`
Width string `json:"width,omitempty"`
Height string `json:"height,omitempty"`
Caption string `json:"caption,omitempty"`
}
ParsedAttachment holds metadata extracted from a <bc-attachment> tag in HTML content.
func ParseAttachments ¶ added in v0.7.0
func ParseAttachments(content string) []ParsedAttachment
ParseAttachments extracts file attachment metadata from HTML content. It finds all <bc-attachment> tags and returns their metadata, excluding mention attachments (content-type="application/vnd.basecamp.mention").
func (*ParsedAttachment) DisplayName ¶ added in v0.7.0
func (a *ParsedAttachment) DisplayName() string
DisplayName returns the best display name: caption, then filename, then fallback.
func (*ParsedAttachment) DisplayURL ¶ added in v0.7.0
func (a *ParsedAttachment) DisplayURL() string
DisplayURL returns the best available URL for the attachment. Href is preferred because it points at the real blob download endpoint (storage.3.basecamp.com/.../download/<filename>). URL points at the preview endpoint (preview.3.basecamp.com/.../previews/full), which for non-image content types returns a generic SVG file-type icon instead of the real file. Every internal caller is a download path, so preferring Href is correct; URL is retained as a fallback for the rare case where an attachment has no downloadable blob (e.g. externally hosted images).
func (*ParsedAttachment) IsImage ¶ added in v0.7.0
func (a *ParsedAttachment) IsImage() bool
IsImage returns true if the attachment has an image content type.
type PersonByIDFunc ¶ added in v0.7.0
PersonByIDFunc resolves a person ID to an attachable SGID and canonical name. Used by the person:ID mention syntax.