@supersuit/artifacts 0.11.0 → 0.11.1
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 +12 -0
- package/lib/reader/comment-layer.js +11 -1
- package/lib/reader/theme-vars.d.ts +3 -0
- package/lib/reader/theme-vars.js +33 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
`@supersuit/artifacts`. One entry per version, newest first. Each entry says what changed, how a
|
|
4
4
|
host can tell whether it is affected (DETECTOR), what a host does about it (REMEDY), and the tests.
|
|
5
5
|
|
|
6
|
+
## 0.11.1 (2026-09-28)
|
|
7
|
+
|
|
8
|
+
**The comment controls draw in the page's colours.** The comment overlay is portalled onto
|
|
9
|
+
`document.body`, outside the wrapper that sets the `--a-*` theme variables, so on a dark page the
|
|
10
|
+
Comment chip and the Comment button rendered black text on the dark ground. The overlay now
|
|
11
|
+
carries the variables it finds around the page root (`themeVarsAround`), re-read when a `system`
|
|
12
|
+
page changes scheme.
|
|
13
|
+
|
|
14
|
+
- DETECTOR: on a dark page, the Comment button bottom right is hard to read.
|
|
15
|
+
- REMEDY: bump.
|
|
16
|
+
- Tests: `comment-layer.test.tsx` pins the variables on the portalled overlay (mutation-checked).
|
|
17
|
+
|
|
6
18
|
## 0.11.0 (2026-09-28)
|
|
7
19
|
|
|
8
20
|
**Comment on any part of a page.** Select text, or drag a box over anything that is not text
|
|
@@ -23,6 +23,7 @@ import { createPortal } from 'react-dom';
|
|
|
23
23
|
import { MAX_QUOTE_CHARS, textAnchorFrom } from '../artifacts/anchor.js';
|
|
24
24
|
import { COMMENTS_SLOT } from '../artifacts/comments.js';
|
|
25
25
|
import { CommentCard } from './comment-card.js';
|
|
26
|
+
import { themeVarsAround } from './theme-vars.js';
|
|
26
27
|
import { addDeviceNote, deviceNotes, removeDeviceNote, replaceDeviceNote } from './device-notes.js';
|
|
27
28
|
import { boxOf, headingAt, indexText, offsetAt, placeAnchor, regionFrom } from './comment-place.js';
|
|
28
29
|
const isValue = (v) => !!v && typeof v === 'object' && typeof v.body === 'string' && !!v.anchor && typeof v.anchor === 'object';
|
|
@@ -71,6 +72,7 @@ export function CommentLayer({ artifactId, rootId, ownerName, comments, canShare
|
|
|
71
72
|
const [thread, setThread] = useState(null);
|
|
72
73
|
const [drag, setDrag] = useState(null);
|
|
73
74
|
const [tick, setTick] = useState(0);
|
|
75
|
+
const [theme, setTheme] = useState({});
|
|
74
76
|
const call = useCallback(async (url, init) => {
|
|
75
77
|
try {
|
|
76
78
|
const r = await fetch(url, { credentials: 'same-origin', cache: 'no-store', ...init });
|
|
@@ -81,6 +83,14 @@ export function CommentLayer({ artifactId, rootId, ownerName, comments, canShare
|
|
|
81
83
|
}
|
|
82
84
|
}, []);
|
|
83
85
|
const refreshDevice = useCallback(() => setDevice(personalFrom(deviceNotes(artifactId), 'device')), [artifactId]);
|
|
86
|
+
// The theme the overlay carries, re-read when a `system` page flips between light and dark.
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
const read = () => setTheme(themeVarsAround(document.getElementById(rootId)));
|
|
89
|
+
read();
|
|
90
|
+
const mq = typeof window.matchMedia === 'function' ? window.matchMedia('(prefers-color-scheme: dark)') : null;
|
|
91
|
+
mq?.addEventListener?.('change', read);
|
|
92
|
+
return () => mq?.removeEventListener?.('change', read);
|
|
93
|
+
}, [rootId]);
|
|
84
94
|
useEffect(() => {
|
|
85
95
|
setMounted(true);
|
|
86
96
|
refreshDevice();
|
|
@@ -349,7 +359,7 @@ export function CommentLayer({ artifactId, rootId, ownerName, comments, canShare
|
|
|
349
359
|
const open = layout.placed.find((p) => p.item.id === thread);
|
|
350
360
|
const openItem = open?.item ?? layout.earlier.find((i) => i.id === thread);
|
|
351
361
|
const count = tops.length;
|
|
352
|
-
const overlay = mounted ? (_jsxs("div", { "data-comment-ui": true, "data-nospeak": true, className: "font-sans", children: [showPins ? layout.placed.map(({ item, left, top, box }) => (_jsxs("div", { children: [box ? (_jsx("div", { "data-comment-box": item.id, "aria-hidden": true, className: "pointer-events-none absolute z-30 rounded-md border-2", style: { left: box.left, top: box.top, width: box.width, height: box.height, borderColor: accent, borderStyle: item.kind === 'personal' ? 'dashed' : 'solid', opacity: thread === item.id ? 1 : 0.55 } })) : null, _jsx(Pin, { item: item, left: left, top: top, accent: accent, ground: ground, onOpen: () => setThread(thread === item.id ? null : item.id) })] }, item.id))) : null, chip && !draft ? (_jsx("button", { type: "button", "data-comment-chip": true, className: "absolute z-50 -translate-x-full rounded-full px-3 py-1 text-xs font-medium shadow-lg", style: { left: chip.x, top: chip.y, background: accent, color: 'var(--a-on-accent, #111)' }, onMouseDown: (e) => e.preventDefault(), onClick: () => { setDraft(chip); setChip(null); }, children: "Comment" })) : null, draft ? (_jsx(Floating, { x: draft.x, y: draft.y, children: _jsx(CommentCard, { ownerName: ownerName, mode: comments, canShare: canShare, signedIn: signedIn && onServer, quote: draft.quote, accent: accent, signIn: signIn, onSave: (body, share) => saveNew(body, share, draft), onCancel: () => setDraft(null) }) })) : null, openItem && open ? (_jsx(Floating, { x: open.left + 28, y: open.top, children: _jsx(Thread, { item: openItem, replies: items.filter((i) => i.value.parent === openItem.id), canReply: openItem.kind === 'shared' && canShare, accent: accent, onClose: () => setThread(null), onRemove: remove, onEdit: edit, onReply: reply }) })) : null, drag ? (_jsx("div", { "aria-hidden": true, className: "pointer-events-none fixed z-40 rounded-md border-2 border-dashed", style: { left: Math.min(drag.x1, drag.x2), top: Math.min(drag.y1, drag.y2), width: Math.abs(drag.x2 - drag.x1), height: Math.abs(drag.y2 - drag.y1), borderColor: accent, background: `color-mix(in srgb, ${accent} 12%, transparent)` } })) : null, _jsxs("div", { className: "fixed right-4 z-40 flex flex-col items-end gap-2", style: { bottom: 'calc(env(safe-area-inset-bottom, 0px) + 5.5rem)' }, children: [panel ? (_jsxs("div", { "data-comment-panel": true, className: "w-64 rounded-xl border border-[color:var(--a-line-strong)] bg-[color:var(--a-surface)] p-3 text-xs text-[color:var(--a-strong)] shadow-xl", children: [_jsx("p", { className: "opacity-80", children: "Select any text, or hold and drag a box over anything, to leave a comment." }), isOwner ? _jsx("p", { "data-comment-owner": true, className: "mt-1 opacity-80", children: "This is your page: you see every comment, with the name of who left it." }) : null, _jsx("button", { type: "button", "data-comment-pins-toggle": true, className: "mt-2 underline", onClick: () => setShowPins((v) => !v), children: showPins ? 'Hide comments' : 'Show comments' })] })) : null, _jsx("button", { type: "button", "data-comment-button": true, "aria-expanded": panel, onClick: () => setPanel((v) => !v), className: "rounded-full border px-3 py-1.5 text-xs font-medium shadow-lg backdrop-blur", style: { borderColor: accent, color: accent, background: `color-mix(in srgb, ${ground} 85%, transparent)` }, children: count ? `Comments · ${count}` : 'Comment' })] })] })) : null;
|
|
362
|
+
const overlay = mounted ? (_jsxs("div", { "data-comment-ui": true, "data-nospeak": true, className: "font-sans", style: theme, children: [showPins ? layout.placed.map(({ item, left, top, box }) => (_jsxs("div", { children: [box ? (_jsx("div", { "data-comment-box": item.id, "aria-hidden": true, className: "pointer-events-none absolute z-30 rounded-md border-2", style: { left: box.left, top: box.top, width: box.width, height: box.height, borderColor: accent, borderStyle: item.kind === 'personal' ? 'dashed' : 'solid', opacity: thread === item.id ? 1 : 0.55 } })) : null, _jsx(Pin, { item: item, left: left, top: top, accent: accent, ground: ground, onOpen: () => setThread(thread === item.id ? null : item.id) })] }, item.id))) : null, chip && !draft ? (_jsx("button", { type: "button", "data-comment-chip": true, className: "absolute z-50 -translate-x-full rounded-full px-3 py-1 text-xs font-medium shadow-lg", style: { left: chip.x, top: chip.y, background: accent, color: 'var(--a-on-accent, #111)' }, onMouseDown: (e) => e.preventDefault(), onClick: () => { setDraft(chip); setChip(null); }, children: "Comment" })) : null, draft ? (_jsx(Floating, { x: draft.x, y: draft.y, children: _jsx(CommentCard, { ownerName: ownerName, mode: comments, canShare: canShare, signedIn: signedIn && onServer, quote: draft.quote, accent: accent, signIn: signIn, onSave: (body, share) => saveNew(body, share, draft), onCancel: () => setDraft(null) }) })) : null, openItem && open ? (_jsx(Floating, { x: open.left + 28, y: open.top, children: _jsx(Thread, { item: openItem, replies: items.filter((i) => i.value.parent === openItem.id), canReply: openItem.kind === 'shared' && canShare, accent: accent, onClose: () => setThread(null), onRemove: remove, onEdit: edit, onReply: reply }) })) : null, drag ? (_jsx("div", { "aria-hidden": true, className: "pointer-events-none fixed z-40 rounded-md border-2 border-dashed", style: { left: Math.min(drag.x1, drag.x2), top: Math.min(drag.y1, drag.y2), width: Math.abs(drag.x2 - drag.x1), height: Math.abs(drag.y2 - drag.y1), borderColor: accent, background: `color-mix(in srgb, ${accent} 12%, transparent)` } })) : null, _jsxs("div", { className: "fixed right-4 z-40 flex flex-col items-end gap-2", style: { bottom: 'calc(env(safe-area-inset-bottom, 0px) + 5.5rem)' }, children: [panel ? (_jsxs("div", { "data-comment-panel": true, className: "w-64 rounded-xl border border-[color:var(--a-line-strong)] bg-[color:var(--a-surface)] p-3 text-xs text-[color:var(--a-strong)] shadow-xl", children: [_jsx("p", { className: "opacity-80", children: "Select any text, or hold and drag a box over anything, to leave a comment." }), isOwner ? _jsx("p", { "data-comment-owner": true, className: "mt-1 opacity-80", children: "This is your page: you see every comment, with the name of who left it." }) : null, _jsx("button", { type: "button", "data-comment-pins-toggle": true, className: "mt-2 underline", onClick: () => setShowPins((v) => !v), children: showPins ? 'Hide comments' : 'Show comments' })] })) : null, _jsx("button", { type: "button", "data-comment-button": true, "aria-expanded": panel, onClick: () => setPanel((v) => !v), className: "rounded-full border px-3 py-1.5 text-xs font-medium shadow-lg backdrop-blur", style: { borderColor: accent, color: accent, background: `color-mix(in srgb, ${ground} 85%, transparent)` }, children: count ? `Comments · ${count}` : 'Comment' })] })] })) : null;
|
|
353
363
|
return (_jsxs(_Fragment, { children: [_jsx("span", { hidden: true, "data-comment-layer": comments, "data-comment-can-share": canShare ? 'yes' : 'no' }), mounted && layout.earlier.length ? (_jsxs("section", { "data-comments-earlier": true, "data-comment-ui": true, "data-nospeak": true, className: "mx-auto max-w-2xl px-6 pb-16 text-sm", children: [_jsx("p", { className: "text-[11px] uppercase tracking-[0.2em]", style: { color: accent }, children: "Comments on earlier versions" }), _jsx("ul", { className: "m-0 mt-2 grid list-none gap-3 p-0", children: layout.earlier.map((item) => (_jsxs("li", { className: "m-0 border-l-2 py-1 pl-3", style: { borderColor: accent, borderStyle: item.kind === 'personal' ? 'dashed' : 'solid' }, children: [item.value.anchor.kind === 'text' ? _jsxs("span", { className: "block text-[13px] italic opacity-70", children: ["\u201C", item.value.anchor.quote, "\u201D"] }) : _jsx("span", { className: "block text-[13px] italic opacity-70", children: "A box on part of the page that has changed" }), _jsx("span", { className: "block whitespace-pre-wrap text-[15px] text-[color:var(--a-strong)]", children: item.value.body }), _jsxs("span", { className: "block text-xs opacity-60", children: [item.kind === 'personal' ? 'only you' : item.name, ` · version ${item.value.version}`, item.mine ? _jsx("button", { type: "button", onClick: () => void remove(item), className: "ml-2 underline", children: "delete" }) : null] })] }, item.id))) })] })) : null, overlay ? createPortal(overlay, document.body) : null] }));
|
|
354
364
|
}
|
|
355
365
|
function Pin({ item, left, top, accent, ground, onOpen }) {
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** The page's theme variables (`--a-*`) as they resolve around `el`. Computed style covers variables set by a stylesheet (the `system` mode's media
|
|
2
|
+
* query); the inline walk covers engines that do not list custom properties in computed style. */
|
|
3
|
+
export declare function themeVarsAround(el: Element | null): Record<string, string>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Anything the reader portals onto document.body leaves the wrapper that sets the page's theme,
|
|
2
|
+
// so it has to carry the theme with it. One helper for every portal (comment overlay, definition
|
|
3
|
+
// sheet, version history).
|
|
4
|
+
/** The page's theme variables (`--a-*`) as they resolve around `el`. Computed style covers variables set by a stylesheet (the `system` mode's media
|
|
5
|
+
* query); the inline walk covers engines that do not list custom properties in computed style. */
|
|
6
|
+
export function themeVarsAround(el) {
|
|
7
|
+
const out = {};
|
|
8
|
+
if (!el)
|
|
9
|
+
return out;
|
|
10
|
+
const cs = getComputedStyle(el);
|
|
11
|
+
for (let i = 0; i < cs.length; i++) {
|
|
12
|
+
const name = cs[i];
|
|
13
|
+
if (name.startsWith('--a-')) {
|
|
14
|
+
const v = cs.getPropertyValue(name).trim();
|
|
15
|
+
if (v)
|
|
16
|
+
out[name] = v;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
for (let n = el; n; n = n.parentElement) {
|
|
20
|
+
const st = n.style;
|
|
21
|
+
if (!st)
|
|
22
|
+
continue;
|
|
23
|
+
for (let i = 0; i < st.length; i++) {
|
|
24
|
+
const name = st[i];
|
|
25
|
+
if (name.startsWith('--a-') && !(name in out)) {
|
|
26
|
+
const v = st.getPropertyValue(name).trim();
|
|
27
|
+
if (v)
|
|
28
|
+
out[name] = v;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supersuit/artifacts",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "Publish markdown as branded, read-aloud pages from a Next.js app: the artifacts store, the route factory, the read-along reader, brand packs, password and confidential pages, and share cards.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|