cantip 0.1.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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -0
  3. package/app/components/CanvasMount.tsx +62 -0
  4. package/app/components/CodeWrapToggle.tsx +78 -0
  5. package/app/components/FindOnPage.tsx +224 -0
  6. package/app/components/MobileBottomBar.tsx +93 -0
  7. package/app/components/MobileProjectsPanel.tsx +113 -0
  8. package/app/components/PageFloatingMenu.tsx +224 -0
  9. package/app/components/ProjectSwitcher.tsx +124 -0
  10. package/app/components/Search.tsx +930 -0
  11. package/app/components/ShortcutsHelp.tsx +113 -0
  12. package/app/components/Sidebar.tsx +1049 -0
  13. package/app/components/TabBar.tsx +227 -0
  14. package/app/components/Toc.tsx +129 -0
  15. package/app/components/TopBar.tsx +74 -0
  16. package/app/components/theme-toggle.tsx +71 -0
  17. package/app/components/ui/button.tsx +56 -0
  18. package/app/components/ui/card.tsx +55 -0
  19. package/app/components/ui/dropdown-menu.tsx +156 -0
  20. package/app/components/ui/input.tsx +21 -0
  21. package/app/entry.client.tsx +12 -0
  22. package/app/entry.server.tsx +155 -0
  23. package/app/generated/site.ts +19 -0
  24. package/app/generated/slots.ts +10 -0
  25. package/app/generated/theme.generated.css +60 -0
  26. package/app/lib/config/config.server.ts +50 -0
  27. package/app/lib/config/defaults.ts +120 -0
  28. package/app/lib/config/load.ts +82 -0
  29. package/app/lib/config/schema.ts +131 -0
  30. package/app/lib/config/site.ts +43 -0
  31. package/app/lib/content.server.ts +105 -0
  32. package/app/lib/projects.ts +86 -0
  33. package/app/lib/sidebar.server.ts +113 -0
  34. package/app/lib/site.ts +27 -0
  35. package/app/lib/slots.tsx +33 -0
  36. package/app/lib/tabs.tsx +128 -0
  37. package/app/lib/useKeyboardShortcuts.ts +149 -0
  38. package/app/lib/utils.ts +17 -0
  39. package/app/root.tsx +171 -0
  40. package/app/routes/$.tsx +158 -0
  41. package/app/routes/_index.tsx +60 -0
  42. package/app/styles/app.css +461 -0
  43. package/app/styles/obsidian.css +83 -0
  44. package/app/styles/tailwind.css +227 -0
  45. package/cli.js +119 -0
  46. package/components.json +21 -0
  47. package/dist/config.mjs +87 -0
  48. package/dist/generate-content.mjs +1665 -0
  49. package/package.json +112 -0
  50. package/scripts/build-search-index.ts +129 -0
  51. package/scripts/canonical.ts +34 -0
  52. package/scripts/canvas-to-md.ts +73 -0
  53. package/scripts/compile.ts +242 -0
  54. package/scripts/emit-config.ts +163 -0
  55. package/scripts/generate-content.ts +197 -0
  56. package/scripts/obsidian/files.ts +222 -0
  57. package/scripts/obsidian/fs.ts +34 -0
  58. package/scripts/obsidian/generate.ts +36 -0
  59. package/scripts/obsidian/html.ts +17 -0
  60. package/scripts/obsidian/logger.ts +10 -0
  61. package/scripts/obsidian/markdown.ts +56 -0
  62. package/scripts/obsidian/obsidian.ts +229 -0
  63. package/scripts/obsidian/path.ts +60 -0
  64. package/scripts/obsidian/rehype.ts +60 -0
  65. package/scripts/obsidian/remark.ts +712 -0
  66. package/scripts/obsidian/types.ts +31 -0
  67. package/vite.config.ts +62 -0
