#meta/api

This implements the widget API. Widgets are values describing what to render: static (markdown, html) or live (source, content). Consider using ^Library/Std/APIs/DOM to construct HTML widgets.

-- priority: 50

widget = widget or {}

widgets = widgets or {}

local widgetSchema = {
  type = "object",
  properties = {
    markdown = { type = "string"},
    html = {
      anyOf = {
        -- HTMLElement
        { type = "object"},
        -- Plain HTML code
        { type = "string" }
      }
    },
    cssClasses = {
      type = "array",
      items = { type = "string" },
    },
    display = {
      type = "string",
      enum = {"block", "inline"}
    },
    events = {
      type = "object",
      additionalProperties = true
    },
    -- false renders markdown without evaluating it: Lua directives, custom
    -- syntax and transclusions stay literal (for user- or agent-written text).
    evaluate = { type = "boolean" },
    -- A fragment: text parts and embedded widgets (from .., table.concat, templates)
    parts = { type = "array" },
    -- When true, html + script render inside an isolated sandbox iframe.
    sandbox = { type = "boolean" },
    script = { type = "string" },
  }
}

--- Creates a widget; a spec with `source` or `content` creates a live widget.
--- @param spec table { markdown?: string|widget, html?: string|HTMLElement, display?: "block"|"inline", cssClasses?: string[], events?: table, evaluate?: boolean, sandbox?: boolean, script?: string, source?: any, content?: any }
--- @return table a widget
--- @see API/widget
function widget.new(spec)
  if spec.source ~= nil or spec.content ~= nil then
    return widget.newLive(spec)
  end
  if type(spec.markdown) == "table" and spec.markdown._isWidget then
    local f = {}
    for k, v in pairs(spec.markdown) do f[k] = v end
    f.display = spec.display or f.display
    f.cssClasses = spec.cssClasses or f.cssClasses
    return f
  end
  local validationResult = jsonschema.validateObject(widgetSchema, spec)
  if validationResult then
    error(validationResult)
  end
  spec._isWidget = true
  return spec
end

--- Creates an inline HTML widget.
--- @param html string|HTMLElement HTML code or a DOM node, e.g. built with `dom.*`
--- @return table a widget
--- @see API/widget
function widget.html(html)
  return widget.new {
    html = html
  }
end

--- Creates a block-level HTML widget.
--- @param html string|HTMLElement HTML code or a DOM node, e.g. built with `dom.*`
--- @return table a widget
--- @see API/widget
function widget.htmlBlock(html)
  return widget.new {
    html = html,
    display = "block"
  }
end

--- Creates an inline Markdown widget.
--- @param markdown string the Markdown to render
--- @param opts? table { evaluate?: boolean }; `evaluate = false` renders the Markdown without running `${...}` or transclusions
--- @return table a widget
--- @see API/widget
function widget.markdown(markdown, opts)
  return widget.new {
    markdown = markdown,
    evaluate = opts and opts.evaluate
  }
end

--- Creates a block-level Markdown widget.
--- @param markdown string the Markdown to render
--- @param opts? table { evaluate?: boolean }; `evaluate = false` renders the Markdown without running `${...}` or transclusions
--- @return table a widget
--- @see API/widget
function widget.markdownBlock(markdown, opts)
  return widget.new {
    markdown = markdown,
    display = "block",
    evaluate = opts and opts.evaluate
  }
end

--- Creates a sandboxed, scripted widget: `html` and `script` run inside an isolated iframe, the same machinery code widgets use.
--- `script` has access to `syscall()`, `loadJsByUrl` and auto height. The Copy button copies `markdown`. Equivalent to `widget.new` with `sandbox = true`.
--- @param spec table { html?: string, script?: string, markdown?: string, display?: "block"|"inline", cssClasses?: string[] }; `display` defaults to "block"
--- @return table a widget
--- @see API/widget
function widget.sandbox(spec)
  return widget.new {
    sandbox = true,
    html = spec.html,
    script = spec.script,
    display = spec.display or "block",
    markdown = spec.markdown,
    cssClasses = spec.cssClasses,
  }
end
Loading SilverBullet…