wiki-formant 0.105.0 → 0.105.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/bin/maintenance-log.mjs +2 -2
- package/dist/block-views.js +3 -1
- package/dist/dom.js +3 -1
- package/dist/editor.js +11 -7
- package/dist/maintenance.d.ts +1 -1
- package/dist/maintenance.js +8 -2
- package/dist/react.js +14 -0
- package/package.json +1 -1
package/bin/maintenance-log.mjs
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
// the item back in, so a run's whole-state write cannot drop it. Every mode
|
|
32
32
|
// reads and writes under the row lock. Only `state` in the page's metadata is
|
|
33
33
|
// written; other keys are kept. A revision bumps the page's version a minor.
|
|
34
|
-
import { PINNED, addBacklog, compact, lateBacklog, render, staleWrite } from '../dist/maintenance.js';
|
|
34
|
+
import { PINNED, addBacklog, compact, isOpen, lateBacklog, render, staleWrite } from '../dist/maintenance.js';
|
|
35
35
|
import { bump } from '../dist/versioning.js';
|
|
36
36
|
import { arg, connect, invalidate } from './_cli.mjs';
|
|
37
37
|
|
|
@@ -131,7 +131,7 @@ try {
|
|
|
131
131
|
const kept = new Set(state.backlog.map(b => b.item));
|
|
132
132
|
for (const b of input.backlog ?? []) {
|
|
133
133
|
const item = typeof b === 'string' ? b : b.item;
|
|
134
|
-
if (PINNED.test(item ?? '') && (
|
|
134
|
+
if (PINNED.test(item ?? '') && isOpen(b) && !kept.has(item)) {
|
|
135
135
|
console.log(` flag dropped over the cap: ${item}`);
|
|
136
136
|
}
|
|
137
137
|
}
|
package/dist/block-views.js
CHANGED
|
@@ -32,9 +32,11 @@ const line = (html) => ({ dangerouslySetInnerHTML: { __html: html } });
|
|
|
32
32
|
* whatever the consumer's highlighter attached to it on every tab press.
|
|
33
33
|
*/
|
|
34
34
|
export function CodeTabsView({ tabs, highlighted = false }) {
|
|
35
|
-
const [
|
|
35
|
+
const [picked, setActiveTab] = useState(0);
|
|
36
36
|
if (!tabs.length)
|
|
37
37
|
return null;
|
|
38
|
+
// Clamped, so tabs removed under the selection leave the last one showing.
|
|
39
|
+
const activeTab = Math.min(picked, tabs.length - 1);
|
|
38
40
|
return (_jsxs("div", { className: "code-tabs", children: [_jsx("div", { className: "code-tabs-list", children: tabs.map((tab, i) => (_jsx("button", { type: "button", className: cx('code-tabs-btn', i === activeTab && 'code-tabs-btn-active'), onClick: () => setActiveTab(i), children: tab.label }, i))) }), tabs.map((tab, i) => highlighted ? (_jsx("div", { className: "code-tabs-panel", hidden: i !== activeTab, dangerouslySetInnerHTML: { __html: tab.code } }, i)) : (_jsx("div", { className: "code-tabs-panel", hidden: i !== activeTab, children: _jsx("pre", { children: _jsx("code", { className: tab.language ? `language-${tab.language}` : undefined, children: tab.code }) }) }, i)))] }));
|
|
39
41
|
}
|
|
40
42
|
/**
|
package/dist/dom.js
CHANGED
|
@@ -35,13 +35,15 @@ export function addCopyButtons(root, options = {}) {
|
|
|
35
35
|
btn.className = className;
|
|
36
36
|
btn.setAttribute('aria-label', label);
|
|
37
37
|
btn.innerHTML = COPY_SVG;
|
|
38
|
+
let revert;
|
|
38
39
|
btn.onclick = () => {
|
|
39
40
|
const code = pre.querySelector('code')?.textContent || pre.textContent || '';
|
|
40
41
|
// A rejected clipboard write (permissions, insecure origin) must not
|
|
41
42
|
// leave an unhandled rejection behind — the button simply does nothing.
|
|
42
43
|
navigator.clipboard.writeText(code).then(() => {
|
|
43
44
|
btn.innerHTML = CHECK_SVG;
|
|
44
|
-
|
|
45
|
+
clearTimeout(revert);
|
|
46
|
+
revert = setTimeout(() => {
|
|
45
47
|
btn.innerHTML = COPY_SVG;
|
|
46
48
|
}, revertAfter);
|
|
47
49
|
}, () => { });
|
package/dist/editor.js
CHANGED
|
@@ -482,13 +482,17 @@ export function useWikiEditor({ value, onChange, placeholder = '', nodes = [], p
|
|
|
482
482
|
if (!file || !editor)
|
|
483
483
|
return;
|
|
484
484
|
setIsUploading(true);
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
485
|
+
try {
|
|
486
|
+
const url = await uploadImage(file);
|
|
487
|
+
if (url)
|
|
488
|
+
editor.chain().focus().setImage({ src: url }).run();
|
|
489
|
+
}
|
|
490
|
+
finally {
|
|
491
|
+
setIsUploading(false);
|
|
492
|
+
// Cleared so choosing the same file twice in a row still fires a change.
|
|
493
|
+
if (fileInputRef.current)
|
|
494
|
+
fileInputRef.current.value = '';
|
|
495
|
+
}
|
|
492
496
|
}, [editor, uploadImage]);
|
|
493
497
|
const triggerUpload = useCallback(() => fileInputRef.current?.click(), []);
|
|
494
498
|
return { editor, fileInputRef, isUploading, handleFileChange, triggerUpload };
|
package/dist/maintenance.d.ts
CHANGED
package/dist/maintenance.js
CHANGED
|
@@ -31,13 +31,19 @@ const JOURNAL = /^run\s+\d+\b/i;
|
|
|
31
31
|
export const PINNED = /FLAG FOR A HUMAN/i;
|
|
32
32
|
const BANKED = /\bbanked\b|bot[- ]?(wall|block)|\b(403|429|999)\b|cloudflare/i;
|
|
33
33
|
const itemOf = (b) => (typeof b === 'string' ? b : b.item);
|
|
34
|
+
/**
|
|
35
|
+
* @internal The one test of open, which the bin shares: a bare string, or no
|
|
36
|
+
* status, is open; `compact` deletes anything else for good.
|
|
37
|
+
*/
|
|
38
|
+
export const isOpen = (b) => typeof b === 'string' || (b.status ?? 'open') === 'open';
|
|
34
39
|
const recency = (a, b) => String(b[1]?.lastChecked ?? '').localeCompare(String(a[1]?.lastChecked ?? ''));
|
|
35
40
|
/** Drops what is closed or a diary entry, and caps what is append-only. Newest first. */
|
|
36
41
|
export function compact(state) {
|
|
37
42
|
// Undated items fall back to append order: a later index is newer.
|
|
38
43
|
const open = (state.backlog ?? [])
|
|
39
|
-
.
|
|
40
|
-
.
|
|
44
|
+
.filter(isOpen)
|
|
45
|
+
.map((b, i) => ({ ...(typeof b === 'string' ? { date: '', item: b } : b), status: 'open', i }))
|
|
46
|
+
.filter(b => b.item && (PINNED.test(b.item) || !JOURNAL.test(b.item)))
|
|
41
47
|
.sort((a, b) => String(b.date ?? '').localeCompare(String(a.date ?? '')) || b.i - a.i);
|
|
42
48
|
const backlog = [...open.filter(b => PINNED.test(b.item)).slice(0, MAX.flags), ...open.filter(b => !PINNED.test(b.item))]
|
|
43
49
|
.slice(0, MAX.backlog)
|
package/dist/react.js
CHANGED
|
@@ -394,6 +394,10 @@ export function useTypeahead({ fetch, onPick, onEscape, delay = 200, minLength =
|
|
|
394
394
|
setHighlight(h => (h - 1 + items.length) % items.length);
|
|
395
395
|
}
|
|
396
396
|
else if (e.key === 'Enter') {
|
|
397
|
+
// The list answers the last settled query. Typed past it, Enter falls
|
|
398
|
+
// through to the form, so it submits what was typed, not a stale pick.
|
|
399
|
+
if (query !== debounced || isSearching)
|
|
400
|
+
return;
|
|
397
401
|
e.preventDefault();
|
|
398
402
|
const target = items[highlight];
|
|
399
403
|
if (target)
|
|
@@ -463,7 +467,10 @@ export function useLinkPreview({ eligible, fetch, cardClassName = 'link-preview-
|
|
|
463
467
|
clearTimeout(hideTimer.current);
|
|
464
468
|
return;
|
|
465
469
|
}
|
|
470
|
+
// A new link retires the last one's card at once: the grace is for
|
|
471
|
+
// crossing into that card, and the cursor has gone somewhere else.
|
|
466
472
|
clearTimers();
|
|
473
|
+
setCard(null);
|
|
467
474
|
activeHref.current = href;
|
|
468
475
|
showTimer.current = setTimeout(async () => {
|
|
469
476
|
let data = cache.current.get(href);
|
|
@@ -754,11 +761,17 @@ export function restoreViaPost(endpoint) {
|
|
|
754
761
|
export function useRevisionRestore(restore, options) {
|
|
755
762
|
const [busyId, setBusyId] = useState(null);
|
|
756
763
|
const [error, setError] = useState(null);
|
|
764
|
+
// One restore at a time: a second, started while the first is in flight,
|
|
765
|
+
// would have its "Restoring…" cleared by the first one finishing.
|
|
766
|
+
const inFlight = useRef(false);
|
|
757
767
|
const { onRestored, confirm = (label) => `Restore ${label}? The current state stays in the history.` } = options;
|
|
758
768
|
const run = useCallback(async (id, label) => {
|
|
769
|
+
if (inFlight.current)
|
|
770
|
+
return;
|
|
759
771
|
const question = confirm(label);
|
|
760
772
|
if (question && !window.confirm(question))
|
|
761
773
|
return;
|
|
774
|
+
inFlight.current = true;
|
|
762
775
|
setBusyId(id);
|
|
763
776
|
setError(null);
|
|
764
777
|
try {
|
|
@@ -769,6 +782,7 @@ export function useRevisionRestore(restore, options) {
|
|
|
769
782
|
onRestored();
|
|
770
783
|
}
|
|
771
784
|
finally {
|
|
785
|
+
inFlight.current = false;
|
|
772
786
|
setBusyId(null);
|
|
773
787
|
}
|
|
774
788
|
}, [restore, onRestored, confirm]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wiki-formant",
|
|
3
|
-
"version": "0.105.
|
|
3
|
+
"version": "0.105.1",
|
|
4
4
|
"description": "The portable half of a wiki: derived taxonomy and facet controls, a version-negotiating MCP transport, markdown twins, block rendering, a rich-text editor engine, conditional-GET plumbing, and the headers and CSP every origin sends.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|