tsquare 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/LICENSE +0 -3
- package/README.md +7 -2
- package/dist/browser/index.js +76 -0
- package/dist/dom.d.ts +14 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +76 -0
- package/dist/tsquare.min.js +3473 -0
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.11.0
|
|
4
|
+
|
|
5
|
+
The language and the renderer haven't changed: every wireframe renders exactly as in 0.10.1, and render URLs give the same images.
|
|
6
|
+
|
|
7
|
+
- **A script for any web page:** `<script src="https://cdn.jsdelivr.net/npm/tsquare@0.11/dist/tsquare.min.js"></script>` draws every ```` ```tsquare ```` code block on the page once it has loaded, so wireframes work on Markdown sites (MkDocs, Hugo, Jekyll, Docsify, VitePress, …) with no build step. It finds the HTML Markdown renderers make of a fence, lists a block's problems by line in its place, and sets `window.tsquare` with the whole API. Rendering happens in the reader's browser and matches Node byte for byte. `data-start="manual"` on the script tag leaves it to you to call `tsquare.renderAll()`.
|
|
8
|
+
- **`renderAll()`** is exported for pages that bundle tsquare themselves: it does the same for a document or part of one (`{ root, selector }`).
|
|
9
|
+
|
|
10
|
+
## 0.10.1
|
|
11
|
+
|
|
12
|
+
Docs and license only: the code is the same as 0.10.0, and every wireframe renders the same.
|
|
13
|
+
|
|
14
|
+
- **`LICENSE` is the plain MIT text**, so GitHub recognizes it (it showed "Other"). The note about the Inter font's license is in the README's License section, which also points to the browser build's `THIRD_PARTY_LICENSES.txt`.
|
|
15
|
+
- **Obsidian:** the [tsquare plugin](https://tsquare.dev/docs/obsidian) draws ```` ```tsquare ```` code blocks in your notes, on desktop and mobile. The README links it and the VS Code extension, and the embedding guide links it.
|
|
16
|
+
|
|
3
17
|
## 0.10.0
|
|
4
18
|
|
|
5
19
|
The language and the renderer haven't changed: every wireframe renders exactly as in 0.9.1, and render URLs give the same images.
|
package/LICENSE
CHANGED
|
@@ -19,6 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
SOFTWARE.
|
|
22
|
-
|
|
23
|
-
The Inter font (the @fontsource/inter dependency) is licensed separately under
|
|
24
|
-
the SIL Open Font License 1.1.
|
package/README.md
CHANGED
|
@@ -52,6 +52,11 @@ A wireframe can be embedded anywhere an image works (Notion, GitHub, docs sites)
|
|
|
52
52
|
|
|
53
53
|
The playground's **Copy link** menu makes these; [Embedding](docs/embedding.md) has the steps for Notion, GitHub and your own site. The text is compressed into the URL, not encrypted, so anyone with the link can read it; see [privacy](https://tsquare.dev/privacy). `encodeWireframe` and `decodeWireframe` in the library produce and read the same links.
|
|
54
54
|
|
|
55
|
+
## In your editor and notes
|
|
56
|
+
|
|
57
|
+
- **VS Code** (and Cursor, VSCodium, Windsurf): the [tsquare extension](https://marketplace.visualstudio.com/items?itemName=tsquare-js.tsquare-vscode) gives `.tsq` files a live preview, problems as you type, highlighting and autocomplete. Also on [Open VSX](https://open-vsx.org/extension/tsquare-js/tsquare-vscode).
|
|
58
|
+
- **Obsidian**: the [tsquare plugin](https://community.obsidian.md/plugins/tsquare) draws ```` ```tsquare ```` code blocks in your notes, on desktop and mobile. See [the Obsidian page](https://tsquare.dev/docs/obsidian).
|
|
59
|
+
|
|
55
60
|
## The language
|
|
56
61
|
|
|
57
62
|
One element per line; children are indented two spaces under their parent. The first line is the board.
|
|
@@ -123,7 +128,7 @@ const { issues } = compileWireframe(source); // problems by line, empt
|
|
|
123
128
|
const png = await renderWireframe(source, { format: "png", scale: 2 });
|
|
124
129
|
```
|
|
125
130
|
|
|
126
|
-
`tsquare/playground` exports the playground's request handler, for hosting it inside your own Node server. `tsquare/viewer` is the playground's zoomable board (zoom, pan, pinch, a chip per screen) for your own page: pass it the elements, then `show()` the SVG from `renderWireframeSvg` with the size and screens from `boardLayout`. It sets the styles its zoom math needs itself; give the stage a size, and optionally style the classes it toggles on the stage: `panning` (while dragging), `animate` (while fitting or zooming to a screen, e.g. `.animate > * { transition: transform .25s }`) and `stale` (from `setStale`, e.g. `.stale img { opacity: .35 }`). `languageData()` lists every component, prop, option word, icon and accent, for editor highlighting and autocomplete. In a browser bundle (esbuild, Vite, webpack) the same imports render SVG with no setup; PNG and share links need Node. See [Using it with AI](docs/ai.md) for the prompt and the repair loop.
|
|
131
|
+
`tsquare/playground` exports the playground's request handler, for hosting it inside your own Node server. `tsquare/viewer` is the playground's zoomable board (zoom, pan, pinch, a chip per screen) for your own page: pass it the elements, then `show()` the SVG from `renderWireframeSvg` with the size and screens from `boardLayout`. It sets the styles its zoom math needs itself; give the stage a size, and optionally style the classes it toggles on the stage: `panning` (while dragging), `animate` (while fitting or zooming to a screen, e.g. `.animate > * { transition: transform .25s }`) and `stale` (from `setStale`, e.g. `.stale img { opacity: .35 }`). `languageData()` lists every component, prop, option word, icon and accent, for editor highlighting and autocomplete. In a browser bundle (esbuild, Vite, webpack) the same imports render SVG with no setup; PNG and share links need Node. On a page without a build step, `<script src="https://cdn.jsdelivr.net/npm/tsquare@0.11/dist/tsquare.min.js"></script>` draws every ```` ```tsquare ```` code block on the page (see [Embedding](docs/embedding.md#pages-written-in-markdown)). See [Using it with AI](docs/ai.md) for the prompt and the repair loop.
|
|
127
132
|
|
|
128
133
|
## Using it with Claude (skill)
|
|
129
134
|
|
|
@@ -158,4 +163,4 @@ How it was measured, the tasks, every model output, and the scripts: [`eval/`](e
|
|
|
158
163
|
|
|
159
164
|
## License
|
|
160
165
|
|
|
161
|
-
MIT. The Inter font comes from the `@fontsource/inter` dependency, under the SIL Open Font License 1.1. Icons are from [Lucide](https://lucide.dev), under the ISC license. Brand logos are from [Simple Icons](https://simpleicons.org) (CC0); the logos themselves are trademarks of their owners.
|
|
166
|
+
MIT. The Inter font comes from the `@fontsource/inter` dependency, under the SIL Open Font License 1.1. Icons are from [Lucide](https://lucide.dev), under the ISC license. Brand logos are from [Simple Icons](https://simpleicons.org) (CC0); the logos themselves are trademarks of their owners. The browser build (`dist/browser/`) bundles some dependencies, Inter and satori (MPL-2.0) among them; their notices are in `dist/browser/THIRD_PARTY_LICENSES.txt`.
|
package/dist/browser/index.js
CHANGED
|
@@ -23382,6 +23382,80 @@ async function callMcpTool(name, args = {}, opts = {}) {
|
|
|
23382
23382
|
return name === "render_wireframe" ? renderWireframeTool(parsed.data) : shareWireframeTool(parsed.data, opts);
|
|
23383
23383
|
}
|
|
23384
23384
|
|
|
23385
|
+
// src/dom.ts
|
|
23386
|
+
var DEFAULT_SELECTOR = ".tsquare, .language-tsquare, [data-lang='tsquare']";
|
|
23387
|
+
var DONE = "data-tsquare";
|
|
23388
|
+
var STYLES = `:where(.tsquare-board){margin:1em 0}
|
|
23389
|
+
:where(.tsquare-board) img{display:block;max-width:100%;height:auto}
|
|
23390
|
+
:where(.tsquare-problems){margin:1em 0;padding:.5em .75em;border-left:3px solid #d33;font-size:.9em}
|
|
23391
|
+
:where(.tsquare-problems) ul{margin:.25em 0 0;padding-left:1.25em;font-family:ui-monospace,Menlo,monospace}`;
|
|
23392
|
+
async function renderAll(opts = {}) {
|
|
23393
|
+
const root = opts.root ?? document;
|
|
23394
|
+
const doc = root.ownerDocument ?? root;
|
|
23395
|
+
const result = { rendered: 0, invalid: 0 };
|
|
23396
|
+
const blocks = [];
|
|
23397
|
+
for (const el2 of root.querySelectorAll(opts.selector ?? DEFAULT_SELECTOR)) {
|
|
23398
|
+
const target = el2.tagName === "CODE" ? el2.closest("pre") ?? el2 : el2;
|
|
23399
|
+
if (target.hasAttribute(DONE) || target.closest(`[${DONE}]`)) continue;
|
|
23400
|
+
if (blocks.some((b) => b.target.contains(target) || target.contains(b.target))) continue;
|
|
23401
|
+
blocks.push({ target, text: el2.tagName === "CODE" ? el2 : el2.querySelector("code") ?? el2.querySelector("pre") ?? el2 });
|
|
23402
|
+
}
|
|
23403
|
+
if (blocks.length) addStyles(doc);
|
|
23404
|
+
for (const { target, text: text2 } of blocks) {
|
|
23405
|
+
const source = text2.textContent ?? "";
|
|
23406
|
+
const replacement = await renderBlock(doc, source);
|
|
23407
|
+
replacement.setAttribute(DONE, "");
|
|
23408
|
+
target.replaceWith(replacement);
|
|
23409
|
+
if (replacement.classList.contains("tsquare-board")) result.rendered++;
|
|
23410
|
+
else result.invalid++;
|
|
23411
|
+
}
|
|
23412
|
+
return result;
|
|
23413
|
+
}
|
|
23414
|
+
async function renderBlock(doc, source) {
|
|
23415
|
+
const { spec, issues } = compileWireframe(source);
|
|
23416
|
+
if (!spec || issues.length) return problems2(doc, issues.map((i2) => `line ${i2.line}: ${i2.message}`));
|
|
23417
|
+
try {
|
|
23418
|
+
const svg = await renderWireframeSvg(spec, { skipValidation: true });
|
|
23419
|
+
const { width, height } = boardLayout(spec);
|
|
23420
|
+
const title = spec.elements[spec.root]?.props?.title;
|
|
23421
|
+
const figure = doc.createElement("figure");
|
|
23422
|
+
figure.className = "tsquare-board";
|
|
23423
|
+
const img = doc.createElement("img");
|
|
23424
|
+
img.src = svgDataUrl(svg);
|
|
23425
|
+
img.width = width;
|
|
23426
|
+
img.height = height;
|
|
23427
|
+
img.alt = title ? `Wireframe: ${title}` : "Wireframe";
|
|
23428
|
+
figure.append(img);
|
|
23429
|
+
return figure;
|
|
23430
|
+
} catch (err2) {
|
|
23431
|
+
return problems2(doc, [`tsquare couldn't render this board: ${err2 instanceof Error ? err2.message : String(err2)}`]);
|
|
23432
|
+
}
|
|
23433
|
+
}
|
|
23434
|
+
function svgDataUrl(svg) {
|
|
23435
|
+
const bytes = new TextEncoder().encode(svg);
|
|
23436
|
+
let binary = "";
|
|
23437
|
+
for (let i2 = 0; i2 < bytes.length; i2 += 32768) binary += String.fromCharCode(...bytes.subarray(i2, i2 + 32768));
|
|
23438
|
+
return `data:image/svg+xml;base64,${btoa(binary)}`;
|
|
23439
|
+
}
|
|
23440
|
+
function problems2(doc, lines) {
|
|
23441
|
+
const box = doc.createElement("div");
|
|
23442
|
+
box.className = "tsquare-problems";
|
|
23443
|
+
box.setAttribute("role", "note");
|
|
23444
|
+
const title = doc.createElement("strong");
|
|
23445
|
+
title.textContent = lines.length === 1 ? "1 problem in this wireframe" : `${lines.length} problems in this wireframe`;
|
|
23446
|
+
const list = doc.createElement("ul");
|
|
23447
|
+
for (const line of lines) list.append(Object.assign(doc.createElement("li"), { textContent: line }));
|
|
23448
|
+
box.append(title, list);
|
|
23449
|
+
return box;
|
|
23450
|
+
}
|
|
23451
|
+
function addStyles(doc) {
|
|
23452
|
+
if (doc.getElementById("tsquare-styles")) return;
|
|
23453
|
+
const style = doc.createElement("style");
|
|
23454
|
+
style.id = "tsquare-styles";
|
|
23455
|
+
style.textContent = STYLES;
|
|
23456
|
+
doc.head.append(style);
|
|
23457
|
+
}
|
|
23458
|
+
|
|
23385
23459
|
// src/playground/language-data.ts
|
|
23386
23460
|
function unwrap4(t) {
|
|
23387
23461
|
let cur = t;
|
|
@@ -23435,6 +23509,7 @@ function languageData() {
|
|
|
23435
23509
|
}
|
|
23436
23510
|
export {
|
|
23437
23511
|
DEFAULT_LINKS,
|
|
23512
|
+
DEFAULT_SELECTOR,
|
|
23438
23513
|
DEVICES,
|
|
23439
23514
|
LINK_KINDS,
|
|
23440
23515
|
MAX_SHARED_TEXT,
|
|
@@ -23456,6 +23531,7 @@ export {
|
|
|
23456
23531
|
mcpTools,
|
|
23457
23532
|
parseWireframeText,
|
|
23458
23533
|
registry,
|
|
23534
|
+
renderAll,
|
|
23459
23535
|
renderWireframe,
|
|
23460
23536
|
renderWireframePng,
|
|
23461
23537
|
renderWireframeSvg,
|
package/dist/dom.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const DEFAULT_SELECTOR = ".tsquare, .language-tsquare, [data-lang='tsquare']";
|
|
2
|
+
export interface RenderAllOptions {
|
|
3
|
+
/** Where to look (default: the whole document). */
|
|
4
|
+
root?: ParentNode;
|
|
5
|
+
/** Which elements hold wireframe text (default: DEFAULT_SELECTOR). */
|
|
6
|
+
selector?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface RenderAllResult {
|
|
9
|
+
/** Boards drawn. */
|
|
10
|
+
rendered: number;
|
|
11
|
+
/** Blocks whose text has problems (listed in place of the board). */
|
|
12
|
+
invalid: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function renderAll(opts?: RenderAllOptions): Promise<RenderAllResult>;
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { printWireframeText as formatWireframe } from "./print.js";
|
|
|
5
5
|
export { encodeWireframe, decodeWireframe, MAX_SHARED_TEXT } from "./share.js";
|
|
6
6
|
export { mcpTools, MCP_INSTRUCTIONS, LINK_KINDS, DEFAULT_LINKS, type LinkKind, wireframeGuide, renderWireframeTool, shareWireframeTool, callMcpTool, type ToolResult, type ToolContent, type ToolOptions, type McpToolName } from "./mcp.js";
|
|
7
7
|
export { upgradeWireframe, upgradeSpec, RENAMED_PROPS, type UpgradeOptions } from "./upgrade.js";
|
|
8
|
+
export { renderAll, DEFAULT_SELECTOR, type RenderAllOptions, type RenderAllResult } from "./dom.js";
|
|
8
9
|
export { renderWireframeSvg, renderWireframePng, checkSpec, boardSize, boardLayout, type BoardItem, type RenderWireframeOptions } from "./render.js";
|
|
9
10
|
export { parseWireframeText, type TextIssue, type ParseResult } from "./text.js";
|
|
10
11
|
export { catalog, componentDefinitions } from "./catalog.js";
|
package/dist/index.js
CHANGED
|
@@ -166,8 +166,83 @@ async function callMcpTool(name, args = {}, opts = {}) {
|
|
|
166
166
|
if (!parsed.success) return { content: [text(`Invalid arguments: ${z.prettifyError(parsed.error)}`)], isError: true };
|
|
167
167
|
return name === "render_wireframe" ? renderWireframeTool(parsed.data) : shareWireframeTool(parsed.data, opts);
|
|
168
168
|
}
|
|
169
|
+
|
|
170
|
+
// src/dom.ts
|
|
171
|
+
var DEFAULT_SELECTOR = ".tsquare, .language-tsquare, [data-lang='tsquare']";
|
|
172
|
+
var DONE = "data-tsquare";
|
|
173
|
+
var STYLES = `:where(.tsquare-board){margin:1em 0}
|
|
174
|
+
:where(.tsquare-board) img{display:block;max-width:100%;height:auto}
|
|
175
|
+
:where(.tsquare-problems){margin:1em 0;padding:.5em .75em;border-left:3px solid #d33;font-size:.9em}
|
|
176
|
+
:where(.tsquare-problems) ul{margin:.25em 0 0;padding-left:1.25em;font-family:ui-monospace,Menlo,monospace}`;
|
|
177
|
+
async function renderAll(opts = {}) {
|
|
178
|
+
const root = opts.root ?? document;
|
|
179
|
+
const doc = root.ownerDocument ?? root;
|
|
180
|
+
const result = { rendered: 0, invalid: 0 };
|
|
181
|
+
const blocks = [];
|
|
182
|
+
for (const el of root.querySelectorAll(opts.selector ?? DEFAULT_SELECTOR)) {
|
|
183
|
+
const target = el.tagName === "CODE" ? el.closest("pre") ?? el : el;
|
|
184
|
+
if (target.hasAttribute(DONE) || target.closest(`[${DONE}]`)) continue;
|
|
185
|
+
if (blocks.some((b) => b.target.contains(target) || target.contains(b.target))) continue;
|
|
186
|
+
blocks.push({ target, text: el.tagName === "CODE" ? el : el.querySelector("code") ?? el.querySelector("pre") ?? el });
|
|
187
|
+
}
|
|
188
|
+
if (blocks.length) addStyles(doc);
|
|
189
|
+
for (const { target, text: text2 } of blocks) {
|
|
190
|
+
const source = text2.textContent ?? "";
|
|
191
|
+
const replacement = await renderBlock(doc, source);
|
|
192
|
+
replacement.setAttribute(DONE, "");
|
|
193
|
+
target.replaceWith(replacement);
|
|
194
|
+
if (replacement.classList.contains("tsquare-board")) result.rendered++;
|
|
195
|
+
else result.invalid++;
|
|
196
|
+
}
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
async function renderBlock(doc, source) {
|
|
200
|
+
const { spec, issues } = compileWireframe(source);
|
|
201
|
+
if (!spec || issues.length) return problems2(doc, issues.map((i) => `line ${i.line}: ${i.message}`));
|
|
202
|
+
try {
|
|
203
|
+
const svg = await renderWireframeSvg(spec, { skipValidation: true });
|
|
204
|
+
const { width, height } = boardLayout(spec);
|
|
205
|
+
const title = spec.elements[spec.root]?.props?.title;
|
|
206
|
+
const figure = doc.createElement("figure");
|
|
207
|
+
figure.className = "tsquare-board";
|
|
208
|
+
const img = doc.createElement("img");
|
|
209
|
+
img.src = svgDataUrl(svg);
|
|
210
|
+
img.width = width;
|
|
211
|
+
img.height = height;
|
|
212
|
+
img.alt = title ? `Wireframe: ${title}` : "Wireframe";
|
|
213
|
+
figure.append(img);
|
|
214
|
+
return figure;
|
|
215
|
+
} catch (err) {
|
|
216
|
+
return problems2(doc, [`tsquare couldn't render this board: ${err instanceof Error ? err.message : String(err)}`]);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function svgDataUrl(svg) {
|
|
220
|
+
const bytes = new TextEncoder().encode(svg);
|
|
221
|
+
let binary = "";
|
|
222
|
+
for (let i = 0; i < bytes.length; i += 32768) binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
223
|
+
return `data:image/svg+xml;base64,${btoa(binary)}`;
|
|
224
|
+
}
|
|
225
|
+
function problems2(doc, lines) {
|
|
226
|
+
const box = doc.createElement("div");
|
|
227
|
+
box.className = "tsquare-problems";
|
|
228
|
+
box.setAttribute("role", "note");
|
|
229
|
+
const title = doc.createElement("strong");
|
|
230
|
+
title.textContent = lines.length === 1 ? "1 problem in this wireframe" : `${lines.length} problems in this wireframe`;
|
|
231
|
+
const list = doc.createElement("ul");
|
|
232
|
+
for (const line of lines) list.append(Object.assign(doc.createElement("li"), { textContent: line }));
|
|
233
|
+
box.append(title, list);
|
|
234
|
+
return box;
|
|
235
|
+
}
|
|
236
|
+
function addStyles(doc) {
|
|
237
|
+
if (doc.getElementById("tsquare-styles")) return;
|
|
238
|
+
const style = doc.createElement("style");
|
|
239
|
+
style.id = "tsquare-styles";
|
|
240
|
+
style.textContent = STYLES;
|
|
241
|
+
doc.head.append(style);
|
|
242
|
+
}
|
|
169
243
|
export {
|
|
170
244
|
DEFAULT_LINKS,
|
|
245
|
+
DEFAULT_SELECTOR,
|
|
171
246
|
DEVICES,
|
|
172
247
|
LINK_KINDS,
|
|
173
248
|
MAX_SHARED_TEXT,
|
|
@@ -189,6 +264,7 @@ export {
|
|
|
189
264
|
mcpTools,
|
|
190
265
|
parseWireframeText,
|
|
191
266
|
registry,
|
|
267
|
+
renderAll,
|
|
192
268
|
renderWireframe,
|
|
193
269
|
renderWireframePng,
|
|
194
270
|
renderWireframeSvg,
|