Macro Utility Library
The global hai library gives macros access to application context, document
content, and notifications. It exposes two namespaces — app and doc — and
a DirectiveObject type returned by most doc methods.
Syntax:
const {app, doc} = hai
Quick Reference
hai.app
| Member | Kind | Description |
|---|---|---|
notify | Method | Displays a notification to the user |
hai.doc
| Member | Kind | Description |
|---|---|---|
docTitle | Property | The document title |
docContent | Property | The document content |
getTitle | Method | Gets the document title |
setTitle | Method | Sets the document title |
getContent | Method | Gets the document content |
setContent | Method | Replaces the document content |
appendContent | Method | Appends content to the end of the document |
prependContent | Method | Inserts content at the start of the document |
copyToClipboard | Method | Copies text to the clipboard |
createDirective | Method | Creates a new directive |
getDirectiveById (doc) | Method | Finds a directive in the document by id |
getDirectiveByAttVal (doc) | Method | Finds the first directive with a matching attribute |
getDirectivesByAttVal (doc) | Method | Finds all directives with a matching attribute |
getDirectivesByClass (doc) | Method | Finds all directives with a class |
getDirectivesByTagName (doc) | Method | Finds all directives with a tag name |
DirectiveObject
| Member | Kind | Description |
|---|---|---|
tagName | Property (read-only) | The directive's tag name |
innerMD | Property | Raw inner Markdown source of the directive |
innerText | Property | Deprecated alias for innerMD |
value | Property | On textarea, alias for innerMD; on other tags, alias for the value attribute |
getAttribute | Method | Reads an attribute value |
setAttribute | Method | Sets or removes an attribute |
getDirectiveById (directive) | Method | Finds a nested directive by id |
getDirectiveByAttVal (directive) | Method | Finds the first nested directive with a matching attribute |
getDirectivesByAttVal (directive) | Method | Finds all nested directives with a matching attribute |
getDirectivesByClass (directive) | Method | Finds all nested directives with a class |
getDirectivesByTagName (directive) | Method | Finds all nested directives with a tag name |
insertBefore | Method | Inserts content immediately before this directive |
insertAfter | Method | Inserts content immediately after this directive |
replaceWith | Method | Replaces this directive |
remove | Method | Removes this directive from the document |
toString | Method | Serializes the directive back to {{tagName ...}} text |
Directive Syntax
Directives are the template elements that make up a document:
{{tagName attr="value" booleanAttr}}content{{/tagName}}
Self-closing directives (input, button, option) have no closing tag. All
other tag names — including textarea, note, div, and any custom name —
are block directives with an opening and closing tag:
{{input type="text" id="name"}}
Examples:
{{div id="container" class="main"}}
Some content here
{{/div}}
{{note remove}}Important note{{/note}}
{{input type="text" placeholder="Enter name"}}
See Markdown Directives for the full directive reference.
hai.app
User-facing notifications.
notify
Displays a notification to the user.
Syntax:
notify(notification: {
message: string
type: "info" | "success" | "warning" | "error"
sticky?: boolean
title?: string
}): void
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
notification.message | string | Yes | — | The main notification message |
notification.type | string | Yes | — | One of info | success | warning | error |
notification.sticky | boolean | No | false | Keeps the notification visible until manually dismissed |
notification.title | string | No | — | Optional title shown above the message |
Example:
// Simple info notification
app.notify({
type: "info",
message: "Processing started...",
})
// Success notification with title
app.notify({
type: "success",
title: "Complete!",
message: "All tasks have been processed",
})
// Sticky warning that requires dismissal
app.notify({
type: "warning",
message: "Please review the changes before saving",
sticky: true,
})
hai.doc
Utilities for reading and manipulating document content, title, and
directives. Methods that locate directives return DirectiveObject
instances — see DirectiveObject.
docTitle
The document title. Writable directly, or via setTitle — both
update the same underlying value.
Type: string
Example:
console.log("Current title:", doc.docTitle)
doc.docTitle = "My Updated Document"
docContent
The document content. Writable directly, or via setContent —
both update the same underlying value.
Type: string
Example:
console.log("Document has", doc.docContent.length, "characters")
doc.docContent = "{{div}}New document content{{/div}}"
getTitle
Gets the current document title.
Syntax:
getTitle(): string
Returns: The document title.
Example:
const title = doc.getTitle()
console.log("Document title:", title)
setTitle
Sets the document title.
Syntax:
setTitle(title: string | DirectiveObject): void
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
title | string | DirectiveObject | Yes | — | The new document title |
Example:
doc.setTitle("My Updated Document")
getContent
Gets the current document content.
Syntax:
getContent(): string
Returns: The document content.
Example:
const content = doc.getContent()
console.log("Document has", content.length, "characters")
setContent
Replaces the document content.
Syntax:
setContent(content: string | DirectiveObject): void
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
content | string | DirectiveObject | Yes | — | The new document content |
Example:
doc.setContent("{{div}}New document content{{/div}}")
appendContent
Appends content to the end of the document.
Syntax:
appendContent(content: string | DirectiveObject): void
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
content | string | DirectiveObject | Yes | — | The content to append |
Example:
const summary = doc.createDirective("note")
summary.innerText = "Generated summary"
doc.appendContent(summary)
prependContent
Inserts content at the start of the document.
Syntax:
prependContent(content: string | DirectiveObject): void
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
content | string | DirectiveObject | Yes | — | The content to prepend |
Example:
doc.prependContent('{{note type="info"}}Generated by macro{{/note}}')
copyToClipboard
Copies text to the clipboard.
Syntax:
copyToClipboard(options?: {
text?: string | DirectiveObject
format?: "plain" | "html"
}): void
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
options.text | string | DirectiveObject | No | Document content | Text to copy. Defaults to the current document content when omitted |
options.format | "plain" | "html" | No | "plain" | Clipboard format |
Example:
// Copy the current document content as plain text
doc.copyToClipboard()
// Copy specific HTML content
doc.copyToClipboard({text: "<b>Hello</b>", format: "html"})
createDirective
Creates a new directive that can be inserted into the document.
Syntax:
createDirective(localName: string): DirectiveObject
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
localName | string | Yes | — | The tag name for the directive (e.g. div, input) |
Returns: A new DirectiveObject. Self-closing tags (input, button,
option) are created without a closing tag. All other tag names — including
textarea — are created as a block directive with an opening and closing
tag.
Example:
const newDiv = doc.createDirective("div")
newDiv.id = "my-div"
newDiv.class = "container"
newDiv.innerText = "Hello World"
const existingDiv = doc.getDirectiveById("parent")
existingDiv.insertAfter(newDiv)
getDirectiveById (doc)
Finds a directive in the document by its id attribute.
Syntax:
getDirectiveById(id: string): DirectiveObject | undefined
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | Yes | — | The id to search for |
Returns: The matching DirectiveObject, or undefined if not found.
Example:
const container = doc.getDirectiveById("main-container")
if (container) {
container.class = "active"
}
getDirectiveByAttVal (doc)
Finds the first directive in the document with a specific attribute value.
Syntax:
getDirectiveByAttVal(att: string, val: string): DirectiveObject | undefined
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
att | string | Yes | — | The attribute name to search for |
val | string | Yes | — | The attribute value to match |
Returns: The matching DirectiveObject, or undefined if not found.
Example:
const important = doc.getDirectiveByAttVal("data-type", "important")
if (important) {
console.log("Found:", important.tagName)
}
getDirectivesByAttVal (doc)
Finds all directives in the document with a specific attribute value.
Syntax:
getDirectivesByAttVal(att: string, val: string): DirectiveObject[]
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
att | string | Yes | — | The attribute name to search for |
val | string | Yes | — | The attribute value to match |
Returns: Array of matching DirectiveObject instances.
Example:
const pending = doc.getDirectivesByAttVal("status", "pending")
pending.forEach((item) => {
item.status = "completed"
})
getDirectivesByClass (doc)
Finds all directives in the document with a specific CSS class name.
Syntax:
getDirectivesByClass(cls: string): DirectiveObject[]
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
cls | string | Yes | — | The class name to search for (matched within space-separated lists) |
Returns: Array of matching DirectiveObject instances.
Example:
const highlighted = doc.getDirectivesByClass("highlight")
highlighted.forEach((item) => {
item.class = item.class.replace("highlight", "").trim()
})
getDirectivesByTagName (doc)
Finds all directives in the document with a specific tag name.
Syntax:
getDirectivesByTagName(tagName: string): DirectiveObject[]
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
tagName | string | Yes | — | The tag name to search for (e.g. note) |
Returns: Array of matching DirectiveObject instances.
Example:
const notes = doc.getDirectivesByTagName("note")
notes.forEach((note) => {
note.remove = true
})
DirectiveObject
Represents a directive in the document. Attributes can be read and written directly as object properties — see Dynamic attribute access.
tagName
The tag name of the directive.
Type: string (read-only)
Example:
const dir = doc.getDirectiveById("my-div")
console.log(dir.tagName) // "div"
innerMD
The raw inner Markdown source inside the directive — the innerHTML analogue
for Markdown. Returns "" when empty, including for self-closing directives
(never undefined). Setting it on a self-closing directive throws.
Type: string | DirectiveObject
Example:
const note = doc.getDirectiveById("my-note")
console.log(note.innerMD)
note.innerMD = "Updated note content"
const input = doc.getDirectivesByTagName("input")[0]
console.log(input.innerMD) // "" (self-closing, DOM parity)
innerText (deprecated)
Deprecated alias for innerMD, kept so existing scripts keep working. Returns
and sets the identical value. Prefer innerMD in new scripts.
value
DOM-parity property whose meaning depends on the tag:
- On
textarea, it's an alias forinnerMD— leading/trailing single newlines from the block markdown syntax are trimmed on read. Setting it writes the inner content, same asinnerMD. - On any other tag (e.g.
input), it's a plain alias for thevalueattribute — equivalent togetAttribute("value")/setAttribute("value", ...).
Type: string | undefined
Example:
const textarea = doc.getDirectivesByTagName("textarea")[0]
textarea.value = "Multi-line\ncontent"
console.log(textarea.value) // "Multi-line\ncontent"
const input = doc.getDirectivesByTagName("input")[0]
input.value = "default text" // same as input.setAttribute("value", "default text")
Dynamic attribute access
Any directive attribute can be read or written directly as a property,
without going through getAttribute/setAttribute.
Example:
const dir = doc.getDirectiveById("example")
// Reading attributes
console.log(dir.id) // "example"
console.log(dir.class) // "container main"
console.log(dir.disabled) // true (for boolean attributes)
// Setting attributes
dir.class = "new-class"
dir["data-value"] = "123"
// Boolean attributes
dir.disabled = true // adds the attribute
dir.disabled = false // removes it
// Removing attributes
delete dir.class
getAttribute
Gets the value of a specified attribute.
Syntax:
getAttribute(name: string): string | null
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | — | The attribute name |
Returns: The attribute value, an empty string for boolean attributes, or
null if the attribute is not set.
Example:
const dir = doc.getDirectiveById("my-div")
dir.getAttribute("id") // "my-div"
dir.getAttribute("disabled") // "" (boolean attribute is present)
dir.getAttribute("nonexistent") // null
setAttribute
Sets or removes an attribute.
Syntax:
setAttribute(name: string, val: string | boolean): void
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | — | The attribute name |
val | string | boolean | Yes | — | The value to set. Use true for boolean attributes; false/null/undefined removes the attribute |
Example:
const dir = doc.getDirectiveById("my-div")
dir.setAttribute("class", "container")
dir.setAttribute("disabled", true)
dir.setAttribute("disabled", false) // removes it
getDirectiveById (directive)
Searches this directive's content for a nested directive by id.
Syntax:
getDirectiveById(id: string): DirectiveObject | undefined
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | Yes | — | The id to search for |
Returns: The matching DirectiveObject, or undefined if not found.
Example:
const container = doc.getDirectiveById("container")
const nested = container.getDirectiveById("nested-item")
getDirectiveByAttVal (directive)
Searches this directive's content for the first nested directive with a specific attribute value.
Syntax:
getDirectiveByAttVal(att: string, val: string): DirectiveObject | undefined
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
att | string | Yes | — | The attribute name to search for |
val | string | Yes | — | The attribute value to match |
Returns: The matching DirectiveObject, or undefined if not found.
Example:
const form = doc.getDirectiveById("my-form")
const submitBtn = form.getDirectiveByAttVal("type", "submit")
getDirectivesByAttVal (directive)
Searches this directive's content for all nested directives with a specific attribute value.
Syntax:
getDirectivesByAttVal(att: string, val: string): DirectiveObject[]
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
att | string | Yes | — | The attribute name to search for |
val | string | Yes | — | The attribute value to match |
Returns: Array of matching DirectiveObject instances.
Example:
const container = doc.getDirectiveById("task-list")
const pending = container.getDirectivesByAttVal("status", "pending")
getDirectivesByClass (directive)
Searches this directive's content for all nested directives with a specific class name.
Syntax:
getDirectivesByClass(cls: string): DirectiveObject[]
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
cls | string | Yes | — | The class name to search for |
Returns: Array of matching DirectiveObject instances.
Example:
const section = doc.getDirectiveById("main-section")
const alerts = section.getDirectivesByClass("alert")
getDirectivesByTagName (directive)
Searches this directive's content for all nested directives with a specific tag name.
Syntax:
getDirectivesByTagName(tagName: string): DirectiveObject[]
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
tagName | string | Yes | — | The tag name to search for |
Returns: Array of matching DirectiveObject instances.
Example:
const form = doc.getDirectiveById("my-form")
const inputs = form.getDirectivesByTagName("input")
inputs.forEach((input) => {
input.required = true
})
insertBefore
Inserts a directive or string immediately before this directive.
Syntax:
insertBefore(dir: DirectiveObject | string): void
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
dir | DirectiveObject | string | Yes | — | The directive to insert |
Example:
const div = doc.getDirectiveById("my-div")
const newNote = doc.createDirective("note")
newNote.innerText = "Added before"
div.insertBefore(newNote)
div.insertBefore("{{div}}Another div{{/div}}")
insertAfter
Inserts a directive or string immediately after this directive.
Syntax:
insertAfter(dir: DirectiveObject | string): void
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
dir | DirectiveObject | string | Yes | — | The directive to insert |
Example:
const div = doc.getDirectiveById("my-div")
const newNote = doc.createDirective("note")
newNote.innerText = "Added after"
div.insertAfter(newNote)
div.insertAfter("{{div}}Another div{{/div}}")
replaceWith
Replaces this directive with another directive or string.
Syntax:
replaceWith(dir: DirectiveObject | string): void
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
dir | DirectiveObject | string | Yes | — | The directive to replace with |
Example:
const oldDiv = doc.getDirectiveById("old-div")
const newDiv = doc.createDirective("div")
newDiv.id = "new-div"
newDiv.innerText = "Replaced content"
oldDiv.replaceWith(newDiv)
remove
Removes this directive from the document.
Syntax:
remove(): void
Warning: The DirectiveObject instance becomes invalid after removal —
do not use it afterwards.
Example:
const oldDiv = doc.getDirectiveById("obsolete")
if (oldDiv) {
oldDiv.remove()
// don't use oldDiv after this point
}
toString
Returns the string representation of the directive.
Syntax:
toString(): string
Returns: The directive as a string, e.g. {{tagName attr="value"}}...{{/tagName}}.
Example:
const div = doc.getDirectiveById("my-div")
console.log(div.toString())
// {{div id="my-div" class="container"}}Content{{/div}}
Tips and best practices
- Check for existence before manipulating.
getDirectiveByIdand similar lookups returnundefinedwhen nothing matches — guard before accessing properties. - Use the right search method.
getDirectiveByIdfor unique elements,getDirectivesByTagNamefor all elements of a type,getDirectivesByClassfor elements sharing a class,getDirectivesByAttValfor custom attributes. - Prefer property access for simple changes (
dir.class = "active") oversetAttribute— both work, but property access reads better for everyday use. - Don't overwrite space-separated classes. Split, modify, and rejoin
instead of assigning a single new value:
dir.class = (dir.class ?? "").split(" ").concat("new").join(" "). - Self-closing directives have no
innerText/innerMD. Setting either oninput,button, oroptionthrows — use their attributes instead.textareais a block directive and can hold text — use itsvalueproperty. - Don't reuse a
DirectiveObjectafter callingremove()— the instance becomes invalid. - Use
createDirective()for dynamic content, theninsertBefore/insertAfter/replaceWithto place it.