@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.
Files changed (305) hide show
  1. package/README.md +60 -0
  2. package/assets/diff-options.png +0 -0
  3. package/assets/icon-codex.png +0 -0
  4. package/assets/look-flat.png +0 -0
  5. package/assets/look-grid.png +0 -0
  6. package/assets/review-sections.png +0 -0
  7. package/assets/review-tree.png +0 -0
  8. package/assets/workspaces.webp +0 -0
  9. package/components/AISettingsTab.tsx +145 -0
  10. package/components/ActionMenu.tsx +101 -0
  11. package/components/AgentControls.tsx +216 -0
  12. package/components/AgentsTab.tsx +1294 -0
  13. package/components/AnnotationPanel.tsx +731 -0
  14. package/components/AnnotationSidebar.tsx +86 -0
  15. package/components/AnnotationToolbar.tsx +323 -0
  16. package/components/AnnotationToolstrip.tsx +355 -0
  17. package/components/ApproveDropdown.tsx +170 -0
  18. package/components/AttachmentsButton.tsx +410 -0
  19. package/components/BlockRenderer.tsx +163 -0
  20. package/components/BorderTrail.tsx +38 -0
  21. package/components/CodeFilePicker.tsx +64 -0
  22. package/components/CodeFilePopout.tsx +602 -0
  23. package/components/CodePathValidationContext.tsx +14 -0
  24. package/components/CommentPopover.tsx +512 -0
  25. package/components/CompletionOverlay.tsx +99 -0
  26. package/components/ConfirmDialog.tsx +128 -0
  27. package/components/DocBadges.tsx +231 -0
  28. package/components/EditorAnnotationCard.tsx +65 -0
  29. package/components/ExportModal.tsx +565 -0
  30. package/components/FloatingQuickLabelPicker.tsx +144 -0
  31. package/components/GitHubIcon.tsx +16 -0
  32. package/components/GitLabIcon.tsx +30 -0
  33. package/components/GraphvizBlock.tsx +511 -0
  34. package/components/ImageAnnotator/Canvas.tsx +118 -0
  35. package/components/ImageAnnotator/Toolbar.tsx +218 -0
  36. package/components/ImageAnnotator/index.tsx +272 -0
  37. package/components/ImageAnnotator/types.ts +39 -0
  38. package/components/ImageAnnotator/utils.ts +147 -0
  39. package/components/ImageThumbnail.tsx +126 -0
  40. package/components/ImportModal.tsx +146 -0
  41. package/components/InlineMarkdown.tsx +1094 -0
  42. package/components/KeyboardShortcuts.tsx +208 -0
  43. package/components/Landing.tsx +496 -0
  44. package/components/ListItemBody.tsx +62 -0
  45. package/components/ListMarker.tsx +73 -0
  46. package/components/LookAndFeelAnnouncementDialog.tsx +230 -0
  47. package/components/MarkdownEditor.tsx +44 -0
  48. package/components/MenuVersionSection.tsx +91 -0
  49. package/components/MermaidBlock.tsx +579 -0
  50. package/components/ModeToggle.tsx +68 -0
  51. package/components/OpenInAppButton.tsx +299 -0
  52. package/components/OverlayScrollArea.tsx +82 -0
  53. package/components/PermissionModeSetup.tsx +92 -0
  54. package/components/PinpointOverlay.tsx +99 -0
  55. package/components/PlanAIAnnouncementDialog.tsx +187 -0
  56. package/components/PlanHeaderMenu.tsx +299 -0
  57. package/components/PopoutDialog.tsx +91 -0
  58. package/components/Popover.tsx +27 -0
  59. package/components/ProviderIcons.tsx +51 -0
  60. package/components/PullRequestIcon.tsx +12 -0
  61. package/components/QuickLabelDropdown.tsx +63 -0
  62. package/components/RenderedMarkdown.tsx +54 -0
  63. package/components/RepoIcon.tsx +12 -0
  64. package/components/ResizeHandle.tsx +86 -0
  65. package/components/ReviewAgentsIcon.tsx +18 -0
  66. package/components/SearchableSelect.tsx +157 -0
  67. package/components/Settings.tsx +2220 -0
  68. package/components/SparklesIcon.tsx +45 -0
  69. package/components/StickyHeaderLane.tsx +260 -0
  70. package/components/TableOfContents.tsx +157 -0
  71. package/components/TaterSpritePullup.tsx +34 -0
  72. package/components/TaterSpriteRunning.tsx +54 -0
  73. package/components/TaterSpriteSitting.tsx +37 -0
  74. package/components/TextShimmer.tsx +57 -0
  75. package/components/ThemeProvider.tsx +162 -0
  76. package/components/ThemeTab.tsx +139 -0
  77. package/components/ToolbarButtons.tsx +116 -0
  78. package/components/Tooltip.tsx +45 -0
  79. package/components/Viewer.tsx +977 -0
  80. package/components/ai/AIProviderBar.tsx +95 -0
  81. package/components/ai/DocumentAIChatPanel.tsx +313 -0
  82. package/components/blocks/AlertBlock.tsx +58 -0
  83. package/components/blocks/Callout.tsx +60 -0
  84. package/components/blocks/CodeBlock.tsx +75 -0
  85. package/components/blocks/HtmlBlock.tsx +122 -0
  86. package/components/blocks/MathBlock.tsx +36 -0
  87. package/components/blocks/TableBlock.tsx +146 -0
  88. package/components/blocks/TablePopout.tsx +279 -0
  89. package/components/blocks/TableToolbar.tsx +153 -0
  90. package/components/blocks/proseBody.tsx +102 -0
  91. package/components/core/button.tsx +44 -0
  92. package/components/core/textarea.tsx +26 -0
  93. package/components/diagramLanguages.ts +14 -0
  94. package/components/goal-setup/GoalSetupSurface.tsx +1354 -0
  95. package/components/html-viewer/HtmlViewer.tsx +386 -0
  96. package/components/html-viewer/bridge-script.ts +505 -0
  97. package/components/html-viewer/index.ts +1 -0
  98. package/components/html-viewer/useHtmlAnnotation.ts +400 -0
  99. package/components/icons/AgentIcons.tsx +66 -0
  100. package/components/icons/AppIcon.tsx +56 -0
  101. package/components/icons/MessagesIcon.tsx +11 -0
  102. package/components/icons/ObsidianIcons.tsx +208 -0
  103. package/components/icons/app/android-studio.svg +369 -0
  104. package/components/icons/app/antigravity.svg +97 -0
  105. package/components/icons/app/cursor.svg +16 -0
  106. package/components/icons/app/file-explorer.svg +20 -0
  107. package/components/icons/app/finder.png +0 -0
  108. package/components/icons/app/ghostty.svg +13 -0
  109. package/components/icons/app/iterm2.svg +13 -0
  110. package/components/icons/app/powershell.svg +14 -0
  111. package/components/icons/app/sublime-text.svg +17 -0
  112. package/components/icons/app/terminal.png +0 -0
  113. package/components/icons/app/textmate.png +0 -0
  114. package/components/icons/app/vscode.svg +39 -0
  115. package/components/icons/app/warp.png +0 -0
  116. package/components/icons/app/xcode.png +0 -0
  117. package/components/icons/app/zed-dark.svg +15 -0
  118. package/components/icons/app/zed.svg +15 -0
  119. package/components/icons/themeIcons.tsx +47 -0
  120. package/components/mermaidSvg.ts +33 -0
  121. package/components/plan-diff/PlanCleanDiffView.tsx +884 -0
  122. package/components/plan-diff/PlanDiffBadge.tsx +48 -0
  123. package/components/plan-diff/PlanDiffModeSwitcher.tsx +103 -0
  124. package/components/plan-diff/PlanDiffViewer.tsx +214 -0
  125. package/components/plan-diff/PlanRawDiffView.tsx +102 -0
  126. package/components/plan-diff/VSCodeIcon.tsx +132 -0
  127. package/components/settings/HooksTab.tsx +208 -0
  128. package/components/sidebar/ArchiveBrowser.tsx +98 -0
  129. package/components/sidebar/CountBadge.tsx +12 -0
  130. package/components/sidebar/FileBrowser.tsx +498 -0
  131. package/components/sidebar/MessagesBrowser.tsx +109 -0
  132. package/components/sidebar/SidebarContainer.tsx +353 -0
  133. package/components/sidebar/SidebarTabs.tsx +155 -0
  134. package/components/sidebar/VersionBrowser.tsx +141 -0
  135. package/components/types.d.ts +7 -0
  136. package/components/ui/badge.tsx +44 -0
  137. package/components/ui/button.tsx +83 -0
  138. package/components/ui/card.tsx +58 -0
  139. package/components/ui/dialog.tsx +106 -0
  140. package/components/ui/dropdown-menu.tsx +240 -0
  141. package/components/ui/state-pill.tsx +45 -0
  142. package/components/ui/tabs.tsx +46 -0
  143. package/components/ui/textarea.tsx +25 -0
  144. package/config/configStore.ts +214 -0
  145. package/config/index.ts +4 -0
  146. package/config/reviewView.ts +42 -0
  147. package/config/settings.ts +310 -0
  148. package/config/useConfig.ts +20 -0
  149. package/configure.ts +69 -0
  150. package/globals.d.ts +14 -0
  151. package/hooks/pfm/useCodeFilePopout.ts +111 -0
  152. package/hooks/useAIChat.ts +532 -0
  153. package/hooks/useAIProviderConfig.ts +115 -0
  154. package/hooks/useActiveSection.ts +78 -0
  155. package/hooks/useAgentJobs.ts +306 -0
  156. package/hooks/useAgentSettings.ts +579 -0
  157. package/hooks/useAgents.ts +90 -0
  158. package/hooks/useAnnotationDraft.ts +523 -0
  159. package/hooks/useAnnotationHighlighter.ts +1170 -0
  160. package/hooks/useArchive.ts +171 -0
  161. package/hooks/useAutoClose.ts +104 -0
  162. package/hooks/useCodeAnnotationDraft.ts +194 -0
  163. package/hooks/useCodeFilePopout.ts +1 -0
  164. package/hooks/useDismissOnOutsideAndEscape.ts +50 -0
  165. package/hooks/useDraggable.ts +108 -0
  166. package/hooks/useEditorAnnotations.ts +64 -0
  167. package/hooks/useExternalAnnotationHighlights.ts +105 -0
  168. package/hooks/useExternalAnnotations.ts +268 -0
  169. package/hooks/useFileBrowser.ts +402 -0
  170. package/hooks/useInputMethodSwitch.ts +89 -0
  171. package/hooks/useIsMobile.ts +17 -0
  172. package/hooks/useLinkedDoc.ts +494 -0
  173. package/hooks/useOverlayViewport.ts +36 -0
  174. package/hooks/usePinpoint.ts +182 -0
  175. package/hooks/usePlanDiff.ts +176 -0
  176. package/hooks/usePrintMode.ts +27 -0
  177. package/hooks/useResizablePanel.ts +173 -0
  178. package/hooks/useScrollViewport.ts +38 -0
  179. package/hooks/useSharing.ts +441 -0
  180. package/hooks/useSidebar.ts +55 -0
  181. package/hooks/useUpdateCheck.ts +132 -0
  182. package/hooks/useValidatedCodePaths.ts +94 -0
  183. package/icons/GitUser.tsx +17 -0
  184. package/lib/utils.ts +11 -0
  185. package/package.json +117 -0
  186. package/plannotator.webp +0 -0
  187. package/print.css +427 -0
  188. package/shortcuts/code-review/ai.shortcuts.ts +25 -0
  189. package/shortcuts/code-review/allFilesDiff.shortcuts.ts +46 -0
  190. package/shortcuts/code-review/annotationToolbar.shortcuts.ts +30 -0
  191. package/shortcuts/code-review/fileTree.shortcuts.ts +35 -0
  192. package/shortcuts/code-review/prComments.shortcuts.ts +18 -0
  193. package/shortcuts/code-review/suggestionModal.shortcuts.ts +25 -0
  194. package/shortcuts/code-review/tourDialog.shortcuts.ts +18 -0
  195. package/shortcuts/core.ts +399 -0
  196. package/shortcuts/index.ts +21 -0
  197. package/shortcuts/plan-review/annotationPanel.shortcuts.ts +25 -0
  198. package/shortcuts/plan-review/annotationToolbar.shortcuts.ts +39 -0
  199. package/shortcuts/plan-review/commentPopover.shortcuts.ts +21 -0
  200. package/shortcuts/plan-review/goalSetup.shortcuts.ts +10 -0
  201. package/shortcuts/plan-review/imageAnnotator.shortcuts.ts +42 -0
  202. package/shortcuts/plan-review/inputMethod.shortcuts.ts +30 -0
  203. package/shortcuts/plan-review/sidebar.shortcuts.ts +34 -0
  204. package/shortcuts/plan-review/viewer.shortcuts.ts +24 -0
  205. package/shortcuts/runtime.ts +259 -0
  206. package/sprite_package_additional/index.html +34 -0
  207. package/sprite_package_additional/sprite.png +0 -0
  208. package/sprite_package_new/index.html +34 -0
  209. package/sprite_package_new/sprite.png +0 -0
  210. package/sprite_package_pulluphang/index.html +34 -0
  211. package/sprite_package_pulluphang/sprite.png +0 -0
  212. package/styles.css +1 -0
  213. package/theme.css +893 -0
  214. package/themes/adwaita.css +62 -0
  215. package/themes/andromeeda.css +66 -0
  216. package/themes/aurora-x.css +66 -0
  217. package/themes/ayu-dark.css +66 -0
  218. package/themes/caffeine.css +113 -0
  219. package/themes/catppuccin.css +62 -0
  220. package/themes/claude-plus.css +60 -0
  221. package/themes/cursor-hc.css +34 -0
  222. package/themes/cursor-midnight.css +34 -0
  223. package/themes/cursor.css +62 -0
  224. package/themes/dark-plus.css +66 -0
  225. package/themes/doom-64.css +109 -0
  226. package/themes/dracula.css +33 -0
  227. package/themes/everforest-hard.css +62 -0
  228. package/themes/everforest-soft.css +62 -0
  229. package/themes/everforest.css +62 -0
  230. package/themes/github.css +66 -0
  231. package/themes/gruvbox.css +62 -0
  232. package/themes/houston.css +66 -0
  233. package/themes/kanagawa-dragon.css +34 -0
  234. package/themes/kanagawa-lotus.css +34 -0
  235. package/themes/kanagawa-wave.css +34 -0
  236. package/themes/laserwave.css +66 -0
  237. package/themes/material.css +62 -0
  238. package/themes/min.css +66 -0
  239. package/themes/monokai-pro.css +34 -0
  240. package/themes/neutral.css +59 -0
  241. package/themes/night-owl.css +66 -0
  242. package/themes/nord.css +66 -0
  243. package/themes/one-dark-pro.css +66 -0
  244. package/themes/one-light.css +66 -0
  245. package/themes/paulmillr.css +34 -0
  246. package/themes/plannotator.css +60 -0
  247. package/themes/plastic.css +66 -0
  248. package/themes/poimandres.css +66 -0
  249. package/themes/quantum-rose.css +109 -0
  250. package/themes/red.css +66 -0
  251. package/themes/rose-pine.css +62 -0
  252. package/themes/simple.css +124 -0
  253. package/themes/slack.css +66 -0
  254. package/themes/snazzy-light.css +66 -0
  255. package/themes/soft-pop.css +60 -0
  256. package/themes/solar-dusk.css +109 -0
  257. package/themes/solarized.css +66 -0
  258. package/themes/synthwave-84.css +34 -0
  259. package/themes/terminal.css +62 -0
  260. package/themes/tinacious.css +57 -0
  261. package/themes/tokyo-night.css +62 -0
  262. package/themes/vesper.css +62 -0
  263. package/themes/vitesse-black.css +66 -0
  264. package/themes/vitesse.css +62 -0
  265. package/types.ts +264 -0
  266. package/utils/agentSwitch.ts +71 -0
  267. package/utils/aiChatFormat.ts +33 -0
  268. package/utils/aiPrompt.ts +29 -0
  269. package/utils/aiProvider.ts +217 -0
  270. package/utils/anchors.ts +11 -0
  271. package/utils/annotateAgentTerminal.ts +29 -0
  272. package/utils/annotationHelpers.ts +101 -0
  273. package/utils/bear.ts +59 -0
  274. package/utils/blockTargeting.ts +240 -0
  275. package/utils/callback.ts +99 -0
  276. package/utils/commentContent.ts +8 -0
  277. package/utils/defaultNotesApp.ts +20 -0
  278. package/utils/diffFonts.ts +33 -0
  279. package/utils/editorMode.ts +31 -0
  280. package/utils/fileBrowser.ts +40 -0
  281. package/utils/generateId.ts +7 -0
  282. package/utils/generateIdentity.ts +29 -0
  283. package/utils/identity.ts +112 -0
  284. package/utils/inlineTransforms.ts +41 -0
  285. package/utils/inputMethod.ts +17 -0
  286. package/utils/lookAndFeelAnnouncement.ts +18 -0
  287. package/utils/obsidian.ts +201 -0
  288. package/utils/octarine.ts +53 -0
  289. package/utils/parser.ts +991 -0
  290. package/utils/permissionMode.ts +77 -0
  291. package/utils/planAIAnnouncement.ts +17 -0
  292. package/utils/planAgentInstructions.ts +137 -0
  293. package/utils/planDiffEngine.ts +590 -0
  294. package/utils/planSave.ts +49 -0
  295. package/utils/platform.ts +11 -0
  296. package/utils/quickLabels.ts +77 -0
  297. package/utils/reviewAgentInstructions.ts +182 -0
  298. package/utils/sanitizeHtml.ts +45 -0
  299. package/utils/sharing.ts +350 -0
  300. package/utils/slugify.ts +37 -0
  301. package/utils/storage.ts +141 -0
  302. package/utils/themeRegistry.ts +567 -0
  303. package/utils/uiPreferences.ts +34 -0
  304. package/utils/upload.ts +56 -0
  305. 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
+ }