@plannotator/ui 0.53.1 → 0.55.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 +274 -14
- package/README.md +114 -11
- package/components/AgentToolAnnouncementDialog.tsx +1 -1
- package/components/AgentsTab.tsx +1 -1
- package/components/AnnotationPanel.tsx +142 -24
- package/components/AnnotationSidebar.tsx +1 -1
- package/components/AnnotationToolbar.tsx +64 -9
- package/components/ApproveDropdown.tsx +7 -9
- package/components/AskSessionAnnouncementDialog.tsx +1 -1
- package/components/AttachmentsButton.tsx +1 -1
- package/components/BlockRenderer.tsx +22 -5
- package/components/CodeFilePopout.tsx +139 -4
- package/components/CodeSnippetPreview.tsx +174 -0
- package/components/CommentPopover.tsx +44 -8
- package/components/DecisionControl.tsx +31 -5
- package/components/DeferredMarkdownContentsRail.tsx +38 -0
- package/components/DiagramBlock.tsx +17 -12
- package/components/DiffFileTree.tsx +7 -6
- package/components/DocumentContents.tsx +74 -0
- package/components/EditorAnnotationCard.tsx +1 -1
- package/components/ExportModal.tsx +77 -7
- package/components/HostTitle.tsx +0 -4
- package/components/ImageAnnotator/Toolbar.tsx +25 -1
- package/components/ImageAnnotator/index.tsx +24 -4
- package/components/ImageLightbox.tsx +722 -126
- package/components/ImagePeek.tsx +38 -4
- package/components/ImageRegions.tsx +369 -0
- package/components/ImageThumbnail.tsx +1 -1
- package/components/ImageZoomStage.tsx +796 -0
- package/components/InboxAnnouncementDialog.tsx +202 -0
- package/components/InlineMarkdown.tsx +167 -174
- package/components/KeyboardShortcuts.tsx +23 -0
- package/components/LazyBlockBoundary.tsx +100 -0
- package/components/ListMarker.tsx +3 -3
- package/components/LookAndFeelAnnouncementDialog.tsx +12 -2
- package/components/MarkdownContentsRail.tsx +777 -0
- package/components/MenuVersionSection.tsx +31 -9
- package/components/PlanDecisionMenuExtras.tsx +129 -0
- package/components/PlanHeaderMenu.tsx +15 -1
- package/components/ProviderIcons.tsx +8 -0
- package/components/RefHoverCard.tsx +913 -0
- package/components/SendFailureToast.tsx +126 -0
- package/components/Settings.tsx +617 -126
- package/components/TableOfContents.tsx +20 -6
- package/components/TerminalToolsAnnouncementDialog.tsx +1 -1
- package/components/ThemeProvider.tsx +14 -1
- package/components/ThemeTab.tsx +16 -24
- package/components/Tooltip.tsx +49 -18
- package/components/Viewer.tsx +288 -43
- package/components/ai/DocumentAIChatPanel.tsx +29 -3
- package/components/ai/NewChatButton.tsx +74 -0
- package/components/blocks/CodeBlock.tsx +1 -1
- package/components/blocks/HtmlBlock.tsx +7 -1
- package/components/blocks/QuestionBlock.tsx +532 -62
- package/components/diagram/DiagramComposer.tsx +5 -1
- package/components/diagram/DiagramPending.tsx +12 -2
- package/components/diagram/DiagramViewer.tsx +19 -3
- package/components/html-embed/HtmlEmbedBlock.tsx +374 -0
- package/components/html-embed/SvgFenceBlock.tsx +46 -0
- package/components/html-embed/index.ts +3 -0
- package/components/html-embed/languages.ts +15 -0
- package/components/html-embed/loadGate.ts +107 -0
- package/components/html-viewer/HtmlViewer.tsx +82 -6
- package/components/html-viewer/bridge-script.asset.js +173 -1
- package/components/html-viewer/bridge-script.ts +173 -1
- package/components/html-viewer/useHtmlAnnotation.ts +125 -0
- package/components/imageZoom.ts +276 -0
- package/components/plan-diff/PlanCleanDiffView.tsx +1 -1
- package/components/plan-diff/PlanDiffViewer.tsx +13 -2
- package/components/plan-diff/PlanRawDiffView.tsx +1 -1
- package/components/sidebar/FileBrowser.tsx +16 -2
- package/components/sidebar/SidebarTabs.tsx +3 -0
- package/config/index.ts +3 -0
- package/config/reviewView.ts +41 -1
- package/config/settings.ts +155 -4
- package/configure.ts +28 -0
- package/contents-rail.css +337 -0
- package/hooks/pfm/useCodeFilePopout.ts +7 -1
- package/hooks/useAIChat.ts +72 -25
- package/hooks/useAnnotationDraft.ts +71 -6
- package/hooks/useAnnotationHighlighter.ts +57 -3
- package/hooks/useCodeAnnotationDraft.ts +113 -17
- package/hooks/useCookieHostSetting.ts +96 -0
- package/hooks/useDocumentFont.ts +17 -0
- package/hooks/useDocumentTextSize.ts +20 -0
- package/hooks/useFileBrowser.ts +10 -1
- package/hooks/useHeadingScrollSpy.ts +77 -0
- package/hooks/useHoverPreview.ts +94 -0
- package/hooks/useLinkedDoc.ts +226 -13
- package/hooks/usePackageChangesSetting.ts +90 -0
- package/hooks/usePlanLookDialog.ts +67 -0
- package/hooks/usePrefersReducedMotion.ts +23 -0
- package/hooks/useSharing.ts +11 -7
- package/hooks/useSwipeStep.ts +597 -0
- package/hooks/useThemePaletteHalves.ts +37 -0
- package/hooks/useUpdateCheck.ts +22 -4
- package/hooks/useUpdateToast.ts +1 -1
- package/package.json +26 -23
- package/shortcuts/code-review/annotationToolbar.shortcuts.ts +1 -0
- package/shortcuts/code-review/explorer.shortcuts.ts +82 -0
- package/shortcuts/core.ts +63 -4
- package/shortcuts/index.ts +1 -0
- package/shortcuts/plan-review/annotationPanel.shortcuts.ts +1 -1
- package/shortcuts/plan-review/commentPopover.shortcuts.ts +1 -0
- package/shortcuts/runtime.ts +4 -3
- package/styles.css +1 -1
- package/theme.css +179 -1
- package/themes/plannotator-graphite.css +66 -0
- package/types.ts +26 -9
- package/utils/agentSwitch.ts +41 -20
- package/utils/agentToolSetting.ts +1 -1
- package/utils/aiChatFormat.ts +23 -7
- package/utils/annotationHelpers.ts +32 -4
- package/utils/annotationOwnsHighlight.ts +4 -2
- package/utils/asciidocParser.ts +570 -0
- package/utils/autoUpdateNotice.ts +24 -0
- package/utils/belowTitleSlot.ts +174 -0
- package/utils/blockParse.ts +45 -0
- package/utils/codeLineScroll.ts +100 -0
- package/utils/commentSubmitKey.ts +48 -0
- package/utils/cssFontFamily.ts +65 -0
- package/utils/decisionSpec.ts +190 -2
- package/utils/defaultNotesApp.ts +1 -1
- package/utils/diffFonts.ts +4 -1
- package/utils/documentFont.ts +68 -0
- package/utils/documentTextSize.ts +54 -0
- package/utils/fontDetect.ts +63 -0
- package/utils/headingScrollSpy.ts +92 -0
- package/utils/inboxAnnouncement.ts +78 -0
- package/utils/lookAndFeelAnnouncement.ts +82 -5
- package/utils/markdownExtensions.ts +2 -2
- package/utils/mermaid.ts +20 -3
- package/utils/mermaidTheme.ts +3 -51
- package/utils/notion.ts +58 -0
- package/utils/parser.ts +28 -1
- package/utils/platform.ts +46 -0
- package/utils/refCard.ts +82 -0
- package/utils/richClipboard.ts +380 -0
- package/utils/richClipboardLists.ts +57 -0
- package/utils/richCopyPolicy.ts +22 -0
- package/utils/scrollAnchor.ts +82 -0
- package/utils/sectionRefs.ts +123 -0
- package/utils/selectionBoundary.ts +40 -0
- package/utils/sendFailure.ts +85 -0
- package/utils/sharing.ts +5 -2
- package/utils/themeRegistry.ts +10 -0
- package/utils/themeTokens.ts +74 -0
- package/utils/updateNotice.ts +30 -8
- package/utils/vscodePanel.ts +9 -0
package/theme.css
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/* Theme files — each defines .theme-{name} (dark) and .theme-{name}.light */
|
|
6
6
|
@import "./themes/plannotator.css";
|
|
7
|
+
@import "./themes/plannotator-graphite.css";
|
|
7
8
|
@import "./themes/claude-plus.css";
|
|
8
9
|
@import "./themes/soft-pop.css";
|
|
9
10
|
@import "./themes/adwaita.css";
|
|
@@ -63,10 +64,17 @@
|
|
|
63
64
|
* selection stay on the textarea: the caret takes the foreground token, and
|
|
64
65
|
* the selection uses a translucent primary wash so the overlay text stays
|
|
65
66
|
* readable through it. During IME composition `.pn-ref-composing` restores
|
|
66
|
-
* the native text so composition underlines render normally.
|
|
67
|
+
* the native text so composition underlines render normally.
|
|
68
|
+
*
|
|
69
|
+
* No transition: the overlay's `visibility` flips instantly, so the global
|
|
70
|
+
* `*` 150ms color transition would fade the textarea's glyphs in (blank text
|
|
71
|
+
* at every composition start) and out (doubled text at every end). With
|
|
72
|
+
* Korean/Japanese/Chinese IMEs that swap happens on nearly every keystroke,
|
|
73
|
+
* and the whole field flickers. */
|
|
67
74
|
.pn-ref-input {
|
|
68
75
|
color: transparent;
|
|
69
76
|
caret-color: var(--foreground);
|
|
77
|
+
transition: none;
|
|
70
78
|
}
|
|
71
79
|
.pn-ref-input::selection {
|
|
72
80
|
background: color-mix(in oklab, var(--primary) 30%, transparent);
|
|
@@ -719,6 +727,14 @@
|
|
|
719
727
|
--card-ring: rgba(255, 255, 255, 0.08);
|
|
720
728
|
--card-shadow:
|
|
721
729
|
0 0 0 1px var(--card-ring), 0 1px 3px 0 rgba(0, 0, 0, 0.2), 0 2px 8px -2px rgba(0, 0, 0, 0.3);
|
|
730
|
+
/* Floating surfaces with no border (the tooltip): a faint ring in the
|
|
731
|
+
* shadow plus a soft drop. The ink is the background taken toward black in
|
|
732
|
+
* its own hue (the Inbox's Soft float shadow, same formula). Hosts override
|
|
733
|
+
* --float-shadow to restyle every borderless float at once. */
|
|
734
|
+
--float-shadow-ink: var(--background);
|
|
735
|
+
--float-shadow:
|
|
736
|
+
0 0 0 1px color-mix(in oklch, var(--foreground) 8%, transparent),
|
|
737
|
+
0 16px 40px -10px color-mix(in oklch, var(--float-shadow-ink) 70%, transparent);
|
|
722
738
|
}
|
|
723
739
|
|
|
724
740
|
html {
|
|
@@ -760,6 +776,16 @@
|
|
|
760
776
|
--card-shadow:
|
|
761
777
|
0 0 0 1px var(--card-ring), 0 1px 3px 0 rgba(0, 0, 0, 0.04),
|
|
762
778
|
0 2px 8px -2px rgba(0, 0, 0, 0.06);
|
|
779
|
+
--float-shadow-ink: var(--foreground);
|
|
780
|
+
--float-shadow:
|
|
781
|
+
0 0 0 1px color-mix(in oklch, var(--border) 70%, transparent),
|
|
782
|
+
0 12px 32px -8px color-mix(in oklch, var(--float-shadow-ink) 22%, transparent);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
@supports (color: oklch(from red l c h)) {
|
|
786
|
+
:root:not(.light) {
|
|
787
|
+
--float-shadow-ink: oklch(from var(--background) calc(l * 0.2) calc(c * 0.5) h);
|
|
788
|
+
}
|
|
763
789
|
}
|
|
764
790
|
|
|
765
791
|
* {
|
|
@@ -767,6 +793,28 @@
|
|
|
767
793
|
}
|
|
768
794
|
}
|
|
769
795
|
|
|
796
|
+
/* Inter's stylistic sets: ss01 (open digits), ss02 (disambiguation: I, l, 0)
|
|
797
|
+
* and cv01 (alternate one), set on body with font-feature-settings. That
|
|
798
|
+
* property is inherited by every font on the page, and feature tags mean
|
|
799
|
+
* different things in each font. On macOS, iOS and Windows that is the look
|
|
800
|
+
* users know (SF Pro, the system-ui themes' font, draws an open 4 and
|
|
801
|
+
* straight 6 and 9 with them), so it stays. On desktop Linux, Ubuntu Mono, the
|
|
802
|
+
* monospace fallback that code review diffs and gutters reach, draws ss02 as
|
|
803
|
+
* superscript digits (#1961). There (`pn-os-linux`, put on <html> before the
|
|
804
|
+
* first paint by ThemeProvider: see markRootPlatform in utils/platform.ts) the
|
|
805
|
+
* sets are named for Inter's families and applied with
|
|
806
|
+
* font-variant-alternates instead: a named value applies only to text a listed
|
|
807
|
+
* family renders, so monospace text, Pierre's diffs (inside their shadow
|
|
808
|
+
* roots), system-ui themes and any fallback font get none of them. */
|
|
809
|
+
@font-feature-values "Inter Variable", "Inter", "InterVariable" {
|
|
810
|
+
@styleset {
|
|
811
|
+
pn-inter: 1 2;
|
|
812
|
+
}
|
|
813
|
+
@character-variant {
|
|
814
|
+
pn-inter-one: 1;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
770
818
|
body {
|
|
771
819
|
font-family: var(--font-sans);
|
|
772
820
|
background: var(--background);
|
|
@@ -774,6 +822,11 @@ body {
|
|
|
774
822
|
font-feature-settings: "ss01", "ss02", "cv01";
|
|
775
823
|
}
|
|
776
824
|
|
|
825
|
+
:root.pn-os-linux body {
|
|
826
|
+
font-feature-settings: normal;
|
|
827
|
+
font-variant-alternates: styleset(pn-inter) character-variant(pn-inter-one);
|
|
828
|
+
}
|
|
829
|
+
|
|
777
830
|
/* Safari extends the page canvas beneath its floating browser controls. The
|
|
778
831
|
* plan document is a card-colored nested scroller, so leaving the outer canvas
|
|
779
832
|
* on `--background` produces an opaque dark band around those controls. Match
|
|
@@ -1654,6 +1707,31 @@ body:has([data-popout="true"]) [data-sidebar-tabs="true"] {
|
|
|
1654
1707
|
padding-left: 0.6rem;
|
|
1655
1708
|
color: var(--muted-foreground);
|
|
1656
1709
|
}
|
|
1710
|
+
/* GFM tables in Ask AI answers. The chat panel is narrow, so a wide table
|
|
1711
|
+
scrolls sideways inside itself instead of widening the message. */
|
|
1712
|
+
.ai-markdown table {
|
|
1713
|
+
display: block;
|
|
1714
|
+
max-width: 100%;
|
|
1715
|
+
overflow-x: auto;
|
|
1716
|
+
border-collapse: collapse;
|
|
1717
|
+
margin: 0.5rem 0;
|
|
1718
|
+
font-size: 0.95em;
|
|
1719
|
+
}
|
|
1720
|
+
.ai-markdown th,
|
|
1721
|
+
.ai-markdown td {
|
|
1722
|
+
border: 1px solid var(--border);
|
|
1723
|
+
padding: 0.25rem 0.5rem;
|
|
1724
|
+
text-align: left;
|
|
1725
|
+
vertical-align: top;
|
|
1726
|
+
}
|
|
1727
|
+
.ai-markdown th {
|
|
1728
|
+
background: color-mix(in oklab, var(--muted) 55%, transparent);
|
|
1729
|
+
font-weight: 600;
|
|
1730
|
+
}
|
|
1731
|
+
.ai-markdown th[align="center"],
|
|
1732
|
+
.ai-markdown td[align="center"] { text-align: center; }
|
|
1733
|
+
.ai-markdown th[align="right"],
|
|
1734
|
+
.ai-markdown td[align="right"] { text-align: right; }
|
|
1657
1735
|
|
|
1658
1736
|
/* Annotation highlights — shared by the plan editor and the code-review
|
|
1659
1737
|
description annotations. (Moved here from packages/editor/index.css so both
|
|
@@ -1714,6 +1792,106 @@ body:has([data-popout="true"]) [data-sidebar-tabs="true"] {
|
|
|
1714
1792
|
cursor: pointer;
|
|
1715
1793
|
}
|
|
1716
1794
|
|
|
1795
|
+
/* Document text size (Settings → Display → Text size, #2066; see
|
|
1796
|
+
utils/documentTextSize). The app sets data-doc-text-size and
|
|
1797
|
+
--doc-text-scale (size / 15) on the document element only away from the
|
|
1798
|
+
default, so at the default none of this applies and the document draws
|
|
1799
|
+
exactly as it always has. Scoped to the blocks of a document container
|
|
1800
|
+
(data-doc-text-root: the Viewer's article and the plan diff's): every block
|
|
1801
|
+
carries data-block-id, the plan diff's blocks data-diff-block-index, the
|
|
1802
|
+
frontmatter card and the raw diff data-doc-text. So the header, panels,
|
|
1803
|
+
badges, menus and popovers keep their size; diagrams draw themselves and the
|
|
1804
|
+
table / diagram popouts are app chrome, so both are left out. Named sizes
|
|
1805
|
+
scale through Tailwind's type scale, the inherited size through each block
|
|
1806
|
+
root, the renderers' fixed sizes one by one (sizes under 12 px, the card's
|
|
1807
|
+
pills and the diff gutters, stay as they are), and the list markers,
|
|
1808
|
+
checkboxes and the question card's controls with the text they sit beside.
|
|
1809
|
+
Unlayered, so it outranks Tailwind's utilities, except the inherited size,
|
|
1810
|
+
which sits in the base layer so a block's own size class still wins. */
|
|
1811
|
+
[data-doc-text-size] [data-doc-text-root] :is([data-block-id], [data-diff-block-index], [data-doc-text]):not([data-diagram-block], [data-diagram-block] *, [data-popout], [data-popout] *) {
|
|
1812
|
+
--text-xs: max(11px, calc(0.75rem * var(--doc-text-scale, 1)));
|
|
1813
|
+
--text-sm: calc(0.875rem * var(--doc-text-scale, 1));
|
|
1814
|
+
--text-base: calc(1rem * var(--doc-text-scale, 1));
|
|
1815
|
+
--text-lg: calc(1.0625rem * var(--doc-text-scale, 1));
|
|
1816
|
+
--text-xl: calc(1.1875rem * var(--doc-text-scale, 1));
|
|
1817
|
+
--text-2xl: calc(1.375rem * var(--doc-text-scale, 1));
|
|
1818
|
+
--text-3xl: calc(1.625rem * var(--doc-text-scale, 1));
|
|
1819
|
+
--text-4xl: calc(1.875rem * var(--doc-text-scale, 1));
|
|
1820
|
+
--text-5xl: calc(2rem * var(--doc-text-scale, 1));
|
|
1821
|
+
}
|
|
1822
|
+
@layer base {
|
|
1823
|
+
/* The size a block inherits (a list row and its marker, an alert's title):
|
|
1824
|
+
only on the outermost block, so a nested one never compounds it. */
|
|
1825
|
+
[data-doc-text-size] [data-doc-text-root] :is([data-block-id], [data-diff-block-index], [data-doc-text]):not([data-diagram-block], [data-diagram-block] *, [data-popout], [data-popout] *):not(:is([data-block-id], [data-diff-block-index], [data-doc-text]) *) {
|
|
1826
|
+
font-size: calc(1em * var(--doc-text-scale, 1));
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
[data-doc-text-size] [data-doc-text-root] :is([data-block-id], [data-diff-block-index], [data-doc-text], [data-block-id] *, [data-diff-block-index] *, [data-doc-text] *):not([data-diagram-block], [data-diagram-block] *, [data-popout], [data-popout] *).text-\[15px\] {
|
|
1830
|
+
font-size: calc(15px * var(--doc-text-scale, 1));
|
|
1831
|
+
}
|
|
1832
|
+
[data-doc-text-size] [data-doc-text-root] :is([data-block-id], [data-diff-block-index], [data-doc-text], [data-block-id] *, [data-diff-block-index] *, [data-doc-text] *):not([data-diagram-block], [data-diagram-block] *, [data-popout], [data-popout] *).text-\[13\.5px\] {
|
|
1833
|
+
font-size: calc(13.5px * var(--doc-text-scale, 1));
|
|
1834
|
+
}
|
|
1835
|
+
[data-doc-text-size] [data-doc-text-root] :is([data-block-id], [data-diff-block-index], [data-doc-text], [data-block-id] *, [data-diff-block-index] *, [data-doc-text] *):not([data-diagram-block], [data-diagram-block] *, [data-popout], [data-popout] *).text-\[13px\] {
|
|
1836
|
+
font-size: calc(13px * var(--doc-text-scale, 1));
|
|
1837
|
+
}
|
|
1838
|
+
[data-doc-text-size] [data-doc-text-root] :is([data-block-id], [data-diff-block-index], [data-doc-text], [data-block-id] *, [data-diff-block-index] *, [data-doc-text] *):not([data-diagram-block], [data-diagram-block] *, [data-popout], [data-popout] *).text-\[12\.5px\] {
|
|
1839
|
+
font-size: calc(12.5px * var(--doc-text-scale, 1));
|
|
1840
|
+
}
|
|
1841
|
+
/* Fenced code (`pre code.pn-code`, 0.8125rem in the app's stylesheet). */
|
|
1842
|
+
[data-doc-text-size] [data-doc-text-root] :is([data-block-id], [data-diff-block-index], [data-doc-text]):not([data-diagram-block], [data-diagram-block] *, [data-popout], [data-popout] *) pre code.pn-code {
|
|
1843
|
+
font-size: calc(0.8125rem * var(--doc-text-scale, 1));
|
|
1844
|
+
}
|
|
1845
|
+
/* A question card's context (QuestionContext's own stylesheet). */
|
|
1846
|
+
[data-doc-text-size] [data-doc-text-root] [data-block-id] .question-context :is(p, .text-sm) {
|
|
1847
|
+
font-size: calc(13px * var(--doc-text-scale, 1));
|
|
1848
|
+
}
|
|
1849
|
+
[data-doc-text-size] [data-doc-text-root] [data-block-id] .question-context :is(h1, h2) {
|
|
1850
|
+
font-size: calc(14px * var(--doc-text-scale, 1));
|
|
1851
|
+
}
|
|
1852
|
+
[data-doc-text-size] [data-doc-text-root] [data-block-id] .question-context h3 {
|
|
1853
|
+
font-size: calc(13.5px * var(--doc-text-scale, 1));
|
|
1854
|
+
}
|
|
1855
|
+
/* List markers (ListMarker): the numeral's column and the task circle. */
|
|
1856
|
+
[data-doc-text-size] [data-doc-text-root] [data-list-numeral] {
|
|
1857
|
+
min-width: calc(1.5rem * var(--doc-text-scale, 1)) !important;
|
|
1858
|
+
}
|
|
1859
|
+
[data-doc-text-size] [data-doc-text-root] [data-list-check] {
|
|
1860
|
+
width: calc(1rem * var(--doc-text-scale, 1));
|
|
1861
|
+
height: calc(1rem * var(--doc-text-scale, 1));
|
|
1862
|
+
margin-top: calc(3px * var(--doc-text-scale, 1));
|
|
1863
|
+
}
|
|
1864
|
+
/* A question card's choice controls (QuestionBlock), beside 13.5 px text. */
|
|
1865
|
+
[data-doc-text-size] [data-doc-text-root] [data-question-control] {
|
|
1866
|
+
width: calc(15px * var(--doc-text-scale, 1));
|
|
1867
|
+
height: calc(15px * var(--doc-text-scale, 1));
|
|
1868
|
+
}
|
|
1869
|
+
[data-doc-text-size] [data-doc-text-root] [data-question-control]:is(input) {
|
|
1870
|
+
margin-top: calc(3px * var(--doc-text-scale, 1));
|
|
1871
|
+
}
|
|
1872
|
+
[data-doc-text-size] [data-doc-text-root] [data-question-control]:not(input) {
|
|
1873
|
+
font-size: calc(11px * var(--doc-text-scale, 1));
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
/* Document font (Settings → Display → Document font, #1657; see
|
|
1877
|
+
utils/documentFont). The app sets data-doc-font and --doc-font-family on the
|
|
1878
|
+
document element only away from the default, so at the default none of this
|
|
1879
|
+
applies. Same scope as Text size: the outermost blocks of a document
|
|
1880
|
+
container, leaving out diagrams and the popouts. Headings, lists, tables and
|
|
1881
|
+
question cards inherit it; code keeps its monospace font because Tailwind's
|
|
1882
|
+
preflight sets it on code / pre and font-mono marks inline code, the raw
|
|
1883
|
+
diff and the gutters. In the base layer so a block's own font utility
|
|
1884
|
+
(font-mono on the raw diff) still wins. */
|
|
1885
|
+
@layer base {
|
|
1886
|
+
[data-doc-font] [data-doc-text-root] :is([data-block-id], [data-diff-block-index], [data-doc-text]):not([data-diagram-block], [data-diagram-block] *, [data-popout], [data-popout] *):not(:is([data-block-id], [data-diff-block-index], [data-doc-text]) *) {
|
|
1887
|
+
font-family: var(--doc-font-family);
|
|
1888
|
+
}
|
|
1889
|
+
/* The HTML embed's and SVG fence's captions and Show more are controls. */
|
|
1890
|
+
[data-doc-font] [data-doc-text-root] :is(figcaption, [data-html-embed-more]) {
|
|
1891
|
+
font-family: var(--font-sans);
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1717
1895
|
/* Reduced motion: Base UI popups (dialogs, menus, popovers, tooltips) animate
|
|
1718
1896
|
via [data-starting-style]/[data-ending-style] transitions — those attributes
|
|
1719
1897
|
only exist during the enter/exit phases, so zeroing transitions here makes
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/* Plannotator Graphite. Dark half: a near-neutral graphite tuned to stay readable
|
|
2
|
+
in daylight (lifted ground, larger surface steps, secondary text at APCA Lc 60+,
|
|
3
|
+
the purple primary kept inside sRGB). Light half: Plannotator's light, unchanged.
|
|
4
|
+
Approved 2026-10-24 (.product/approved/plannotator-graphite/). */
|
|
5
|
+
.theme-plannotator-graphite {
|
|
6
|
+
--background: oklch(0.225 0.006 286);
|
|
7
|
+
--foreground: oklch(0.945 0.003 286);
|
|
8
|
+
--card: oklch(0.295 0.006 286);
|
|
9
|
+
--card-foreground: oklch(0.945 0.003 286);
|
|
10
|
+
--popover: oklch(0.375 0.006 286);
|
|
11
|
+
--popover-foreground: oklch(0.945 0.003 286);
|
|
12
|
+
--primary: oklch(0.765 0.124 283);
|
|
13
|
+
--primary-foreground: oklch(0.20 0.01 286);
|
|
14
|
+
--secondary: oklch(0.740 0.123 180);
|
|
15
|
+
--secondary-foreground: oklch(0.20 0.01 286);
|
|
16
|
+
--muted: oklch(0.355 0.006 286);
|
|
17
|
+
--muted-foreground: oklch(0.820 0.007 286);
|
|
18
|
+
--accent: oklch(0.765 0.156 60);
|
|
19
|
+
--accent-foreground: oklch(0.20 0.01 286);
|
|
20
|
+
--destructive: oklch(0.770 0.124 25);
|
|
21
|
+
--destructive-foreground: oklch(0.20 0.01 286);
|
|
22
|
+
--border: oklch(0.435 0.006 286);
|
|
23
|
+
--input: oklch(0.355 0.006 286);
|
|
24
|
+
--ring: oklch(0.765 0.124 283);
|
|
25
|
+
--success: oklch(0.735 0.170 150);
|
|
26
|
+
--success-foreground: oklch(0.20 0.01 286);
|
|
27
|
+
--warning: oklch(0.760 0.144 85);
|
|
28
|
+
--warning-foreground: oklch(0.20 0.01 286);
|
|
29
|
+
|
|
30
|
+
--font-sans: 'Inter Variable', 'Inter', system-ui, sans-serif;
|
|
31
|
+
--font-mono: 'Geist Mono Variable', 'Geist Mono', 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;
|
|
32
|
+
--radius: 0.625rem;
|
|
33
|
+
|
|
34
|
+
--code-bg: oklch(0.355 0.006 286);
|
|
35
|
+
--focus-highlight: oklch(0.745 0.116 200);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.theme-plannotator-graphite.light {
|
|
39
|
+
/* Plannotator light inherits this from its own dark block; keep the same value. */
|
|
40
|
+
--focus-highlight: oklch(0.70 0.20 200);
|
|
41
|
+
--background: oklch(0.97 0.005 260);
|
|
42
|
+
--foreground: oklch(0.18 0.02 260);
|
|
43
|
+
--card: oklch(1 0 0);
|
|
44
|
+
--card-foreground: oklch(0.18 0.02 260);
|
|
45
|
+
--popover: oklch(1 0 0);
|
|
46
|
+
--popover-foreground: oklch(0.18 0.02 260);
|
|
47
|
+
--primary: oklch(0.50 0.25 280);
|
|
48
|
+
--primary-foreground: oklch(1 0 0);
|
|
49
|
+
--secondary: oklch(0.50 0.18 180);
|
|
50
|
+
--secondary-foreground: oklch(1 0 0);
|
|
51
|
+
--muted: oklch(0.92 0.01 260);
|
|
52
|
+
--muted-foreground: oklch(0.40 0.02 260);
|
|
53
|
+
--accent: oklch(0.60 0.22 50);
|
|
54
|
+
--accent-foreground: oklch(0.18 0.02 260);
|
|
55
|
+
--destructive: oklch(0.50 0.25 25);
|
|
56
|
+
--destructive-foreground: oklch(1 0 0);
|
|
57
|
+
--border: oklch(0.88 0.01 260);
|
|
58
|
+
--input: oklch(0.92 0.01 260);
|
|
59
|
+
--ring: oklch(0.50 0.25 280);
|
|
60
|
+
--success: oklch(0.45 0.20 150);
|
|
61
|
+
--success-foreground: oklch(1 0 0);
|
|
62
|
+
--warning: oklch(0.55 0.18 85);
|
|
63
|
+
--warning-foreground: oklch(0.18 0.02 260);
|
|
64
|
+
|
|
65
|
+
--code-bg: oklch(0.92 0.01 260);
|
|
66
|
+
}
|
package/types.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import type { DiagramAnchor } from '@plannotator/core/diagram-anchor';
|
|
2
|
-
import type {
|
|
2
|
+
import type { ImageRegion } from '@plannotator/core/image-region';
|
|
3
|
+
import type { DocumentRenderKind } from '@plannotator/core/annotatable';
|
|
3
4
|
import type { QuestionAnswer } from '@plannotator/core/question-block';
|
|
4
5
|
|
|
5
6
|
export type { DiagramAnchor } from '@plannotator/core/diagram-anchor';
|
|
7
|
+
export type { ImageRegion, ImageRegionBox } from '@plannotator/core/image-region';
|
|
6
8
|
export type { QuestionAnswer } from '@plannotator/core/question-block';
|
|
7
|
-
export type { DiagramRenderKind } from '@plannotator/core/annotatable';
|
|
9
|
+
export type { DiagramRenderKind, DocumentRenderKind } from '@plannotator/core/annotatable';
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* How a document's body is rendered. `markdown` and `html` are the original
|
|
11
13
|
* pair; the two diagram kinds are whole-file diagram sources (.mmd/.mermaid,
|
|
12
14
|
* .dot/.gv) that render as ONE diagram through the same engine a ```mermaid
|
|
13
|
-
* fence uses — see diagramDocumentBlocks in utils/parser.
|
|
15
|
+
* fence uses — see diagramDocumentBlocks in utils/parser. `asciidoc` is an
|
|
16
|
+
* .adoc source parsed by utils/asciidocParser into the same block model.
|
|
14
17
|
*/
|
|
15
|
-
export type DocumentRenderAs = 'markdown' | 'html' |
|
|
18
|
+
export type DocumentRenderAs = 'markdown' | 'html' | DocumentRenderKind;
|
|
16
19
|
|
|
17
20
|
export enum AnnotationType {
|
|
18
21
|
DELETION = 'DELETION',
|
|
@@ -102,6 +105,7 @@ export interface Annotation {
|
|
|
102
105
|
elementContext?: HtmlElementContext; // raw-HTML / live-app pinpoint: bounded agent-facing description of the primary element (never used by restore)
|
|
103
106
|
htmlAdditionalTargets?: HtmlAnnotationTarget[]; // raw-HTML shift-click multi-select: extra elements this one comment covers (primary stays htmlAnchor/originalText)
|
|
104
107
|
diagramAnchor?: DiagramAnchor; // a comment on a rendered diagram part (Mermaid / Graphviz fence): the part's own id, label and document source line; the highlighter skips it and the diagram overlay restores it (see @plannotator/core/diagram-anchor)
|
|
108
|
+
imageRegion?: ImageRegion; // a comment on part of an image in a markdown document (#2100): the image's src as written, its document line, and the box in the image's own pixels; drawn by the image viewer, skipped by the highlighter, exported as `Image:` / `Region:` lines (see @plannotator/core/image-region)
|
|
105
109
|
questionAnswer?: QuestionAnswer; // the reviewer's answer to a `:::question` block (id `ann-question-<key>`): the Viewer draws the answer from it, the highlighter skips it, and the export prints it in the "Answers to your questions" section instead of as a numbered comment (see @plannotator/core/question-block)
|
|
106
110
|
// web-highlighter metadata for cross-element selections
|
|
107
111
|
startMeta?: AnnotationTextMeta;
|
|
@@ -355,8 +359,10 @@ export interface CodeAnnotation {
|
|
|
355
359
|
gitButlerBase?: string;
|
|
356
360
|
/** Exact server snapshot that supplied the GitButler line coordinates. */
|
|
357
361
|
gitButlerSnapshotId?: string;
|
|
362
|
+
/** Local Git worktree + branch + comparison identity supplied by the server. */
|
|
363
|
+
localReviewTarget?: string;
|
|
358
364
|
/**
|
|
359
|
-
* PR reviews
|
|
365
|
+
* PR and local Git reviews (#1590): the text of the diff lines this line comment
|
|
360
366
|
* was anchored to at creation (lineStart..lineEnd on `side`, joined with
|
|
361
367
|
* "\n"). When a saved draft is restored against a different patch, the
|
|
362
368
|
* comment keeps its position only if these lines still read the same;
|
|
@@ -365,7 +371,7 @@ export interface CodeAnnotation {
|
|
|
365
371
|
*/
|
|
366
372
|
anchorText?: string;
|
|
367
373
|
/**
|
|
368
|
-
* PR reviews
|
|
374
|
+
* PR and local Git reviews (#1590): the lines around the anchor on the same side
|
|
369
375
|
* (up to two before and two after; `null` where a line is outside the patch
|
|
370
376
|
* hunks) and the hunk header's function context (`hunk`, when git printed
|
|
371
377
|
* one), so a comment on a common line (`}`, `return null;`) only keeps its
|
|
@@ -373,19 +379,30 @@ export interface CodeAnnotation {
|
|
|
373
379
|
*/
|
|
374
380
|
anchorContext?: { before: (string | null)[]; after: (string | null)[]; hunk?: string };
|
|
375
381
|
/**
|
|
376
|
-
* PR reviews
|
|
382
|
+
* PR and local Git reviews (#1590): the review snapshot id of the diff whose line
|
|
377
383
|
* coordinates this comment uses. Re-stamped when a later diff passes the
|
|
378
384
|
* anchor check; a line comment is only posted inline when this matches the
|
|
379
385
|
* diff currently known for its PR.
|
|
380
386
|
*/
|
|
381
387
|
anchorSnapshot?: string;
|
|
382
388
|
/**
|
|
383
|
-
* Set when a
|
|
384
|
-
*
|
|
389
|
+
* Set when a comment no longer matches the code it was written on.
|
|
390
|
+
* Its line numbers refer to
|
|
385
391
|
* the earlier version: it is listed and exported (labelled) but never drawn
|
|
386
392
|
* inline on the current diff and never posted as an inline PR comment.
|
|
387
393
|
*/
|
|
388
394
|
outdated?: boolean;
|
|
395
|
+
/**
|
|
396
|
+
* Code review only: a comment on lines outside the diff — a file the review
|
|
397
|
+
* does not change (opened from All files), or lines of a changed file
|
|
398
|
+
* outside its hunks (its Whole file view). Line-scoped on the new side,
|
|
399
|
+
* with the commented code in `anchorText`. The agent gets it with the file,
|
|
400
|
+
* lines and code; a PR review posts it in the review body with the quoted
|
|
401
|
+
* code and a permalink, never inline (no platform accepts an inline comment
|
|
402
|
+
* outside the PR's diff). Never marked outdated by a diff re-check.
|
|
403
|
+
* Additive: annotations without it behave exactly as before.
|
|
404
|
+
*/
|
|
405
|
+
outsideDiff?: boolean;
|
|
389
406
|
/**
|
|
390
407
|
* Code review only: a comment made by selecting text in a Markdown file's
|
|
391
408
|
* rendered preview. Always file-scoped (`scope: 'file'`); `lineStart` /
|
package/utils/agentSwitch.ts
CHANGED
|
@@ -9,14 +9,19 @@
|
|
|
9
9
|
* stored is surface-specific: plan approval keeps its historical hand-off to the
|
|
10
10
|
* build agent, while review feedback stays on the current agent.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Stored through the settings registry (`agentSwitch` in config/settings.ts):
|
|
13
|
+
* cookies (not localStorage, because each hook invocation runs on a random
|
|
14
|
+
* port) plus `agentSwitch` in ~/.plannotator/config.json, which wins over the
|
|
15
|
+
* cookie (#1390: the Glimpse webview keeps no cookies between runs).
|
|
14
16
|
*/
|
|
15
17
|
|
|
16
|
-
import {
|
|
18
|
+
import { configStore } from '../config/configStore';
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
/** The two configStore calls this module makes; tests pass a fresh store. */
|
|
21
|
+
interface AgentSwitchStore {
|
|
22
|
+
get(key: 'agentSwitch'): { switchTo: string; customName?: string } | null;
|
|
23
|
+
set(key: 'agentSwitch', value: { switchTo: string; customName?: string } | null): void;
|
|
24
|
+
}
|
|
20
25
|
|
|
21
26
|
// AgentSwitchOption is now a string to support dynamic agent names from OpenCode
|
|
22
27
|
export type AgentSwitchOption = string;
|
|
@@ -58,25 +63,26 @@ export function getAgentSwitchDefaults(surface: AgentSwitchSurface = 'plan'): Ag
|
|
|
58
63
|
* An explicit user choice applies to every surface; only the unset default
|
|
59
64
|
* varies by surface.
|
|
60
65
|
*/
|
|
61
|
-
export function getAgentSwitchSettings(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return { switchTo: stored, customName };
|
|
68
|
-
}
|
|
69
|
-
return getAgentSwitchDefaults(surface);
|
|
66
|
+
export function getAgentSwitchSettings(
|
|
67
|
+
surface: AgentSwitchSurface = 'plan',
|
|
68
|
+
store: AgentSwitchStore = configStore,
|
|
69
|
+
): AgentSwitchSettings {
|
|
70
|
+
const stored = store.get('agentSwitch');
|
|
71
|
+
return stored ? { ...stored } : getAgentSwitchDefaults(surface);
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
/**
|
|
73
|
-
* Save agent switch settings
|
|
75
|
+
* Save agent switch settings: cookie at once, config.json through the
|
|
76
|
+
* registry's debounced POST /api/config.
|
|
74
77
|
*/
|
|
75
|
-
export function saveAgentSwitchSettings(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
export function saveAgentSwitchSettings(
|
|
79
|
+
settings: AgentSwitchSettings,
|
|
80
|
+
store: AgentSwitchStore = configStore,
|
|
81
|
+
): void {
|
|
82
|
+
store.set('agentSwitch', {
|
|
83
|
+
switchTo: settings.switchTo,
|
|
84
|
+
...(settings.customName && { customName: settings.customName }),
|
|
85
|
+
});
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
/**
|
|
@@ -95,3 +101,18 @@ export function getEffectiveAgentName(settings: AgentSwitchSettings): string | u
|
|
|
95
101
|
}
|
|
96
102
|
return settings.switchTo;
|
|
97
103
|
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The agent name an approval hands off to, for display ("Approve → build"):
|
|
107
|
+
* a discovered agent's display name, the custom name, or the stored id when
|
|
108
|
+
* it matches nothing discovered. Null when switching is off.
|
|
109
|
+
*/
|
|
110
|
+
export function agentSwitchTargetLabel(
|
|
111
|
+
setting: AgentSwitchSettings,
|
|
112
|
+
agents: ReadonlyArray<{ id: string; name: string }>,
|
|
113
|
+
): string | null {
|
|
114
|
+
if (setting.switchTo === 'disabled') return null;
|
|
115
|
+
if (setting.switchTo === 'custom') return setting.customName || null;
|
|
116
|
+
const match = agents.find((a) => a.id.toLowerCase() === setting.switchTo.toLowerCase());
|
|
117
|
+
return match?.name ?? setting.switchTo;
|
|
118
|
+
}
|
|
@@ -111,5 +111,5 @@ export function agentToolAppliesWhen(host: AgentToolHost): string {
|
|
|
111
111
|
export function agentToolCostNote(host: AgentToolHost): string {
|
|
112
112
|
if (host === 'claude-code') return 'Claude Code loads it only when needed.';
|
|
113
113
|
if (host === 'oh-my-pi') return 'Oh My Pi lists it in one short line (about 50 tokens) and loads the rest only when needed.';
|
|
114
|
-
return 'Adds about
|
|
114
|
+
return 'Adds about 800 tokens to every request.';
|
|
115
115
|
}
|
package/utils/aiChatFormat.ts
CHANGED
|
@@ -2,14 +2,30 @@ import { createElement, type ReactNode } from 'react';
|
|
|
2
2
|
import { marked } from 'marked';
|
|
3
3
|
import DOMPurify from 'dompurify';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* What DOMPurify keeps of an Ask AI answer. Every tag and attribute marked
|
|
7
|
+
* writes for the markdown an answer uses must be listed here, or DOMPurify
|
|
8
|
+
* drops the element and keeps only its text (#2069: tables ran together).
|
|
9
|
+
*/
|
|
10
|
+
export const CHAT_SANITIZE_CONFIG = {
|
|
11
|
+
ALLOWED_TAGS: [
|
|
12
|
+
'p', 'br', 'strong', 'em', 'code', 'pre', 'ul', 'ol', 'li',
|
|
13
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'a', 'blockquote',
|
|
14
|
+
'table', 'thead', 'tbody', 'tr', 'th', 'td',
|
|
15
|
+
],
|
|
16
|
+
// `align` is what marked writes on th/td for aligned GFM columns.
|
|
17
|
+
ALLOWED_ATTR: ['href', 'rel', 'class', 'align'],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** The markdown step of {@link renderChatMarkdown}, before sanitizing. */
|
|
21
|
+
export function chatMarkdownToHtml(text: string): string {
|
|
22
|
+
return marked.parse(text, { async: false, breaks: true }) as string;
|
|
23
|
+
}
|
|
24
|
+
|
|
5
25
|
export function renderChatMarkdown(text: string): ReactNode {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
'p', 'br', 'strong', 'em', 'code', 'pre', 'ul', 'ol', 'li',
|
|
10
|
-
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'a', 'blockquote',
|
|
11
|
-
],
|
|
12
|
-
ALLOWED_ATTR: ['href', 'rel', 'class'],
|
|
26
|
+
const clean = DOMPurify.sanitize(chatMarkdownToHtml(text), {
|
|
27
|
+
ALLOWED_TAGS: [...CHAT_SANITIZE_CONFIG.ALLOWED_TAGS],
|
|
28
|
+
ALLOWED_ATTR: [...CHAT_SANITIZE_CONFIG.ALLOWED_ATTR],
|
|
13
29
|
});
|
|
14
30
|
|
|
15
31
|
return createElement('div', {
|
|
@@ -60,13 +60,41 @@ export interface TocItem {
|
|
|
60
60
|
annotationCount: number;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* The headings a Contents list shows, in document order: levels 1 to
|
|
65
|
+
* `maxLevel` (3 by default, plan review's list). The one place that picks
|
|
66
|
+
* them: `TableOfContents`, plan review's "has entries" check and the contents
|
|
67
|
+
* rail (`MarkdownContentsRail`, levels 1 to 4) all read it.
|
|
68
|
+
*/
|
|
69
|
+
export function tocHeadings(blocks: readonly Block[], maxLevel = 3): Block[] {
|
|
70
|
+
return blocks
|
|
71
|
+
.filter(b => b.type === 'heading' && (b.level ?? 0) <= maxLevel)
|
|
72
|
+
.sort((a, b) => a.order - b.order);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The contents rail lists headings of levels 1 to this (`MarkdownContentsRail`). */
|
|
76
|
+
export const CONTENTS_RAIL_MAX_LEVEL = 4;
|
|
77
|
+
/** The contents rail shows for a document with at least this many of them. */
|
|
78
|
+
export const CONTENTS_RAIL_MIN_HEADINGS = 4;
|
|
79
|
+
/** The contents rail shows in a scroll element at least this wide (and with a 68 px gutter). */
|
|
80
|
+
export const CONTENTS_RAIL_MIN_PANE_WIDTH = 480;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Whether a document gets the contents rail: at least
|
|
84
|
+
* `CONTENTS_RAIL_MIN_HEADINGS` headings of levels 1 to
|
|
85
|
+
* `CONTENTS_RAIL_MAX_LEVEL`. Kept out of the rail's own module, so a host can
|
|
86
|
+
* decide without loading the (lazy) rail.
|
|
87
|
+
*/
|
|
88
|
+
export function hasContentsRailHeadings(blocks: readonly Block[]): boolean {
|
|
89
|
+
return tocHeadings(blocks, CONTENTS_RAIL_MAX_LEVEL).length >= CONTENTS_RAIL_MIN_HEADINGS;
|
|
90
|
+
}
|
|
91
|
+
|
|
63
92
|
export function buildTocHierarchy(
|
|
64
93
|
blocks: Block[],
|
|
65
|
-
annotationCounts: Map<string, number
|
|
94
|
+
annotationCounts: Map<string, number>,
|
|
95
|
+
maxLevel = 3
|
|
66
96
|
): TocItem[] {
|
|
67
|
-
const headings = blocks
|
|
68
|
-
.filter(b => b.type === 'heading' && (b.level ?? 0) <= 3)
|
|
69
|
-
.sort((a, b) => a.order - b.order);
|
|
97
|
+
const headings = tocHeadings(blocks, maxLevel);
|
|
70
98
|
|
|
71
99
|
const root: TocItem[] = [];
|
|
72
100
|
const stack: TocItem[] = [];
|
|
@@ -10,18 +10,20 @@ export const CHECKBOX_ANNOTATION_PREFIX = 'ann-checkbox-';
|
|
|
10
10
|
*
|
|
11
11
|
* Not: comments made in the plan diff view (`diffContext`, `blockId`
|
|
12
12
|
* `diff-block-N`, drawn by the diff view), general comments, checkbox
|
|
13
|
-
* toggles,
|
|
13
|
+
* toggles, answers to `:::question` blocks (drawn by the question card), and
|
|
14
|
+
* comments on part of an image (`imageRegion`, drawn by the image viewer).
|
|
14
15
|
* Restoring any of those through the highlighter finds no text, so it would
|
|
15
16
|
* be reported unanchored and its `blockId` rewritten. Every caller that
|
|
16
17
|
* re-applies a stored list filters with this, and the highlighter skips the
|
|
17
18
|
* same rows itself.
|
|
18
19
|
*/
|
|
19
20
|
export function annotationOwnsHighlight(
|
|
20
|
-
annotation: Pick<Annotation, 'id' | 'type' | 'diffContext' | 'questionAnswer'
|
|
21
|
+
annotation: Pick<Annotation, 'id' | 'type' | 'diffContext' | 'questionAnswer'> & Partial<Pick<Annotation, 'imageRegion'>>,
|
|
21
22
|
): boolean {
|
|
22
23
|
return !annotation.diffContext
|
|
23
24
|
&& annotation.type !== AnnotationType.GLOBAL_COMMENT
|
|
24
25
|
&& !annotation.id.startsWith(CHECKBOX_ANNOTATION_PREFIX)
|
|
25
26
|
&& annotation.questionAnswer == null
|
|
27
|
+
&& annotation.imageRegion == null
|
|
26
28
|
&& !annotation.id.startsWith(QUESTION_ANSWER_ANNOTATION_PREFIX);
|
|
27
29
|
}
|