cantip 0.1.0 → 0.1.1
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/app/components/CodeWrapToggle.tsx +4 -2
- package/app/components/FindOnPage.tsx +6 -5
- package/app/components/MobileBottomBar.tsx +1 -1
- package/app/components/MobileProjectsPanel.tsx +1 -1
- package/app/components/PageFloatingMenu.tsx +14 -13
- package/app/components/Search.tsx +24 -24
- package/app/components/ShortcutsHelp.tsx +4 -4
- package/app/components/Sidebar.tsx +18 -18
- package/app/components/TabBar.tsx +7 -6
- package/app/components/Toc.tsx +1 -1
- package/app/lib/config/defaults.ts +137 -0
- package/app/lib/useKeyboardShortcuts.ts +23 -11
- package/dist/generate-content.mjs +139 -2
- package/package.json +1 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { useEffect } from 'react'
|
|
2
2
|
|
|
3
|
+
import { t } from '~/lib/site'
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* The document body HTML is rendered server-side and injected via
|
|
5
7
|
* `dangerouslySetInnerHTML`, so code blocks aren't React elements we can give a
|
|
@@ -21,8 +23,8 @@ const WRAP_ICON =
|
|
|
21
23
|
const NOWRAP_ICON =
|
|
22
24
|
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>'
|
|
23
25
|
|
|
24
|
-
const WRAP_LABEL = '
|
|
25
|
-
const NOWRAP_LABEL = '
|
|
26
|
+
const WRAP_LABEL = t('wrapLines')
|
|
27
|
+
const NOWRAP_LABEL = t('noWrapLines')
|
|
26
28
|
|
|
27
29
|
export function CodeWrapToggle({ deps }: { deps?: string }) {
|
|
28
30
|
useEffect(() => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
2
2
|
import { ChevronDown, ChevronUp, Search as SearchIcon, X } from 'lucide-react'
|
|
3
3
|
|
|
4
|
+
import { t } from '~/lib/site'
|
|
4
5
|
import { cn } from '~/lib/utils'
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -171,7 +172,7 @@ export default function FindOnPage({ onClose }: { onClose: () => void }) {
|
|
|
171
172
|
'flex items-center gap-1 rounded-2xl border bg-sidebar/95 px-2 py-1.5 shadow-lg backdrop-blur',
|
|
172
173
|
)}
|
|
173
174
|
role="search"
|
|
174
|
-
aria-label=
|
|
175
|
+
aria-label={t('findOnPage')}
|
|
175
176
|
>
|
|
176
177
|
<SearchIcon className="size-4 shrink-0 text-muted-foreground" />
|
|
177
178
|
<input
|
|
@@ -179,7 +180,7 @@ export default function FindOnPage({ onClose }: { onClose: () => void }) {
|
|
|
179
180
|
value={query}
|
|
180
181
|
onChange={(e) => setQuery(e.target.value)}
|
|
181
182
|
onKeyDown={onKeyDown}
|
|
182
|
-
placeholder=
|
|
183
|
+
placeholder={t('findOnPagePlaceholder')}
|
|
183
184
|
className="h-8 min-w-0 flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
|
|
184
185
|
autoComplete="off"
|
|
185
186
|
spellCheck={false}
|
|
@@ -198,7 +199,7 @@ export default function FindOnPage({ onClose }: { onClose: () => void }) {
|
|
|
198
199
|
onClick={() => step(-1)}
|
|
199
200
|
disabled={!has}
|
|
200
201
|
className="rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:opacity-40"
|
|
201
|
-
aria-label=
|
|
202
|
+
aria-label={t('prevMatch')}
|
|
202
203
|
>
|
|
203
204
|
<ChevronUp className="size-4" />
|
|
204
205
|
</button>
|
|
@@ -207,7 +208,7 @@ export default function FindOnPage({ onClose }: { onClose: () => void }) {
|
|
|
207
208
|
onClick={() => step(1)}
|
|
208
209
|
disabled={!has}
|
|
209
210
|
className="rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:opacity-40"
|
|
210
|
-
aria-label=
|
|
211
|
+
aria-label={t('nextMatch')}
|
|
211
212
|
>
|
|
212
213
|
<ChevronDown className="size-4" />
|
|
213
214
|
</button>
|
|
@@ -215,7 +216,7 @@ export default function FindOnPage({ onClose }: { onClose: () => void }) {
|
|
|
215
216
|
type="button"
|
|
216
217
|
onClick={onClose}
|
|
217
218
|
className="rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
218
|
-
aria-label=
|
|
219
|
+
aria-label={t('closeFindOnPage')}
|
|
219
220
|
>
|
|
220
221
|
<X className="size-4" />
|
|
221
222
|
</button>
|
|
@@ -45,7 +45,7 @@ export default function MobileBottomBar({
|
|
|
45
45
|
'fixed inset-x-3 bottom-[calc(env(safe-area-inset-bottom)+0.75rem)] z-100 md:hidden',
|
|
46
46
|
'flex items-stretch gap-1 rounded-2xl border bg-sidebar/95 px-1.5 py-1 shadow-lg backdrop-blur',
|
|
47
47
|
)}
|
|
48
|
-
aria-label=
|
|
48
|
+
aria-label={t('navigation')}
|
|
49
49
|
>
|
|
50
50
|
<Link to="/" className={tab} aria-label={t('home')}>
|
|
51
51
|
<Home className="size-5" />
|
|
@@ -105,7 +105,7 @@ export default function MobileProjectsPanel({ activeId, open, onClose }: Props)
|
|
|
105
105
|
|
|
106
106
|
{/* Theme toggle row — a normal "settings" row at the bottom of the menu. */}
|
|
107
107
|
<div className="flex shrink-0 items-center justify-between border-t px-4 py-3">
|
|
108
|
-
<span className="text-sm text-foreground"
|
|
108
|
+
<span className="text-sm text-foreground">{t('theme')}</span>
|
|
109
109
|
<ThemeToggle className="text-muted-foreground" />
|
|
110
110
|
</div>
|
|
111
111
|
</div>
|
|
@@ -4,6 +4,7 @@ import { ArrowUp, ChevronLeft, ChevronRight, List, Search as SearchIcon, X } fro
|
|
|
4
4
|
import type { Heading } from '~/lib/content.server'
|
|
5
5
|
import { TocLinks, tocHeadings } from '~/components/Toc'
|
|
6
6
|
import FindOnPage from '~/components/FindOnPage'
|
|
7
|
+
import { t } from '~/lib/site'
|
|
7
8
|
import { cn } from '~/lib/utils'
|
|
8
9
|
|
|
9
10
|
/** Shared tab class — IDENTICAL to MobileBottomBar's `tab`, so this row reads as
|
|
@@ -119,29 +120,29 @@ export default function PageFloatingMenu({ headings }: { headings: Heading[] })
|
|
|
119
120
|
'fixed inset-x-3 bottom-[calc(var(--mobile-bar-height)+env(safe-area-inset-bottom)+1.5rem)] z-80 md:hidden',
|
|
120
121
|
'flex items-stretch gap-1 rounded-2xl border bg-sidebar/95 px-1.5 py-1 shadow-lg backdrop-blur',
|
|
121
122
|
)}
|
|
122
|
-
aria-label=
|
|
123
|
+
aria-label={t('pageActions')}
|
|
123
124
|
>
|
|
124
|
-
<button type="button" onClick={scrollTop} className={tab} aria-label=
|
|
125
|
+
<button type="button" onClick={scrollTop} className={tab} aria-label={t('scrollTop')}>
|
|
125
126
|
<ArrowUp className="size-5" />
|
|
126
|
-
<span
|
|
127
|
+
<span>{t('scrollTop')}</span>
|
|
127
128
|
</button>
|
|
128
|
-
<button type="button" onClick={openFind} className={tab} aria-label=
|
|
129
|
+
<button type="button" onClick={openFind} className={tab} aria-label={t('findOnPage')}>
|
|
129
130
|
<SearchIcon className="size-5" />
|
|
130
|
-
<span
|
|
131
|
+
<span>{t('find')}</span>
|
|
131
132
|
</button>
|
|
132
|
-
<button type="button" onClick={openToc} className={tab} aria-label=
|
|
133
|
+
<button type="button" onClick={openToc} className={tab} aria-label={t('toc')}>
|
|
133
134
|
<List className="size-5" />
|
|
134
|
-
<span
|
|
135
|
+
<span>{t('toc')}</span>
|
|
135
136
|
</button>
|
|
136
137
|
<button
|
|
137
138
|
type="button"
|
|
138
139
|
onClick={() => setOpen(false)}
|
|
139
140
|
className={tab}
|
|
140
|
-
aria-label=
|
|
141
|
+
aria-label={t('hide')}
|
|
141
142
|
aria-expanded
|
|
142
143
|
>
|
|
143
144
|
<ChevronRight className="size-5" />
|
|
144
|
-
<span
|
|
145
|
+
<span>{t('hide')}</span>
|
|
145
146
|
</button>
|
|
146
147
|
</nav>
|
|
147
148
|
) : (
|
|
@@ -157,7 +158,7 @@ export default function PageFloatingMenu({ headings }: { headings: Heading[] })
|
|
|
157
158
|
type="button"
|
|
158
159
|
onClick={() => setOpen(true)}
|
|
159
160
|
className="flex items-center gap-1 rounded-xl border bg-sidebar/95 px-3 py-2 text-[0.6875rem] font-medium text-muted-foreground shadow-md backdrop-blur transition-colors hover:text-foreground"
|
|
160
|
-
aria-label=
|
|
161
|
+
aria-label={t('pageActions')}
|
|
161
162
|
aria-expanded={false}
|
|
162
163
|
>
|
|
163
164
|
<ChevronLeft className="size-3.5" />
|
|
@@ -197,19 +198,19 @@ function TocSheet({ shown, onClose }: { shown: Heading[]; onClose: () => void })
|
|
|
197
198
|
onClick={onClose}
|
|
198
199
|
role="dialog"
|
|
199
200
|
aria-modal="true"
|
|
200
|
-
aria-label=
|
|
201
|
+
aria-label={t('toc')}
|
|
201
202
|
>
|
|
202
203
|
<div
|
|
203
204
|
className="flex h-[75vh] flex-col overflow-hidden rounded-t-2xl border-t bg-popover pb-[env(safe-area-inset-bottom)]"
|
|
204
205
|
onClick={(e) => e.stopPropagation()}
|
|
205
206
|
>
|
|
206
207
|
<div className="flex shrink-0 items-center justify-between border-b px-4 py-3">
|
|
207
|
-
<span className="text-sm font-semibold text-foreground"
|
|
208
|
+
<span className="text-sm font-semibold text-foreground">{t('toc')}</span>
|
|
208
209
|
<button
|
|
209
210
|
type="button"
|
|
210
211
|
onClick={onClose}
|
|
211
212
|
className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
212
|
-
aria-label=
|
|
213
|
+
aria-label={t('close')}
|
|
213
214
|
>
|
|
214
215
|
<X className="size-5" />
|
|
215
216
|
</button>
|
|
@@ -584,10 +584,10 @@ export function Search({
|
|
|
584
584
|
'inline-flex items-center gap-2 rounded-md border bg-background/60 px-3 py-1.5 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground',
|
|
585
585
|
className,
|
|
586
586
|
)}
|
|
587
|
-
aria-label=
|
|
587
|
+
aria-label={t('search')}
|
|
588
588
|
>
|
|
589
589
|
<SearchIcon className="size-4 shrink-0" />
|
|
590
|
-
<span className="max-md:hidden"
|
|
590
|
+
<span className="max-md:hidden">{t('searchEllipsis')}</span>
|
|
591
591
|
<kbd className="ml-auto hidden rounded border px-1.5 text-xs md:inline">Ctrl K</kbd>
|
|
592
592
|
</button>
|
|
593
593
|
)}
|
|
@@ -604,7 +604,7 @@ export function Search({
|
|
|
604
604
|
onMouseDown={dismiss}
|
|
605
605
|
role="dialog"
|
|
606
606
|
aria-modal="true"
|
|
607
|
-
aria-label=
|
|
607
|
+
aria-label={t('searchDocs')}
|
|
608
608
|
>
|
|
609
609
|
<div
|
|
610
610
|
className="fixed inset-0 flex flex-col overflow-hidden bg-popover md:static md:max-h-[80vh] md:w-full md:max-w-3xl md:rounded-lg md:border md:shadow-xl"
|
|
@@ -617,7 +617,7 @@ export function Search({
|
|
|
617
617
|
ref={inputRef}
|
|
618
618
|
value={query}
|
|
619
619
|
onChange={(e) => setQuery(e.target.value)}
|
|
620
|
-
placeholder=
|
|
620
|
+
placeholder={t('searchDocsPlaceholder')}
|
|
621
621
|
className="h-12 w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground"
|
|
622
622
|
autoComplete="off"
|
|
623
623
|
spellCheck={false}
|
|
@@ -629,9 +629,9 @@ export function Search({
|
|
|
629
629
|
'rounded p-1 hover:bg-accent hover:text-foreground',
|
|
630
630
|
isExact ? 'bg-accent text-foreground' : 'text-muted-foreground',
|
|
631
631
|
)}
|
|
632
|
-
aria-label=
|
|
632
|
+
aria-label={t('exactMatch')}
|
|
633
633
|
aria-pressed={isExact}
|
|
634
|
-
title=
|
|
634
|
+
title={t('exactMatchHint')}
|
|
635
635
|
>
|
|
636
636
|
<WholeWord className="size-4" />
|
|
637
637
|
</button>
|
|
@@ -644,8 +644,8 @@ export function Search({
|
|
|
644
644
|
onClick={reset}
|
|
645
645
|
disabled={!query && !dir}
|
|
646
646
|
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:opacity-40"
|
|
647
|
-
aria-label=
|
|
648
|
-
title=
|
|
647
|
+
aria-label={t('clearSearch')}
|
|
648
|
+
title={t('clear')}
|
|
649
649
|
>
|
|
650
650
|
<X className="size-4" />
|
|
651
651
|
</button>
|
|
@@ -657,14 +657,14 @@ export function Search({
|
|
|
657
657
|
{projectOptions.length > 0 && (
|
|
658
658
|
<div className="border-b px-3 py-2 text-xs text-muted-foreground">
|
|
659
659
|
<div className="flex flex-wrap items-center gap-2">
|
|
660
|
-
<span className="shrink-0"
|
|
660
|
+
<span className="shrink-0">{t('searchIn')}</span>
|
|
661
661
|
<select
|
|
662
662
|
value={project}
|
|
663
663
|
onChange={(e) => changeProject(e.target.value)}
|
|
664
664
|
className="rounded border bg-background px-2 py-1 text-foreground outline-none focus:ring-1 focus:ring-ring"
|
|
665
665
|
aria-label={t('searchProjectFilter')}
|
|
666
666
|
>
|
|
667
|
-
<option value=""
|
|
667
|
+
<option value="">{t('allProjects')}</option>
|
|
668
668
|
{projectOptions.map((p) => (
|
|
669
669
|
<option key={p} value={p}>
|
|
670
670
|
{projectLabel(p)}
|
|
@@ -697,9 +697,9 @@ export function Search({
|
|
|
697
697
|
setDirFocused(false)
|
|
698
698
|
}
|
|
699
699
|
}}
|
|
700
|
-
placeholder=
|
|
700
|
+
placeholder={t('folderPlaceholder')}
|
|
701
701
|
className="w-full rounded border bg-background px-2 py-1 text-foreground outline-none placeholder:text-muted-foreground focus:ring-1 focus:ring-ring"
|
|
702
|
-
aria-label=
|
|
702
|
+
aria-label={t('folder')}
|
|
703
703
|
autoComplete="off"
|
|
704
704
|
spellCheck={false}
|
|
705
705
|
/>
|
|
@@ -708,7 +708,7 @@ export function Search({
|
|
|
708
708
|
type="button"
|
|
709
709
|
onClick={() => commitDir('')}
|
|
710
710
|
className="absolute right-1 top-1/2 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
711
|
-
aria-label=
|
|
711
|
+
aria-label={t('clearFolder')}
|
|
712
712
|
>
|
|
713
713
|
<X className="size-3" />
|
|
714
714
|
</button>
|
|
@@ -780,9 +780,9 @@ export function Search({
|
|
|
780
780
|
'shrink-0 rounded border p-1 hover:bg-accent hover:text-foreground',
|
|
781
781
|
showTree ? 'bg-accent text-foreground' : 'text-muted-foreground',
|
|
782
782
|
)}
|
|
783
|
-
aria-label=
|
|
783
|
+
aria-label={t('pickFolderFromTree')}
|
|
784
784
|
aria-pressed={showTree}
|
|
785
|
-
title=
|
|
785
|
+
title={t('folderTree')}
|
|
786
786
|
>
|
|
787
787
|
<FolderTree className="size-4" />
|
|
788
788
|
</button>
|
|
@@ -796,13 +796,13 @@ export function Search({
|
|
|
796
796
|
<div className="min-h-0 flex-1 overflow-y-auto p-2">
|
|
797
797
|
{!query.trim() ? (
|
|
798
798
|
<p className="px-3 py-6 text-center text-sm text-muted-foreground">
|
|
799
|
-
{ready ? '
|
|
799
|
+
{ready ? t('typeToSearch') : t('loadingIndex')}
|
|
800
800
|
</p>
|
|
801
801
|
) : loading && results.length === 0 ? (
|
|
802
|
-
<p className="px-3 py-6 text-center text-sm text-muted-foreground"
|
|
802
|
+
<p className="px-3 py-6 text-center text-sm text-muted-foreground">{t('searchEllipsis')}</p>
|
|
803
803
|
) : results.length === 0 ? (
|
|
804
804
|
<p className="px-3 py-6 text-center text-sm text-muted-foreground">
|
|
805
|
-
|
|
805
|
+
{t('noResultsFor')} «{query.trim()}»
|
|
806
806
|
</p>
|
|
807
807
|
) : (
|
|
808
808
|
<ul className="flex flex-col gap-1.5">
|
|
@@ -843,7 +843,7 @@ export function Search({
|
|
|
843
843
|
onMouseDown={() => setShowTree(false)}
|
|
844
844
|
role="dialog"
|
|
845
845
|
aria-modal="true"
|
|
846
|
-
aria-label=
|
|
846
|
+
aria-label={t('folderPicker')}
|
|
847
847
|
>
|
|
848
848
|
<div
|
|
849
849
|
className="fixed inset-0 flex flex-col overflow-hidden bg-popover md:static md:max-h-[70vh] md:w-full md:max-w-md md:rounded-lg md:border md:shadow-xl"
|
|
@@ -852,7 +852,7 @@ export function Search({
|
|
|
852
852
|
<div className="flex items-center justify-between gap-2 border-b px-3 py-2">
|
|
853
853
|
<span className="flex items-center gap-2 text-sm font-medium">
|
|
854
854
|
<FolderTree className="size-4 shrink-0 text-muted-foreground" />
|
|
855
|
-
|
|
855
|
+
{t('folderIn')} «{projectLabel(project)}»
|
|
856
856
|
</span>
|
|
857
857
|
{/* Top-right X cancels, like the backdrop — no scope change. */}
|
|
858
858
|
<button
|
|
@@ -878,7 +878,7 @@ export function Search({
|
|
|
878
878
|
)}
|
|
879
879
|
>
|
|
880
880
|
<Folder className="size-3.5 shrink-0 text-muted-foreground" />
|
|
881
|
-
<span className="truncate">{projectLabel(project)} (
|
|
881
|
+
<span className="truncate">{projectLabel(project)} ({t('wholeProject')})</span>
|
|
882
882
|
</button>
|
|
883
883
|
</li>
|
|
884
884
|
{dirTree.map((node) => (
|
|
@@ -893,7 +893,7 @@ export function Search({
|
|
|
893
893
|
</ul>
|
|
894
894
|
) : (
|
|
895
895
|
<p className="px-3 py-6 text-center text-sm text-muted-foreground">
|
|
896
|
-
|
|
896
|
+
{t('noSubfolders')}
|
|
897
897
|
</p>
|
|
898
898
|
)}
|
|
899
899
|
</div>
|
|
@@ -907,7 +907,7 @@ export function Search({
|
|
|
907
907
|
onClick={() => setShowTree(false)}
|
|
908
908
|
className="flex-1 rounded-md border px-3 py-1.5 text-sm hover:bg-accent hover:text-foreground max-md:py-2 md:flex-none"
|
|
909
909
|
>
|
|
910
|
-
|
|
910
|
+
{t('cancel')}
|
|
911
911
|
</button>
|
|
912
912
|
<button
|
|
913
913
|
type="button"
|
|
@@ -918,7 +918,7 @@ export function Search({
|
|
|
918
918
|
}}
|
|
919
919
|
className="flex-1 rounded-md border border-transparent bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 max-md:py-2 md:flex-none"
|
|
920
920
|
>
|
|
921
|
-
|
|
921
|
+
{t('ok')}
|
|
922
922
|
</button>
|
|
923
923
|
</div>
|
|
924
924
|
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react'
|
|
2
2
|
import { createPortal } from 'react-dom'
|
|
3
3
|
|
|
4
|
-
import { ALL_SHORTCUTS, type ShortcutGroup, type ShortcutInfo } from '~/lib/useKeyboardShortcuts'
|
|
4
|
+
import { ALL_SHORTCUTS, groupLabelKey, type ShortcutGroup, type ShortcutInfo } from '~/lib/useKeyboardShortcuts'
|
|
5
5
|
import { t } from '~/lib/site'
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -15,7 +15,7 @@ import { t } from '~/lib/site'
|
|
|
15
15
|
* on mobile since `?` and the modal simply never get triggered.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
const GROUP_ORDER: ShortcutGroup[] = ['
|
|
18
|
+
const GROUP_ORDER: ShortcutGroup[] = ['nav', 'tree', 'tabs']
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Open the cheatsheet from anywhere (e.g. the TopBar button) by dispatching this
|
|
@@ -91,12 +91,12 @@ export function ShortcutsHelp() {
|
|
|
91
91
|
{GROUP_ORDER.filter((g) => byGroup.has(g)).map((group) => (
|
|
92
92
|
<div key={group} className="mb-4 last:mb-0">
|
|
93
93
|
<div className="mb-1.5 text-xs font-medium tracking-wide text-muted-foreground uppercase">
|
|
94
|
-
{group}
|
|
94
|
+
{t(groupLabelKey(group))}
|
|
95
95
|
</div>
|
|
96
96
|
<ul className="space-y-1">
|
|
97
97
|
{byGroup.get(group)!.map((r, i) => (
|
|
98
98
|
<li key={i} className="flex items-center justify-between gap-4 text-sm">
|
|
99
|
-
<span className="text-foreground">{r.
|
|
99
|
+
<span className="text-foreground">{t(r.labelKey)}</span>
|
|
100
100
|
<kbd className="shrink-0 rounded border bg-muted px-1.5 py-0.5 font-mono text-xs text-muted-foreground">
|
|
101
101
|
{r.hint}
|
|
102
102
|
</kbd>
|
|
@@ -201,9 +201,9 @@ interface SearchHit {
|
|
|
201
201
|
/** What the file-search modal matches against. */
|
|
202
202
|
type SearchScope = 'all' | 'files' | 'directories'
|
|
203
203
|
const SCOPE_OPTIONS: { value: SearchScope; label: string }[] = [
|
|
204
|
-
{ value: 'all', label: '
|
|
205
|
-
{ value: 'files', label: '
|
|
206
|
-
{ value: 'directories', label: '
|
|
204
|
+
{ value: 'all', label: t('scopeAll') },
|
|
205
|
+
{ value: 'files', label: t('scopeFiles') },
|
|
206
|
+
{ value: 'directories', label: t('scopeDirectories') },
|
|
207
207
|
]
|
|
208
208
|
|
|
209
209
|
/**
|
|
@@ -318,7 +318,7 @@ function FileSearchModal({
|
|
|
318
318
|
value={query}
|
|
319
319
|
onChange={(e) => setQuery(e.target.value)}
|
|
320
320
|
onKeyDown={onInputKey}
|
|
321
|
-
placeholder=
|
|
321
|
+
placeholder={t('fileSearchPlaceholder')}
|
|
322
322
|
autoComplete="off"
|
|
323
323
|
className="h-11 flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
|
|
324
324
|
/>
|
|
@@ -345,7 +345,7 @@ function FileSearchModal({
|
|
|
345
345
|
</div>
|
|
346
346
|
<ul ref={listRef} className="min-h-0 flex-1 overflow-y-auto p-1">
|
|
347
347
|
{hits.length === 0 ? (
|
|
348
|
-
<li className="px-3 py-6 text-center text-sm text-muted-foreground"
|
|
348
|
+
<li className="px-3 py-6 text-center text-sm text-muted-foreground">{t('nothingFound')}</li>
|
|
349
349
|
) : (
|
|
350
350
|
hits.map((hit, i) => {
|
|
351
351
|
const isActive = i === active
|
|
@@ -457,7 +457,7 @@ function RowMenu({
|
|
|
457
457
|
<button
|
|
458
458
|
ref={btnRef}
|
|
459
459
|
type="button"
|
|
460
|
-
aria-label=
|
|
460
|
+
aria-label={t('treeActions')}
|
|
461
461
|
aria-haspopup="menu"
|
|
462
462
|
aria-expanded={open}
|
|
463
463
|
// stop dblclick from opening a tab when interacting with the menu button
|
|
@@ -691,8 +691,8 @@ export default function Sidebar({ data, currentPath, open = false, className }:
|
|
|
691
691
|
// field): `l` reveals the current page, `g c` collapses every folder. ---
|
|
692
692
|
const treeShortcuts = useMemo<Shortcut[]>(
|
|
693
693
|
() => [
|
|
694
|
-
{ keys: 'l', label: '
|
|
695
|
-
{ keys: 'c', label: '
|
|
694
|
+
{ keys: 'l', label: 'locate page in tree', group: 'tree', run: locate },
|
|
695
|
+
{ keys: 'c', label: 'collapse all folders', group: 'tree', run: collapseAll },
|
|
696
696
|
],
|
|
697
697
|
[locate, collapseAll],
|
|
698
698
|
)
|
|
@@ -820,7 +820,7 @@ export default function Sidebar({ data, currentPath, open = false, className }:
|
|
|
820
820
|
onPointerDown={startResize}
|
|
821
821
|
role="separator"
|
|
822
822
|
aria-orientation="vertical"
|
|
823
|
-
aria-label=
|
|
823
|
+
aria-label={t('resizePanel')}
|
|
824
824
|
className={cn(
|
|
825
825
|
'absolute right-0 top-0 z-10 hidden h-full w-1.5 cursor-col-resize md:block',
|
|
826
826
|
'hover:bg-sidebar-accent',
|
|
@@ -833,8 +833,8 @@ export default function Sidebar({ data, currentPath, open = false, className }:
|
|
|
833
833
|
variant="ghost"
|
|
834
834
|
size="icon"
|
|
835
835
|
onClick={() => setSearchModalOpen(true)}
|
|
836
|
-
title=
|
|
837
|
-
aria-label=
|
|
836
|
+
title={t('fileSearchHint')}
|
|
837
|
+
aria-label={t('fileSearch')}
|
|
838
838
|
className="size-9 text-muted-foreground md:size-6"
|
|
839
839
|
>
|
|
840
840
|
<Search className="size-4 md:size-3.5" />
|
|
@@ -844,8 +844,8 @@ export default function Sidebar({ data, currentPath, open = false, className }:
|
|
|
844
844
|
variant="ghost"
|
|
845
845
|
size="icon"
|
|
846
846
|
onClick={locate}
|
|
847
|
-
title=
|
|
848
|
-
aria-label=
|
|
847
|
+
title={t('locateInTreeHint')}
|
|
848
|
+
aria-label={t('locateInTree')}
|
|
849
849
|
className="size-9 text-muted-foreground md:size-6"
|
|
850
850
|
>
|
|
851
851
|
<Locate className="size-4 md:size-3.5" />
|
|
@@ -855,8 +855,8 @@ export default function Sidebar({ data, currentPath, open = false, className }:
|
|
|
855
855
|
variant="ghost"
|
|
856
856
|
size="icon"
|
|
857
857
|
onClick={collapseAll}
|
|
858
|
-
title=
|
|
859
|
-
aria-label=
|
|
858
|
+
title={t('collapseAllHint')}
|
|
859
|
+
aria-label={t('collapseAll')}
|
|
860
860
|
className="size-9 text-muted-foreground md:size-6"
|
|
861
861
|
>
|
|
862
862
|
<ChevronsDownUp className="size-4 md:size-3.5" />
|
|
@@ -1001,7 +1001,7 @@ export default function Sidebar({ data, currentPath, open = false, className }:
|
|
|
1001
1001
|
<RowMenu
|
|
1002
1002
|
actions={[
|
|
1003
1003
|
{
|
|
1004
|
-
label: '
|
|
1004
|
+
label: t('openInNewTab'),
|
|
1005
1005
|
onSelect: () => {
|
|
1006
1006
|
item.setFocused()
|
|
1007
1007
|
openFile(itemData.href!, itemData.name, true)
|
|
@@ -1014,7 +1014,7 @@ export default function Sidebar({ data, currentPath, open = false, className }:
|
|
|
1014
1014
|
<RowMenu
|
|
1015
1015
|
actions={[
|
|
1016
1016
|
{
|
|
1017
|
-
label: '
|
|
1017
|
+
label: t('expandRecursively'),
|
|
1018
1018
|
onSelect: () => {
|
|
1019
1019
|
item.setFocused()
|
|
1020
1020
|
// expandAll on the item instance opens this folder and
|
|
@@ -1030,7 +1030,7 @@ export default function Sidebar({ data, currentPath, open = false, className }:
|
|
|
1030
1030
|
)
|
|
1031
1031
|
})}
|
|
1032
1032
|
{isFiltering && matchingIds.size === 0 && (
|
|
1033
|
-
<div className="p-4 text-center text-[0.8125rem] text-muted-foreground"
|
|
1033
|
+
<div className="p-4 text-center text-[0.8125rem] text-muted-foreground">{t('nothingFound')}</div>
|
|
1034
1034
|
)}
|
|
1035
1035
|
</div>
|
|
1036
1036
|
)}
|
|
@@ -5,6 +5,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
|
5
5
|
import { DropdownMenu, DropdownMenuItem } from "~/components/ui/dropdown-menu";
|
|
6
6
|
import { useTabs, normTabPath } from "~/lib/tabs";
|
|
7
7
|
import { useKeyboardShortcuts, type Shortcut } from "~/lib/useKeyboardShortcuts";
|
|
8
|
+
import { t } from "~/lib/site";
|
|
8
9
|
import { cn } from "~/lib/utils";
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -106,8 +107,8 @@ export default function TabBar() {
|
|
|
106
107
|
() => [
|
|
107
108
|
{
|
|
108
109
|
keys: "w",
|
|
109
|
-
label: "
|
|
110
|
-
group: "
|
|
110
|
+
label: "close current tab",
|
|
111
|
+
group: "tabs",
|
|
111
112
|
run: () => {
|
|
112
113
|
if (tabs.some((t) => normTabPath(t.path) === cur)) closePath(cur);
|
|
113
114
|
},
|
|
@@ -165,9 +166,9 @@ export default function TabBar() {
|
|
|
165
166
|
<span className="min-w-0 truncate">{tab.title}</span>
|
|
166
167
|
<button
|
|
167
168
|
type="button"
|
|
168
|
-
aria-label={
|
|
169
|
+
aria-label={`${t('close')} ${tab.title}`}
|
|
169
170
|
// The `w` shortcut closes the *active* tab, so only hint it there.
|
|
170
|
-
title={isActive ?
|
|
171
|
+
title={isActive ? t('closeTabHint') : undefined}
|
|
171
172
|
onClick={(e) => close(e, tab.path)}
|
|
172
173
|
className={cn(
|
|
173
174
|
"flex size-5 shrink-0 items-center justify-center rounded hover:bg-foreground/10",
|
|
@@ -207,7 +208,7 @@ export default function TabBar() {
|
|
|
207
208
|
vertical-dots button opens a dropdown with "close all tabs". */}
|
|
208
209
|
<DropdownMenu
|
|
209
210
|
align="end"
|
|
210
|
-
label=
|
|
211
|
+
label={t('tabActions')}
|
|
211
212
|
className={({ open }) =>
|
|
212
213
|
cn(
|
|
213
214
|
"flex h-full w-9 shrink-0 items-center justify-center border-l text-muted-foreground",
|
|
@@ -219,7 +220,7 @@ export default function TabBar() {
|
|
|
219
220
|
>
|
|
220
221
|
<DropdownMenuItem onSelect={closeAll}>
|
|
221
222
|
<X className="size-4 shrink-0 text-muted-foreground" />
|
|
222
|
-
<span
|
|
223
|
+
<span>{t('closeAllTabs')} ({tabs.length})</span>
|
|
223
224
|
</DropdownMenuItem>
|
|
224
225
|
</DropdownMenu>
|
|
225
226
|
</div>
|
package/app/components/Toc.tsx
CHANGED
|
@@ -66,7 +66,7 @@ export function TocLinks({ shown }: { shown: Heading[] }) {
|
|
|
66
66
|
const activeSlug = useActiveHeading(shown)
|
|
67
67
|
|
|
68
68
|
if (shown.length === 0) {
|
|
69
|
-
return <p className="m-0 text-muted-foreground"
|
|
69
|
+
return <p className="m-0 text-muted-foreground">{t('noToc')}</p>
|
|
70
70
|
}
|
|
71
71
|
return (
|
|
72
72
|
<ul className="m-0 list-none p-0">
|
|
@@ -94,6 +94,77 @@ export const UI_STRINGS: Record<string, Record<string, string>> = {
|
|
|
94
94
|
files: 'Файлы',
|
|
95
95
|
search: 'Поиск',
|
|
96
96
|
toggleTheme: 'Переключить тему',
|
|
97
|
+
// File-search (Ctrl/Cmd+P) in the sidebar
|
|
98
|
+
fileSearchPlaceholder: 'Поиск по файлам...',
|
|
99
|
+
fileSearch: 'Поиск по файлам',
|
|
100
|
+
fileSearchHint: 'Поиск по файлам (Ctrl+P)',
|
|
101
|
+
scopeAll: 'Все',
|
|
102
|
+
scopeFiles: 'Файлы',
|
|
103
|
+
scopeDirectories: 'Папки',
|
|
104
|
+
nothingFound: 'Ничего не найдено',
|
|
105
|
+
// Sidebar tree actions
|
|
106
|
+
treeActions: 'Действия',
|
|
107
|
+
resizePanel: 'Изменить ширину панели',
|
|
108
|
+
locateInTree: 'Найти текущую страницу в дереве',
|
|
109
|
+
locateInTreeHint: 'Найти в дереве (L)',
|
|
110
|
+
collapseAll: 'Свернуть все папки',
|
|
111
|
+
collapseAllHint: 'Свернуть всё (C)',
|
|
112
|
+
openInNewTab: 'Открыть в новой вкладке (Shift+Enter)',
|
|
113
|
+
expandRecursively: 'Развернуть рекурсивно (Shift+Enter)',
|
|
114
|
+
// Content search (Ctrl/Cmd+K)
|
|
115
|
+
searchDocs: 'Поиск по документации',
|
|
116
|
+
searchDocsPlaceholder: 'Поиск по документации…',
|
|
117
|
+
exactMatch: 'Точное совпадение',
|
|
118
|
+
exactMatchHint: 'Точное совпадение фразы',
|
|
119
|
+
clearSearch: 'Очистить поиск',
|
|
120
|
+
clear: 'Очистить',
|
|
121
|
+
searchIn: 'Искать в:',
|
|
122
|
+
allProjects: 'Все проекты',
|
|
123
|
+
wholeProject: 'весь проект',
|
|
124
|
+
folder: 'Папка',
|
|
125
|
+
folderPlaceholder: 'Папка (напр. требования/управление-клиентами)',
|
|
126
|
+
clearFolder: 'Очистить папку',
|
|
127
|
+
pickFolderFromTree: 'Выбрать папку из дерева',
|
|
128
|
+
folderTree: 'Дерево папок',
|
|
129
|
+
folderPicker: 'Выбор папки',
|
|
130
|
+
typeToSearch: 'Введите запрос для поиска',
|
|
131
|
+
loadingIndex: 'Загрузка индекса…',
|
|
132
|
+
noResultsFor: 'Ничего не найдено по запросу',
|
|
133
|
+
cancel: 'Отмена',
|
|
134
|
+
ok: 'ОК',
|
|
135
|
+
searchEllipsis: 'Поиск…',
|
|
136
|
+
folderIn: 'Папка в',
|
|
137
|
+
noSubfolders: 'В этом проекте нет вложенных папок',
|
|
138
|
+
// Keyboard-shortcut cheatsheet: group display names + per-shortcut labels.
|
|
139
|
+
groupNav: 'Навигация',
|
|
140
|
+
groupTree: 'Дерево',
|
|
141
|
+
groupTabs: 'Вкладки',
|
|
142
|
+
scSearchContent: 'Поиск по содержимому',
|
|
143
|
+
scShowShortcuts: 'Показать список горячих клавиш',
|
|
144
|
+
scSearchFile: 'Поиск файла по имени',
|
|
145
|
+
scLocate: 'Найти текущую страницу в дереве',
|
|
146
|
+
scCollapseAll: 'Свернуть все папки',
|
|
147
|
+
scCloseTab: 'Закрыть текущую вкладку',
|
|
148
|
+
scShiftEnter: 'Файл — открыть в новой вкладке; папку — развернуть рекурсивно',
|
|
149
|
+
// Misc chrome (Toc, mobile menus, code-wrap, find-on-page, tabs)
|
|
150
|
+
noToc: 'Нет содержания',
|
|
151
|
+
theme: 'Тема оформления',
|
|
152
|
+
wrapLines: 'Переносить строки',
|
|
153
|
+
noWrapLines: 'Не переносить строки',
|
|
154
|
+
navigation: 'Навигация',
|
|
155
|
+
pageActions: 'Действия страницы',
|
|
156
|
+
scrollTop: 'Наверх',
|
|
157
|
+
findOnPage: 'Найти на странице',
|
|
158
|
+
findOnPagePlaceholder: 'Найти на странице…',
|
|
159
|
+
toc: 'Содержание',
|
|
160
|
+
hide: 'Скрыть',
|
|
161
|
+
find: 'Найти',
|
|
162
|
+
prevMatch: 'Предыдущее совпадение',
|
|
163
|
+
nextMatch: 'Следующее совпадение',
|
|
164
|
+
closeFindOnPage: 'Закрыть поиск по странице',
|
|
165
|
+
closeTabHint: 'Закрыть (W)',
|
|
166
|
+
tabActions: 'Действия с вкладками',
|
|
167
|
+
closeAllTabs: 'Закрыть все вкладки',
|
|
97
168
|
},
|
|
98
169
|
en: {
|
|
99
170
|
projects: 'Projects',
|
|
@@ -111,6 +182,72 @@ export const UI_STRINGS: Record<string, Record<string, string>> = {
|
|
|
111
182
|
files: 'Files',
|
|
112
183
|
search: 'Search',
|
|
113
184
|
toggleTheme: 'Toggle theme',
|
|
185
|
+
fileSearchPlaceholder: 'Search files...',
|
|
186
|
+
fileSearch: 'Search files',
|
|
187
|
+
fileSearchHint: 'Search files (Ctrl+P)',
|
|
188
|
+
scopeAll: 'All',
|
|
189
|
+
scopeFiles: 'Files',
|
|
190
|
+
scopeDirectories: 'Folders',
|
|
191
|
+
nothingFound: 'Nothing found',
|
|
192
|
+
treeActions: 'Actions',
|
|
193
|
+
resizePanel: 'Resize panel',
|
|
194
|
+
locateInTree: 'Locate current page in the tree',
|
|
195
|
+
locateInTreeHint: 'Locate in tree (L)',
|
|
196
|
+
collapseAll: 'Collapse all folders',
|
|
197
|
+
collapseAllHint: 'Collapse all (C)',
|
|
198
|
+
openInNewTab: 'Open in a new tab (Shift+Enter)',
|
|
199
|
+
expandRecursively: 'Expand recursively (Shift+Enter)',
|
|
200
|
+
searchDocs: 'Search the docs',
|
|
201
|
+
searchDocsPlaceholder: 'Search the docs…',
|
|
202
|
+
exactMatch: 'Exact match',
|
|
203
|
+
exactMatchHint: 'Exact phrase match',
|
|
204
|
+
clearSearch: 'Clear search',
|
|
205
|
+
clear: 'Clear',
|
|
206
|
+
searchIn: 'Search in:',
|
|
207
|
+
allProjects: 'All projects',
|
|
208
|
+
wholeProject: 'whole project',
|
|
209
|
+
folder: 'Folder',
|
|
210
|
+
folderPlaceholder: 'Folder (e.g. guides/getting-started)',
|
|
211
|
+
clearFolder: 'Clear folder',
|
|
212
|
+
pickFolderFromTree: 'Pick a folder from the tree',
|
|
213
|
+
folderTree: 'Folder tree',
|
|
214
|
+
folderPicker: 'Folder picker',
|
|
215
|
+
typeToSearch: 'Type to search',
|
|
216
|
+
loadingIndex: 'Loading index…',
|
|
217
|
+
noResultsFor: 'No results for',
|
|
218
|
+
cancel: 'Cancel',
|
|
219
|
+
ok: 'OK',
|
|
220
|
+
searchEllipsis: 'Search…',
|
|
221
|
+
folderIn: 'Folder in',
|
|
222
|
+
noSubfolders: 'This project has no nested folders',
|
|
223
|
+
groupNav: 'Navigation',
|
|
224
|
+
groupTree: 'Tree',
|
|
225
|
+
groupTabs: 'Tabs',
|
|
226
|
+
scSearchContent: 'Search content',
|
|
227
|
+
scShowShortcuts: 'Show keyboard shortcuts',
|
|
228
|
+
scSearchFile: 'Find a file by name',
|
|
229
|
+
scLocate: 'Locate current page in the tree',
|
|
230
|
+
scCollapseAll: 'Collapse all folders',
|
|
231
|
+
scCloseTab: 'Close current tab',
|
|
232
|
+
scShiftEnter: 'File — open in a new tab; folder — expand recursively',
|
|
233
|
+
noToc: 'No table of contents',
|
|
234
|
+
theme: 'Theme',
|
|
235
|
+
wrapLines: 'Wrap lines',
|
|
236
|
+
noWrapLines: 'Don’t wrap lines',
|
|
237
|
+
navigation: 'Navigation',
|
|
238
|
+
pageActions: 'Page actions',
|
|
239
|
+
scrollTop: 'Top',
|
|
240
|
+
findOnPage: 'Find on page',
|
|
241
|
+
findOnPagePlaceholder: 'Find on page…',
|
|
242
|
+
toc: 'Contents',
|
|
243
|
+
hide: 'Hide',
|
|
244
|
+
find: 'Find',
|
|
245
|
+
prevMatch: 'Previous match',
|
|
246
|
+
nextMatch: 'Next match',
|
|
247
|
+
closeFindOnPage: 'Close find on page',
|
|
248
|
+
closeTabHint: 'Close (W)',
|
|
249
|
+
tabActions: 'Tab actions',
|
|
250
|
+
closeAllTabs: 'Close all tabs',
|
|
114
251
|
},
|
|
115
252
|
}
|
|
116
253
|
|
|
@@ -14,13 +14,23 @@ import { useEffect } from 'react'
|
|
|
14
14
|
* of "g then c").
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Stable, language-neutral group keys. The display name is resolved via `t()`
|
|
19
|
+
* from the `group<Key>` UI strings (see defaults.ts) at render time, so the
|
|
20
|
+
* cheatsheet localizes with the rest of the chrome.
|
|
21
|
+
*/
|
|
22
|
+
export type ShortcutGroup = 'tree' | 'tabs' | 'nav'
|
|
23
|
+
|
|
24
|
+
/** Map a group key to its UI-string key, for `t(groupLabelKey(group))`. */
|
|
25
|
+
export function groupLabelKey(group: ShortcutGroup): string {
|
|
26
|
+
return group === 'tree' ? 'groupTree' : group === 'tabs' ? 'groupTabs' : 'groupNav'
|
|
27
|
+
}
|
|
18
28
|
|
|
19
29
|
/** A binding the caller wires to behavior, plus the metadata the `?` overlay shows. */
|
|
20
30
|
export type Shortcut = {
|
|
21
31
|
/** Single key (e.g. 'l') OR a two-key sequence (e.g. ['g', 'c']). Compared case-insensitively. */
|
|
22
32
|
keys: string | [string, string]
|
|
23
|
-
/** What it does
|
|
33
|
+
/** What it does. Live bindings aren't shown in the cheatsheet; this is dev-facing. */
|
|
24
34
|
label: string
|
|
25
35
|
/** Cheatsheet section. */
|
|
26
36
|
group: ShortcutGroup
|
|
@@ -31,12 +41,14 @@ export type Shortcut = {
|
|
|
31
41
|
/**
|
|
32
42
|
* A display-only entry for shortcuts owned elsewhere (the existing Cmd+K / Cmd+P
|
|
33
43
|
* chords, the Shift+Enter row action) so the `?` overlay can list everything in
|
|
34
|
-
* one place without those handlers being re-registered here.
|
|
44
|
+
* one place without those handlers being re-registered here. `labelKey` is a UI
|
|
45
|
+
* string key resolved via `t()` at render.
|
|
35
46
|
*/
|
|
36
47
|
export type ShortcutInfo = {
|
|
37
48
|
/** Human-readable key hint, e.g. '⌘K', 'Shift+Enter'. */
|
|
38
49
|
hint: string
|
|
39
|
-
|
|
50
|
+
/** UI-string key for the description (resolved via `t()` in the overlay). */
|
|
51
|
+
labelKey: string
|
|
40
52
|
group: ShortcutGroup
|
|
41
53
|
}
|
|
42
54
|
|
|
@@ -48,13 +60,13 @@ export type ShortcutInfo = {
|
|
|
48
60
|
* Keep in sync when adding a binding.
|
|
49
61
|
*/
|
|
50
62
|
export const ALL_SHORTCUTS: ShortcutInfo[] = [
|
|
51
|
-
{ hint: '⌘/Ctrl K',
|
|
52
|
-
{ hint: '?',
|
|
53
|
-
{ hint: '⌘/Ctrl P',
|
|
54
|
-
{ hint: 'l',
|
|
55
|
-
{ hint: 'c',
|
|
56
|
-
{ hint: 'w',
|
|
57
|
-
{ hint: 'Shift+Enter',
|
|
63
|
+
{ hint: '⌘/Ctrl K', labelKey: 'scSearchContent', group: 'nav' },
|
|
64
|
+
{ hint: '?', labelKey: 'scShowShortcuts', group: 'nav' },
|
|
65
|
+
{ hint: '⌘/Ctrl P', labelKey: 'scSearchFile', group: 'tree' },
|
|
66
|
+
{ hint: 'l', labelKey: 'scLocate', group: 'tree' },
|
|
67
|
+
{ hint: 'c', labelKey: 'scCollapseAll', group: 'tree' },
|
|
68
|
+
{ hint: 'w', labelKey: 'scCloseTab', group: 'tabs' },
|
|
69
|
+
{ hint: 'Shift+Enter', labelKey: 'scShiftEnter', group: 'tree' },
|
|
58
70
|
]
|
|
59
71
|
|
|
60
72
|
/** True when focus is in an editable surface — single-key shortcuts must not fire there. */
|
|
@@ -1387,7 +1387,78 @@ var UI_STRINGS = {
|
|
|
1387
1387
|
home: "\u0413\u043B\u0430\u0432\u043D\u0430\u044F",
|
|
1388
1388
|
files: "\u0424\u0430\u0439\u043B\u044B",
|
|
1389
1389
|
search: "\u041F\u043E\u0438\u0441\u043A",
|
|
1390
|
-
toggleTheme: "\u041F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0442\u0435\u043C\u0443"
|
|
1390
|
+
toggleTheme: "\u041F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0442\u0435\u043C\u0443",
|
|
1391
|
+
// File-search (Ctrl/Cmd+P) in the sidebar
|
|
1392
|
+
fileSearchPlaceholder: "\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u0444\u0430\u0439\u043B\u0430\u043C...",
|
|
1393
|
+
fileSearch: "\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u0444\u0430\u0439\u043B\u0430\u043C",
|
|
1394
|
+
fileSearchHint: "\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u0444\u0430\u0439\u043B\u0430\u043C (Ctrl+P)",
|
|
1395
|
+
scopeAll: "\u0412\u0441\u0435",
|
|
1396
|
+
scopeFiles: "\u0424\u0430\u0439\u043B\u044B",
|
|
1397
|
+
scopeDirectories: "\u041F\u0430\u043F\u043A\u0438",
|
|
1398
|
+
nothingFound: "\u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E",
|
|
1399
|
+
// Sidebar tree actions
|
|
1400
|
+
treeActions: "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044F",
|
|
1401
|
+
resizePanel: "\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u0448\u0438\u0440\u0438\u043D\u0443 \u043F\u0430\u043D\u0435\u043B\u0438",
|
|
1402
|
+
locateInTree: "\u041D\u0430\u0439\u0442\u0438 \u0442\u0435\u043A\u0443\u0449\u0443\u044E \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0443 \u0432 \u0434\u0435\u0440\u0435\u0432\u0435",
|
|
1403
|
+
locateInTreeHint: "\u041D\u0430\u0439\u0442\u0438 \u0432 \u0434\u0435\u0440\u0435\u0432\u0435 (L)",
|
|
1404
|
+
collapseAll: "\u0421\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0432\u0441\u0435 \u043F\u0430\u043F\u043A\u0438",
|
|
1405
|
+
collapseAllHint: "\u0421\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0432\u0441\u0451 (C)",
|
|
1406
|
+
openInNewTab: "\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0432 \u043D\u043E\u0432\u043E\u0439 \u0432\u043A\u043B\u0430\u0434\u043A\u0435 (Shift+Enter)",
|
|
1407
|
+
expandRecursively: "\u0420\u0430\u0437\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0440\u0435\u043A\u0443\u0440\u0441\u0438\u0432\u043D\u043E (Shift+Enter)",
|
|
1408
|
+
// Content search (Ctrl/Cmd+K)
|
|
1409
|
+
searchDocs: "\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430\u0446\u0438\u0438",
|
|
1410
|
+
searchDocsPlaceholder: "\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430\u0446\u0438\u0438\u2026",
|
|
1411
|
+
exactMatch: "\u0422\u043E\u0447\u043D\u043E\u0435 \u0441\u043E\u0432\u043F\u0430\u0434\u0435\u043D\u0438\u0435",
|
|
1412
|
+
exactMatchHint: "\u0422\u043E\u0447\u043D\u043E\u0435 \u0441\u043E\u0432\u043F\u0430\u0434\u0435\u043D\u0438\u0435 \u0444\u0440\u0430\u0437\u044B",
|
|
1413
|
+
clearSearch: "\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u043F\u043E\u0438\u0441\u043A",
|
|
1414
|
+
clear: "\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C",
|
|
1415
|
+
searchIn: "\u0418\u0441\u043A\u0430\u0442\u044C \u0432:",
|
|
1416
|
+
allProjects: "\u0412\u0441\u0435 \u043F\u0440\u043E\u0435\u043A\u0442\u044B",
|
|
1417
|
+
wholeProject: "\u0432\u0435\u0441\u044C \u043F\u0440\u043E\u0435\u043A\u0442",
|
|
1418
|
+
folder: "\u041F\u0430\u043F\u043A\u0430",
|
|
1419
|
+
folderPlaceholder: "\u041F\u0430\u043F\u043A\u0430 (\u043D\u0430\u043F\u0440. \u0442\u0440\u0435\u0431\u043E\u0432\u0430\u043D\u0438\u044F/\u0443\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435-\u043A\u043B\u0438\u0435\u043D\u0442\u0430\u043C\u0438)",
|
|
1420
|
+
clearFolder: "\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u043F\u0430\u043F\u043A\u0443",
|
|
1421
|
+
pickFolderFromTree: "\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u043F\u0430\u043F\u043A\u0443 \u0438\u0437 \u0434\u0435\u0440\u0435\u0432\u0430",
|
|
1422
|
+
folderTree: "\u0414\u0435\u0440\u0435\u0432\u043E \u043F\u0430\u043F\u043E\u043A",
|
|
1423
|
+
folderPicker: "\u0412\u044B\u0431\u043E\u0440 \u043F\u0430\u043F\u043A\u0438",
|
|
1424
|
+
typeToSearch: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0437\u0430\u043F\u0440\u043E\u0441 \u0434\u043B\u044F \u043F\u043E\u0438\u0441\u043A\u0430",
|
|
1425
|
+
loadingIndex: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u0438\u043D\u0434\u0435\u043A\u0441\u0430\u2026",
|
|
1426
|
+
noResultsFor: "\u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E \u043F\u043E \u0437\u0430\u043F\u0440\u043E\u0441\u0443",
|
|
1427
|
+
cancel: "\u041E\u0442\u043C\u0435\u043D\u0430",
|
|
1428
|
+
ok: "\u041E\u041A",
|
|
1429
|
+
searchEllipsis: "\u041F\u043E\u0438\u0441\u043A\u2026",
|
|
1430
|
+
folderIn: "\u041F\u0430\u043F\u043A\u0430 \u0432",
|
|
1431
|
+
noSubfolders: "\u0412 \u044D\u0442\u043E\u043C \u043F\u0440\u043E\u0435\u043A\u0442\u0435 \u043D\u0435\u0442 \u0432\u043B\u043E\u0436\u0435\u043D\u043D\u044B\u0445 \u043F\u0430\u043F\u043E\u043A",
|
|
1432
|
+
// Keyboard-shortcut cheatsheet: group display names + per-shortcut labels.
|
|
1433
|
+
groupNav: "\u041D\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044F",
|
|
1434
|
+
groupTree: "\u0414\u0435\u0440\u0435\u0432\u043E",
|
|
1435
|
+
groupTabs: "\u0412\u043A\u043B\u0430\u0434\u043A\u0438",
|
|
1436
|
+
scSearchContent: "\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u043C\u043E\u043C\u0443",
|
|
1437
|
+
scShowShortcuts: "\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u0441\u043F\u0438\u0441\u043E\u043A \u0433\u043E\u0440\u044F\u0447\u0438\u0445 \u043A\u043B\u0430\u0432\u0438\u0448",
|
|
1438
|
+
scSearchFile: "\u041F\u043E\u0438\u0441\u043A \u0444\u0430\u0439\u043B\u0430 \u043F\u043E \u0438\u043C\u0435\u043D\u0438",
|
|
1439
|
+
scLocate: "\u041D\u0430\u0439\u0442\u0438 \u0442\u0435\u043A\u0443\u0449\u0443\u044E \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0443 \u0432 \u0434\u0435\u0440\u0435\u0432\u0435",
|
|
1440
|
+
scCollapseAll: "\u0421\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0432\u0441\u0435 \u043F\u0430\u043F\u043A\u0438",
|
|
1441
|
+
scCloseTab: "\u0417\u0430\u043A\u0440\u044B\u0442\u044C \u0442\u0435\u043A\u0443\u0449\u0443\u044E \u0432\u043A\u043B\u0430\u0434\u043A\u0443",
|
|
1442
|
+
scShiftEnter: "\u0424\u0430\u0439\u043B \u2014 \u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0432 \u043D\u043E\u0432\u043E\u0439 \u0432\u043A\u043B\u0430\u0434\u043A\u0435; \u043F\u0430\u043F\u043A\u0443 \u2014 \u0440\u0430\u0437\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0440\u0435\u043A\u0443\u0440\u0441\u0438\u0432\u043D\u043E",
|
|
1443
|
+
// Misc chrome (Toc, mobile menus, code-wrap, find-on-page, tabs)
|
|
1444
|
+
noToc: "\u041D\u0435\u0442 \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u044F",
|
|
1445
|
+
theme: "\u0422\u0435\u043C\u0430 \u043E\u0444\u043E\u0440\u043C\u043B\u0435\u043D\u0438\u044F",
|
|
1446
|
+
wrapLines: "\u041F\u0435\u0440\u0435\u043D\u043E\u0441\u0438\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u0438",
|
|
1447
|
+
noWrapLines: "\u041D\u0435 \u043F\u0435\u0440\u0435\u043D\u043E\u0441\u0438\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u0438",
|
|
1448
|
+
navigation: "\u041D\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044F",
|
|
1449
|
+
pageActions: "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B",
|
|
1450
|
+
scrollTop: "\u041D\u0430\u0432\u0435\u0440\u0445",
|
|
1451
|
+
findOnPage: "\u041D\u0430\u0439\u0442\u0438 \u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435",
|
|
1452
|
+
findOnPagePlaceholder: "\u041D\u0430\u0439\u0442\u0438 \u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435\u2026",
|
|
1453
|
+
toc: "\u0421\u043E\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u0435",
|
|
1454
|
+
hide: "\u0421\u043A\u0440\u044B\u0442\u044C",
|
|
1455
|
+
find: "\u041D\u0430\u0439\u0442\u0438",
|
|
1456
|
+
prevMatch: "\u041F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0435\u0435 \u0441\u043E\u0432\u043F\u0430\u0434\u0435\u043D\u0438\u0435",
|
|
1457
|
+
nextMatch: "\u0421\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0435 \u0441\u043E\u0432\u043F\u0430\u0434\u0435\u043D\u0438\u0435",
|
|
1458
|
+
closeFindOnPage: "\u0417\u0430\u043A\u0440\u044B\u0442\u044C \u043F\u043E\u0438\u0441\u043A \u043F\u043E \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435",
|
|
1459
|
+
closeTabHint: "\u0417\u0430\u043A\u0440\u044B\u0442\u044C (W)",
|
|
1460
|
+
tabActions: "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u0441 \u0432\u043A\u043B\u0430\u0434\u043A\u0430\u043C\u0438",
|
|
1461
|
+
closeAllTabs: "\u0417\u0430\u043A\u0440\u044B\u0442\u044C \u0432\u0441\u0435 \u0432\u043A\u043B\u0430\u0434\u043A\u0438"
|
|
1391
1462
|
},
|
|
1392
1463
|
en: {
|
|
1393
1464
|
projects: "Projects",
|
|
@@ -1404,7 +1475,73 @@ var UI_STRINGS = {
|
|
|
1404
1475
|
home: "Home",
|
|
1405
1476
|
files: "Files",
|
|
1406
1477
|
search: "Search",
|
|
1407
|
-
toggleTheme: "Toggle theme"
|
|
1478
|
+
toggleTheme: "Toggle theme",
|
|
1479
|
+
fileSearchPlaceholder: "Search files...",
|
|
1480
|
+
fileSearch: "Search files",
|
|
1481
|
+
fileSearchHint: "Search files (Ctrl+P)",
|
|
1482
|
+
scopeAll: "All",
|
|
1483
|
+
scopeFiles: "Files",
|
|
1484
|
+
scopeDirectories: "Folders",
|
|
1485
|
+
nothingFound: "Nothing found",
|
|
1486
|
+
treeActions: "Actions",
|
|
1487
|
+
resizePanel: "Resize panel",
|
|
1488
|
+
locateInTree: "Locate current page in the tree",
|
|
1489
|
+
locateInTreeHint: "Locate in tree (L)",
|
|
1490
|
+
collapseAll: "Collapse all folders",
|
|
1491
|
+
collapseAllHint: "Collapse all (C)",
|
|
1492
|
+
openInNewTab: "Open in a new tab (Shift+Enter)",
|
|
1493
|
+
expandRecursively: "Expand recursively (Shift+Enter)",
|
|
1494
|
+
searchDocs: "Search the docs",
|
|
1495
|
+
searchDocsPlaceholder: "Search the docs\u2026",
|
|
1496
|
+
exactMatch: "Exact match",
|
|
1497
|
+
exactMatchHint: "Exact phrase match",
|
|
1498
|
+
clearSearch: "Clear search",
|
|
1499
|
+
clear: "Clear",
|
|
1500
|
+
searchIn: "Search in:",
|
|
1501
|
+
allProjects: "All projects",
|
|
1502
|
+
wholeProject: "whole project",
|
|
1503
|
+
folder: "Folder",
|
|
1504
|
+
folderPlaceholder: "Folder (e.g. guides/getting-started)",
|
|
1505
|
+
clearFolder: "Clear folder",
|
|
1506
|
+
pickFolderFromTree: "Pick a folder from the tree",
|
|
1507
|
+
folderTree: "Folder tree",
|
|
1508
|
+
folderPicker: "Folder picker",
|
|
1509
|
+
typeToSearch: "Type to search",
|
|
1510
|
+
loadingIndex: "Loading index\u2026",
|
|
1511
|
+
noResultsFor: "No results for",
|
|
1512
|
+
cancel: "Cancel",
|
|
1513
|
+
ok: "OK",
|
|
1514
|
+
searchEllipsis: "Search\u2026",
|
|
1515
|
+
folderIn: "Folder in",
|
|
1516
|
+
noSubfolders: "This project has no nested folders",
|
|
1517
|
+
groupNav: "Navigation",
|
|
1518
|
+
groupTree: "Tree",
|
|
1519
|
+
groupTabs: "Tabs",
|
|
1520
|
+
scSearchContent: "Search content",
|
|
1521
|
+
scShowShortcuts: "Show keyboard shortcuts",
|
|
1522
|
+
scSearchFile: "Find a file by name",
|
|
1523
|
+
scLocate: "Locate current page in the tree",
|
|
1524
|
+
scCollapseAll: "Collapse all folders",
|
|
1525
|
+
scCloseTab: "Close current tab",
|
|
1526
|
+
scShiftEnter: "File \u2014 open in a new tab; folder \u2014 expand recursively",
|
|
1527
|
+
noToc: "No table of contents",
|
|
1528
|
+
theme: "Theme",
|
|
1529
|
+
wrapLines: "Wrap lines",
|
|
1530
|
+
noWrapLines: "Don\u2019t wrap lines",
|
|
1531
|
+
navigation: "Navigation",
|
|
1532
|
+
pageActions: "Page actions",
|
|
1533
|
+
scrollTop: "Top",
|
|
1534
|
+
findOnPage: "Find on page",
|
|
1535
|
+
findOnPagePlaceholder: "Find on page\u2026",
|
|
1536
|
+
toc: "Contents",
|
|
1537
|
+
hide: "Hide",
|
|
1538
|
+
find: "Find",
|
|
1539
|
+
prevMatch: "Previous match",
|
|
1540
|
+
nextMatch: "Next match",
|
|
1541
|
+
closeFindOnPage: "Close find on page",
|
|
1542
|
+
closeTabHint: "Close (W)",
|
|
1543
|
+
tabActions: "Tab actions",
|
|
1544
|
+
closeAllTabs: "Close all tabs"
|
|
1408
1545
|
}
|
|
1409
1546
|
};
|
|
1410
1547
|
function defaultUiFor(lang) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cantip",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.1",
|
|
5
5
|
"description": "Config-driven Remix documentation site engine — ingest Obsidian/markdown vaults, build an SSR docs site. \"How (to)\" in Kyrgyz.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"documentation",
|