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.
- package/LICENSE +21 -0
- package/README.md +61 -0
- package/app/components/CanvasMount.tsx +62 -0
- package/app/components/CodeWrapToggle.tsx +78 -0
- package/app/components/FindOnPage.tsx +224 -0
- package/app/components/MobileBottomBar.tsx +93 -0
- package/app/components/MobileProjectsPanel.tsx +113 -0
- package/app/components/PageFloatingMenu.tsx +224 -0
- package/app/components/ProjectSwitcher.tsx +124 -0
- package/app/components/Search.tsx +930 -0
- package/app/components/ShortcutsHelp.tsx +113 -0
- package/app/components/Sidebar.tsx +1049 -0
- package/app/components/TabBar.tsx +227 -0
- package/app/components/Toc.tsx +129 -0
- package/app/components/TopBar.tsx +74 -0
- package/app/components/theme-toggle.tsx +71 -0
- package/app/components/ui/button.tsx +56 -0
- package/app/components/ui/card.tsx +55 -0
- package/app/components/ui/dropdown-menu.tsx +156 -0
- package/app/components/ui/input.tsx +21 -0
- package/app/entry.client.tsx +12 -0
- package/app/entry.server.tsx +155 -0
- package/app/generated/site.ts +19 -0
- package/app/generated/slots.ts +10 -0
- package/app/generated/theme.generated.css +60 -0
- package/app/lib/config/config.server.ts +50 -0
- package/app/lib/config/defaults.ts +120 -0
- package/app/lib/config/load.ts +82 -0
- package/app/lib/config/schema.ts +131 -0
- package/app/lib/config/site.ts +43 -0
- package/app/lib/content.server.ts +105 -0
- package/app/lib/projects.ts +86 -0
- package/app/lib/sidebar.server.ts +113 -0
- package/app/lib/site.ts +27 -0
- package/app/lib/slots.tsx +33 -0
- package/app/lib/tabs.tsx +128 -0
- package/app/lib/useKeyboardShortcuts.ts +149 -0
- package/app/lib/utils.ts +17 -0
- package/app/root.tsx +171 -0
- package/app/routes/$.tsx +158 -0
- package/app/routes/_index.tsx +60 -0
- package/app/styles/app.css +461 -0
- package/app/styles/obsidian.css +83 -0
- package/app/styles/tailwind.css +227 -0
- package/cli.js +119 -0
- package/components.json +21 -0
- package/dist/config.mjs +87 -0
- package/dist/generate-content.mjs +1665 -0
- package/package.json +112 -0
- package/scripts/build-search-index.ts +129 -0
- package/scripts/canonical.ts +34 -0
- package/scripts/canvas-to-md.ts +73 -0
- package/scripts/compile.ts +242 -0
- package/scripts/emit-config.ts +163 -0
- package/scripts/generate-content.ts +197 -0
- package/scripts/obsidian/files.ts +222 -0
- package/scripts/obsidian/fs.ts +34 -0
- package/scripts/obsidian/generate.ts +36 -0
- package/scripts/obsidian/html.ts +17 -0
- package/scripts/obsidian/logger.ts +10 -0
- package/scripts/obsidian/markdown.ts +56 -0
- package/scripts/obsidian/obsidian.ts +229 -0
- package/scripts/obsidian/path.ts +60 -0
- package/scripts/obsidian/rehype.ts +60 -0
- package/scripts/obsidian/remark.ts +712 -0
- package/scripts/obsidian/types.ts +31 -0
- 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
|
+
}
|