Compare commits

...
21 changed files with 780 additions and 244 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ A note that explains a workflow. It looks like a panel, not a node: no title bar
- **Lists and quote**: Puts - , 1. or > before each selected line (press again to take it off).
- **Link** (Ctrl+K): A Docs page (type to find any page of these docs, then pick a section or the whole page), a Web address, a Library button (below) or a Community pack (owner/pack, or paste the pack's address), drawn as a Button with an icon or as a Text link, in its own colour or the note's. The selected text becomes the link's text. It goes where the caret is when you press Insert link, so you can click in the text after opening the panel.
- **Icon**: Any PrimeIcons icon, searchable, in a colour or the text's own, inserted where the caret is.
- **Pictures**: Add a picture from a file, or paste or drop one into the text; it goes on a line of its own where the caret is. Or pick one the extension ships (the node toolbar with its ? help button). The panel lists the note's pictures with a Width slider (a share of the note), Own size and Remove.
- **Pictures**: Add a picture from a file, or paste or drop one into the text; it goes on a line of its own where the caret is. Or pick one the extension ships (the node toolbar with its ? help button, or any docs screenshot), with a search box to find it. The panel stays open after adding and lists the note's pictures with a Width slider (a share of the note), Own size and Remove.
<!-- fields:end -->
Keys you type in the editor stay in the editor: Delete, Ctrl+Z and the other ComfyUI shortcuts don't reach the canvas.
+2 -1
View File
@@ -2,7 +2,8 @@
- **WP nodes follow your ComfyUI color palette.** On Solarized, Arc, Nord, Github, palettes other extensions add (Obsidian, Milk White …) or one you imported, WP nodes now take the palette's node, widget and text colors instead of staying grey, so they match the nodes around them. Text is kept readable whatever the palette, and the accent, module kind and variable colors keep their meaning. A light palette that doesn't mark itself light (like Milk White) now gets the light colors instead of dark ones. Dark and Light look as before. To keep the WP colors everywhere, set **Wildcard Pipeline › Sizing › Canvas colors** to **Wildcard Pipeline colors**.
- **The manager can wear your ComfyUI palette too.** Settings › Appearance › Colours has a new **ComfyUI palette** theme: the manager takes the colour palette ComfyUI is using, imported ones included, and switches the moment you change it in ComfyUI. Dark and Light wear Native; other palettes give the pages, cards and text their colours, kept readable, and decide dark or light themselves.
- **WP Note's text has its own window.** Writing a long note inside the node was cramped, so the text now opens in a window like the module editors: press **Edit text** in the note's editor and you get the Markdown editor with its formatting bar on the left and the note as it will read on the right, updating as you type. **Done** (or Ctrl+Enter) puts the text on the note; **Cancel**, Escape or × leave the note as it was, and ask first if you changed something. The editor on the node keeps the settings you set while looking at the canvas: tone, look, colour, title, pin and the bookmark's landing spot, zoom and shortcut. The node shows the text's first line and its length.
- **WP Note's text has its own window.** Writing a long note inside the node was cramped, so the text now opens in a window like the module editors: press **Edit text** in the note's editor and you get the Markdown editor with its formatting bar on the left and the note as it will read on the right, updating as you type. The preview is the node's own card, with its look, tone, colour, title and width; a label shows its tag and the card you see when you point at it. **Done** (or Ctrl+Enter) puts the text on the note; **Cancel**, Escape or × leave the note as it was, and ask first if you changed something. The editor on the node keeps the settings you set while looking at the canvas: tone, look, colour, title, pin and the bookmark's landing spot, zoom and shortcut. The node shows the text's first line and its length.
- **Any extension picture in a WP Note.** The Pictures panel's **From the extension** now offers every screenshot from the docs, not just the toolbar one, found by typing part of its name (the grid shows only matches, so the panel stays tidy). After you add a picture the panel stays open, so its width slider is right there, and the icon list and picture grid use the window's height instead of a small scrolling box. The icon picker on the node now fills the editor's width.
- **Export as plain wildcard files.** Import / Export → Export has a new **Wildcard files (.zip)** button: the picks become `.txt` wildcard files for Dynamic Prompts, Impact Pack, Prompt-PostProcessor, Easy-Use and A1111/Forge, so you can share a pack with people who don't use Wildcard Pipeline. References and `$variables` become `__folder/name__`, uneven weights become repeated lines, and a README in the zip lists what had no plain form (rules, tags, the chance of nothing, negatives).
- **More looks for WP Toggles.** Pick one has its own **Style** in the edit view: Radio (as before), Chip, Switch or Check, the same for every row; each row's Style is greyed out there since it no longer applies. A pick's **Value** is now a text box like the Assembler's that you can make taller: `$` lists every variable upstream (Context Injector ones too) and the toggles' own, tag, LoRA and embedding autocomplete work in it, and a `$name` nothing upstream sets is flagged under the box and reported in WP Debug after a run. Inline `{a|b}` picks need a seed, so in the Value and in Context Injector templates they print as written; both editors now say so. A Chip toggle now spans the whole row, in Any on and Pick one, so it's an easy target and lines up with the rows around it. In the edit view you can drag a toggle into or out of a label's group (the Group field still works), and a grouped toggle shows its label's colour on its left edge.
+4 -4
View File
@@ -2279,19 +2279,19 @@
},
{
"title": "Moving, sizing and editing",
"text": "Unpin a note to drag it like any node. A Card or Sticky resizes from its corner: the width is yours and the height follows the text. A Label sizes itself to its title. Double-click a note, press the indigo pencil in ComfyUI's selection toolbar while it is selected, or press Edit on a label's hover card to open the editor; Done goes back to reading. The editor on the node holds the note's settings: tone, look, colour, title and the bookmark settings. Drag the editor's background (anywhere that isn't a control) to move the note while it is open.\nThe text has its own window: press Edit text (Write text on an empty note). The Markdown editor is on the left and the note as it will read on the right, following what you type. Done (or Ctrl+Enter) puts the text on the note; Cancel, Escape or × leaves the note as it was, and asks first when you changed something. The node shows the text's first line and how long it is.\nTone\nStep (a numbered circle), Icon (pick any icon from the list next to it) or Plain (no badge).\nColour\nPresets that follow the theme (Accent, Tip, Info, Warning, Danger, Teal, Violet, Neutral), or any colour from the picker.\nLook\nCard, Sticky or Label (above).\nPinned\nComfyUI's own pin: a pinned note can't be moved or resized by accident. New notes start pinned.\nTitle\nShown in bold at the top, or on the pill of a Label.\nText\nIts first line and length on the node; Edit text opens the text window, with the editor beside a live preview. Done (Ctrl+Enter) puts it on the note, Cancel or Escape leaves the note as it was. Markdown, with the formatting bar below over it: headings (# to ####), bold, italic, strikethrough, code, bulleted, numbered and nested lists, task lists (- [x] / - [ ]), quotes and callouts (> [!TIP], > [!WARNING]), tables with column alignment (:--, :-:, --:), rules (---) and code blocks.\nKeys you type in the editor stay in the editor: Delete, Ctrl+Z and ComfyUI's other shortcuts don't reach the canvas."
"text": "Unpin a note to drag it like any node. A Card or Sticky resizes from its corner: the width is yours and the height follows the text. A Label sizes itself to its title. Double-click a note, press the indigo pencil in ComfyUI's selection toolbar while it is selected, or press Edit on a label's hover card to open the editor; Done goes back to reading. The editor on the node holds the note's settings: tone, look, colour, title and the bookmark settings. Drag the editor's background (anywhere that isn't a control) to move the note while it is open.\nThe text has its own window: press Edit text (Write text on an empty note). The Markdown editor is on the left and the note as it will read on the right, following what you type: the node's own card, with its look, tone, colour, title and width. A label shows its tag and, under it, the card that opens when you point at it. Done (or Ctrl+Enter) puts the text on the note; Cancel, Escape or × leaves the note as it was, and asks first when you changed something. The node shows the text's first line and how long it is.\nTone\nStep (a numbered circle), Icon (pick any icon from the list next to it) or Plain (no badge).\nColour\nPresets that follow the theme (Accent, Tip, Info, Warning, Danger, Teal, Violet, Neutral), or any colour from the picker.\nLook\nCard, Sticky or Label (above).\nPinned\nComfyUI's own pin: a pinned note can't be moved or resized by accident. New notes start pinned.\nTitle\nShown in bold at the top, or on the pill of a Label.\nText\nIts first line and length on the node; Edit text opens the text window, with the editor beside a live preview. Done (Ctrl+Enter) puts it on the note, Cancel or Escape leaves the note as it was. Markdown, with the formatting bar below over it: headings (# to ####), bold, italic, strikethrough, code, bulleted, numbered and nested lists, task lists (- [x] / - [ ]), quotes and callouts (> [!TIP], > [!WARNING]), tables with column alignment (:--, :-:, --:), rules (---) and code blocks.\nKeys you type in the editor stay in the editor: Delete, Ctrl+Z and ComfyUI's other shortcuts don't reach the canvas."
},
{
"title": "The text window and its formatting bar",
"text": "Figure: The text window: the Markdown on the left with the link panel open (a docs page found by typing), the note as it will read on the right. A WP Note open in its editor next to a finished Step card. The editor has Tone (Step, Icon, Plain) with the step number 2, Look (Card, Sticky, Label) with the Pinned switch, a row of colour presets with the custom colour #ef4444, the Title Label each frame, and the Text with its formatting bar: undo and redo (both lit), bold, italic, strikethrough, code, bulleted and numbered list, quote, link (pressed), icon and a preview eye. The link panel is open: Docs page is picked, the page is WP Label & Grid and the section Set it up, the text is Set up the grid, the style is Button with the Th Large icon, and Colour is #22c55e (None follows the note), with Cancel and Insert link. Under it is the note's Markdown and a Done button. The card on the right, step 1 Load your model, reads Pick the checkpoint in the loader. $model_family follows it, so the prompt fits the model, with two link buttons: WP Model Info in the note's colour and Writing prompts for your model in green.\nUndo and redo Ctrl+Z / Ctrl+Y\nStep back or forward through your changes, typing and the bar's own edits alike (Ctrl+Shift+Z and the right-click Undo and Redo work too). Typing counts as one step until you pause, move the caret or start a new line. The steps last while the text window is open.\nBold, italic, strikethrough Ctrl+B / Ctrl+I\nWraps the selection in **, * or ~~.\nCode\nWraps it in backticks, for a $variable.\nLists and quote\nPuts - , 1. or > before each selected line (press again to take it off).\nLink Ctrl+K\nA Docs page (type to find any page of these docs, then pick a section or the whole page), a Web address, a Library button (below) or a Community pack (owner/pack, or paste the pack's address), drawn as a Button with an icon or as a Text link, in its own colour or the note's. The selected text becomes the link's text. It goes where the caret is when you press Insert link, so you can click in the text after opening the panel.\nIcon\nAny PrimeIcons icon, searchable, in a colour or the text's own, inserted where the caret is.\nPictures\nAdd a picture from a file, or paste or drop one into the text; it goes on a line of its own where the caret is. Or pick one the extension ships (the node toolbar with its ? help button). The panel lists the note's pictures with a Width slider (a share of the note), Own size and Remove."
"text": "Figure: The text window: the Markdown on the left with the link panel open (a docs page found by typing), the note as it will read on the right. Screenshot of a WP Note's text window, titled Load your model with a NOTE TEXT chip. On the left, under Write, the formatting bar has its link button pressed and the link panel open: Docs is picked, the page is Seeds & loops (Whole page), with an optional Text box, the Button style with the Book icon, Colour none (None follows the note), Cancel and Insert link; under it is the note's Markdown. On the right, under Preview, is the note as the node draws it: a Card with the accent edge, the step badge 1 and the title Load your model, then Pick the checkpoint in the loader. $model_family follows it, so the prompt fits the model, a bullet, a ticked and an open task, a tip callout Lock the seed before you compare two settings, and a WP Model Info button. At the bottom: Ctrl+Enter Done, Cancel and Done.\nUndo and redo Ctrl+Z / Ctrl+Y\nStep back or forward through your changes, typing and the bar's own edits alike (Ctrl+Shift+Z and the right-click Undo and Redo work too). Typing counts as one step until you pause, move the caret or start a new line. The steps last while the text window is open.\nBold, italic, strikethrough Ctrl+B / Ctrl+I\nWraps the selection in **, * or ~~.\nCode\nWraps it in backticks, for a $variable.\nLists and quote\nPuts - , 1. or > before each selected line (press again to take it off).\nLink Ctrl+K\nA Docs page (type to find any page of these docs, then pick a section or the whole page), a Web address, a Library button (below) or a Community pack (owner/pack, or paste the pack's address), drawn as a Button with an icon or as a Text link, in its own colour or the note's. The selected text becomes the link's text. It goes where the caret is when you press Insert link, so you can click in the text after opening the panel.\nIcon\nAny PrimeIcons icon, searchable, in a colour or the text's own, inserted where the caret is.\nPictures\nAdd a picture from a file, or paste or drop one into the text; it goes on a line of its own where the caret is. Or pick one the extension ships (the node toolbar with its ? help button, or any docs screenshot), with a search box to find it. The panel stays open after adding and lists the note's pictures with a Width slider (a share of the note), Own size and Remove."
},
{
"title": "Links and icons in the text",
"text": "The bar writes these for you; you can also type them. A note can hold any number of links, anywhere in the text. Any other kind of link shows as plain text.\n[Read more](https://example.com)\nA link that opens in a new tab.\n[Seeds](docs:seeds-and-loops)\nA link to that page of these docs.\n[The grid](docs:wp-label-grid#s-the-grid)\nA link to one section of it.\n[Start here](docs:quick-start){button pi-play}\nThe link drawn as a button with an icon. {button} alone has no icon.\n{button pi-play #rrggbb} [Read more](https://example.com){#rrggbb}\nA button or a text link in its own colour, a hex code like the colour field shows. Without one it takes the note's.\n[Add the pack](wp:library){button pi-download}\nA button that adds this workflow's bundles and modules to your library, after a review. Always a button.\n[Add the Scene lists](wp:library#node-12){button}\nThe same for one WP Context only (its node id; inside a subgraph the editor writes the subgraph's id too).\n[Get the pack](wp:community/owner/pack)\nOpens that published pack in the manager's Community tab, where it installs.\n:pi-heart: :pi-heart:{#rrggbb}\nAn icon, optionally in a colour: a hex code like the custom colour field shows.\n![What it shows](wp:image/1a2b3c4d)\nA picture saved in the note: the bar's Pictures panel, a paste or a drop writes it and its id.\n![The ? button](wp:asset/toolbar-help)\nA picture the extension ships.\n![What it shows](wp:image/1a2b3c4d){width=60%}\nThe picture as wide as 60% of the note (10 to 100). Without it, its own size, never wider than the note."
"text": "The bar writes these for you; you can also type them. A note can hold any number of links, anywhere in the text. Any other kind of link shows as plain text.\n[Read more](https://example.com)\nA link that opens in a new tab.\n[Seeds](docs:seeds-and-loops)\nA link to that page of these docs.\n[The grid](docs:wp-label-grid#s-the-grid)\nA link to one section of it.\n[Start here](docs:quick-start){button pi-play}\nThe link drawn as a button with an icon. {button} alone has no icon.\n{button pi-play #rrggbb} [Read more](https://example.com){#rrggbb}\nA button or a text link in its own colour, a hex code like the colour field shows. Without one it takes the note's.\n[Add the pack](wp:library){button pi-download}\nA button that adds this workflow's bundles and modules to your library, after a review. Always a button.\n[Add the Scene lists](wp:library#node-12){button}\nThe same for one WP Context only (its node id; inside a subgraph the editor writes the subgraph's id too).\n[Get the pack](wp:community/owner/pack)\nOpens that published pack in the manager's Community tab, where it installs.\n:pi-heart: :pi-heart:{#rrggbb}\nAn icon, optionally in a colour: a hex code like the custom colour field shows.\n![What it shows](wp:image/1a2b3c4d)\nA picture saved in the note: the bar's Pictures panel, a paste or a drop writes it and its id.\n![The ? button](wp:asset/toolbar-help)\nA picture the extension ships: the toolbar one, or any docs screenshot, named by its file.\n![What it shows](wp:image/1a2b3c4d){width=60%}\nThe picture as wide as 60% of the note (10 to 100). Without it, its own size, never wider than the note."
},
{
"title": "Pictures",
"text": "A note can show pictures: a screenshot of the step it explains, or the picture the extension ships of the node toolbar with its ? button, which opens any node's help page. Open Pictures in the bar and press Add a picture…, or paste or drop a picture straight into the text. Each one goes on a line of its own where the caret is, named by what you type in the box (or the file's name).\nFigure: The text window with the Pictures panel: the picture the extension ships, the note's own pictures with a Width slider, and the preview showing it. Screenshot of a WP Note with pictures, and the same note open in its editor. The card on the left, Find the help page with a question-mark icon, reads Every WP node has a help page. Select the node and press the ? in its toolbar, then shows the picture the extension ships: ComfyUI's node toolbar over a WP Context · Character node, with the indigo ? button lit. Under it, The page opens next to the canvas, and a pasted screenshot of two chained Contexts at 70% of the note's width. On the right, the editor's formatting bar has its Pictures button pressed; the panel has a box for what the picture shows, Add a picture… (or paste or drop one into the text), From the extension with the toolbar picture, and In this note: Node toolbar with the ? help button at Own size and Two Contexts chained at 70%, each with a Width slider and a remove button, then a hint that pictures are saved in the workflow, made smaller, and never make the note bigger. Below is the note's Markdown with ![Two Contexts chained](wp:image/…){width=70%}.\nA picture is never wider than the note. Its width is a share of the note's, so it grows and shrinks with the note and never makes the note bigger; set it with the panel's Width slider, or Own size for its own width up to the note's. Remove takes it out (Undo brings it back while the text window is open).\nA pasted or dropped picture is saved in the workflow, so a shared workflow shows it too. It is made smaller first: at most 1280 px on its long side and about 400 KB, which also drops camera and location data. A note holds up to six pictures and 2 MB. Pictures never load from a web address, so opening a shared workflow fetches nothing."
"text": "A note can show pictures: a screenshot of the step it explains, or one the extension ships, such as the node toolbar with its ? button or any screenshot from these docs. Open Pictures in the bar and press Add a picture…, paste or drop a picture straight into the text, or pick one under From the extension: type part of its name in the search box and click it in the grid that shows up. Each one goes on a line of its own where the caret is, named by what you type in the box (or the picture's name). The panel stays open after you add one, so you can set its width right away.\nFigure: The text window with the Pictures panel: the pictures the extension ships, the note's own pictures with a Width slider, and the preview showing it. Screenshot of a WP Note with pictures, and the same note open in its editor. The card on the left, Find the help page with a question-mark icon, reads Every WP node has a help page. Select the node and press the ? in its toolbar, then shows the picture the extension ships: ComfyUI's node toolbar over a WP Context · Character node, with the indigo ? button lit. Under it, The page opens next to the canvas, and a pasted screenshot of two chained Contexts at 70% of the note's width. On the right, the editor's formatting bar has its Pictures button pressed; the panel has a box for what the picture shows, Add a picture… (or paste or drop one into the text), From the extension with a search box for the 53 pictures it ships (its grid shows only what matches), and In this note: Node toolbar with the ? help button at Own size and Two Contexts chained at 70%, each with a Width slider and a remove button, then a hint that pictures are saved in the workflow, made smaller, and never make the note bigger. Below is the note's Markdown with ![Two Contexts chained](wp:image/…){width=70%}.\nA picture is never wider than the note. Its width is a share of the note's, so it grows and shrinks with the note and never makes the note bigger; set it with the panel's Width slider, or Own size for its own width up to the note's. Remove takes it out (Undo brings it back while the text window is open).\nA pasted or dropped picture is saved in the workflow, so a shared workflow shows it too. It is made smaller first: at most 1280 px on its long side and about 400 KB, which also drops camera and location data. A note holds up to six pictures and 2 MB. Pictures never load from a web address, so opening a shared workflow fetches nothing."
},
{
"title": "An add-to-library button",
+1
View File
@@ -26,6 +26,7 @@
"kit:baseline": "WP_WRITE_KIT_BASELINE=1 vitest run scripts/kit-guard.test.mjs",
"test": "vitest run --passWithNoTests",
"docs:help": "WP_WRITE_NODE_HELP=1 vitest run src/manager/docs/__tests__/node-help.test.ts",
"notes:assets": "WP_WRITE_NOTE_ASSETS=1 vitest run src/components/note/note-assets.test.ts",
"docs:index": "WP_WRITE_DOCS_INDEX=1 vitest run src/manager/docs/__tests__/docs-index.test.ts",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage --passWithNoTests",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 163 KiB

+4 -1
View File
@@ -376,7 +376,10 @@ const ENTRY_LIMIT = 30 * 1024; // 30 KB
// syntax ride in the note's lazy editor chunk (dev was already ~636.9 KB).
// RAISED 640 -> 650 KB on 2026-11-10: kit 0.14 -> 0.15.3 (colour picker
// fixes, PostCard samples, overlay-scrollbar room) put the total at ~640.9 KB.
const TOTAL_LIMIT = 650 * 1024; // 650 KB
// RAISED 650 -> 660 KB on 2026-11-12: every docs screenshot can go in a WP
// Note (note-assets.json + the kit's ImageGrid/SearchInput in the note's lazy
// editor chunk) put the total at ~652.9 KB.
const TOTAL_LIMIT = 660 * 1024; // 660 KB
// The total budget is a guide, not a cap (Dumi, 2026-10-06), so the gate has
// two bands: over TOTAL_LIMIT it warns (CI stays green and shows a
// `::warning` annotation), and only past TOTAL_LIMIT + 5% does it fail. A
+162
View File
@@ -0,0 +1,162 @@
<script setup lang="ts">
/**
* A WP Note as it reads on the canvas: its look (card, sticky, label), its
* tone (step number or icon badge), title, marks and text. The node draws
* it, and the note text window draws the same card as its preview, so the
* preview always matches the node's settings.
*/
import { computed } from "vue";
import NoteMarkdown from "./note-markdown";
import { vTooltip } from "@/kit/tooltip";
import type { NoteConfig } from "../../extension/note";
const props = withDefaults(defineProps<{
config: Pick<NoteConfig, "tone" | "look" | "step" | "icon" | "text" | "images" | "bookmark">;
title: string;
pinned?: boolean;
/** What the body says when there is no text yet. */
emptyText?: string;
}>(), { pinned: false, emptyText: "Double-click to write this note." });
const hasTitle = computed(() => props.title.trim() !== "" && props.title !== "WP Note");
/** Any link in the text: a label shows a mark for it. */
const hasLink = computed(() => /\]\((?:https?:\/\/|docs:|wp:)/i.test(props.config.text));
const bookmarkHint = computed(() => {
const b = props.config.bookmark;
return b ? `Also a bookmark${b.shortcut ? ` · ${b.shortcut}` : ""}` : "";
});
</script>
<template>
<div class="wp-note__panel" :class="`wp-note__panel--${config.look}`" data-test="note-panel">
<div class="wp-note__head">
<span v-if="config.tone === 'step'" class="wp-note__step" data-test="note-step">{{ config.step }}</span>
<span v-else-if="config.tone === 'icon'" class="wp-note__badge" aria-hidden="true"><i class="pi" :class="`pi-${config.icon}`"></i></span>
<span v-if="hasTitle || config.look === 'label'" class="wp-note__title" data-test="note-title-text">{{ title }}</span>
<!-- A label keeps its text and docs link for the hover card; these
marks say there is something to see. -->
<span v-if="config.look === 'label' && config.text.trim()" class="wp-note__marks" aria-hidden="true">
<i class="pi pi-align-left" data-test="note-has-text"></i>
<i v-if="hasLink" class="pi pi-link" data-test="note-has-link"></i>
</span>
<i
v-if="pinned"
v-tooltip="'Pinned'"
class="pi pi-thumbtack wp-note__bm wp-note__pinmark"
aria-label="Pinned"
data-test="note-pinned-mark"
></i>
<i
v-if="config.bookmark"
v-tooltip="bookmarkHint"
class="pi pi-bookmark-fill wp-note__bm"
:aria-label="bookmarkHint"
data-test="note-bookmark-mark"
></i>
</div>
<div v-if="config.look !== 'label'" class="wp-note__body">
<NoteMarkdown v-if="config.text.trim()" :text="config.text" :images="config.images" />
<p v-else class="wp-note__empty">{{ emptyText }}</p>
</div>
</div>
</template>
<style scoped>
.wp-note__panel {
position: relative;
box-sizing: border-box;
border-radius: min(12px, var(--wp-radius-cap, 999px));
padding: 12px 14px 12px 16px;
box-shadow: var(--wp-shadow-md, 0 4px 14px rgb(0 0 0 / 0.35));
font-family: var(--wp-font-sans, Inter, sans-serif);
font-size: 13px;
line-height: 20px;
color: var(--wp-text, inherit);
}
/* Card: solid panel, a tone edge on the left. */
.wp-note__panel--card {
background: var(--wp-bg-2);
border: 1px solid var(--wp-border);
overflow: hidden;
}
.wp-note__panel--card::before {
content: "";
position: absolute;
inset: 0 auto 0 0;
width: 4px;
background: var(--wp-note-tone);
}
/* Sticky: tinted with the tone, dashed tone outline inside. */
.wp-note__panel--sticky {
background: color-mix(in srgb, var(--wp-note-tone) 18%, var(--wp-bg-2));
border: 1px solid color-mix(in srgb, var(--wp-note-tone) 55%, transparent);
outline: 1px dashed color-mix(in srgb, var(--wp-note-tone) 45%, transparent);
outline-offset: -6px;
}
/* Label: a small solid tag, title only. Sized to its text (the node follows
it, see widgets/note.ts), up to a cap where the title ellipsizes. */
.wp-note__panel--label {
display: inline-flex;
width: max-content;
max-width: 480px;
padding: 6px 12px 6px 8px;
border-radius: var(--wp-radius-cap, 999px);
background: var(--wp-note-tone);
color: white;
}
.wp-note__head {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
min-height: 24px;
}
.wp-note__step,
.wp-note__badge {
flex: none;
width: 24px;
height: 24px;
border-radius: var(--wp-radius-cap, 999px);
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 700;
}
.wp-note__step { background: var(--wp-note-tone); color: white; }
.wp-note__badge {
background: color-mix(in srgb, var(--wp-note-tone) 22%, transparent);
color: var(--wp-note-tone);
}
.wp-note__badge .pi { font-size: 13px; }
.wp-note__panel--label .wp-note__step,
.wp-note__panel--label .wp-note__badge { background: rgb(255 255 255 / 0.22); color: inherit; }
.wp-note__title {
flex: 1 1 auto;
min-width: 0;
font-size: 15px;
font-weight: 650;
letter-spacing: 0.01em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.wp-note__panel--label .wp-note__title { font-size: 14px; flex: 0 1 auto; }
.wp-note__marks { display: inline-flex; gap: 4px; flex: none; opacity: 0.8; }
.wp-note__marks .pi { font-size: 11px; }
/* Also a bookmark: a small mark in the head (its tooltip names the key). */
.wp-note__bm {
flex: none;
margin-left: auto;
font-size: 12px;
color: var(--wp-note-tone);
pointer-events: auto;
}
.wp-note__bm + .wp-note__bm { margin-left: 2px; }
.wp-note__pinmark { color: var(--wp-text-dim); font-size: 11px; }
.wp-note__panel--label .wp-note__bm { color: inherit; opacity: 0.85; }
.wp-note__body { margin-top: 8px; color: var(--wp-text-muted, inherit); }
.wp-note__empty { margin: 0; color: var(--wp-text-dim); }
</style>
+11 -8
View File
@@ -5,11 +5,13 @@ import NoteMarkdown from "./note-markdown";
import { noteColorCss, type NoteConfig } from "../../extension/note";
/** The hover card of a Label note: its text (with its links and icons) and
* an Edit button. Placed above the label (below when there is no room). */
* an Edit button. Placed above the label (below when there is no room).
* Without an anchor it sits in the page with no Edit button: the note text
* window's preview of a label. */
const props = defineProps<{
config: NoteConfig;
config: Pick<NoteConfig, "text" | "images" | "color">;
title: string;
anchor: DOMRect;
anchor?: DOMRect | undefined;
}>();
const emit = defineEmits<{ (e: "edit"): void }>();
@@ -21,8 +23,8 @@ const EDGE = 8;
function place(): void {
const el = card.value;
if (!el) return;
const a = props.anchor;
if (!el || !a) return;
const w = el.offsetWidth;
const hgt = el.offsetHeight;
let top = a.top - hgt - GAP;
@@ -34,8 +36,7 @@ onMounted(place);
watch(() => [props.anchor, props.config, props.title], () => nextTick(place));
const style = computed(() => ({
left: `${pos.value.left}px`,
top: `${pos.value.top}px`,
...(props.anchor ? { left: `${pos.value.left}px`, top: `${pos.value.top}px` } : {}),
...(props.config.color ? { "--wp-note-tone": noteColorCss(props.config.color) } : {}),
}));
const hasTitle = computed(() => props.title.trim() !== "" && props.title !== "WP Note");
@@ -45,8 +46,9 @@ const hasTitle = computed(() => props.title.trim() !== "" && props.title !== "WP
<div
ref="card"
class="wp-note-peek"
:class="{ 'wp-note-peek--static': !anchor }"
:style="style"
role="tooltip"
:role="anchor ? 'tooltip' : undefined"
data-test="note-peek"
>
<div v-if="hasTitle" class="wp-note-peek__title">{{ title }}</div>
@@ -54,7 +56,7 @@ const hasTitle = computed(() => props.title.trim() !== "" && props.title !== "WP
<NoteMarkdown v-if="config.text.trim()" :text="config.text" :images="config.images" />
<p v-else class="wp-note-peek__empty">No text yet.</p>
</div>
<div class="wp-note-peek__actions">
<div v-if="anchor" class="wp-note-peek__actions">
<Button class="wp-note-peek__edit" size="sm" variant="ghost" icon="pi-pencil" data-test="note-peek-edit" @click="emit('edit')">Edit</Button>
</div>
</div>
@@ -80,6 +82,7 @@ const hasTitle = computed(() => props.title.trim() !== "" && props.title !== "WP
color: var(--wp-text-muted, var(--wp-text));
overflow-wrap: anywhere;
}
.wp-note-peek--static { position: relative; z-index: auto; max-width: 340px; }
.wp-note-peek__title { font-weight: 650; color: var(--wp-text); margin-bottom: 4px; }
.wp-note-peek__empty { margin: 0; color: var(--wp-text-dim); }
.wp-note-peek__actions { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
+10 -1
View File
@@ -1,6 +1,7 @@
import { describe, expect, it } from "vitest";
import { mount } from "@vue/test-utils";
import NoteTextEditor from "./NoteTextEditor.vue";
import { NOTE_ASSETS } from "./note-format";
import { ref } from "vue";
import NoteMarkdown from "./note-markdown";
import { NOTE_ACTIONS, NOTE_CONTEXTS, type NoteActions } from "./note-actions";
@@ -250,8 +251,16 @@ describe("NoteTextEditor pictures", () => {
const area = w.get('[data-test="note-text"]').element as HTMLTextAreaElement;
area.setSelectionRange(5, 5);
await w.get('[data-test="note-fmt-image"]').trigger("click");
await w.get('[data-test="note-asset-toolbar-help"]').trigger("click");
// No grid until something is searched; the box says how many there are.
expect(w.find('[data-test="note-assets"]').exists()).toBe(false);
expect(w.text()).toContain(`${NOTE_ASSETS.length} pictures`);
await w.get('input[aria-label="Find a picture from the extension"]').setValue("help button");
const tiles = w.findAll('[data-test="note-assets"] .wp-image-grid__open');
expect(tiles).toHaveLength(1);
await tiles[0]?.trigger("click");
expect(lastText(w)).toBe("Look:\n\n![Node toolbar with the ? help button](wp:asset/toolbar-help)");
// The panel stays open, now listing the picture with its size controls.
expect(w.find('[data-test="note-image-panel"]').exists()).toBe(true);
w.unmount();
});
+45 -39
View File
@@ -13,6 +13,8 @@ import CATALOG from "../../extension/docs-catalog.json";
import IconPicker from "@/kit/IconPicker.vue";
import NoteMarkdown from "./note-markdown";
import Slider from "@/kit/Slider.vue";
import SearchInput from "@/kit/SearchInput.vue";
import ImageGrid from "@/kit/ImageGrid.vue";
import {
assetImageTarget, assetUrl, communityTarget, docsTarget, findImages, iconMarkdown, imageMarkdown, insert, libraryTarget,
linkMarkdown, NOTE_ASSETS, prefixLines, removeImage, savedImageTarget, setImageWidth, wrap, type Edit, type ImageRef,
@@ -297,7 +299,7 @@ async function addFile(file: File | null): Promise<void> {
if (problem) { pushToast(problem, { severity: "warning" }); return; }
const id = newImageId(props.images);
const alt = pictureAlt.value.trim() || file.name.replace(/\.[a-z0-9]+$/i, "").replace(/[-_]+/g, " ").trim() || "Picture";
panel.value = "";
// The panel stays open: its list now shows the picture with its size.
pictureAlt.value = "";
apply(pictureEdit(imageMarkdown(alt, savedImageTarget(id))), before, { ...props.images, [id]: pic });
} catch (err) {
@@ -312,9 +314,22 @@ function onFile(ev: Event): void {
input.value = "";
void addFile(file);
}
/** The extension's pictures matching the search box; none until something is typed. */
const assetQuery = ref("");
const assetTiles = computed(() => {
const q = assetQuery.value.trim().toLowerCase();
if (!q) return [];
const origin = typeof window === "undefined" ? "" : window.location.origin;
return NOTE_ASSETS.filter((a) => a.label.toLowerCase().includes(q) || a.id.includes(q))
.map((a) => ({ id: a.id, src: assetUrl(a.id, origin) ?? "", alt: a.label, caption: a.label }));
});
function onAssetTile(i: number): void {
const t = assetTiles.value[i];
if (t) insertAsset(t.id, t.alt);
}
function insertAsset(id: string, label: string): void {
const before = { value: sent, start: sel.start, end: sel.end };
panel.value = "";
// The panel stays open: its list now shows the picture with its size.
const alt = pictureAlt.value.trim() || label;
pictureAlt.value = "";
apply(pictureEdit(imageMarkdown(alt, assetImageTarget(id))), before);
@@ -582,20 +597,27 @@ function insertIcon(name: string): void {
<input ref="fileInput" class="wp-note-ed__file" type="file" :accept="ACCEPTED_TYPES.join(',')" tabindex="-1" aria-hidden="true" data-test="note-image-input" @change="onFile" />
</div>
<span class="wp-note-ed__label">From the extension</span>
<div class="wp-note-ed__assets">
<Button
v-for="a in NOTE_ASSETS"
:key="a.id"
variant="secondary"
size="sm"
class="wp-note-ed__asset"
:hint="`Insert: ${a.label}`"
:data-test="`note-asset-${a.id}`"
@click="insertAsset(a.id, a.label)"
>
<img :src="assetUrl(a.id, '') ?? ''" alt="" :width="a.w" :height="a.h" draggable="false" />
<span>{{ a.label }}</span>
</Button>
<SearchInput
v-model="assetQuery"
size="sm"
placeholder="Find a picture…"
aria-label="Find a picture from the extension"
:visible="assetTiles.length"
:total="NOTE_ASSETS.length"
noun="pictures"
test-prefix="note-asset-search"
/>
<div v-if="assetQuery.trim()" class="wp-note-ed__assets" data-test="note-assets">
<ImageGrid
:images="assetTiles"
:lightbox="false"
:aspect="2"
:min-size="150"
gap="sm"
aria-label="Pictures from the extension"
@open="onAssetTile"
/>
<p v-if="!assetTiles.length" class="wp-note-ed__hint">No picture matches “{{ assetQuery }}”.</p>
</div>
<template v-if="shown.length">
<span class="wp-note-ed__label">In this note</span>
@@ -668,7 +690,12 @@ function insertIcon(name: string): void {
<style scoped>
/* The kit picker's list, sized for a node: as wide as the panel, a short
* scroll area, cells packed by width instead of a fixed 8 columns. */
.wp-note-ed__icons :deep(.wp-iconpick__panel) { width: auto; max-height: 184px; }
.wp-note-ed__icons :deep(.wp-iconpick__panel) { max-height: 340px; }
/* As wide as the space it has. The kit fixes the full set's panel at 264 px
(more with a scrollbar) with selectors as strong as ours, so name them. */
.wp-note-ed__icons.wp-iconpick :deep(.wp-iconpick__panel),
.wp-note-ed__icons.wp-iconpick :deep(.wp-iconpick__panel[data-all]),
.wp-note-ed__icons.wp-iconpick :deep(.wp-iconpick__panel[data-all][data-wp-bar~="y"]) { width: auto; }
.wp-note-ed__icons :deep(.wp-iconpick__grid) { grid-template-columns: repeat(auto-fill, minmax(28px, 1fr)); }
.wp-note-ed__icons :deep(.wp-iconpick__cell) { aspect-ratio: auto; height: 28px; }
.wp-note-ed { display: flex; flex-direction: column; gap: 6px; }
@@ -708,29 +735,8 @@ function insertIcon(name: string): void {
.wp-note-ed__iconpick.wp-btn { font-size: 12px; text-transform: capitalize; }
.wp-note-ed__chev { font-size: 9px; color: var(--wp-text-dim); }
.wp-note-ed__file { display: none; }
.wp-note-ed__assets { display: flex; flex-wrap: wrap; gap: 6px; }
.wp-note-ed__assets { max-height: 300px; overflow-y: auto; padding-right: 2px; }
/* A kit Button holding a thumbnail: taller than a control, the picture over its name. */
.wp-note-ed__asset.wp-btn {
flex-direction: column;
align-items: stretch;
gap: 4px;
height: auto;
max-width: 100%;
padding: 4px;
font-size: 11px;
font-weight: 400;
text-align: left;
white-space: normal;
}
.wp-note-ed__asset :deep(.wp-btn__label) {
display: flex;
flex-direction: column;
gap: 4px;
overflow: visible;
white-space: normal;
line-height: 14px;
}
.wp-note-ed__asset img { display: block; width: 100%; max-width: 280px; height: auto; border-radius: min(4px, var(--wp-radius-cap, 999px)); }
.wp-note-ed__pics { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.wp-note-ed__pic { display: flex; gap: 8px; align-items: center; }
.wp-note-ed__thumb {
+27 -3
View File
@@ -3,11 +3,11 @@ import { flushPromises, mount } from "@vue/test-utils";
import { ref } from "vue";
import NoteTextModal from "./NoteTextModal.vue";
import { NOTE_CONTEXTS } from "./note-actions";
import type { NoteImage } from "../../extension/note";
import { defaultNoteConfig, type NoteConfig, type NoteImage } from "../../extension/note";
function make(text = "Hello", images: Record<string, NoteImage> = {}) {
function make(text = "Hello", images: Record<string, NoteImage> = {}, more: Partial<NoteConfig> = {}, width?: number) {
return mount(NoteTextModal, {
props: { text, images, title: "Read me", look: "card", toneCss: "" },
props: { config: { ...defaultNoteConfig(), text, images, ...more }, title: "Read me", width },
attachTo: document.body,
global: { provide: { [NOTE_CONTEXTS as symbol]: ref([]) } },
});
@@ -42,6 +42,30 @@ describe("NoteTextModal", () => {
w.unmount();
});
it("previews the note with the node's own settings", async () => {
const w = make("Body", {}, { tone: "step", step: 3, look: "sticky", color: "#ff0000" }, 320);
await flushPromises();
const panel = $('[data-test="note-text-preview"] [data-test="note-panel"]');
expect(panel?.classList.contains("wp-note__panel--sticky")).toBe(true);
expect($('[data-test="note-text-preview"] [data-test="note-step"]')?.textContent).toBe("3");
expect($('[data-test="note-text-preview"] [data-test="note-title-text"]')?.textContent).toBe("Read me");
const card = panel?.parentElement;
expect(card?.style.width).toBe("320px");
expect(card?.style.getPropertyValue("--wp-note-tone")).toBe("#ff0000");
w.unmount();
});
it("previews a label as its tag plus the hover card with the text", async () => {
const w = make("**Hidden** until hover", {}, { look: "label" });
await flushPromises();
expect($('[data-test="note-text-preview"] .wp-note__panel--label')?.textContent).toContain("Read me");
expect($('[data-test="note-text-preview"] .wp-note__body')).toBeNull();
const peek = $('[data-test="note-text-preview"] [data-test="note-peek"]');
expect(peek?.textContent).toContain("Hidden until hover");
expect($('[data-test="note-peek-edit"]')).toBeNull();
w.unmount();
});
it("writes the draft on Done", async () => {
const w = make("old");
await flushPromises();
+42 -32
View File
@@ -1,7 +1,9 @@
<script setup lang="ts">
/**
* WP Note's text in its own window: the Markdown editor on the left, the
* note as it will read on the right, updated as you type. It edits a draft
* note as it will read on the right, updated as you type. The preview is
* the node's own card (its look, tone, colour, title and width); a label
* shows its tag and the hover card that holds its text. It edits a draft
* (text + pictures); Done writes it to the node, Cancel drops it. The
* frame is the kit's Modal.
*/
@@ -10,30 +12,36 @@ import Button from "@/kit/Button.vue";
import ConfirmModal from "@/kit/ConfirmModal.vue";
import Kbd from "@/kit/Kbd.vue";
import Modal from "@/kit/Modal.vue";
import NoteMarkdown from "./note-markdown";
import NoteCard from "./NoteCard.vue";
import NotePeek from "./NotePeek.vue";
import NoteTextEditor from "./NoteTextEditor.vue";
import type { NoteImage, NoteLook } from "../../extension/note";
import { noteColorCss, type NoteConfig, type NoteImage } from "../../extension/note";
const props = defineProps<{
text: string;
images: Record<string, NoteImage>;
/** The note's title, for the window's header. */
/** The note's settings; its text and pictures are the draft's start. */
config: NoteConfig;
/** The note's title, for the window's header and the preview. */
title: string;
look: NoteLook;
/** The note's colour as CSS (`""` = the accent). */
toneCss: string;
pinned?: boolean | undefined;
/** The node's width in px, so the preview wraps (and sizes pictures) as the node does. */
width?: number | undefined;
}>();
const emit = defineEmits<{
(e: "done", v: { text: string; images: Record<string, NoteImage> }): void;
(e: "close"): void;
}>();
const text = ref(props.text);
const images = ref<Record<string, NoteImage>>({ ...props.images });
const dirty = computed(() => text.value !== props.text || Object.keys(images.value).join() !== Object.keys(props.images).join());
const text = ref(props.config.text);
const images = ref<Record<string, NoteImage>>({ ...props.config.images });
const dirty = computed(() => text.value !== props.config.text || Object.keys(images.value).join() !== Object.keys(props.config.images).join());
/** The note's colour as CSS (`""` = the accent). */
const toneCss = computed(() => (props.config.color ? noteColorCss(props.config.color) : ""));
/** The node as it will look with the draft's text. */
const draft = computed<NoteConfig>(() => ({ ...props.config, text: text.value, images: images.value }));
const toneStyle = computed(() => (toneCss.value ? { "--wp-note-tone": toneCss.value } : {}));
const shownTitle = computed(() => (props.title.trim() ? props.title : "WP Note"));
const isLabel = computed(() => props.look === "label");
const isLabel = computed(() => props.config.look === "label");
function done(): void {
emit("done", { text: text.value, images: images.value });
@@ -85,9 +93,17 @@ function onKey(ev: KeyboardEvent): void {
</section>
<section class="wp-note-tx__pane" aria-label="Preview">
<span class="wp-note-tx__label">Preview</span>
<div class="wp-note-tx__card" :style="toneCss ? { '--wp-note-tone': toneCss } : undefined" data-test="note-text-preview">
<NoteMarkdown v-if="text.trim()" :text="text" :images="images" />
<p v-else class="wp-note-tx__empty">Nothing written yet.</p>
<div class="wp-note-tx__stage" data-test="note-text-preview">
<template v-if="isLabel">
<div :style="toneStyle">
<NoteCard :config="draft" :title="title" :pinned="pinned" />
</div>
<span class="wp-note-tx__label">When you point at the label</span>
<NotePeek :config="draft" :title="title" />
</template>
<div v-else class="wp-note-tx__card" :style="[toneStyle, width ? { width: `${Math.round(width)}px` } : {}]">
<NoteCard :config="draft" :title="title" :pinned="pinned" empty-text="Nothing written yet." />
</div>
</div>
</section>
</div>
@@ -137,28 +153,22 @@ function onKey(ev: KeyboardEvent): void {
}
.wp-note-tx__pane { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.wp-note-tx__label { font-size: 11px; font-weight: 600; color: var(--wp-text-muted); }
/* The note as a card, the way the node shows it: its colour on the left edge. */
.wp-note-tx__card {
/* The note on a canvas-like backdrop, at the node's width (never wider than the pane). */
.wp-note-tx__stage {
--wp-note-tone: var(--wp-accent);
position: relative;
flex: 1 1 auto;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
min-height: 120px;
max-height: 62vh;
overflow: auto;
padding: 12px 14px 12px 18px;
padding: 14px;
border: 1px solid var(--wp-border);
border-radius: min(12px, var(--wp-radius-cap, 999px));
background: var(--wp-bg-1, var(--wp-bg-2));
font-size: 13px;
line-height: 20px;
color: var(--wp-text-muted, var(--wp-text));
background: var(--wp-bg-0, var(--wp-bg-1));
}
.wp-note-tx__card::before {
content: "";
position: absolute;
inset: 0 auto 0 0;
width: 4px;
background: var(--wp-note-tone);
}
.wp-note-tx__empty { margin: 0; color: var(--wp-text-dim); }
.wp-note-tx__card { max-width: 100%; min-width: min(240px, 100%); }
.wp-note-tx__stage > .wp-note-tx__label { margin-top: 6px; }
</style>
+14 -138
View File
@@ -7,7 +7,7 @@ import WpNumber from "@/kit/WpNumber.vue";
import WpSegmented from "@/kit/WpSegmented.vue";
import WpSwitch from "@/kit/WpSwitch.vue";
import RadioGroup from "@/kit/RadioGroup.vue";
import NoteMarkdown from "./note-markdown";
import NoteCard from "./NoteCard.vue";
// The text and its preview edit in their own window (loaded on first open).
const NoteTextModal = defineAsyncComponent(() => import("./NoteTextModal.vue"));
import { NOTE_ACTIONS, NOTE_CONTEXTS } from "./note-actions";
@@ -15,7 +15,6 @@ import { useNoteLibrary } from "./note-library-state";
import { prunePictures } from "./note-images";
import type { NoteImage } from "../../extension/note";
import IconPicker from "@/kit/IconPicker.vue";
import { vTooltip } from "@/kit/tooltip";
import BookmarkFields from "../bookmark/BookmarkFields.vue";
import type { BookmarkJump, KeyOwner } from "../../extension/bookmarks";
import {
@@ -56,7 +55,6 @@ const library = useNoteLibrary(() => props.config.text, () => props.editing);
provide(NOTE_ACTIONS, library.actions);
provide(NOTE_CONTEXTS, library.contexts);
const hasTitle = computed(() => props.title.trim() !== "" && props.title !== "WP Note");
const toneStyle = computed(() => (props.config.color ? { "--wp-note-tone": noteColorCss(props.config.color) } : undefined));
const isHex = computed(() => props.config.color.startsWith("#"));
/** The preset swatch that is picked (the accent is `""`); none for a custom hex. */
@@ -95,13 +93,12 @@ function onBookmark(on: boolean): void {
function onJump(part: Partial<BookmarkJump>): void {
if (props.config.bookmark) patch({ bookmark: { ...props.config.bookmark, ...part } });
}
const bookmarkHint = computed(() => {
const b = props.config.bookmark;
return b ? `Also a bookmark${b.shortcut ? ` · ${b.shortcut}` : ""}` : "";
});
// ── text window ────────────────────────────────────────────────────
const textOpen = ref(false);
/** The node's width when the window opened, so its preview wraps the same way. */
const textWidth = ref<number | undefined>(undefined);
watch(textOpen, (on) => { if (on) textWidth.value = root.value?.offsetWidth || undefined; });
function onText(next: { text: string; images: Record<string, NoteImage> }): void {
textOpen.value = false;
patch({ text: next.text, images: prunePictures(next.text, next.images) });
@@ -121,9 +118,6 @@ const textCount = computed(() => {
const pics = Object.keys(props.config.images).length;
return [`${lines} line${lines === 1 ? "" : "s"}`, pics ? `${pics} picture${pics === 1 ? "" : "s"}` : ""].filter(Boolean).join(" · ");
});
/** Any link in the text: a label shows a mark for it. */
const hasLink = computed(() => /\]\((?:https?:\/\/|docs:|wp:)/i.test(props.config.text));
</script>
<template>
@@ -136,37 +130,7 @@ const hasLink = computed(() => /\]\((?:https?:\/\/|docs:|wp:)/i.test(props.confi
>
<!-- Read view. The panel lets clicks through to the canvas (drag,
select, double-click to edit); only its buttons take them. -->
<div v-if="!editing" class="wp-note__panel" data-test="note-panel">
<div class="wp-note__head">
<span v-if="config.tone === 'step'" class="wp-note__step" data-test="note-step">{{ config.step }}</span>
<span v-else-if="config.tone === 'icon'" class="wp-note__badge" aria-hidden="true"><i class="pi" :class="`pi-${config.icon}`"></i></span>
<span v-if="hasTitle || config.look === 'label'" class="wp-note__title" data-test="note-title-text">{{ title }}</span>
<!-- A label keeps its text and docs link for the hover card; these
marks say there is something to see. -->
<span v-if="config.look === 'label' && config.text.trim()" class="wp-note__marks" aria-hidden="true">
<i class="pi pi-align-left" data-test="note-has-text"></i>
<i v-if="hasLink" class="pi pi-link" data-test="note-has-link"></i>
</span>
<i
v-if="pinned"
v-tooltip="'Pinned'"
class="pi pi-thumbtack wp-note__bm wp-note__pinmark"
aria-label="Pinned"
data-test="note-pinned-mark"
></i>
<i
v-if="config.bookmark"
v-tooltip="bookmarkHint"
class="pi pi-bookmark-fill wp-note__bm"
:aria-label="bookmarkHint"
data-test="note-bookmark-mark"
></i>
</div>
<div v-if="config.look !== 'label'" class="wp-note__body">
<NoteMarkdown v-if="config.text.trim()" :text="config.text" :images="config.images" />
<p v-else class="wp-note__empty">Double-click to write this note.</p>
</div>
</div>
<NoteCard v-if="!editing" :config="config" :title="title" :pinned="pinned" />
<!-- Keys typed here never reach ComfyUI's keybindings (Delete, Ctrl+Z,
single-letter shortcuts…). Escape still closes an open picker:
@@ -309,11 +273,10 @@ const hasLink = computed(() => /\]\((?:https?:\/\/|docs:|wp:)/i.test(props.confi
</form>
<NoteTextModal
v-if="textOpen"
:text="config.text"
:images="config.images"
:config="config"
:title="title"
:look="config.look"
:tone-css="config.color ? noteColorCss(config.color) : ''"
:pinned="pinned"
:width="textWidth"
@done="onText"
@close="textOpen = false"
/>
@@ -323,7 +286,12 @@ const hasLink = computed(() => /\]\((?:https?:\/\/|docs:|wp:)/i.test(props.confi
<style scoped>
/* The kit picker's list, sized for a node: as wide as the panel, a short
* scroll area, cells packed by width instead of a fixed 8 columns. */
.wp-note__icons :deep(.wp-iconpick__panel) { width: auto; max-height: 184px; }
.wp-note__icons :deep(.wp-iconpick__panel) { max-height: 184px; }
/* As wide as the space it has. The kit fixes the full set's panel at 264 px
(more with a scrollbar) with selectors as strong as ours, so name them. */
.wp-note__icons.wp-iconpick :deep(.wp-iconpick__panel),
.wp-note__icons.wp-iconpick :deep(.wp-iconpick__panel[data-all]),
.wp-note__icons.wp-iconpick :deep(.wp-iconpick__panel[data-all][data-wp-bar~="y"]) { width: auto; }
.wp-note__icons :deep(.wp-iconpick__grid) { grid-template-columns: repeat(auto-fill, minmax(28px, 1fr)); }
.wp-note__icons :deep(.wp-iconpick__cell) { aspect-ratio: auto; height: 28px; }
.wp-note {
@@ -338,98 +306,6 @@ const hasLink = computed(() => /\]\((?:https?:\/\/|docs:|wp:)/i.test(props.confi
pointer-events: none;
}
/* ── panel looks ─────────────────────────────────────────────────── */
.wp-note__panel {
position: relative;
border-radius: min(12px, var(--wp-radius-cap, 999px));
padding: 12px 14px 12px 16px;
box-shadow: var(--wp-shadow-md, 0 4px 14px rgb(0 0 0 / 0.35));
}
/* Card: solid panel, a tone edge on the left. */
.wp-note--card .wp-note__panel {
background: var(--wp-bg-2);
border: 1px solid var(--wp-border);
overflow: hidden;
}
.wp-note--card .wp-note__panel::before {
content: "";
position: absolute;
inset: 0 auto 0 0;
width: 4px;
background: var(--wp-note-tone);
}
/* Sticky: tinted with the tone, dashed tone outline inside. */
.wp-note--sticky .wp-note__panel {
background: color-mix(in srgb, var(--wp-note-tone) 18%, var(--wp-bg-2));
border: 1px solid color-mix(in srgb, var(--wp-note-tone) 55%, transparent);
outline: 1px dashed color-mix(in srgb, var(--wp-note-tone) 45%, transparent);
outline-offset: -6px;
}
/* Label: a small solid tag, title only. Sized to its text (the node follows
it, see widgets/note.ts), up to a cap where the title ellipsizes. */
.wp-note--label .wp-note__panel {
display: inline-flex;
width: max-content;
max-width: 480px;
padding: 6px 12px 6px 8px;
border-radius: var(--wp-radius-cap, 999px);
background: var(--wp-note-tone);
color: white;
}
.wp-note__head {
display: flex;
align-items: center;
gap: 8px;
min-height: 24px;
}
.wp-note__step,
.wp-note__badge {
flex: none;
width: 24px;
height: 24px;
border-radius: var(--wp-radius-cap, 999px);
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 700;
}
.wp-note__step { background: var(--wp-note-tone); color: white; }
.wp-note__badge {
background: color-mix(in srgb, var(--wp-note-tone) 22%, transparent);
color: var(--wp-note-tone);
}
.wp-note__badge .pi { font-size: 13px; }
.wp-note--label .wp-note__step,
.wp-note--label .wp-note__badge { background: rgb(255 255 255 / 0.22); color: inherit; }
.wp-note__title {
flex: 1 1 auto;
min-width: 0;
font-size: 15px;
font-weight: 650;
letter-spacing: 0.01em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.wp-note--label .wp-note__title { font-size: 14px; flex: 0 1 auto; }
.wp-note__marks { display: inline-flex; gap: 4px; flex: none; opacity: 0.8; }
.wp-note__marks .pi { font-size: 11px; }
/* Also a bookmark: a small mark in the head (its tooltip names the key). */
.wp-note__bm {
flex: none;
margin-left: auto;
font-size: 12px;
color: var(--wp-note-tone);
pointer-events: auto;
}
.wp-note__bm + .wp-note__bm { margin-left: 2px; }
.wp-note__pinmark { color: var(--wp-text-dim); font-size: 11px; }
.wp-note--label .wp-note__bm { color: inherit; opacity: 0.85; }
.wp-note__body { margin-top: 8px; color: var(--wp-text-muted, inherit); }
.wp-note__empty { margin: 0; color: var(--wp-text-dim); }
/* ── edit form ───────────────────────────────────────────────────── */
/* The form lets presses on its background through, so the note drags while
* it is open (frameless-node.css: wp-frameless__pass). */
+373
View File
@@ -0,0 +1,373 @@
[
{
"id": "toolbar-help",
"label": "Node toolbar with the ? help button",
"file": "note/toolbar-help.png",
"w": 610,
"h": 116
},
{
"id": "ai-setup",
"label": "AI assistant setup in Settings",
"file": "docs/ai-setup.png",
"w": 1720,
"h": 860
},
{
"id": "assembler-negative",
"label": "Assembler with its negative output",
"file": "docs/assembler-negative.png",
"w": 1720,
"h": 752
},
{
"id": "bundles",
"label": "A bundle inside a Context",
"file": "docs/bundles.png",
"w": 1720,
"h": 752
},
{
"id": "combine",
"label": "Combine editor",
"file": "docs/combine.png",
"w": 1720,
"h": 860
},
{
"id": "conflicts-warning",
"label": "Missing variable warning on a Context row",
"file": "docs/conflicts-warning.png",
"w": 1720,
"h": 644
},
{
"id": "constraint-matrix",
"label": "Constraint rule matrix",
"file": "docs/constraint-matrix.png",
"w": 1720,
"h": 860
},
{
"id": "context-chaining",
"label": "Two chained Contexts into an Assembler",
"file": "docs/context-chaining.png",
"w": 1720,
"h": 644
},
{
"id": "derivation",
"label": "Derivation editor",
"file": "docs/derivation.png",
"w": 1720,
"h": 860
},
{
"id": "fixed-values",
"label": "Fixed value editor",
"file": "docs/fixed-values.png",
"w": 1720,
"h": 860
},
{
"id": "introduction-graph",
"label": "A first Wildcard Pipeline graph",
"file": "docs/introduction-graph.png",
"w": 1720,
"h": 752
},
{
"id": "library-health",
"label": "Library health page",
"file": "docs/library-health.png",
"w": 1720,
"h": 860
},
{
"id": "loop-frames",
"label": "Context Loop frames",
"file": "docs/loop-frames.png",
"w": 1720,
"h": 752
},
{
"id": "loop-sweep",
"label": "Context Loop sweep",
"file": "docs/loop-sweep.png",
"w": 1720,
"h": 860
},
{
"id": "quick-start-graph",
"label": "Quick start graph",
"file": "docs/quick-start-graph.png",
"w": 1720,
"h": 752
},
{
"id": "quick-start-starter",
"label": "Quick start: the starter library",
"file": "docs/quick-start-starter.png",
"w": 1720,
"h": 1625
},
{
"id": "spa-manager",
"label": "The library manager",
"file": "docs/spa_manager.png",
"w": 1720,
"h": 860
},
{
"id": "test-runner",
"label": "Test runner",
"file": "docs/test-runner.png",
"w": 1720,
"h": 860
},
{
"id": "wildcard-constraint-order",
"label": "Wildcard, constraint and target order",
"file": "docs/wildcard-constraint-order.png",
"w": 1720,
"h": 644
},
{
"id": "wildcard-editor",
"label": "Wildcard editor",
"file": "docs/wildcard-editor.png",
"w": 1720,
"h": 860
},
{
"id": "wildcard-import",
"label": "Wildcard import review",
"file": "docs/wildcard-import.png",
"w": 1720,
"h": 1075
},
{
"id": "wp-bookmark-dock",
"label": "Bookmarks dock",
"file": "docs/wp-bookmark-dock.png",
"w": 1720,
"h": 752
},
{
"id": "wp-bookmark",
"label": "WP Bookmark editing",
"file": "docs/wp-bookmark.png",
"w": 1720,
"h": 752
},
{
"id": "wp-context-injector",
"label": "WP Context Injector",
"file": "docs/wp-context-injector.png",
"w": 1720,
"h": 752
},
{
"id": "wp-context-loop-graph",
"label": "WP Context Loop in a graph",
"file": "docs/wp-context-loop-graph.png",
"w": 1720,
"h": 860
},
{
"id": "wp-context-loop-grid",
"label": "Looped workflow and its image grid",
"file": "docs/wp-context-loop-grid.png",
"w": 1720,
"h": 752
},
{
"id": "wp-context",
"label": "WP Context nodes",
"file": "docs/wp-context.png",
"w": 1720,
"h": 752
},
{
"id": "wp-debug-two",
"label": "Two WP Debug nodes",
"file": "docs/wp-debug-two.png",
"w": 1720,
"h": 860
},
{
"id": "wp-debug-viewer",
"label": "WP Debug variables",
"file": "docs/wp-debug-viewer.png",
"w": 1720,
"h": 860
},
{
"id": "wp-image-filter-grid",
"label": "Image Filter: table layout",
"file": "docs/wp-image-filter-grid.png",
"w": 1720,
"h": 644
},
{
"id": "wp-image-filter-labels",
"label": "Image Filter: labels",
"file": "docs/wp-image-filter-labels.png",
"w": 1720,
"h": 860
},
{
"id": "wp-image-filter-mask",
"label": "Image Filter: mask painter",
"file": "docs/wp-image-filter-mask.png",
"w": 1720,
"h": 860
},
{
"id": "wp-image-filter-picker",
"label": "Image Filter: picking",
"file": "docs/wp-image-filter-picker.png",
"w": 1720,
"h": 860
},
{
"id": "wp-image-filter-refine",
"label": "Image Filter: detail view",
"file": "docs/wp-image-filter-refine.png",
"w": 1720,
"h": 860
},
{
"id": "wp-image-filter-zoom",
"label": "Image Filter: zoom",
"file": "docs/wp-image-filter-zoom.png",
"w": 1720,
"h": 860
},
{
"id": "wp-image-filter",
"label": "WP Image Filter",
"file": "docs/wp-image-filter.png",
"w": 1720,
"h": 860
},
{
"id": "wp-label-grid",
"label": "WP Label & Grid",
"file": "docs/wp-label-grid.png",
"w": 1720,
"h": 860
},
{
"id": "wp-mode-relay",
"label": "WP Mode Relay",
"file": "docs/wp-mode-relay.png",
"w": 1720,
"h": 752
},
{
"id": "wp-model-info",
"label": "WP Model Info",
"file": "docs/wp-model-info.png",
"w": 1720,
"h": 752
},
{
"id": "wp-node-group",
"label": "WP Node Group",
"file": "docs/wp-node-group.png",
"w": 1720,
"h": 752
},
{
"id": "wp-note-bookmark",
"label": "A note with a bookmark",
"file": "docs/wp-note-bookmark.png",
"w": 1720,
"h": 860
},
{
"id": "wp-note-editor",
"label": "Note text window",
"file": "docs/wp-note-editor.png",
"w": 1720,
"h": 860
},
{
"id": "wp-note-library",
"label": "Note library",
"file": "docs/wp-note-library.png",
"w": 1720,
"h": 860
},
{
"id": "wp-note-pictures",
"label": "Pictures in a note",
"file": "docs/wp-note-pictures.png",
"w": 1720,
"h": 860
},
{
"id": "wp-note",
"label": "WP Note",
"file": "docs/wp-note.png",
"w": 1720,
"h": 752
},
{
"id": "wp-prompt-assembler",
"label": "WP Prompt Assembler",
"file": "docs/wp-prompt-assembler.png",
"w": 1720,
"h": 860
},
{
"id": "wp-prompt-cleaner",
"label": "WP Prompt Cleaner",
"file": "docs/wp-prompt-cleaner.png",
"w": 1720,
"h": 860
},
{
"id": "wp-seed-list",
"label": "WP Seed List",
"file": "docs/wp-seed-list.png",
"w": 1720,
"h": 752
},
{
"id": "wp-toggles",
"label": "WP Toggles",
"file": "docs/wp-toggles.png",
"w": 1720,
"h": 860
},
{
"id": "wp-var-to-bool",
"label": "WP Var to Bool",
"file": "docs/wp-var-to-bool.png",
"w": 1720,
"h": 752
},
{
"id": "wp-var-to-choice",
"label": "WP Var to Choice",
"file": "docs/wp-var-to-choice.png",
"w": 1720,
"h": 752
},
{
"id": "wp-var-to-float",
"label": "WP Var to Float",
"file": "docs/wp-var-to-float.png",
"w": 1720,
"h": 752
},
{
"id": "wp-var-to-int",
"label": "WP Var to Int",
"file": "docs/wp-var-to-int.png",
"w": 1720,
"h": 644
}
]
+57
View File
@@ -0,0 +1,57 @@
/**
* `note-assets.json` lists the pictures a note can show from the extension:
* the note pictures plus every docs screenshot, with their pixel sizes (so a
* note lays out before they load). It must match the files on disk; run
* `pnpm notes:assets` after adding, removing or re-shooting a picture
* (labels already in the file are kept, so they can be written by hand).
*/
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it } from "vitest";
import listed from "./note-assets.json";
interface Asset { id: string; label: string; file: string; w: number; h: number }
const ROOT = resolve(__dirname, "../../../public/images");
const FOLDERS = ["note", "docs"];
const JSON_PATH = resolve(__dirname, "note-assets.json");
function pngSize(path: string): { w: number; h: number } {
const b = readFileSync(path);
return { w: b.readUInt32BE(16), h: b.readUInt32BE(20) };
}
/** "wp-context-loop-grid.png" → "WP Context Loop grid". */
function labelFor(stem: string): string {
const words = stem.split(/[-_]+/).filter(Boolean);
const out = words.map((w, i) => (w === "wp" ? "WP" : i === 0 ? w[0]?.toUpperCase() + w.slice(1) : w));
return out.join(" ");
}
function onDisk(old: readonly Asset[]): Asset[] {
const labels = new Map(old.map((a) => [a.file, a.label]));
const out: Asset[] = [];
for (const folder of FOLDERS) {
for (const name of readdirSync(resolve(ROOT, folder)).sort()) {
if (!name.endsWith(".png")) continue;
const stem = name.replace(/\.png$/, "");
const file = `${folder}/${name}`;
const id = stem.toLowerCase().replace(/[^a-z0-9]+/g, "-");
out.push({ id, label: labels.get(file) ?? labelFor(stem), file, ...pngSize(resolve(ROOT, file)) });
}
}
return out;
}
describe("note-assets.json", () => {
it("lists every note and docs picture with its size", () => {
const want = onDisk(listed);
if (process.env.WP_WRITE_NOTE_ASSETS) writeFileSync(JSON_PATH, `${JSON.stringify(want, null, 2)}\n`);
else expect(listed, "note-assets.json is stale: run `pnpm notes:assets`").toEqual(want);
});
it("gives every picture a unique id a note can name", () => {
const ids = listed.map((a) => a.id);
expect(new Set(ids).size).toBe(ids.length);
for (const id of ids) expect(id).toMatch(/^[a-z0-9-]{1,64}$/);
});
});
+7 -6
View File
@@ -24,6 +24,7 @@
* Pure functions: the renderer (note-markdown.ts) and the editor share them.
*/
import { PI_ICON_SET } from "@/kit/pi-icons";
import noteAssets from "./note-assets.json";
const HEX = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
@@ -176,11 +177,11 @@ export function insert(value: string, start: number, end: number, text: string):
// ── pictures ───────────────────────────────────────────────────────────
/** The pictures the extension ships, served from `/wp/images/note/`. A fixed
* list: a note can't name any other file. */
export const NOTE_ASSETS: ReadonlyArray<{ id: string; label: string; file: string; w: number; h: number }> = [
{ id: "toolbar-help", label: "Node toolbar with the ? help button", file: "toolbar-help.png", w: 610, h: 116 },
];
/** The pictures the extension ships, served from `/wp/images/`: the note
* pictures and every docs screenshot (`note-assets.json`, kept in step with
* the files by `note-assets.test.ts`). A fixed list: a note can't name any
* other file. */
export const NOTE_ASSETS: ReadonlyArray<{ id: string; label: string; file: string; w: number; h: number }> = noteAssets;
export type ImageTarget =
| { kind: "saved"; id: string }
@@ -204,7 +205,7 @@ export function parseImageTarget(src: string): ImageTarget {
export function assetUrl(id: string, origin: string): string | null {
const a = NOTE_ASSETS.find((x) => x.id === id);
return a ? `${origin}/wp/images/note/${a.file}` : null;
return a ? `${origin}/wp/images/${a.file}` : null;
}
export const IMAGE_WIDTH_MIN = 10;
@@ -63,6 +63,11 @@ describe("note pictures", () => {
expect(w.get('[data-test="note-image"]').attributes("src")).toMatch(/\/wp\/images\/note\/toolbar-help\.png$/);
});
it("draws a docs screenshot too, named by its file", () => {
const w = draw("![Context](wp:asset/wp-context-loop-grid)");
expect(w.get('[data-test="note-image"]').attributes("src")).toMatch(/\/wp\/images\/docs\/wp-context-loop-grid\.png$/);
});
it("never loads a web address, an unknown asset or a missing picture", () => {
const w = draw("![a](https://example.com/x.png) ![b](wp:asset/nope) ![c](wp:image/00000000) ![d](data:image/png;base64,AAAA)");
expect(w.find("img").exists()).toBe(false);
+2 -2
View File
@@ -88,11 +88,11 @@ export const FIGURE_TEXT: Record<string, string> = {
"wp-seed-list-flow":
"Flow diagram of three nodes left to right: WP Context Loop (N prompts), an arrow labelled loop_config, WP Seed List (N seeds), an arrow labelled paired in order, and KSampler (N different images). Caption: wire the loop's loop_config into the Seed List and its seed output into the sampler's seed.",
"images/docs/wp-note-editor.png":
"A WP Note open in its editor next to a finished Step card. The editor has Tone (Step, Icon, Plain) with the step number 2, Look (Card, Sticky, Label) with the Pinned switch, a row of colour presets with the custom colour #ef4444, the Title Label each frame, and the Text with its formatting bar: undo and redo (both lit), bold, italic, strikethrough, code, bulleted and numbered list, quote, link (pressed), icon and a preview eye. The link panel is open: Docs page is picked, the page is WP Label & Grid and the section Set it up, the text is Set up the grid, the style is Button with the Th Large icon, and Colour is #22c55e (None follows the note), with Cancel and Insert link. Under it is the note's Markdown and a Done button. The card on the right, step 1 Load your model, reads Pick the checkpoint in the loader. $model_family follows it, so the prompt fits the model, with two link buttons: WP Model Info in the note's colour and Writing prompts for your model in green.",
"Screenshot of a WP Note's text window, titled Load your model with a NOTE TEXT chip. On the left, under Write, the formatting bar has its link button pressed and the link panel open: Docs is picked, the page is Seeds & loops (Whole page), with an optional Text box, the Button style with the Book icon, Colour none (None follows the note), Cancel and Insert link; under it is the note's Markdown. On the right, under Preview, is the note as the node draws it: a Card with the accent edge, the step badge 1 and the title Load your model, then Pick the checkpoint in the loader. $model_family follows it, so the prompt fits the model, a bullet, a ticked and an open task, a tip callout Lock the seed before you compare two settings, and a WP Model Info button. At the bottom: Ctrl+Enter Done, Cancel and Done.",
"images/docs/wp-note-library.png":
"Screenshot of the example workflow One image with the review dialog open over its Start here note, whose button reads Add this workflow's lists to my library. The dialog is titled Add to your library, From this workflow, and lists under New: 6 bundles and 15 modules each item with its kind and a grey EXAMPLE chip: the bundles Character, Look, Outfit, Shared lists, Scene and LoRA swaps, then the wildcards Hero, Hair, Eyes, Outfit, Colors, Setting and Time of day and the fixed values Style. At the bottom are Cancel and a primary button Add 6 bundles and 15 modules.",
"images/docs/wp-note-pictures.png":
"Screenshot of a WP Note with pictures, and the same note open in its editor. The card on the left, Find the help page with a question-mark icon, reads Every WP node has a help page. Select the node and press the ? in its toolbar, then shows the picture the extension ships: ComfyUI's node toolbar over a WP Context · Character node, with the indigo ? button lit. Under it, The page opens next to the canvas, and a pasted screenshot of two chained Contexts at 70% of the note's width. On the right, the editor's formatting bar has its Pictures button pressed; the panel has a box for what the picture shows, Add a picture… (or paste or drop one into the text), From the extension with the toolbar picture, and In this note: Node toolbar with the ? help button at Own size and Two Contexts chained at 70%, each with a Width slider and a remove button, then a hint that pictures are saved in the workflow, made smaller, and never make the note bigger. Below is the note's Markdown with ![Two Contexts chained](wp:image/…){width=70%}.",
"Screenshot of a WP Note with pictures, and the same note open in its editor. The card on the left, Find the help page with a question-mark icon, reads Every WP node has a help page. Select the node and press the ? in its toolbar, then shows the picture the extension ships: ComfyUI's node toolbar over a WP Context · Character node, with the indigo ? button lit. Under it, The page opens next to the canvas, and a pasted screenshot of two chained Contexts at 70% of the note's width. On the right, the editor's formatting bar has its Pictures button pressed; the panel has a box for what the picture shows, Add a picture… (or paste or drop one into the text), From the extension with a search box for the 53 pictures it ships (its grid shows only what matches), and In this note: Node toolbar with the ? help button at Own size and Two Contexts chained at 70%, each with a Width slider and a remove button, then a hint that pictures are saved in the workflow, made smaller, and never make the note bigger. Below is the note's Markdown with ![Two Contexts chained](wp:image/…){width=70%}.",
"images/docs/wp-note-bookmark.png":
"Screenshot of WP Notes used as bookmarks. On the left, the Bookmarks dock lists three rows: 1 Load your model (its step number 1 in a blue circle, Alt + 1, highlighted after a jump), 2 Sampler (an amber lightning icon, Alt + 3) and 3 Label each frame (its step number 2 in a red circle, Alt + 2). In the middle, the note Label each frame is open in its editor (Step 2, Card, colour #ef4444, its title and Markdown text); at the bottom the Also a bookmark switch is on, with the hint Jump here from the Bookmarks dock or a shortcut, and under it Lands at with the centre of a 3 × 3 grid picked (Centre of the screen, 24 px in) and a Use this view button, Zoom set to 1 ×, and the shortcut Alt + 2, then Done. On the right, the note card Load your model (step 1, blue) shows a small bookmark mark in its head next to the pencil, and below it a WP Bookmark, Sampler, as a pill: a lightning icon, its name and Alt + 3.",
"images/docs/wp-note.png":
+1 -1
View File
@@ -208,7 +208,7 @@ export const NODE_FIELDS = {
{ term: "Lists and quote", desc: "Puts - , 1. or > before each selected line (press again to take it off)." },
{ term: "Link", kbd: "Ctrl+K", desc: "A Docs page (type to find any page of these docs, then pick a section or the whole page), a Web address, a Library button (below) or a Community pack (owner/pack, or paste the pack's address), drawn as a Button with an icon or as a Text link, in its own colour or the note's. The selected text becomes the link's text. It goes where the caret is when you press Insert link, so you can click in the text after opening the panel." },
{ term: "Icon", desc: "Any PrimeIcons icon, searchable, in a colour or the text's own, inserted where the caret is." },
{ term: "Pictures", desc: "Add a picture from a file, or paste or drop one into the text; it goes on a line of its own where the caret is. Or pick one the extension ships (the node toolbar with its ? help button). The panel lists the note's pictures with a Width slider (a share of the note), Own size and Remove." },
{ term: "Pictures", desc: "Add a picture from a file, or paste or drop one into the text; it goes on a line of its own where the caret is. Or pick one the extension ships (the node toolbar with its ? help button, or any docs screenshot), with a search box to find it. The panel stays open after adding and lists the note's pictures with a Width slider (a share of the note), Own size and Remove." },
],
},
WP_PromptAssembler: {
+12 -7
View File
@@ -25,7 +25,7 @@ const syntax = [
{ term: "[Get the pack](wp:community/owner/pack)", desc: "Opens that published pack in the manager's Community tab, where it installs." },
{ term: ":pi-heart: :pi-heart:{#rrggbb}", desc: "An icon, optionally in a colour: a hex code like the custom colour field shows." },
{ term: "![What it shows](wp:image/1a2b3c4d)", desc: "A picture saved in the note: the bar's Pictures panel, a paste or a drop writes it and its id." },
{ term: "![The ? button](wp:asset/toolbar-help)", desc: "A picture the extension ships." },
{ term: "![The ? button](wp:asset/toolbar-help)", desc: "A picture the extension ships: the toolbar one, or any docs screenshot, named by its file." },
{ term: "![What it shows](wp:image/1a2b3c4d){width=60%}", desc: "The picture as wide as 60% of the note (10 to 100). Without it, its own size, never wider than the note." },
];
</script>
@@ -80,7 +80,9 @@ const syntax = [
</p>
<p>
The text has its own window: press <b>Edit text</b> (<b>Write text</b> on an empty note). The
Markdown editor is on the left and the note as it will read on the right, following what you type.
Markdown editor is on the left and the note as it will read on the right, following what you type:
the node's own card, with its look, tone, colour, title and width. A label shows its tag and, under
it, the card that opens when you point at it.
<b>Done</b> (or Ctrl+Enter) puts the text on the note; <b>Cancel</b>, Escape or × leaves the note
as it was, and asks first when you changed something. The node shows the text's first line and how
long it is.
@@ -111,15 +113,18 @@ const syntax = [
<DocSection title="Pictures">
<p>
A note can show pictures: a screenshot of the step it explains, or the picture the extension ships of
the node toolbar with its <b>?</b> button, which opens any node's help page. Open <b>Pictures</b> in
the bar and press <b>Add a picture…</b>, or paste or drop a picture straight into the text. Each one
goes on a line of its own where the caret is, named by what you type in the box (or the file's name).
A note can show pictures: a screenshot of the step it explains, or one the extension ships, such as
the node toolbar with its <b>?</b> button or any screenshot from these docs. Open <b>Pictures</b> in
the bar and press <b>Add a picture…</b>, paste or drop a picture straight into the text, or pick one
under <b>From the extension</b>: type part of its name in the search box and click it in the grid that
shows up. Each one goes on a line of its own
where the caret is, named by what you type in the box (or the picture's name). The panel stays open
after you add one, so you can set its width right away.
</p>
<DocImage
src="images/docs/wp-note-pictures.png"
ratio="1720 / 860"
caption="The text window with the Pictures panel: the picture the extension ships, the note's own pictures with a Width slider, and the preview showing it."
caption="The text window with the Pictures panel: the pictures the extension ships, the note's own pictures with a Width slider, and the preview showing it."
/>
<p>
A picture is never wider than the note. Its width is a share of the note's, so it grows and shrinks