@peaske7/readit 0.1.4 → 0.1.6

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 (322) hide show
  1. package/.agents/skills/remotion-best-practices/SKILL.md +61 -0
  2. package/.agents/skills/remotion-best-practices/rules/3d.md +86 -0
  3. package/.agents/skills/remotion-best-practices/rules/animations.md +27 -0
  4. package/.agents/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +178 -0
  5. package/.agents/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
  6. package/.agents/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +108 -0
  7. package/.agents/skills/remotion-best-practices/rules/assets.md +78 -0
  8. package/.agents/skills/remotion-best-practices/rules/audio-visualization.md +198 -0
  9. package/.agents/skills/remotion-best-practices/rules/audio.md +169 -0
  10. package/.agents/skills/remotion-best-practices/rules/calculate-metadata.md +134 -0
  11. package/.agents/skills/remotion-best-practices/rules/can-decode.md +75 -0
  12. package/.agents/skills/remotion-best-practices/rules/charts.md +120 -0
  13. package/.agents/skills/remotion-best-practices/rules/compositions.md +154 -0
  14. package/.agents/skills/remotion-best-practices/rules/display-captions.md +184 -0
  15. package/.agents/skills/remotion-best-practices/rules/extract-frames.md +229 -0
  16. package/.agents/skills/remotion-best-practices/rules/ffmpeg.md +38 -0
  17. package/.agents/skills/remotion-best-practices/rules/fonts.md +152 -0
  18. package/.agents/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
  19. package/.agents/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
  20. package/.agents/skills/remotion-best-practices/rules/get-video-duration.md +60 -0
  21. package/.agents/skills/remotion-best-practices/rules/gifs.md +141 -0
  22. package/.agents/skills/remotion-best-practices/rules/images.md +134 -0
  23. package/.agents/skills/remotion-best-practices/rules/import-srt-captions.md +69 -0
  24. package/.agents/skills/remotion-best-practices/rules/light-leaks.md +73 -0
  25. package/.agents/skills/remotion-best-practices/rules/lottie.md +70 -0
  26. package/.agents/skills/remotion-best-practices/rules/maps.md +412 -0
  27. package/.agents/skills/remotion-best-practices/rules/measuring-dom-nodes.md +34 -0
  28. package/.agents/skills/remotion-best-practices/rules/measuring-text.md +140 -0
  29. package/.agents/skills/remotion-best-practices/rules/parameters.md +109 -0
  30. package/.agents/skills/remotion-best-practices/rules/sequencing.md +118 -0
  31. package/.agents/skills/remotion-best-practices/rules/sfx.md +26 -0
  32. package/.agents/skills/remotion-best-practices/rules/subtitles.md +36 -0
  33. package/.agents/skills/remotion-best-practices/rules/tailwind.md +11 -0
  34. package/.agents/skills/remotion-best-practices/rules/text-animations.md +20 -0
  35. package/.agents/skills/remotion-best-practices/rules/timing.md +179 -0
  36. package/.agents/skills/remotion-best-practices/rules/transcribe-captions.md +70 -0
  37. package/.agents/skills/remotion-best-practices/rules/transitions.md +197 -0
  38. package/.agents/skills/remotion-best-practices/rules/transparent-videos.md +106 -0
  39. package/.agents/skills/remotion-best-practices/rules/trimming.md +51 -0
  40. package/.agents/skills/remotion-best-practices/rules/videos.md +171 -0
  41. package/.agents/skills/remotion-best-practices/rules/voiceover.md +99 -0
  42. package/.agents/skills/simple/SKILL.md +52 -0
  43. package/.agents/skills/vercel-react-best-practices/AGENTS.md +3254 -0
  44. package/.agents/skills/vercel-react-best-practices/README.md +123 -0
  45. package/.agents/skills/vercel-react-best-practices/SKILL.md +141 -0
  46. package/.agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  47. package/.agents/skills/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
  48. package/.agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
  49. package/.agents/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
  50. package/.agents/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
  51. package/.agents/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
  52. package/.agents/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
  53. package/.agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
  54. package/.agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
  55. package/.agents/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
  56. package/.agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
  57. package/.agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  58. package/.agents/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
  59. package/.agents/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
  60. package/.agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
  61. package/.agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
  62. package/.agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
  63. package/.agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
  64. package/.agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
  65. package/.agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
  66. package/.agents/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
  67. package/.agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
  68. package/.agents/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
  69. package/.agents/skills/vercel-react-best-practices/rules/js-flatmap-filter.md +60 -0
  70. package/.agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
  71. package/.agents/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
  72. package/.agents/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
  73. package/.agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
  74. package/.agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
  75. package/.agents/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
  76. package/.agents/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
  77. package/.agents/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  78. package/.agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
  79. package/.agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
  80. package/.agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  81. package/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  82. package/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
  83. package/.agents/skills/vercel-react-best-practices/rules/rendering-resource-hints.md +85 -0
  84. package/.agents/skills/vercel-react-best-practices/rules/rendering-script-defer-async.md +68 -0
  85. package/.agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
  86. package/.agents/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
  87. package/.agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
  88. package/.agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
  89. package/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
  90. package/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
  91. package/.agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
  92. package/.agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  93. package/.agents/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
  94. package/.agents/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
  95. package/.agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
  96. package/.agents/skills/vercel-react-best-practices/rules/rerender-no-inline-components.md +82 -0
  97. package/.agents/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
  98. package/.agents/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
  99. package/.agents/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
  100. package/.agents/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
  101. package/.agents/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
  102. package/.agents/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
  103. package/.agents/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
  104. package/.agents/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
  105. package/.agents/skills/vercel-react-best-practices/rules/server-hoist-static-io.md +142 -0
  106. package/.agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
  107. package/.agents/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
  108. package/.claude/CLAUDE.md +142 -0
  109. package/.claude/commands/review.md +120 -0
  110. package/.claude/commands/sync-docs.md +71 -0
  111. package/.claude/roadmap.md +98 -0
  112. package/.claude/rules/style-guide.md +830 -0
  113. package/.claude/settings.json +18 -0
  114. package/.claude/user-stories.md +248 -0
  115. package/AGENTS.md +64 -0
  116. package/README.md +7 -7
  117. package/biome.json +69 -0
  118. package/bun.lock +1124 -0
  119. package/docs/design.md +563 -0
  120. package/docs/plans/2026-03-13-client-mode-design.md +86 -0
  121. package/docs/plans/2026-03-13-client-mode-plan.md +605 -0
  122. package/docs/plans/2026-03-13-keyboard-shortcuts-design.md +129 -0
  123. package/docs/plans/2026-03-13-keyboard-shortcuts-plan.md +1471 -0
  124. package/docs/plans/2026-03-13-multi-document-design.md +183 -0
  125. package/docs/plans/2026-03-13-performance-benchmarks-design.md +121 -0
  126. package/e2e/comments.spec.ts +125 -0
  127. package/e2e/document-load.spec.ts +54 -0
  128. package/e2e/export.spec.ts +58 -0
  129. package/e2e/fixtures/sample.html +13 -0
  130. package/e2e/fixtures/sample.md +7 -0
  131. package/e2e/persistence-file.spec.ts +342 -0
  132. package/e2e/utils/cli.ts +84 -0
  133. package/e2e/utils/selection.ts +135 -0
  134. package/{dist/index.html → index.html} +8 -2
  135. package/lefthook.yml +8 -0
  136. package/package.json +17 -39
  137. package/playwright.config.ts +22 -0
  138. package/skills-lock.json +20 -0
  139. package/src/App.tsx +396 -0
  140. package/src/cli/index.ts +467 -0
  141. package/src/components/ActionsMenu.tsx +110 -0
  142. package/src/components/DocumentViewer/CodeBlock.tsx +83 -0
  143. package/src/components/DocumentViewer/DocumentViewer.tsx +257 -0
  144. package/src/components/DocumentViewer/IframeContainer.tsx +251 -0
  145. package/src/components/DocumentViewer/MermaidDiagram.tsx +137 -0
  146. package/src/components/DocumentViewer/index.ts +1 -0
  147. package/src/components/FloatingTOC.tsx +59 -0
  148. package/src/components/Header.tsx +63 -0
  149. package/src/components/InlineEditor.tsx +72 -0
  150. package/src/components/MarginNote.tsx +198 -0
  151. package/src/components/MarginNotes.tsx +50 -0
  152. package/src/components/RawModal.tsx +141 -0
  153. package/src/components/ReanchorConfirm.tsx +33 -0
  154. package/src/components/SettingsModal.tsx +221 -0
  155. package/src/components/ShortcutCapture.tsx +45 -0
  156. package/src/components/ShortcutList.tsx +157 -0
  157. package/src/components/TabBar.tsx +60 -0
  158. package/src/components/TableOfContents.tsx +108 -0
  159. package/src/components/comments/CommentBadge.tsx +43 -0
  160. package/src/components/comments/CommentInput.tsx +119 -0
  161. package/src/components/comments/CommentListItem.tsx +82 -0
  162. package/src/components/comments/CommentManager.tsx +106 -0
  163. package/src/components/comments/CommentMinimap.tsx +62 -0
  164. package/src/components/comments/CommentNav.tsx +104 -0
  165. package/src/components/ui/ActionBar.tsx +16 -0
  166. package/src/components/ui/ActionLink.tsx +32 -0
  167. package/src/components/ui/Button.tsx +55 -0
  168. package/src/components/ui/Dialog.tsx +156 -0
  169. package/src/components/ui/DropdownMenu.tsx +114 -0
  170. package/src/components/ui/SeparatorDot.tsx +9 -0
  171. package/src/components/ui/Text.tsx +54 -0
  172. package/src/contexts/CommentContext.tsx +222 -0
  173. package/src/contexts/LayoutContext.tsx +76 -0
  174. package/src/hooks/useClickOutside.ts +35 -0
  175. package/src/hooks/useClipboard.ts +79 -0
  176. package/src/hooks/useCommentNavigation.ts +130 -0
  177. package/src/hooks/useComments.ts +323 -0
  178. package/src/hooks/useDocument.ts +141 -0
  179. package/src/hooks/useFontPreference.ts +76 -0
  180. package/src/hooks/useHeadings.test.ts +159 -0
  181. package/src/hooks/useHeadings.ts +129 -0
  182. package/src/hooks/useKeybindings.ts +120 -0
  183. package/src/hooks/useKeyboardShortcuts.ts +63 -0
  184. package/src/hooks/useLayoutMode.ts +44 -0
  185. package/src/hooks/useReanchorMode.ts +33 -0
  186. package/src/hooks/useScrollMetrics.ts +56 -0
  187. package/src/hooks/useScrollSpy.ts +81 -0
  188. package/src/hooks/useTextSelection.ts +123 -0
  189. package/src/hooks/useThemePreference.ts +66 -0
  190. package/src/index.css +823 -0
  191. package/src/lib/__fixtures__/bench-data.ts +167 -0
  192. package/src/lib/anchor.bench.ts +112 -0
  193. package/src/lib/anchor.test.ts +531 -0
  194. package/src/lib/anchor.ts +465 -0
  195. package/src/lib/comment-storage.bench.ts +63 -0
  196. package/src/lib/comment-storage.test.ts +624 -0
  197. package/src/lib/comment-storage.ts +263 -0
  198. package/src/lib/context.bench.ts +41 -0
  199. package/src/lib/context.test.ts +224 -0
  200. package/src/lib/context.ts +193 -0
  201. package/src/lib/export.bench.ts +35 -0
  202. package/src/lib/export.ts +43 -0
  203. package/src/lib/highlight/colors.ts +37 -0
  204. package/src/lib/highlight/core.test.ts +98 -0
  205. package/src/lib/highlight/core.ts +54 -0
  206. package/src/lib/highlight/dom.ts +342 -0
  207. package/src/lib/highlight/highlighter.ts +427 -0
  208. package/src/lib/highlight/index.ts +23 -0
  209. package/src/lib/highlight/script-builder.ts +485 -0
  210. package/src/lib/highlight/types.ts +57 -0
  211. package/src/lib/html-processor.test.tsx +170 -0
  212. package/src/lib/html-processor.tsx +95 -0
  213. package/src/lib/layout-constants.ts +12 -0
  214. package/src/lib/margin-layout.bench.ts +28 -0
  215. package/src/lib/margin-layout.ts +100 -0
  216. package/src/lib/scroll.test.ts +118 -0
  217. package/src/lib/scroll.ts +47 -0
  218. package/src/lib/shortcut-registry.test.ts +173 -0
  219. package/src/lib/shortcut-registry.ts +209 -0
  220. package/src/lib/utils.test.ts +110 -0
  221. package/src/lib/utils.ts +61 -0
  222. package/src/main.tsx +10 -0
  223. package/src/server/index.ts +883 -0
  224. package/src/store/index.test.ts +220 -0
  225. package/src/store/index.ts +234 -0
  226. package/src/test-setup.ts +1 -0
  227. package/src/types/index.ts +115 -0
  228. package/test.md +74 -0
  229. package/tsconfig.cli.json +12 -0
  230. package/tsconfig.json +20 -0
  231. package/vite.config.ts +19 -0
  232. package/vitest.config.ts +15 -0
  233. package/dist/assets/_basePickBy-hOr-yGsE.js +0 -1
  234. package/dist/assets/_baseUniq-b7bzdUSn.js +0 -1
  235. package/dist/assets/arc-D65wG9gm.js +0 -1
  236. package/dist/assets/architecture-PBZL5I3N-DBa6CAv_.js +0 -1
  237. package/dist/assets/architectureDiagram-2XIMDMQ5-Djwpsh98.js +0 -36
  238. package/dist/assets/array-DOVTz2Mq.js +0 -1
  239. package/dist/assets/blockDiagram-WCTKOSBZ-BdW5TTxj.js +0 -132
  240. package/dist/assets/c4Diagram-IC4MRINW-DTmkHEXu.js +0 -10
  241. package/dist/assets/channel-B3MUFipN.js +0 -1
  242. package/dist/assets/chunk-4BX2VUAB-DEqzsvDc.js +0 -1
  243. package/dist/assets/chunk-55IACEB6-BzVuSUV8.js +0 -1
  244. package/dist/assets/chunk-7E7YKBS2-CZ8IcA4c.js +0 -1
  245. package/dist/assets/chunk-7R4GIKGN-CWVVC8HX.js +0 -79
  246. package/dist/assets/chunk-C72U2L5F-B1Tso5TH.js +0 -1
  247. package/dist/assets/chunk-EGIJ26TM-Cx_7CFik.js +0 -1
  248. package/dist/assets/chunk-FMBD7UC4-Cfk_iGhv.js +0 -15
  249. package/dist/assets/chunk-GEFDOKGD-C_5hRbJt.js +0 -2
  250. package/dist/assets/chunk-GLR3WWYH-CkY7IyBj.js +0 -2
  251. package/dist/assets/chunk-HHEYEP7N-B0I4X5cr.js +0 -1
  252. package/dist/assets/chunk-JSJVCQXG-CAjwlVLg.js +0 -1
  253. package/dist/assets/chunk-KX2RTZJC-DWqnZZ02.js +0 -1
  254. package/dist/assets/chunk-KYZI473N-gjRVhJgJ.js +0 -53
  255. package/dist/assets/chunk-L3YUKLVL-D7C9GuxL.js +0 -1
  256. package/dist/assets/chunk-MX3YWQON-i-77iuVj.js +0 -1
  257. package/dist/assets/chunk-NQ4KR5QH-B22Pvemm.js +0 -220
  258. package/dist/assets/chunk-O4XLMI2P-ZQd5L6ZD.js +0 -7
  259. package/dist/assets/chunk-OZEHJAEY-BaPKTELw.js +0 -1
  260. package/dist/assets/chunk-PQ6SQG4A-DqE1eupT.js +0 -1
  261. package/dist/assets/chunk-PU5JKC2W-BTqWqedh.js +0 -70
  262. package/dist/assets/chunk-QZHKN3VN-Nm9TvMss.js +0 -1
  263. package/dist/assets/chunk-R5LLSJPH-DkiNs1dN.js +0 -1
  264. package/dist/assets/chunk-WL4C6EOR-CioD2fv2.js +0 -189
  265. package/dist/assets/chunk-XIRO2GV7-B4GGQONY.js +0 -1
  266. package/dist/assets/chunk-XPW4576I-C0IbbQos.js +0 -32
  267. package/dist/assets/chunk-XZSTWKYB-DMOqFWmT.js +0 -94
  268. package/dist/assets/chunk-YBOYWFTD-CoeQgeVY.js +0 -1
  269. package/dist/assets/classDiagram-VBA2DB6C-DV9ltQ7h.js +0 -1
  270. package/dist/assets/classDiagram-v2-RAHNMMFH-C6nD9wmM.js +0 -1
  271. package/dist/assets/clone-DuY6BQEm.js +0 -1
  272. package/dist/assets/cose-bilkent-S5V4N54A-B6FexK6p.js +0 -1
  273. package/dist/assets/cytoscape.esm-DoTFyJaN.js +0 -321
  274. package/dist/assets/dagre-CCcocoCU.js +0 -1
  275. package/dist/assets/dagre-KLK3FWXG-DIELowj9.js +0 -4
  276. package/dist/assets/defaultLocale-Ck2Xxk-C.js +0 -1
  277. package/dist/assets/diagram-E7M64L7V-D1mm0PoO.js +0 -24
  278. package/dist/assets/diagram-IFDJBPK2-7DVjly8y.js +0 -43
  279. package/dist/assets/diagram-P4PSJMXO-jO7pfyMb.js +0 -24
  280. package/dist/assets/dist-BywRdrPx.js +0 -1
  281. package/dist/assets/erDiagram-INFDFZHY-DSRxlRFy.js +0 -70
  282. package/dist/assets/flowDiagram-PKNHOUZH-CgKzzNdR.js +0 -162
  283. package/dist/assets/ganttDiagram-A5KZAMGK-CtsE7Y4E.js +0 -292
  284. package/dist/assets/gitGraph-HDMCJU4V-BU9uhwtz.js +0 -1
  285. package/dist/assets/gitGraphDiagram-K3NZZRJ6-DOU8RGdw.js +0 -65
  286. package/dist/assets/graphlib-WkJoBgka.js +0 -1
  287. package/dist/assets/index-CKVArt9D.js +0 -562
  288. package/dist/assets/index-DzRKJazf.css +0 -2
  289. package/dist/assets/info-3K5VOQVL-CPpvM-SG.js +0 -1
  290. package/dist/assets/infoDiagram-LFFYTUFH-VKLs5DsF.js +0 -2
  291. package/dist/assets/init-Bft5Ffpj.js +0 -1
  292. package/dist/assets/isArrayLikeObject-icl0H0jo.js +0 -1
  293. package/dist/assets/isEmpty-Du8sNmkE.js +0 -1
  294. package/dist/assets/ishikawaDiagram-PHBUUO56-CsWvEjux.js +0 -70
  295. package/dist/assets/journeyDiagram-4ABVD52K-BzJGTdIT.js +0 -139
  296. package/dist/assets/kanban-definition-K7BYSVSG-B_9ClJ1A.js +0 -89
  297. package/dist/assets/katex-BJrMXEjr.js +0 -261
  298. package/dist/assets/line-CC_tDGId.js +0 -1
  299. package/dist/assets/linear-Cts_d04Y.js +0 -1
  300. package/dist/assets/math-CNhlSIO3.js +0 -1
  301. package/dist/assets/mermaid-parser.core-Vb9KKv1R.js +0 -4
  302. package/dist/assets/mermaid.core-C_7xsp3d.js +0 -11
  303. package/dist/assets/mindmap-definition-YRQLILUH-BWmfy5wB.js +0 -68
  304. package/dist/assets/ordinal-DIg8h6NI.js +0 -1
  305. package/dist/assets/packet-RMMSAZCW-Q-WG6o3b.js +0 -1
  306. package/dist/assets/path-DfRbCp9y.js +0 -1
  307. package/dist/assets/pie-UPGHQEXC-Cwi2tLlt.js +0 -1
  308. package/dist/assets/pieDiagram-SKSYHLDU-Dyf3X_in.js +0 -30
  309. package/dist/assets/quadrantDiagram-337W2JSQ-B5_5m61Q.js +0 -7
  310. package/dist/assets/radar-KQ55EAFF-Dtw2VzxY.js +0 -1
  311. package/dist/assets/requirementDiagram-Z7DCOOCP-BSERBnlW.js +0 -73
  312. package/dist/assets/rough.esm-KjoEK0it.js +0 -1
  313. package/dist/assets/sankeyDiagram-WA2Y5GQK-CMcEY8Cz.js +0 -10
  314. package/dist/assets/sequenceDiagram-2WXFIKYE-D28qcXwC.js +0 -145
  315. package/dist/assets/src-C8kkzlHX.js +0 -1
  316. package/dist/assets/stateDiagram-RAJIS63D-7oVrCmRl.js +0 -1
  317. package/dist/assets/stateDiagram-v2-FVOUBMTO-DtFptQAd.js +0 -1
  318. package/dist/assets/timeline-definition-YZTLITO2-rbCfBEvG.js +0 -61
  319. package/dist/assets/treemap-KZPCXAKY-BlRvF0um.js +0 -1
  320. package/dist/assets/vennDiagram-LZ73GAT5-DBit3zWa.js +0 -34
  321. package/dist/assets/xychartDiagram-JWTSCODW-BVYXv51y.js +0 -7
  322. package/dist/index.js +0 -1040
