notesy::view

What a panel, a tab or a sidebar section shows is a view: plain values naming notesy's own widgets, which notesy draws. A script never draws; it describes, and a plugin's UI always looks like notesy, in any theme.

A view is a tree of parts. Each builder below returns an object (#{ kind: "row", icon: "check", text: "Call Ana" }); set any of its other fields after, like row.target = "call". A list is a column; plain text is a note.

rustuse notesy::{view, Color, Icon};

fn build(note) {
    let row = view::row(Icon::ArrowRight, "Record the demo video");
    row.target = "video";
    row.color = Color::Accent;
    view::column([
        view::header(Icon::Check, "Tasks", "3 left in this note"),
        view::stats([["3", "to do"], ["2", "done"]]),
        view::progress(2, 5),
        view::caption("to do"),
        row,
    ])
}

#Parts

Builder Other fields What it is
view::column(parts) parts one under another
view::columns(parts) weights, gap, min parts side by side, each as wide as its share of weights ([2, 1]: two thirds and one; equal when it has none), gap points apart (12); when there isn't room for each to be min points wide (160), as many to a row as fit, like CSS's flex-wrap. A part that's a list is a column in it
view::caption(text) a small uppercase heading
view::note(text) color, icon wrapped text, faint unless it has a color
view::header(icon, title, subtitle) tint an icon on a tinted tile, a title, a line under it
view::fact(icon, name, value) a name on the left, its value on the right
view::stats(items) tiles of a big value over a label: [[value, label], …] (at most 6)
view::chips(list) target chips in rows; #text ones in the tags' colors
view::row(icon, text) hint, target, command, faint, color a row, like a link in the Links panel
view::block(icon, title, lines) subtitle, target a soft block with lines on a bar, like a mention
view::progress(done, total) color a bar filled to done / total
view::button(label) icon, target, command, tone a button; tone is plain, soft, primary, danger or tag
view::empty(icon, title, body) action, target what shows where there's nothing yet, with a button when it has an action
view::pill(text) tone a small filled label: a state, a count; tone as a button's
view::sparkline(values) unit a line of numbers across, from nothing at its foot to the largest at its top, which is written at the top right with its unit; at most 2,000
view::tiles(items) small tiles side by side, each #{ label, icon, value, detail, color } (color is its icon's): the hours of a forecast, say. Past the panel's edge the strip scrolls sideways; at most 48
view::fold(title, parts) icon, hint, color, open, actions a heading with a chevron that opens and closes what's under it, an icon (in color) before its title and a hint at its right; open unless open is false. Open or closed, it stays as the user leaves it while its view is built again. actions, up to 4 #{ icon, target, tip, lit }, are small buttons at its right while the pointer's on its line, each heard as a click on its target; a lit one shows all the time, in the accent (which of several is chosen)
view::bars(values) labels, unit, max, color a bar a value on a scale from nothing to max (the largest, unless it says), as many of labels under them as fit, and the value under the pointer (with its label) at the top: the hours' chance of rain, say. At most 400
view::metrics(items) small cards in a grid, as many to a row as fit, each #{ label, icon, value, detail, color }: an icon and a label over a big value and a line under it. At most 48
view::range(label, low, high) min, max, mark, unit, icon, hint, color a span from low to high on a bar from min to max (the span itself unless it says): the label, a hint and an icon before it, its ends written beside it with their unit, mark a dot on the bar. A day of a forecast, say
view::markdown(text) markdown, drawn as notesy draws a note (headings, emphasis, code, links and tags, lists and tasks, quotes, code blocks, rules, columns), read-only; at most 20,000 characters
view::divider() a thin line across
view::space(points) room, up to 64 points

Icons, colors and tones are enums of notesy's names (Names: Icon, Color, Tone, Side, Method, Command, Menu, Sidebar, Event): Icon::Check, Color::Accent, Tone::Primary. Its own icons are by their names: the plugin's own [[icons]] (plugin.toml), notesy's (check, clock, file, folder, link, tag, calendar, bulb, alert, info, arrow-right, arrow-up, pin, plugin, canvas …), or another plugin's as <plugin id>.<name>. Colors are a theme color's key (accent, success, warning, danger, text_muted …), one of the plugin's own [[colors]], or #rrggbb.

#Clicks

A row, block, button or empty state with a target tells the script when it's clicked: the panel or tab's on_click handler gets the target (and a chip's target gets target:chip). One with a command runs that command instead, notesy's own or a plugin's. See notesy::panels and notesy::views.

#Inputs

Parts the user changes. Each needs a target to be heard: when it changes, the panel or tab's on_change handler gets the target, the new value, and the note's path (or None), and then notesy builds the view again, so what it shows comes from the script (notesy::panels and notesy::views).

Builder Other fields What it is What on_change gets
view::switch(label, on) hint, icon, target a line with a switch at its right, like a setting true or false
view::check(text, done) target a line with a box to tick, like a task; ticked, its text is struck through true or false
view::input(hint) target, suggest a field to type into; Enter sends what's in it and empties it. With suggest: true, its script suggests as it's typed in (below) the text, or a suggestion's value
view::select(options, selected) target a dropdown of options, one picked the option picked
view::segmented(options, selected) target up to five short options side by side, one picked the option picked

options is a list of text; selected is the one picked, by its text or its place from 0.

rustuse notesy::{panels, store, view};

pub fn ready() {
    let tasks = panels::add("tasks", "Tasks", |note| {
        let hide = view::switch("Hide done tasks", store::get("hide").unwrap_or(false));
        hide.target = "hide";
        let span = view::segmented(["Today", "This week"], store::get("span").unwrap_or("Today"));
        span.target = "span";
        view::column([hide, span])
    });
    tasks.on_change(|target, value, note| store::set(target, value));
}

#Suggestions

A field with suggest: true asks its script what to suggest as it's typed in. Once typing pauses (a quarter of a second), the panel or tab's on_suggest handler gets the target, the text and the note's path, and answers with the view's suggest(target, text, items): then, or later, once it knows. So what it suggests can come from anywhere it may reach: its store, the vault, a site its manifest names (notesy::net).

Each item is text, or #{ text, detail, icon, value, view }. They show under the field, to pick with ↑, ↓ and Enter or a click; Esc puts them away. One with a view (any part of a view: a column of a title and a line under it, a row with a pill) shows that instead of its icon, text and detail, lit and picked as the rest are; what's in it is only looked at. A pick reaches on_change with the item's value (its text, unless it has one); Enter with none lit sends what was typed, as a field without suggestions does. Only the answer for what was last typed shows, so one that comes back late is left out. At most 50 show.

rustuse notesy::{json, net, panels, store, view};

pub fn ready() {
    let app = #{ panel: () };
    app.panel = panels::add("places", "Places", |note| {
        let add = view::input("Add a place");
        add.target = "add";
        add.suggest = true;
        view::column([add])
    });
    app.panel.on_suggest(|target, text, note| {
        net::fetch(`https://geocoding-api.open-meteo.com/v1/search?name=${net::encode(text)}`, |result| {
            let items = [];
            if let Ok(response) = result {
                if let Ok(answer) = json::parse(response.text) {
                    for r in answer.get("results").unwrap_or([]) {
                        items.push(#{ text: r.name, detail: r.country, icon: "pin", value: `${r.name}, ${r.country}` });
                    }
                }
            }
            app.panel.suggest(target, text, items);
        });
    });
    // What was picked (its value), or typed and sent with Enter.
    app.panel.on_change(|target, value, note| store::set("place", value));
}

#When something's wrong

A part that's wrong (no text on a row, a kind there isn't) shows as a red line where it was, saying what's wrong, and the rest of the view still shows. A view has at most 2,000 parts, nested at most 16 deep.

Every page