@plannotator/ui 0.22.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/README.md +60 -0
- package/assets/diff-options.png +0 -0
- package/assets/icon-codex.png +0 -0
- package/assets/look-flat.png +0 -0
- package/assets/look-grid.png +0 -0
- package/assets/review-sections.png +0 -0
- package/assets/review-tree.png +0 -0
- package/assets/workspaces.webp +0 -0
- package/components/AISettingsTab.tsx +145 -0
- package/components/ActionMenu.tsx +101 -0
- package/components/AgentControls.tsx +216 -0
- package/components/AgentsTab.tsx +1294 -0
- package/components/AnnotationPanel.tsx +731 -0
- package/components/AnnotationSidebar.tsx +86 -0
- package/components/AnnotationToolbar.tsx +323 -0
- package/components/AnnotationToolstrip.tsx +355 -0
- package/components/ApproveDropdown.tsx +170 -0
- package/components/AttachmentsButton.tsx +410 -0
- package/components/BlockRenderer.tsx +163 -0
- package/components/BorderTrail.tsx +38 -0
- package/components/CodeFilePicker.tsx +64 -0
- package/components/CodeFilePopout.tsx +602 -0
- package/components/CodePathValidationContext.tsx +14 -0
- package/components/CommentPopover.tsx +512 -0
- package/components/CompletionOverlay.tsx +99 -0
- package/components/ConfirmDialog.tsx +128 -0
- package/components/DocBadges.tsx +231 -0
- package/components/EditorAnnotationCard.tsx +65 -0
- package/components/ExportModal.tsx +565 -0
- package/components/FloatingQuickLabelPicker.tsx +144 -0
- package/components/GitHubIcon.tsx +16 -0
- package/components/GitLabIcon.tsx +30 -0
- package/components/GraphvizBlock.tsx +511 -0
- package/components/ImageAnnotator/Canvas.tsx +118 -0
- package/components/ImageAnnotator/Toolbar.tsx +218 -0
- package/components/ImageAnnotator/index.tsx +272 -0
- package/components/ImageAnnotator/types.ts +39 -0
- package/components/ImageAnnotator/utils.ts +147 -0
- package/components/ImageThumbnail.tsx +126 -0
- package/components/ImportModal.tsx +146 -0
- package/components/InlineMarkdown.tsx +1094 -0
- package/components/KeyboardShortcuts.tsx +208 -0
- package/components/Landing.tsx +496 -0
- package/components/ListItemBody.tsx +62 -0
- package/components/ListMarker.tsx +73 -0
- package/components/LookAndFeelAnnouncementDialog.tsx +230 -0
- package/components/MarkdownEditor.tsx +44 -0
- package/components/MenuVersionSection.tsx +91 -0
- package/components/MermaidBlock.tsx +579 -0
- package/components/ModeToggle.tsx +68 -0
- package/components/OpenInAppButton.tsx +299 -0
- package/components/OverlayScrollArea.tsx +82 -0
- package/components/PermissionModeSetup.tsx +92 -0
- package/components/PinpointOverlay.tsx +99 -0
- package/components/PlanAIAnnouncementDialog.tsx +187 -0
- package/components/PlanHeaderMenu.tsx +299 -0
- package/components/PopoutDialog.tsx +91 -0
- package/components/Popover.tsx +27 -0
- package/components/ProviderIcons.tsx +51 -0
- package/components/PullRequestIcon.tsx +12 -0
- package/components/QuickLabelDropdown.tsx +63 -0
- package/components/RenderedMarkdown.tsx +54 -0
- package/components/RepoIcon.tsx +12 -0
- package/components/ResizeHandle.tsx +86 -0
- package/components/ReviewAgentsIcon.tsx +18 -0
- package/components/SearchableSelect.tsx +157 -0
- package/components/Settings.tsx +2220 -0
- package/components/SparklesIcon.tsx +45 -0
- package/components/StickyHeaderLane.tsx +260 -0
- package/components/TableOfContents.tsx +157 -0
- package/components/TaterSpritePullup.tsx +34 -0
- package/components/TaterSpriteRunning.tsx +54 -0
- package/components/TaterSpriteSitting.tsx +37 -0
- package/components/TextShimmer.tsx +57 -0
- package/components/ThemeProvider.tsx +162 -0
- package/components/ThemeTab.tsx +139 -0
- package/components/ToolbarButtons.tsx +116 -0
- package/components/Tooltip.tsx +45 -0
- package/components/Viewer.tsx +977 -0
- package/components/ai/AIProviderBar.tsx +95 -0
- package/components/ai/DocumentAIChatPanel.tsx +313 -0
- package/components/blocks/AlertBlock.tsx +58 -0
- package/components/blocks/Callout.tsx +60 -0
- package/components/blocks/CodeBlock.tsx +75 -0
- package/components/blocks/HtmlBlock.tsx +122 -0
- package/components/blocks/MathBlock.tsx +36 -0
- package/components/blocks/TableBlock.tsx +146 -0
- package/components/blocks/TablePopout.tsx +279 -0
- package/components/blocks/TableToolbar.tsx +153 -0
- package/components/blocks/proseBody.tsx +102 -0
- package/components/core/button.tsx +44 -0
- package/components/core/textarea.tsx +26 -0
- package/components/diagramLanguages.ts +14 -0
- package/components/goal-setup/GoalSetupSurface.tsx +1354 -0
- package/components/html-viewer/HtmlViewer.tsx +386 -0
- package/components/html-viewer/bridge-script.ts +505 -0
- package/components/html-viewer/index.ts +1 -0
- package/components/html-viewer/useHtmlAnnotation.ts +400 -0
- package/components/icons/AgentIcons.tsx +66 -0
- package/components/icons/AppIcon.tsx +56 -0
- package/components/icons/MessagesIcon.tsx +11 -0
- package/components/icons/ObsidianIcons.tsx +208 -0
- package/components/icons/app/android-studio.svg +369 -0
- package/components/icons/app/antigravity.svg +97 -0
- package/components/icons/app/cursor.svg +16 -0
- package/components/icons/app/file-explorer.svg +20 -0
- package/components/icons/app/finder.png +0 -0
- package/components/icons/app/ghostty.svg +13 -0
- package/components/icons/app/iterm2.svg +13 -0
- package/components/icons/app/powershell.svg +14 -0
- package/components/icons/app/sublime-text.svg +17 -0
- package/components/icons/app/terminal.png +0 -0
- package/components/icons/app/textmate.png +0 -0
- package/components/icons/app/vscode.svg +39 -0
- package/components/icons/app/warp.png +0 -0
- package/components/icons/app/xcode.png +0 -0
- package/components/icons/app/zed-dark.svg +15 -0
- package/components/icons/app/zed.svg +15 -0
- package/components/icons/themeIcons.tsx +47 -0
- package/components/mermaidSvg.ts +33 -0
- package/components/plan-diff/PlanCleanDiffView.tsx +884 -0
- package/components/plan-diff/PlanDiffBadge.tsx +48 -0
- package/components/plan-diff/PlanDiffModeSwitcher.tsx +103 -0
- package/components/plan-diff/PlanDiffViewer.tsx +214 -0
- package/components/plan-diff/PlanRawDiffView.tsx +102 -0
- package/components/plan-diff/VSCodeIcon.tsx +132 -0
- package/components/settings/HooksTab.tsx +208 -0
- package/components/sidebar/ArchiveBrowser.tsx +98 -0
- package/components/sidebar/CountBadge.tsx +12 -0
- package/components/sidebar/FileBrowser.tsx +498 -0
- package/components/sidebar/MessagesBrowser.tsx +109 -0
- package/components/sidebar/SidebarContainer.tsx +353 -0
- package/components/sidebar/SidebarTabs.tsx +155 -0
- package/components/sidebar/VersionBrowser.tsx +141 -0
- package/components/types.d.ts +7 -0
- package/components/ui/badge.tsx +44 -0
- package/components/ui/button.tsx +83 -0
- package/components/ui/card.tsx +58 -0
- package/components/ui/dialog.tsx +106 -0
- package/components/ui/dropdown-menu.tsx +240 -0
- package/components/ui/state-pill.tsx +45 -0
- package/components/ui/tabs.tsx +46 -0
- package/components/ui/textarea.tsx +25 -0
- package/config/configStore.ts +214 -0
- package/config/index.ts +4 -0
- package/config/reviewView.ts +42 -0
- package/config/settings.ts +310 -0
- package/config/useConfig.ts +20 -0
- package/configure.ts +69 -0
- package/globals.d.ts +14 -0
- package/hooks/pfm/useCodeFilePopout.ts +111 -0
- package/hooks/useAIChat.ts +532 -0
- package/hooks/useAIProviderConfig.ts +115 -0
- package/hooks/useActiveSection.ts +78 -0
- package/hooks/useAgentJobs.ts +306 -0
- package/hooks/useAgentSettings.ts +579 -0
- package/hooks/useAgents.ts +90 -0
- package/hooks/useAnnotationDraft.ts +523 -0
- package/hooks/useAnnotationHighlighter.ts +1170 -0
- package/hooks/useArchive.ts +171 -0
- package/hooks/useAutoClose.ts +104 -0
- package/hooks/useCodeAnnotationDraft.ts +194 -0
- package/hooks/useCodeFilePopout.ts +1 -0
- package/hooks/useDismissOnOutsideAndEscape.ts +50 -0
- package/hooks/useDraggable.ts +108 -0
- package/hooks/useEditorAnnotations.ts +64 -0
- package/hooks/useExternalAnnotationHighlights.ts +105 -0
- package/hooks/useExternalAnnotations.ts +268 -0
- package/hooks/useFileBrowser.ts +402 -0
- package/hooks/useInputMethodSwitch.ts +89 -0
- package/hooks/useIsMobile.ts +17 -0
- package/hooks/useLinkedDoc.ts +494 -0
- package/hooks/useOverlayViewport.ts +36 -0
- package/hooks/usePinpoint.ts +182 -0
- package/hooks/usePlanDiff.ts +176 -0
- package/hooks/usePrintMode.ts +27 -0
- package/hooks/useResizablePanel.ts +173 -0
- package/hooks/useScrollViewport.ts +38 -0
- package/hooks/useSharing.ts +441 -0
- package/hooks/useSidebar.ts +55 -0
- package/hooks/useUpdateCheck.ts +132 -0
- package/hooks/useValidatedCodePaths.ts +94 -0
- package/icons/GitUser.tsx +17 -0
- package/lib/utils.ts +11 -0
- package/package.json +117 -0
- package/plannotator.webp +0 -0
- package/print.css +427 -0
- package/shortcuts/code-review/ai.shortcuts.ts +25 -0
- package/shortcuts/code-review/allFilesDiff.shortcuts.ts +46 -0
- package/shortcuts/code-review/annotationToolbar.shortcuts.ts +30 -0
- package/shortcuts/code-review/fileTree.shortcuts.ts +35 -0
- package/shortcuts/code-review/prComments.shortcuts.ts +18 -0
- package/shortcuts/code-review/suggestionModal.shortcuts.ts +25 -0
- package/shortcuts/code-review/tourDialog.shortcuts.ts +18 -0
- package/shortcuts/core.ts +399 -0
- package/shortcuts/index.ts +21 -0
- package/shortcuts/plan-review/annotationPanel.shortcuts.ts +25 -0
- package/shortcuts/plan-review/annotationToolbar.shortcuts.ts +39 -0
- package/shortcuts/plan-review/commentPopover.shortcuts.ts +21 -0
- package/shortcuts/plan-review/goalSetup.shortcuts.ts +10 -0
- package/shortcuts/plan-review/imageAnnotator.shortcuts.ts +42 -0
- package/shortcuts/plan-review/inputMethod.shortcuts.ts +30 -0
- package/shortcuts/plan-review/sidebar.shortcuts.ts +34 -0
- package/shortcuts/plan-review/viewer.shortcuts.ts +24 -0
- package/shortcuts/runtime.ts +259 -0
- package/sprite_package_additional/index.html +34 -0
- package/sprite_package_additional/sprite.png +0 -0
- package/sprite_package_new/index.html +34 -0
- package/sprite_package_new/sprite.png +0 -0
- package/sprite_package_pulluphang/index.html +34 -0
- package/sprite_package_pulluphang/sprite.png +0 -0
- package/styles.css +1 -0
- package/theme.css +893 -0
- package/themes/adwaita.css +62 -0
- package/themes/andromeeda.css +66 -0
- package/themes/aurora-x.css +66 -0
- package/themes/ayu-dark.css +66 -0
- package/themes/caffeine.css +113 -0
- package/themes/catppuccin.css +62 -0
- package/themes/claude-plus.css +60 -0
- package/themes/cursor-hc.css +34 -0
- package/themes/cursor-midnight.css +34 -0
- package/themes/cursor.css +62 -0
- package/themes/dark-plus.css +66 -0
- package/themes/doom-64.css +109 -0
- package/themes/dracula.css +33 -0
- package/themes/everforest-hard.css +62 -0
- package/themes/everforest-soft.css +62 -0
- package/themes/everforest.css +62 -0
- package/themes/github.css +66 -0
- package/themes/gruvbox.css +62 -0
- package/themes/houston.css +66 -0
- package/themes/kanagawa-dragon.css +34 -0
- package/themes/kanagawa-lotus.css +34 -0
- package/themes/kanagawa-wave.css +34 -0
- package/themes/laserwave.css +66 -0
- package/themes/material.css +62 -0
- package/themes/min.css +66 -0
- package/themes/monokai-pro.css +34 -0
- package/themes/neutral.css +59 -0
- package/themes/night-owl.css +66 -0
- package/themes/nord.css +66 -0
- package/themes/one-dark-pro.css +66 -0
- package/themes/one-light.css +66 -0
- package/themes/paulmillr.css +34 -0
- package/themes/plannotator.css +60 -0
- package/themes/plastic.css +66 -0
- package/themes/poimandres.css +66 -0
- package/themes/quantum-rose.css +109 -0
- package/themes/red.css +66 -0
- package/themes/rose-pine.css +62 -0
- package/themes/simple.css +124 -0
- package/themes/slack.css +66 -0
- package/themes/snazzy-light.css +66 -0
- package/themes/soft-pop.css +60 -0
- package/themes/solar-dusk.css +109 -0
- package/themes/solarized.css +66 -0
- package/themes/synthwave-84.css +34 -0
- package/themes/terminal.css +62 -0
- package/themes/tinacious.css +57 -0
- package/themes/tokyo-night.css +62 -0
- package/themes/vesper.css +62 -0
- package/themes/vitesse-black.css +66 -0
- package/themes/vitesse.css +62 -0
- package/types.ts +264 -0
- package/utils/agentSwitch.ts +71 -0
- package/utils/aiChatFormat.ts +33 -0
- package/utils/aiPrompt.ts +29 -0
- package/utils/aiProvider.ts +217 -0
- package/utils/anchors.ts +11 -0
- package/utils/annotateAgentTerminal.ts +29 -0
- package/utils/annotationHelpers.ts +101 -0
- package/utils/bear.ts +59 -0
- package/utils/blockTargeting.ts +240 -0
- package/utils/callback.ts +99 -0
- package/utils/commentContent.ts +8 -0
- package/utils/defaultNotesApp.ts +20 -0
- package/utils/diffFonts.ts +33 -0
- package/utils/editorMode.ts +31 -0
- package/utils/fileBrowser.ts +40 -0
- package/utils/generateId.ts +7 -0
- package/utils/generateIdentity.ts +29 -0
- package/utils/identity.ts +112 -0
- package/utils/inlineTransforms.ts +41 -0
- package/utils/inputMethod.ts +17 -0
- package/utils/lookAndFeelAnnouncement.ts +18 -0
- package/utils/obsidian.ts +201 -0
- package/utils/octarine.ts +53 -0
- package/utils/parser.ts +991 -0
- package/utils/permissionMode.ts +77 -0
- package/utils/planAIAnnouncement.ts +17 -0
- package/utils/planAgentInstructions.ts +137 -0
- package/utils/planDiffEngine.ts +590 -0
- package/utils/planSave.ts +49 -0
- package/utils/platform.ts +11 -0
- package/utils/quickLabels.ts +77 -0
- package/utils/reviewAgentInstructions.ts +182 -0
- package/utils/sanitizeHtml.ts +45 -0
- package/utils/sharing.ts +350 -0
- package/utils/slugify.ts +37 -0
- package/utils/storage.ts +141 -0
- package/utils/themeRegistry.ts +567 -0
- package/utils/uiPreferences.ts +34 -0
- package/utils/upload.ts +56 -0
- package/utils/wideMode.ts +48 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tater Identity System
|
|
3
|
+
*
|
|
4
|
+
* Generates anonymous identities for collaborative annotation sharing.
|
|
5
|
+
* Format: {adjective}-{noun}-tater
|
|
6
|
+
* Examples: "swift-falcon-tater", "gentle-crystal-tater"
|
|
7
|
+
*
|
|
8
|
+
* Resolution is delegated to ConfigStore (packages/ui/config/configStore.ts)
|
|
9
|
+
* which handles: server config file > cookie > generated tater name.
|
|
10
|
+
* This module provides the identity-specific API surface.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { configStore } from '../config';
|
|
14
|
+
import { generateIdentity } from './generateIdentity';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Host-overridable identity provider.
|
|
18
|
+
*
|
|
19
|
+
* Default = today's tater behavior (ConfigStore-backed nickname + cookie match).
|
|
20
|
+
* A host (e.g. Workspaces) calls setIdentityProvider once at startup to stamp its
|
|
21
|
+
* logged-in user on comments and drive the `(me)` badge instead. Mirrors the
|
|
22
|
+
* swappable storage backend in ./storage.ts (StorageBackend/setStorageBackend).
|
|
23
|
+
*/
|
|
24
|
+
export interface IdentityProvider {
|
|
25
|
+
/** Display name stamped as `author` on new annotations. */
|
|
26
|
+
getIdentity(): string;
|
|
27
|
+
/** Whether an annotation's `author` is the current user (drives the `(me)` badge). */
|
|
28
|
+
isCurrentUser(author: string | undefined): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Whether the user may change their own display name from the Settings UI.
|
|
31
|
+
* Default (Plannotator's cookie identity) is editable. A host whose identity
|
|
32
|
+
* is owned by its auth system (e.g. a logged-in Workspaces user, whose author
|
|
33
|
+
* is the server-stamped account id) returns `false` so the rename/regenerate
|
|
34
|
+
* controls are hidden — preventing a locally-chosen name from diverging from
|
|
35
|
+
* the server-stamped author. Optional for backward-compat: absent ⇒ editable.
|
|
36
|
+
*/
|
|
37
|
+
isEditable?(): boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Default provider: today's literal Plannotator behavior.
|
|
42
|
+
* `displayName` resolution stays in ConfigStore (server config > cookie > tater).
|
|
43
|
+
*/
|
|
44
|
+
const defaultIdentityProvider: IdentityProvider = {
|
|
45
|
+
getIdentity(): string {
|
|
46
|
+
return configStore.get('displayName');
|
|
47
|
+
},
|
|
48
|
+
isCurrentUser(author: string | undefined): boolean {
|
|
49
|
+
if (!author) return false;
|
|
50
|
+
return author === configStore.get('displayName');
|
|
51
|
+
},
|
|
52
|
+
isEditable(): boolean {
|
|
53
|
+
return true;
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Active provider. Defaults to the tater identity so Plannotator is unchanged.
|
|
58
|
+
let identityProvider: IdentityProvider = defaultIdentityProvider;
|
|
59
|
+
|
|
60
|
+
/** Override the identity provider. Call once at app startup. */
|
|
61
|
+
export function setIdentityProvider(p: IdentityProvider): void {
|
|
62
|
+
identityProvider = p;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Reset to the default (tater) provider. Mainly for tests. */
|
|
66
|
+
export function resetIdentityProvider(): void {
|
|
67
|
+
identityProvider = defaultIdentityProvider;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get current identity. Delegates to the active provider (default = ConfigStore tater).
|
|
72
|
+
*/
|
|
73
|
+
export function getIdentity(): string {
|
|
74
|
+
return identityProvider.getIdentity();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Set a custom display name.
|
|
79
|
+
* Writes to cookie (sync) + queues server write-back (async) via ConfigStore.
|
|
80
|
+
*/
|
|
81
|
+
export function setCustomIdentity(name: string): string {
|
|
82
|
+
const trimmed = name.trim();
|
|
83
|
+
if (!trimmed) return getIdentity(); // reject empty
|
|
84
|
+
configStore.set('displayName', trimmed);
|
|
85
|
+
return trimmed;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Regenerate identity with a new random tater name.
|
|
90
|
+
* Writes to cookie + queues server write-back via ConfigStore.
|
|
91
|
+
*/
|
|
92
|
+
export function regenerateIdentity(): string {
|
|
93
|
+
const identity = generateIdentity();
|
|
94
|
+
configStore.set('displayName', identity);
|
|
95
|
+
return identity;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Check if an identity belongs to the current user. Delegates to the active provider.
|
|
100
|
+
*/
|
|
101
|
+
export function isCurrentUser(author: string | undefined): boolean {
|
|
102
|
+
return identityProvider.isCurrentUser(author);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Whether the current identity is user-editable (drives whether the Settings
|
|
107
|
+
* rename/regenerate controls are shown). Delegates to the active provider;
|
|
108
|
+
* defaults to editable when the provider doesn't implement `isEditable`.
|
|
109
|
+
*/
|
|
110
|
+
export function isIdentityEditable(): boolean {
|
|
111
|
+
return identityProvider.isEditable ? identityProvider.isEditable() : true;
|
|
112
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render-time transforms applied to plain-text fragments inside the inline
|
|
3
|
+
* scanner. Called only after code spans, links, and other markdown syntax
|
|
4
|
+
* have been consumed — so transforms here are guaranteed to operate on prose,
|
|
5
|
+
* not on code or URL strings.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const EMOJI_MAP: Record<string, string> = {
|
|
9
|
+
smile: '😄', heart: '❤️', thumbsup: '👍', thumbsdown: '👎',
|
|
10
|
+
fire: '🔥', star: '⭐', tada: '🎉', rocket: '🚀',
|
|
11
|
+
bug: '🐛', sparkles: '✨', warning: '⚠️', white_check_mark: '✅',
|
|
12
|
+
x: '❌', eyes: '👀', wave: '👋', thinking: '🤔',
|
|
13
|
+
ok: '🆗', construction: '🚧', boom: '💥', gear: '⚙️',
|
|
14
|
+
hourglass: '⏳', zap: '⚡', lock: '🔒', unlock: '🔓',
|
|
15
|
+
memo: '📝', book: '📖', package: '📦', hammer: '🔨',
|
|
16
|
+
checkered_flag: '🏁', question: '❓', exclamation: '❗', bulb: '💡',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function replaceEmoji(s: string): string {
|
|
20
|
+
return s.replace(/:([a-z_]+):/g, (whole, code) => EMOJI_MAP[code] ?? whole);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function smartypants(s: string): string {
|
|
24
|
+
return s
|
|
25
|
+
.replace(/\.{3}/g, '…')
|
|
26
|
+
.replace(/---/g, '—')
|
|
27
|
+
// Narrow en-dash rule to numeric ranges (e.g. "pages 3--5" → "3–5").
|
|
28
|
+
// Previously matched any non-hyphen context, which rewrote CLI flags
|
|
29
|
+
// like "bun --watch" into "bun –watch". Letter-to-letter en-dashes
|
|
30
|
+
// are rare in technical writing; we accept losing them to avoid the
|
|
31
|
+
// false positive on command-line arguments.
|
|
32
|
+
.replace(/(\d)--(?=\d)/g, '$1–')
|
|
33
|
+
.replace(/(^|[\s([{])"/g, '$1“')
|
|
34
|
+
.replace(/"/g, '”')
|
|
35
|
+
.replace(/(^|[\s([{])'/g, '$1‘')
|
|
36
|
+
.replace(/'/g, '’');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function transformPlainText(text: string): string {
|
|
40
|
+
return smartypants(replaceEmoji(text));
|
|
41
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { storage } from './storage';
|
|
2
|
+
import type { InputMethod } from '../types';
|
|
3
|
+
|
|
4
|
+
const STORAGE_KEY = 'plannotator-input-method';
|
|
5
|
+
const DEFAULT_METHOD: InputMethod = 'drag';
|
|
6
|
+
|
|
7
|
+
export function getInputMethod(): InputMethod {
|
|
8
|
+
const stored = storage.getItem(STORAGE_KEY);
|
|
9
|
+
if (stored === 'drag' || stored === 'pinpoint') {
|
|
10
|
+
return stored;
|
|
11
|
+
}
|
|
12
|
+
return DEFAULT_METHOD;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function saveInputMethod(method: InputMethod): void {
|
|
16
|
+
storage.setItem(STORAGE_KEY, method);
|
|
17
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks whether the user has seen the UI 2.0 "look & feel" refresh announcement.
|
|
3
|
+
* Uses cookies so the dismissal survives Plannotator's random localhost ports.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { storage } from './storage';
|
|
7
|
+
|
|
8
|
+
const STORAGE_KEY = 'plannotator-look-feel-announcement-seen';
|
|
9
|
+
// v2: grid is the default again; the dialog became a grid-vs-clean image chooser.
|
|
10
|
+
const CURRENT_VERSION = '2';
|
|
11
|
+
|
|
12
|
+
export function needsLookAndFeelAnnouncement(): boolean {
|
|
13
|
+
return storage.getItem(STORAGE_KEY) !== CURRENT_VERSION;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function markLookAndFeelAnnouncementSeen(): void {
|
|
17
|
+
storage.setItem(STORAGE_KEY, CURRENT_VERSION);
|
|
18
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Obsidian Integration Utility
|
|
3
|
+
*
|
|
4
|
+
* Manages settings for auto-saving plans to Obsidian vaults.
|
|
5
|
+
* Settings are stored in cookies (like other settings) so they persist
|
|
6
|
+
* across different ports used by the hook server.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { storage } from './storage';
|
|
10
|
+
|
|
11
|
+
// Storage keys
|
|
12
|
+
const STORAGE_KEY_ENABLED = 'plannotator-obsidian-enabled';
|
|
13
|
+
const STORAGE_KEY_VAULT = 'plannotator-obsidian-vault';
|
|
14
|
+
const STORAGE_KEY_FOLDER = 'plannotator-obsidian-folder';
|
|
15
|
+
const STORAGE_KEY_CUSTOM_PATH = 'plannotator-obsidian-custom-path';
|
|
16
|
+
const STORAGE_KEY_FILENAME_FORMAT = 'plannotator-obsidian-filename-format';
|
|
17
|
+
const STORAGE_KEY_VAULT_BROWSER = 'plannotator-obsidian-vault-browser';
|
|
18
|
+
const STORAGE_KEY_AUTOSAVE = 'plannotator-obsidian-autosave';
|
|
19
|
+
const STORAGE_KEY_FILENAME_SEPARATOR = 'plannotator-obsidian-filename-separator';
|
|
20
|
+
|
|
21
|
+
// Sentinel value for custom path selection
|
|
22
|
+
export const CUSTOM_PATH_SENTINEL = '__custom__';
|
|
23
|
+
|
|
24
|
+
// Default folder name in the vault
|
|
25
|
+
const DEFAULT_FOLDER = 'plannotator';
|
|
26
|
+
|
|
27
|
+
// Default filename format — matches the original hardcoded behavior
|
|
28
|
+
export const DEFAULT_FILENAME_FORMAT = '{title} - {Mon} {D}, {YYYY} {h}-{mm}{ampm}';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Obsidian integration settings
|
|
32
|
+
*/
|
|
33
|
+
export type FilenameSeparator = 'space' | 'dash' | 'underscore';
|
|
34
|
+
|
|
35
|
+
export interface ObsidianSettings {
|
|
36
|
+
enabled: boolean;
|
|
37
|
+
vaultPath: string; // Selected vault path OR '__custom__' sentinel
|
|
38
|
+
folder: string;
|
|
39
|
+
customPath?: string; // User-entered path when vaultPath === '__custom__'
|
|
40
|
+
filenameFormat?: string; // Custom filename format (e.g. '{YYYY}-{MM}-{DD} - {title}')
|
|
41
|
+
filenameSeparator: FilenameSeparator; // Replace spaces in filename with dash/underscore
|
|
42
|
+
autoSave: boolean; // Auto-save to Obsidian on plan arrival
|
|
43
|
+
vaultBrowserEnabled: boolean; // Show vault file browser in sidebar
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get current Obsidian settings from storage
|
|
48
|
+
*/
|
|
49
|
+
export function getObsidianSettings(): ObsidianSettings {
|
|
50
|
+
return {
|
|
51
|
+
enabled: storage.getItem(STORAGE_KEY_ENABLED) === 'true',
|
|
52
|
+
vaultPath: storage.getItem(STORAGE_KEY_VAULT) || '',
|
|
53
|
+
folder: storage.getItem(STORAGE_KEY_FOLDER) || DEFAULT_FOLDER,
|
|
54
|
+
customPath: storage.getItem(STORAGE_KEY_CUSTOM_PATH) || undefined,
|
|
55
|
+
filenameFormat: storage.getItem(STORAGE_KEY_FILENAME_FORMAT) || undefined,
|
|
56
|
+
filenameSeparator: (storage.getItem(STORAGE_KEY_FILENAME_SEPARATOR) as FilenameSeparator) || 'space',
|
|
57
|
+
autoSave: storage.getItem(STORAGE_KEY_AUTOSAVE) === 'true',
|
|
58
|
+
vaultBrowserEnabled: storage.getItem(STORAGE_KEY_VAULT_BROWSER) === 'true',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Save Obsidian settings to storage
|
|
64
|
+
*/
|
|
65
|
+
export function saveObsidianSettings(settings: ObsidianSettings): void {
|
|
66
|
+
storage.setItem(STORAGE_KEY_ENABLED, String(settings.enabled));
|
|
67
|
+
storage.setItem(STORAGE_KEY_VAULT, settings.vaultPath);
|
|
68
|
+
storage.setItem(STORAGE_KEY_FOLDER, settings.folder);
|
|
69
|
+
storage.setItem(STORAGE_KEY_CUSTOM_PATH, settings.customPath || '');
|
|
70
|
+
storage.setItem(STORAGE_KEY_FILENAME_FORMAT, settings.filenameFormat || '');
|
|
71
|
+
storage.setItem(STORAGE_KEY_FILENAME_SEPARATOR, settings.filenameSeparator || 'space');
|
|
72
|
+
storage.setItem(STORAGE_KEY_AUTOSAVE, String(settings.autoSave));
|
|
73
|
+
storage.setItem(STORAGE_KEY_VAULT_BROWSER, String(settings.vaultBrowserEnabled));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get the effective vault path, resolving custom path if selected
|
|
78
|
+
*/
|
|
79
|
+
export function getEffectiveVaultPath(settings: ObsidianSettings): string {
|
|
80
|
+
if (settings.vaultPath === CUSTOM_PATH_SENTINEL) {
|
|
81
|
+
return settings.customPath || '';
|
|
82
|
+
}
|
|
83
|
+
return settings.vaultPath;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check if Obsidian integration is properly configured
|
|
88
|
+
*/
|
|
89
|
+
export function isObsidianConfigured(): boolean {
|
|
90
|
+
const settings = getObsidianSettings();
|
|
91
|
+
const effectivePath = getEffectiveVaultPath(settings);
|
|
92
|
+
return settings.enabled && effectivePath.trim().length > 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Check if the vault browser sidebar tab should be shown
|
|
97
|
+
*/
|
|
98
|
+
export function isVaultBrowserEnabled(): boolean {
|
|
99
|
+
const settings = getObsidianSettings();
|
|
100
|
+
const effectivePath = getEffectiveVaultPath(settings);
|
|
101
|
+
return settings.enabled && settings.vaultBrowserEnabled && effectivePath.trim().length > 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Extract tags from markdown content using simple heuristics
|
|
106
|
+
*
|
|
107
|
+
* Extracts:
|
|
108
|
+
* - Words from the first H1 title (excluding common words)
|
|
109
|
+
* - Code fence languages (```typescript, ```sql, etc.)
|
|
110
|
+
* - Always includes "plan" as base tag
|
|
111
|
+
*
|
|
112
|
+
* @param markdown - The markdown content to extract tags from
|
|
113
|
+
* @returns Array of lowercase tag strings (max 6)
|
|
114
|
+
*/
|
|
115
|
+
export function extractTags(markdown: string): string[] {
|
|
116
|
+
const tags = new Set<string>(['plannotator']);
|
|
117
|
+
|
|
118
|
+
// Common words to exclude from title extraction
|
|
119
|
+
const stopWords = new Set([
|
|
120
|
+
'the', 'and', 'for', 'with', 'this', 'that', 'from', 'into',
|
|
121
|
+
'plan', 'implementation', 'overview', 'phase', 'step', 'steps',
|
|
122
|
+
]);
|
|
123
|
+
|
|
124
|
+
// 1. Extract from first H1 title
|
|
125
|
+
// Matches: "# Title" or "# Implementation Plan: Title" or "# Plan: Title"
|
|
126
|
+
const h1Match = markdown.match(/^#\s+(?:Implementation\s+Plan:|Plan:)?\s*(.+)$/im);
|
|
127
|
+
if (h1Match) {
|
|
128
|
+
const titleWords = h1Match[1]
|
|
129
|
+
.toLowerCase()
|
|
130
|
+
.replace(/[^\w\s-]/g, ' ') // Remove special chars except hyphens
|
|
131
|
+
.split(/\s+/)
|
|
132
|
+
.filter(word => word.length > 2 && !stopWords.has(word));
|
|
133
|
+
|
|
134
|
+
// Add first 3 meaningful words from title
|
|
135
|
+
titleWords.slice(0, 3).forEach(word => tags.add(word));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 2. Extract code fence languages
|
|
139
|
+
// Matches: ```typescript, ```sql, ```rust, etc.
|
|
140
|
+
const langMatches = markdown.matchAll(/```(\w+)/g);
|
|
141
|
+
const seenLangs = new Set<string>();
|
|
142
|
+
|
|
143
|
+
for (const [, lang] of langMatches) {
|
|
144
|
+
const normalizedLang = lang.toLowerCase();
|
|
145
|
+
// Skip generic/config languages and duplicates
|
|
146
|
+
if (!seenLangs.has(normalizedLang) &&
|
|
147
|
+
!['json', 'yaml', 'yml', 'text', 'txt', 'markdown', 'md'].includes(normalizedLang)) {
|
|
148
|
+
seenLangs.add(normalizedLang);
|
|
149
|
+
tags.add(normalizedLang);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Return max 6 tags
|
|
154
|
+
return Array.from(tags).slice(0, 6);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Generate YAML frontmatter for an Obsidian note
|
|
159
|
+
*
|
|
160
|
+
* @param tags - Array of tags to include
|
|
161
|
+
* @returns Frontmatter string including opening and closing ---
|
|
162
|
+
*/
|
|
163
|
+
export function generateFrontmatter(tags: string[]): string {
|
|
164
|
+
const now = new Date().toISOString();
|
|
165
|
+
const tagList = tags.map(t => t.toLowerCase()).join(', ');
|
|
166
|
+
|
|
167
|
+
return `---
|
|
168
|
+
created: ${now}
|
|
169
|
+
source: plannotator
|
|
170
|
+
tags: [${tagList}]
|
|
171
|
+
---`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Generate a filename for the plan note
|
|
176
|
+
* Format: YYYY-MM-DD-HHmm.md (e.g., 2026-01-02-1430.md)
|
|
177
|
+
*
|
|
178
|
+
* @returns Filename string
|
|
179
|
+
*/
|
|
180
|
+
export function generateFilename(): string {
|
|
181
|
+
const now = new Date();
|
|
182
|
+
const timestamp = now.toISOString()
|
|
183
|
+
.slice(0, 16) // "2026-01-02T14:30"
|
|
184
|
+
.replace('T', '-') // "2026-01-02-14:30"
|
|
185
|
+
.replace(/:/g, ''); // "2026-01-02-1430"
|
|
186
|
+
|
|
187
|
+
return `${timestamp}.md`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Prepare the full note content with frontmatter
|
|
192
|
+
*
|
|
193
|
+
* @param markdown - The plan markdown content
|
|
194
|
+
* @returns Full note content with frontmatter prepended
|
|
195
|
+
*/
|
|
196
|
+
export function prepareNoteContent(markdown: string): string {
|
|
197
|
+
const tags = extractTags(markdown);
|
|
198
|
+
const frontmatter = generateFrontmatter(tags);
|
|
199
|
+
|
|
200
|
+
return `${frontmatter}\n\n${markdown}`;
|
|
201
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Octarine Notes Integration Utility
|
|
3
|
+
*
|
|
4
|
+
* Manages settings for auto-saving plans to Octarine.
|
|
5
|
+
* Uses octarine:// URI scheme — no vault detection needed.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { storage } from './storage';
|
|
9
|
+
|
|
10
|
+
const STORAGE_KEY_ENABLED = 'plannotator-octarine-enabled';
|
|
11
|
+
const STORAGE_KEY_WORKSPACE = 'plannotator-octarine-workspace';
|
|
12
|
+
const STORAGE_KEY_FOLDER = 'plannotator-octarine-folder';
|
|
13
|
+
const STORAGE_KEY_AUTOSAVE = 'plannotator-octarine-autosave';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Octarine integration settings
|
|
17
|
+
*/
|
|
18
|
+
export interface OctarineSettings {
|
|
19
|
+
enabled: boolean;
|
|
20
|
+
workspace: string;
|
|
21
|
+
folder: string;
|
|
22
|
+
autoSave: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get current Octarine settings from storage
|
|
27
|
+
*/
|
|
28
|
+
export function getOctarineSettings(): OctarineSettings {
|
|
29
|
+
return {
|
|
30
|
+
enabled: storage.getItem(STORAGE_KEY_ENABLED) === 'true',
|
|
31
|
+
workspace: storage.getItem(STORAGE_KEY_WORKSPACE) ?? '',
|
|
32
|
+
folder: storage.getItem(STORAGE_KEY_FOLDER) || 'plannotator',
|
|
33
|
+
autoSave: storage.getItem(STORAGE_KEY_AUTOSAVE) === 'true',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Save Octarine settings to storage
|
|
39
|
+
*/
|
|
40
|
+
export function saveOctarineSettings(settings: OctarineSettings): void {
|
|
41
|
+
storage.setItem(STORAGE_KEY_ENABLED, String(settings.enabled));
|
|
42
|
+
storage.setItem(STORAGE_KEY_WORKSPACE, settings.workspace);
|
|
43
|
+
storage.setItem(STORAGE_KEY_FOLDER, settings.folder);
|
|
44
|
+
storage.setItem(STORAGE_KEY_AUTOSAVE, String(settings.autoSave));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Check if Octarine integration is properly configured
|
|
49
|
+
*/
|
|
50
|
+
export function isOctarineConfigured(): boolean {
|
|
51
|
+
const settings = getOctarineSettings();
|
|
52
|
+
return settings.enabled && settings.workspace.trim().length > 0;
|
|
53
|
+
}
|