@plannotator/ui 0.55.0 → 0.56.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/HANDOFF.md +43 -1
- package/README.md +81 -4
- package/components/AnnotationPanel.tsx +2 -1
- package/components/AttachmentsButton.tsx +1 -1
- package/components/CodeFilePopout.tsx +6 -4
- package/components/ComposerTokenTextarea.tsx +43 -2
- package/components/DocBadges.tsx +47 -35
- package/components/ImageZoomStage.tsx +32 -4
- package/components/Settings.tsx +2 -1
- package/components/TableOfContents.tsx +2 -1
- package/components/Viewer.tsx +43 -17
- package/components/chat/ChatComposer.tsx +192 -0
- package/components/chat/ChatFrame.tsx +524 -0
- package/components/chat/ChatTranscript.tsx +422 -0
- package/components/chat/chatMarkdown.ts +436 -0
- package/components/chat/chatRecipes.tsx +39 -0
- package/components/chat/chatWindowStore.ts +196 -0
- package/components/chat/index.ts +72 -0
- package/components/diagram/DiagramCanvas.tsx +7 -0
- package/components/diagram/DiagramComposer.tsx +15 -3
- package/components/diagram/DiagramViewer.tsx +1 -0
- package/components/docBadgesWidth.ts +29 -0
- package/components/imageZoom.ts +10 -0
- package/components/inbox-thread/FileName.tsx +118 -0
- package/components/inbox-thread/GuideCard.tsx +39 -0
- package/components/inbox-thread/ReaskNote.tsx +25 -0
- package/components/inbox-thread/SkillsLine.tsx +40 -0
- package/components/inbox-thread/Tip.tsx +105 -0
- package/components/inbox-thread/icons.tsx +262 -0
- package/components/inbox-thread/reask.ts +222 -0
- package/hooks/useActiveSection.ts +46 -63
- package/hooks/useAnnotationHighlighter.ts +40 -1
- package/hooks/useFileBrowser.ts +4 -3
- package/inbox-thread.css +4542 -0
- package/package.json +10 -3
- package/styles.css +1 -1
- package/theme.css +1 -0
- package/themes/tokyo-night-moon.css +34 -0
- package/utils/aiChatFormat.ts +32 -11
- package/utils/headingScrollSpy.ts +19 -6
- package/utils/syntaxTheme.ts +1 -0
- package/utils/themeRegistry.ts +11 -0
|
@@ -421,6 +421,17 @@ const excludedAncestor = (node: Node | null): HTMLElement | null => {
|
|
|
421
421
|
return element?.closest<HTMLElement>(ANNOTATION_EXCLUDED_SELECTOR) ?? null;
|
|
422
422
|
};
|
|
423
423
|
|
|
424
|
+
/**
|
|
425
|
+
* The whole range sits inside one `.annotation-exclude` subtree of `container`
|
|
426
|
+
* (an Inbox action item or question card under text comments, a diagram):
|
|
427
|
+
* nothing in it can be annotated, so the selection is left to the person.
|
|
428
|
+
* An excluded element around the container itself does not count.
|
|
429
|
+
*/
|
|
430
|
+
export const rangeInsideExcluded = (container: HTMLElement, range: Range): boolean => {
|
|
431
|
+
const excluded = excludedAncestor(range.commonAncestorContainer);
|
|
432
|
+
return excluded !== null && excluded !== container && container.contains(excluded);
|
|
433
|
+
};
|
|
434
|
+
|
|
424
435
|
/**
|
|
425
436
|
* Move a range's start off any `.annotation-exclude` subtree it begins inside,
|
|
426
437
|
* onto the first annotatable text position the range covers.
|
|
@@ -1508,6 +1519,9 @@ export function useAnnotationHighlighter({
|
|
|
1508
1519
|
trimWhitespaceOnlyBoundaryNodes(args[1] as SelectedNodeLike[])) as SelectedNodesTap);
|
|
1509
1520
|
|
|
1510
1521
|
highlighterRef.current = highlighter;
|
|
1522
|
+
// Whether the library's pointer-end handler is attached (`run()`), and whether this effect was cleaned up.
|
|
1523
|
+
let listening = false;
|
|
1524
|
+
let disposed = false;
|
|
1511
1525
|
|
|
1512
1526
|
// Stored positions can resolve into chrome the reviewer could never have
|
|
1513
1527
|
// selected; painting never enters such a subtree, so a boundary left there
|
|
@@ -1650,6 +1664,25 @@ export function useAnnotationHighlighter({
|
|
|
1650
1664
|
// AROUND the document and whose end boundary is an empty <div> — the
|
|
1651
1665
|
// exact shape that throws — so a containment bail here would hand that
|
|
1652
1666
|
// selection straight to the serializer.
|
|
1667
|
+
if (rangeInsideExcluded(container, range)) {
|
|
1668
|
+
// Nothing here can be annotated (an Inbox action item or question
|
|
1669
|
+
// card under text comments, a diagram): the selection is the
|
|
1670
|
+
// person's to copy. The library's own pointer-end handler paints
|
|
1671
|
+
// nothing for it and then removes the DOM selection anyway, so it
|
|
1672
|
+
// sits this one event out (a listener removed during dispatch never
|
|
1673
|
+
// runs) and listens again on the next task.
|
|
1674
|
+
pendingRangeRunsRef.current = null;
|
|
1675
|
+
if (listening) {
|
|
1676
|
+
highlighter.stop();
|
|
1677
|
+
listening = false;
|
|
1678
|
+
window.setTimeout(() => {
|
|
1679
|
+
if (disposed) return;
|
|
1680
|
+
highlighter.run();
|
|
1681
|
+
listening = true;
|
|
1682
|
+
}, 0);
|
|
1683
|
+
}
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1653
1686
|
if (isBlankQuote(selection.toString()) || !isSerializableRange(range)) {
|
|
1654
1687
|
pendingRangeRunsRef.current = null;
|
|
1655
1688
|
selection.removeAllRanges();
|
|
@@ -1676,7 +1709,10 @@ export function useAnnotationHighlighter({
|
|
|
1676
1709
|
// person chooses its Comment (takeSelection); the library painting it at
|
|
1677
1710
|
// touchend would end the system selection and its edit menu first.
|
|
1678
1711
|
const isTouchPrimary = window.matchMedia('(pointer: coarse)').matches;
|
|
1679
|
-
if (!(isTouchPrimary && hostComposesRef.current))
|
|
1712
|
+
if (!(isTouchPrimary && hostComposesRef.current)) {
|
|
1713
|
+
highlighter.run();
|
|
1714
|
+
listening = true;
|
|
1715
|
+
}
|
|
1680
1716
|
|
|
1681
1717
|
const handleMathMouseDown = (event: MouseEvent) => {
|
|
1682
1718
|
mouseDownMathRef.current = closestMathElement(event.target as Node, containerRef.current);
|
|
@@ -1773,6 +1809,8 @@ export function useAnnotationHighlighter({
|
|
|
1773
1809
|
if (!sel || sel.isCollapsed || sel.rangeCount === 0) return false;
|
|
1774
1810
|
if (!containerRef.current?.contains(sel.anchorNode)) return false;
|
|
1775
1811
|
const range = sel.getRangeAt(0).cloneRange();
|
|
1812
|
+
// Nothing to annotate there: the system selection and its copy menu stay the person's.
|
|
1813
|
+
if (rangeInsideExcluded(containerRef.current, range)) return false;
|
|
1776
1814
|
// Same fail-closed rule as the pointer-end guard (#881): touch
|
|
1777
1815
|
// selections settle on block boundaries just as often.
|
|
1778
1816
|
if (isBlankQuote(sel.toString()) || !isSerializableRange(range)) return false;
|
|
@@ -1810,6 +1848,7 @@ export function useAnnotationHighlighter({
|
|
|
1810
1848
|
containerRef.current?.removeEventListener('mouseup', handleMathMouseUp, true);
|
|
1811
1849
|
container.removeEventListener('mouseup', handlePointerEndCapture, true);
|
|
1812
1850
|
container.removeEventListener('touchend', handlePointerEndCapture, true);
|
|
1851
|
+
disposed = true;
|
|
1813
1852
|
highlighter.dispose();
|
|
1814
1853
|
};
|
|
1815
1854
|
}, [clearPendingSelection, enabled]);
|
package/hooks/useFileBrowser.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { useState, useCallback, useEffect, useMemo, useRef } from "react";
|
|
11
11
|
import type { VaultNode } from "../types";
|
|
12
|
+
import { basenameFromPath } from "@plannotator/core/source-save";
|
|
12
13
|
import type { WorkspaceStatusPayload } from "@plannotator/core/workspace-status-types";
|
|
13
14
|
|
|
14
15
|
export interface DirState {
|
|
@@ -200,7 +201,7 @@ export function useFileBrowser(): UseFileBrowserReturn {
|
|
|
200
201
|
}, []);
|
|
201
202
|
|
|
202
203
|
const fetchTree = useCallback(async (dirPath: string, options: { quiet?: boolean } = {}) => {
|
|
203
|
-
const name = dirPath
|
|
204
|
+
const name = basenameFromPath(dirPath);
|
|
204
205
|
|
|
205
206
|
setDirs((prev) => {
|
|
206
207
|
const exists = prev.find((d) => d.path === dirPath);
|
|
@@ -291,7 +292,7 @@ export function useFileBrowser(): UseFileBrowserReturn {
|
|
|
291
292
|
const vaultDirs = prev.filter((d) => d.isVault);
|
|
292
293
|
const regularDirs = directories.map((path) => ({
|
|
293
294
|
path,
|
|
294
|
-
name: path
|
|
295
|
+
name: basenameFromPath(path),
|
|
295
296
|
tree: [],
|
|
296
297
|
// Keep initial roots loading until their first snapshot resolves. If
|
|
297
298
|
// the SSE watcher starts before /api/reference/files finishes,
|
|
@@ -313,7 +314,7 @@ export function useFileBrowser(): UseFileBrowserReturn {
|
|
|
313
314
|
}, []);
|
|
314
315
|
|
|
315
316
|
const addVaultDir = useCallback(async (vaultPath: string) => {
|
|
316
|
-
const name = vaultPath
|
|
317
|
+
const name = basenameFromPath(vaultPath);
|
|
317
318
|
|
|
318
319
|
// Atomically replace any existing vault dirs (handles vault path change without accumulating stale entries)
|
|
319
320
|
setDirs((prev) => {
|