@@ -0,0 +1,467 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import {
4
+ existsSync,
5
+ lstatSync,
6
+ readdirSync,
7
+ readFileSync,
8
+ statSync,
9
+ } from "node:fs";
10
+ import * as fs from "node:fs/promises";
11
+ import * as os from "node:os";
12
+ import { join, resolve } from "node:path";
13
+ import { Command } from "commander";
14
+ import open from "open";
15
+ import { getCommentPath, parseCommentFile } from "../lib/comment-storage.js";
16
+ import { getFileType } from "../lib/utils.js";
17
+ import type { FileEntry } from "../server/index.js";
18
+ import { removeServerInfo, startServer } from "../server/index.js";
19
+ import type { DocumentType } from "../types/index.js";
20
+
21
+ const program = new Command();
22
+
23
+ function isPermissionError(err: unknown): boolean {
24
+ return (
25
+ err instanceof Error &&
26
+ "code" in err &&
27
+ (err as NodeJS.ErrnoException).code === "EACCES"
28
+ );
29
+ }
30
+
31
+ interface ServerInfo {
32
+ port: number;
33
+ pid: number;
34
+ }
35
+
36
+ async function discoverServer(): Promise<ServerInfo | null> {
37
+ const serverInfoPath = join(os.homedir(), ".readit", "server.json");
38
+
39
+ try {
40
+ const content = readFileSync(serverInfoPath, "utf-8");
41
+ const info: ServerInfo = JSON.parse(content);
42
+
43
+ // Verify the process is alive
44
+ try {
45
+ process.kill(info.pid, 0);
46
+ } catch {
47
+ return null;
48
+ }
49
+
50
+ // Verify health endpoint responds
51
+ try {
52
+ const res = await fetch(`http://127.0.0.1:${info.port}/api/health`);
53
+ if (!res.ok) return null;
54
+ } catch {
55
+ return null;
56
+ }
57
+
58
+ return info;
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Recursively find all .comments.md files in a directory.
66
+ */
67
+ function findCommentFiles(dir: string): string[] {
68
+ const results: string[] = [];
69
+
70
+ try {
71
+ const entries = readdirSync(dir);
72
+ for (const entry of entries) {
73
+ const fullPath = join(dir, entry);
74
+ try {
75
+ const lstat = lstatSync(fullPath);
76
+ if (lstat.isSymbolicLink()) continue;
77
+ if (lstat.isDirectory()) {
78
+ results.push(...findCommentFiles(fullPath));
79
+ } else if (entry.endsWith(".comments.md")) {
80
+ results.push(fullPath);
81
+ }
82
+ } catch (err) {
83
+ if (isPermissionError(err)) {
84
+ console.warn(`Warning: Permission denied: ${fullPath}`);
85
+ }
86
+ }
87
+ }
88
+ } catch (err) {
89
+ if (isPermissionError(err)) {
90
+ console.warn(`Warning: Permission denied: ${dir}`);
91
+ }
92
+ }
93
+
94
+ return results;
95
+ }
96
+
97
+ /**
98
+ * Recursively find reviewable files (.md, .markdown, .html, .htm) in a directory.
99
+ */
100
+ function findReviewableFiles(dir: string): FileEntry[] {
101
+ const results: FileEntry[] = [];
102
+
103
+ try {
104
+ const entries = readdirSync(dir);
105
+ for (const entry of entries) {
106
+ // Skip hidden directories and node_modules
107
+ if (entry.startsWith(".") || entry === "node_modules") continue;
108
+
109
+ const fullPath = join(dir, entry);
110
+ try {
111
+ const lstat = lstatSync(fullPath);
112
+ if (lstat.isSymbolicLink()) continue;
113
+ if (lstat.isDirectory()) {
114
+ results.push(...findReviewableFiles(fullPath));
115
+ } else {
116
+ const type = getFileType(entry);
117
+ if (type) {
118
+ results.push({
119
+ content: readFileSync(fullPath, "utf-8"),
120
+ type,
121
+ filePath: fullPath,
122
+ });
123
+ }
124
+ }
125
+ } catch (err) {
126
+ if (isPermissionError(err)) {
127
+ console.warn(`Warning: Permission denied: ${fullPath}`);
128
+ }
129
+ }
130
+ }
131
+ } catch (err) {
132
+ if (isPermissionError(err)) {
133
+ console.warn(`Warning: Permission denied: ${dir}`);
134
+ }
135
+ }
136
+
137
+ return results;
138
+ }
139
+
140
+ /**
141
+ * Resolve CLI arguments into a deduplicated list of FileEntry objects.
142
+ */
143
+ function resolveFiles(args: string[]): FileEntry[] {
144
+ const seen = new Set<string>();
145
+ const files: FileEntry[] = [];
146
+
147
+ for (const arg of args) {
148
+ const filePath = resolve(process.cwd(), arg);
149
+
150
+ if (!existsSync(filePath)) {
151
+ console.error(`error: not found: ${filePath}`);
152
+ process.exit(1);
153
+ }
154
+
155
+ const stat = statSync(filePath);
156
+
157
+ if (stat.isDirectory()) {
158
+ const found = findReviewableFiles(filePath);
159
+ for (const entry of found) {
160
+ if (!seen.has(entry.filePath)) {
161
+ seen.add(entry.filePath);
162
+ files.push(entry);
163
+ }
164
+ }
165
+ } else {
166
+ if (seen.has(filePath)) continue;
167
+
168
+ const type = getFileType(filePath);
169
+ if (!type) {
170
+ console.error(
171
+ `error: unsupported file type: ${arg} (expected .md, .markdown, .html, or .htm)`,
172
+ );
173
+ process.exit(1);
174
+ }
175
+
176
+ seen.add(filePath);
177
+ files.push({
178
+ content: readFileSync(filePath, "utf-8"),
179
+ type,
180
+ filePath,
181
+ });
182
+ }
183
+ }
184
+
185
+ return files;
186
+ }
187
+
188
+ program
189
+ .name("readit")
190
+ .description("Review Markdown and HTML documents with inline comments")
191
+ .version("0.1.3");
192
+
193
+ // List command: show all commented files
194
+ program
195
+ .command("list")
196
+ .description("List all files with comments")
197
+ .action(async () => {
198
+ const readitDir = join(os.homedir(), ".readit", "comments");
199
+
200
+ if (!existsSync(readitDir)) {
201
+ console.log("No comments found.");
202
+ return;
203
+ }
204
+
205
+ const commentFiles = findCommentFiles(readitDir);
206
+
207
+ if (commentFiles.length === 0) {
208
+ console.log("No comments found.");
209
+ return;
210
+ }
211
+
212
+ console.log(`\nFound ${commentFiles.length} file(s) with comments:\n`);
213
+
214
+ for (const file of commentFiles) {
215
+ try {
216
+ const content = readFileSync(file, "utf-8");
217
+ const parsed = parseCommentFile(content);
218
+ const commentCount = parsed.comments.length;
219
+ const sourcePath = parsed.source || "(unknown source)";
220
+
221
+ console.log(` ${sourcePath}`);
222
+ console.log(
223
+ ` ${commentCount} comment${commentCount !== 1 ? "s" : ""}`,
224
+ );
225
+ console.log();
226
+ } catch {
227
+ // Skip unreadable files
228
+ }
229
+ }
230
+ });
231
+
232
+ // Show command: display comments for a file
233
+ program
234
+ .command("show <file>")
235
+ .description("Show comments for a file")
236
+ .action(async (file: string) => {
237
+ const filePath = resolve(process.cwd(), file);
238
+ const commentPath = getCommentPath(filePath);
239
+
240
+ if (!existsSync(commentPath)) {
241
+ console.log(`No comments found for: ${filePath}`);
242
+ return;
243
+ }
244
+
245
+ try {
246
+ const content = await fs.readFile(commentPath, "utf-8");
247
+ const parsed = parseCommentFile(content);
248
+
249
+ if (parsed.comments.length === 0) {
250
+ console.log(`No comments found for: ${filePath}`);
251
+ return;
252
+ }
253
+
254
+ console.log(`\nComments for: ${filePath}`);
255
+ console.log(`${"─".repeat(60)}\n`);
256
+
257
+ for (let i = 0; i < parsed.comments.length; i++) {
258
+ const comment = parsed.comments[i];
259
+ console.log(`[${i + 1}] ${comment.lineHint || "L?"}`);
260
+ console.log(
261
+ `Selected: "${comment.selectedText.slice(0, 80)}${comment.selectedText.length > 80 ? "..." : ""}"`,
262
+ );
263
+ console.log(`Comment: ${comment.comment}`);
264
+ console.log(`Created: ${comment.createdAt}`);
265
+ console.log();
266
+ }
267
+ } catch (err) {
268
+ console.error(
269
+ "error: failed to read comments:",
270
+ err instanceof Error ? err.message : err,
271
+ );
272
+ process.exit(1);
273
+ }
274
+ });
275
+
276
+ // Main review command (default) — accepts one or more files/directories
277
+ program
278
+ .argument("<files...>", "Markdown or HTML files/directories to review")
279
+ .option("-p, --port <number>", "Port to run server on", "4567")
280
+ .option("--host <address>", "Host address to bind to", "127.0.0.1")
281
+ .option("--no-open", "Don't automatically open browser")
282
+ .option("--clean", "Clear all existing comments on startup")
283
+ .action(
284
+ async (
285
+ fileArgs: string[],
286
+ options: {
287
+ port: string;
288
+ host: string;
289
+ open: boolean;
290
+ clean: boolean;
291
+ },
292
+ ) => {
293
+ const files = resolveFiles(fileArgs);
294
+
295
+ if (files.length === 0) {
296
+ console.error("error: no reviewable files found");
297
+ process.exit(1);
298
+ }
299
+
300
+ const preferredPort = Number.parseInt(options.port, 10);
301
+
302
+ if (
303
+ Number.isNaN(preferredPort) ||
304
+ preferredPort < 1 ||
305
+ preferredPort > 65535
306
+ ) {
307
+ console.error(`error: invalid port number: ${options.port}`);
308
+ process.exit(1);
309
+ }
310
+
311
+ try {
312
+ const { url, server } = await startServer({
313
+ files,
314
+ port: preferredPort,
315
+ host: options.host,
316
+ clean: options.clean,
317
+ });
318
+
319
+ const fileList = files.map((f) => ` ${f.filePath} (${f.type})`);
320
+
321
+ console.log(`
322
+ readit - Document Review Tool
323
+
324
+ ${files.length === 1 ? "File:" : "Files:"}
325
+ ${fileList.join("\n")}
326
+ URL: ${url}
327
+
328
+ Server running. Close browser tab to stop.
329
+ Press Ctrl+C to force stop.
330
+ `);
331
+
332
+ if (options.open) {
333
+ open(url);
334
+ }
335
+
336
+ // Graceful shutdown on Ctrl+C
337
+ process.on("SIGINT", async () => {
338
+ console.log("\n\nShutting down...");
339
+ server.stop();
340
+ await removeServerInfo();
341
+ process.exit(0);
342
+ });
343
+ } catch (error) {
344
+ console.error(
345
+ "error: failed to start server:",
346
+ error instanceof Error ? error.message : error,
347
+ );
348
+ process.exit(1);
349
+ }
350
+ },
351
+ );
352
+
353
+ // Open command: add files to running server or start new one
354
+ program
355
+ .command("open")
356
+ .argument("<files...>", "Markdown or HTML files to add to running server")
357
+ .description("Add files to a running readit server, or start a new one")
358
+ .option("-p, --port <number>", "Port for new server (if starting)", "4567")
359
+ .option("--host <address>", "Host for new server (if starting)", "127.0.0.1")
360
+ .action(
361
+ async (fileArgs: string[], options: { port: string; host: string }) => {
362
+ // Resolve and validate files
363
+ const resolvedFiles: { path: string; type: DocumentType }[] = [];
364
+ for (const arg of fileArgs) {
365
+ const filePath = resolve(process.cwd(), arg);
366
+
367
+ if (!existsSync(filePath)) {
368
+ console.error(`error: not found: ${filePath}`);
369
+ process.exit(1);
370
+ }
371
+
372
+ const type = getFileType(filePath);
373
+ if (!type) {
374
+ console.error(
375
+ `error: unsupported file type: ${arg} (expected .md, .markdown, .html, or .htm)`,
376
+ );
377
+ process.exit(1);
378
+ }
379
+
380
+ resolvedFiles.push({ path: filePath, type });
381
+ }
382
+
383
+ // Try to find running server
384
+ const server = await discoverServer();
385
+
386
+ if (server) {
387
+ // Send files to running server
388
+ for (const file of resolvedFiles) {
389
+ try {
390
+ const res = await fetch(
391
+ `http://127.0.0.1:${server.port}/api/files`,
392
+ {
393
+ method: "POST",
394
+ headers: { "Content-Type": "application/json" },
395
+ body: JSON.stringify({ path: file.path }),
396
+ },
397
+ );
398
+
399
+ if (!res.ok) {
400
+ const data = await res.json();
401
+ console.error(`error: failed to add ${file.path}: ${data.error}`);
402
+ process.exit(1);
403
+ }
404
+
405
+ const data = await res.json();
406
+ console.log(`Added: ${data.fileName} (${data.type})`);
407
+ } catch (err) {
408
+ console.error(
409
+ "error: failed to connect to server:",
410
+ err instanceof Error ? err.message : err,
411
+ );
412
+ process.exit(1);
413
+ }
414
+ }
415
+
416
+ console.log(`\nServer: http://127.0.0.1:${server.port}`);
417
+ return;
418
+ }
419
+
420
+ // No running server — start one
421
+ console.log("No running server found, starting new one...\n");
422
+
423
+ const files = resolvedFiles.map((f) => ({
424
+ content: readFileSync(f.path, "utf-8"),
425
+ type: f.type,
426
+ filePath: f.path,
427
+ }));
428
+
429
+ const preferredPort = Number.parseInt(options.port, 10);
430
+ try {
431
+ const { url, server: newServer } = await startServer({
432
+ files,
433
+ port: preferredPort,
434
+ host: options.host,
435
+ });
436
+
437
+ const fileList = files.map((f) => ` ${f.filePath} (${f.type})`);
438
+ console.log(`
439
+ readit - Document Review Tool
440
+
441
+ ${files.length === 1 ? "File:" : "Files:"}
442
+ ${fileList.join("\n")}
443
+ URL: ${url}
444
+
445
+ Server running. Close browser tab to stop.
446
+ Press Ctrl+C to force stop.
447
+ `);
448
+
449
+ open(url);
450
+
451
+ process.on("SIGINT", async () => {
452
+ console.log("\n\nShutting down...");
453
+ newServer.stop();
454
+ await removeServerInfo();
455
+ process.exit(0);
456
+ });
457
+ } catch (error) {
458
+ console.error(
459
+ "error: failed to start server:",
460
+ error instanceof Error ? error.message : error,
461
+ );
462
+ process.exit(1);
463
+ }
464
+ },
465
+ );
466
+
467
+ program.parse();
@@ -0,0 +1,110 @@
1
+ import {
2
+ BotMessageSquare,
3
+ FileDown,
4
+ FileText,
5
+ Maximize2,
6
+ Minimize2,
7
+ MoreHorizontal,
8
+ RefreshCw,
9
+ Settings,
10
+ TextQuote,
11
+ } from "lucide-react";
12
+ import { useState } from "react";
13
+ import { useCommentContext } from "../contexts/CommentContext";
14
+ import { useLayoutContext } from "../contexts/LayoutContext";
15
+ import { RawModal } from "./RawModal";
16
+ import { SettingsModal } from "./SettingsModal";
17
+ import { Button } from "./ui/Button";
18
+ import {
19
+ DropdownMenu,
20
+ DropdownMenuContent,
21
+ DropdownMenuItem,
22
+ DropdownMenuSeparator,
23
+ DropdownMenuTrigger,
24
+ } from "./ui/DropdownMenu";
25
+
26
+ interface ActionsMenuProps {
27
+ onCopyAll: () => void;
28
+ onCopyAllRaw: () => void;
29
+ onExportJson: () => void;
30
+ onReload: () => void;
31
+ }
32
+
33
+ export function ActionsMenu({
34
+ onCopyAll,
35
+ onCopyAllRaw,
36
+ onExportJson,
37
+ onReload,
38
+ }: ActionsMenuProps) {
39
+ const { commentCount } = useCommentContext();
40
+ const { isFullscreen, toggleLayoutMode } = useLayoutContext();
41
+
42
+ const [menuOpen, setMenuOpen] = useState(false);
43
+ const [rawModalOpen, setRawModalOpen] = useState(false);
44
+ const [settingsOpen, setSettingsOpen] = useState(false);
45
+
46
+ return (
47
+ <>
48
+ <DropdownMenu open={menuOpen} onOpenChange={setMenuOpen}>
49
+ <DropdownMenuTrigger asChild>
50
+ <Button
51
+ variant="ghost"
52
+ size="icon"
53
+ className="size-7"
54
+ aria-label="Actions menu"
55
+ >
56
+ <MoreHorizontal className="w-4 h-4" />
57
+ </Button>
58
+ </DropdownMenuTrigger>
59
+ <DropdownMenuContent align="end" className="min-w-[160px]">
60
+ <DropdownMenuItem onSelect={() => toggleLayoutMode()}>
61
+ {isFullscreen ? <Minimize2 /> : <Maximize2 />}
62
+ {isFullscreen ? "Centered" : "Fullscreen"}
63
+ </DropdownMenuItem>
64
+ <DropdownMenuItem onSelect={() => setSettingsOpen(true)}>
65
+ <Settings />
66
+ Settings
67
+ </DropdownMenuItem>
68
+ <DropdownMenuSeparator />
69
+ <DropdownMenuItem onSelect={() => onReload()}>
70
+ <RefreshCw />
71
+ Reload
72
+ </DropdownMenuItem>
73
+ {commentCount > 0 && (
74
+ <>
75
+ <DropdownMenuItem
76
+ onSelect={() => onCopyAll()}
77
+ title="Copy in prompt format for AI assistants"
78
+ >
79
+ <BotMessageSquare />
80
+ Copy All (AI)
81
+ </DropdownMenuItem>
82
+ <DropdownMenuItem
83
+ onSelect={() => onCopyAllRaw()}
84
+ title="Copy as plain text"
85
+ >
86
+ <TextQuote />
87
+ Copy All (Raw)
88
+ </DropdownMenuItem>
89
+ <DropdownMenuItem onSelect={() => onExportJson()}>
90
+ <FileDown />
91
+ Export JSON
92
+ </DropdownMenuItem>
93
+ <DropdownMenuItem onSelect={() => setRawModalOpen(true)}>
94
+ <FileText />
95
+ View Raw
96
+ </DropdownMenuItem>
97
+ </>
98
+ )}
99
+ </DropdownMenuContent>
100
+ </DropdownMenu>
101
+
102
+ <RawModal isOpen={rawModalOpen} onClose={() => setRawModalOpen(false)} />
103
+
104
+ <SettingsModal
105
+ isOpen={settingsOpen}
106
+ onClose={() => setSettingsOpen(false)}
107
+ />
108
+ </>
109
+ );
110
+ }
@@ -0,0 +1,83 @@
1
+ import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter";
2
+ // Import only the languages we need (reduces bundle by ~800KB)
3
+ import bash from "react-syntax-highlighter/dist/esm/languages/prism/bash";
4
+ import css from "react-syntax-highlighter/dist/esm/languages/prism/css";
5
+ import diff from "react-syntax-highlighter/dist/esm/languages/prism/diff";
6
+ import go from "react-syntax-highlighter/dist/esm/languages/prism/go";
7
+ import graphql from "react-syntax-highlighter/dist/esm/languages/prism/graphql";
8
+ import javascript from "react-syntax-highlighter/dist/esm/languages/prism/javascript";
9
+ import json from "react-syntax-highlighter/dist/esm/languages/prism/json";
10
+ import jsx from "react-syntax-highlighter/dist/esm/languages/prism/jsx";
11
+ import markdown from "react-syntax-highlighter/dist/esm/languages/prism/markdown";
12
+ import python from "react-syntax-highlighter/dist/esm/languages/prism/python";
13
+ import rust from "react-syntax-highlighter/dist/esm/languages/prism/rust";
14
+ import sql from "react-syntax-highlighter/dist/esm/languages/prism/sql";
15
+ import tsx from "react-syntax-highlighter/dist/esm/languages/prism/tsx";
16
+ import typescript from "react-syntax-highlighter/dist/esm/languages/prism/typescript";
17
+ import yaml from "react-syntax-highlighter/dist/esm/languages/prism/yaml";
18
+ import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
19
+ import { MermaidDiagram } from "./MermaidDiagram";
20
+
21
+ // Register languages
22
+ SyntaxHighlighter.registerLanguage("bash", bash);
23
+ SyntaxHighlighter.registerLanguage("sh", bash);
24
+ SyntaxHighlighter.registerLanguage("shell", bash);
25
+ SyntaxHighlighter.registerLanguage("css", css);
26
+ SyntaxHighlighter.registerLanguage("diff", diff);
27
+ SyntaxHighlighter.registerLanguage("go", go);
28
+ SyntaxHighlighter.registerLanguage("graphql", graphql);
29
+ SyntaxHighlighter.registerLanguage("javascript", javascript);
30
+ SyntaxHighlighter.registerLanguage("js", javascript);
31
+ SyntaxHighlighter.registerLanguage("json", json);
32
+ SyntaxHighlighter.registerLanguage("jsx", jsx);
33
+ SyntaxHighlighter.registerLanguage("markdown", markdown);
34
+ SyntaxHighlighter.registerLanguage("md", markdown);
35
+ SyntaxHighlighter.registerLanguage("python", python);
36
+ SyntaxHighlighter.registerLanguage("py", python);
37
+ SyntaxHighlighter.registerLanguage("rust", rust);
38
+ SyntaxHighlighter.registerLanguage("rs", rust);
39
+ SyntaxHighlighter.registerLanguage("sql", sql);
40
+ SyntaxHighlighter.registerLanguage("tsx", tsx);
41
+ SyntaxHighlighter.registerLanguage("typescript", typescript);
42
+ SyntaxHighlighter.registerLanguage("ts", typescript);
43
+ SyntaxHighlighter.registerLanguage("yaml", yaml);
44
+ SyntaxHighlighter.registerLanguage("yml", yaml);
45
+
46
+ const CODE_BLOCK_STYLE = {
47
+ margin: "1.5em 0",
48
+ borderRadius: "0.5em",
49
+ fontSize: "0.875em",
50
+ };
51
+
52
+ interface CodeBlockProps {
53
+ className?: string;
54
+ children?: React.ReactNode;
55
+ }
56
+
57
+ export function CodeBlock({ className, children }: CodeBlockProps) {
58
+ // Extract language from className (e.g., "language-typescript" -> "typescript")
59
+ const langMatch = className?.match(/language-(\w+)/);
60
+ const language = langMatch?.[1] ?? "";
61
+ const codeString = String(children).replace(/\n$/, "");
62
+
63
+ // Mermaid diagrams
64
+ if (language === "mermaid") {
65
+ return <MermaidDiagram code={codeString} />;
66
+ }
67
+
68
+ // Inline code (no language specified and no newlines)
69
+ if (!langMatch && !String(children).includes("\n")) {
70
+ return <code className={className}>{children}</code>;
71
+ }
72
+
73
+ return (
74
+ <SyntaxHighlighter
75
+ style={oneDark}
76
+ language={language}
77
+ PreTag="div"
78
+ customStyle={CODE_BLOCK_STYLE}
79
+ >
80
+ {codeString}
81
+ </SyntaxHighlighter>
82
+ );
83
+ }