@@ -0,0 +1,113 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { createPortal } from 'react-dom'
3
+
4
+ import { ALL_SHORTCUTS, type ShortcutGroup, type ShortcutInfo } from '~/lib/useKeyboardShortcuts'
5
+ import { t } from '~/lib/site'
6
+
7
+ /**
8
+ * The `?` cheatsheet. Single-key shortcuts are powerful but invisible, so — like
9
+ * Gmail/GitHub/Linear — pressing `?` (Shift+/) reveals every binding. It owns its
10
+ * own open/close: `?` toggles it, Escape and backdrop-click dismiss. It renders
11
+ * ALL_SHORTCUTS, the hand-maintained list that names every binding (the handlers
12
+ * themselves live with their components).
13
+ *
14
+ * Mounted once near the root. Desktop-only in spirit (needs a keyboard); harmless
15
+ * on mobile since `?` and the modal simply never get triggered.
16
+ */
17
+
18
+ const GROUP_ORDER: ShortcutGroup[] = ['Навигация', 'Дерево', 'Вкладки']
19
+
20
+ /**
21
+ * Open the cheatsheet from anywhere (e.g. the TopBar button) by dispatching this
22
+ * event — ShortcutsHelp listens for it. Keeps the overlay's open state owned here
23
+ * rather than lifted into root, matching the codebase's component-owns-behavior
24
+ * pattern. Use the helper so the event name stays in one place.
25
+ */
26
+ export const OPEN_SHORTCUTS_EVENT = 'docs:open-shortcuts'
27
+ export function openShortcutsHelp() {
28
+ window.dispatchEvent(new CustomEvent(OPEN_SHORTCUTS_EVENT))
29
+ }
30
+
31
+ export function ShortcutsHelp() {
32
+ const [open, setOpen] = useState(false)
33
+
34
+ // `?` toggles the overlay — but only outside text fields and with no other
35
+ // modifier (mirrors useKeyboardShortcuts' guard so it won't fire mid-typing).
36
+ useEffect(() => {
37
+ const onKey = (e: KeyboardEvent) => {
38
+ if (e.metaKey || e.ctrlKey || e.altKey) return
39
+ const el = e.target
40
+ if (el instanceof HTMLElement && (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.isContentEditable))
41
+ return
42
+ if (e.key === '?') {
43
+ e.preventDefault()
44
+ setOpen((o) => !o)
45
+ } else if (e.key === 'Escape') {
46
+ setOpen(false)
47
+ }
48
+ }
49
+ window.addEventListener('keydown', onKey)
50
+ return () => window.removeEventListener('keydown', onKey)
51
+ }, [])
52
+
53
+ // Open on demand from elsewhere (the TopBar button).
54
+ useEffect(() => {
55
+ const onOpen = () => setOpen(true)
56
+ window.addEventListener(OPEN_SHORTCUTS_EVENT, onOpen)
57
+ return () => window.removeEventListener(OPEN_SHORTCUTS_EVENT, onOpen)
58
+ }, [])
59
+
60
+ if (!open) return null
61
+
62
+ const byGroup = new Map<ShortcutGroup, ShortcutInfo[]>()
63
+ for (const r of ALL_SHORTCUTS) {
64
+ const list = byGroup.get(r.group) ?? []
65
+ list.push(r)
66
+ byGroup.set(r.group, list)
67
+ }
68
+
69
+ return createPortal(
70
+ <div
71
+ className="fixed inset-0 z-100 flex items-start justify-center bg-background/40 p-4 pt-[12vh] backdrop-blur-sm"
72
+ onMouseDown={() => setOpen(false)}
73
+ >
74
+ <div
75
+ role="dialog"
76
+ aria-label={t('shortcuts')}
77
+ className="w-[min(34rem,calc(100vw-2rem))] overflow-hidden rounded-lg border bg-popover shadow-xl"
78
+ onMouseDown={(e) => e.stopPropagation()}
79
+ >
80
+ <div className="flex items-center justify-between border-b px-4 py-3">
81
+ <h2 className="text-sm font-semibold">{t('shortcuts')}</h2>
82
+ <button
83
+ type="button"
84
+ onClick={() => setOpen(false)}
85
+ className="text-xs text-muted-foreground hover:text-foreground"
86
+ >
87
+ Esc
88
+ </button>
89
+ </div>
90
+ <div className="max-h-[60vh] overflow-y-auto px-4 py-3">
91
+ {GROUP_ORDER.filter((g) => byGroup.has(g)).map((group) => (
92
+ <div key={group} className="mb-4 last:mb-0">
93
+ <div className="mb-1.5 text-xs font-medium tracking-wide text-muted-foreground uppercase">
94
+ {group}
95
+ </div>
96
+ <ul className="space-y-1">
97
+ {byGroup.get(group)!.map((r, i) => (
98
+ <li key={i} className="flex items-center justify-between gap-4 text-sm">
99
+ <span className="text-foreground">{r.label}</span>
100
+ <kbd className="shrink-0 rounded border bg-muted px-1.5 py-0.5 font-mono text-xs text-muted-foreground">
101
+ {r.hint}
102
+ </kbd>
103
+ </li>
104
+ ))}
105
+ </ul>
106
+ </div>
107
+ ))}
108
+ </div>
109
+ </div>
110
+ </div>,
111
+ document.body,
112
+ )
113
+ }