Skip to content

Commit df4792e

Browse files
Syntax reference guide (#169)
1 parent 1bdb65c commit df4792e

File tree

10 files changed

+306
-23
lines changed

10 files changed

+306
-23
lines changed

packages/web/src/app/[domain]/components/fireHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const FileHeader = ({
3131
{info?.icon ? (
3232
<Image
3333
src={info.icon}
34-
alt={info.costHostName}
34+
alt={info.codeHostName}
3535
className={`w-4 h-4 ${info.iconClassName}`}
3636
/>
3737
): (
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React from 'react'
2+
3+
interface KeyboardShortcutHintProps {
4+
shortcut: string
5+
label?: string
6+
}
7+
8+
export function KeyboardShortcutHint({ shortcut, label }: KeyboardShortcutHintProps) {
9+
return (
10+
<div className="inline-flex items-center" aria-label={label || `Keyboard shortcut: ${shortcut}`}>
11+
<kbd className="px-2 py-1 text-xs font-semibold border rounded-md">
12+
{shortcut}
13+
</kbd>
14+
</div>
15+
)
16+
}

packages/web/src/app/[domain]/components/repositoryCarousel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ const RepositoryBadge = ({
6363
return {
6464
repoIcon: <Image
6565
src={info.icon}
66-
alt={info.costHostName}
66+
alt={info.codeHostName}
6767
className={`w-4 h-4 ${info.iconClassName}`}
6868
/>,
6969
displayName: info.displayName,

packages/web/src/app/[domain]/components/searchBar/searchBar.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import { Separator } from "@/components/ui/separator";
4343
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
4444
import { Toggle } from "@/components/ui/toggle";
4545
import { useDomain } from "@/hooks/useDomain";
46+
import { KeyboardShortcutHint } from "../keyboardShortcutHint";
4647

4748
interface SearchBarProps {
4849
className?: string;
@@ -72,7 +73,7 @@ const searchBarKeymap: readonly KeyBinding[] = ([
7273
] as KeyBinding[]).concat(historyKeymap);
7374

7475
const searchBarContainerVariants = cva(
75-
"search-bar-container flex items-center py-0.5 px-1 border rounded-md relative",
76+
"search-bar-container flex items-center justify-center py-0.5 px-2 border rounded-md relative",
7677
{
7778
variants: {
7879
size: {
@@ -266,6 +267,7 @@ export const SearchBar = ({
266267
indentWithTab={false}
267268
autoFocus={autoFocus ?? false}
268269
/>
270+
<KeyboardShortcutHint shortcut="/" />
269271
<SearchSuggestionsBox
270272
ref={suggestionBoxRef}
271273
query={query}

packages/web/src/app/[domain]/components/searchBar/searchSuggestionsBox.tsx

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { IconType } from "react-icons/lib";
1616
import { VscFile, VscFilter, VscRepo, VscSymbolMisc } from "react-icons/vsc";
1717
import { Skeleton } from "@/components/ui/skeleton";
1818
import { Separator } from "@/components/ui/separator";
19+
import { KeyboardShortcutHint } from "../keyboardShortcutHint";
1920

2021
export type Suggestion = {
2122
value: string;
@@ -337,7 +338,7 @@ const SearchSuggestionsBox = forwardRef(({
337338
onFocus={onFocus}
338339
onBlur={onBlur}
339340
>
340-
<p className="text-muted-foreground text-sm mb-1">
341+
<p className="text-muted-foreground text-sm mb-2">
341342
{suggestionModeText}
342343
</p>
343344
{isLoadingSuggestions ? (
@@ -385,19 +386,29 @@ const SearchSuggestionsBox = forwardRef(({
385386
)}
386387
</div>
387388
))}
388-
{isFocused && (
389-
<>
390-
<Separator
391-
orientation="horizontal"
392-
className="my-2"
393-
/>
394-
<div className="flex flex-row items-center justify-end mt-1">
395-
<span className="text-muted-foreground text-xs">
396-
Press <kbd className="font-mono text-xs font-bold">Enter</kbd> to select
397-
</span>
389+
<Separator
390+
orientation="horizontal"
391+
className="my-2"
392+
/>
393+
<div className="flex flex-row items-center justify-between mt-1">
394+
<div className="flex flex-row gap-1.5 items-center">
395+
<p className="text-muted-foreground text-sm">
396+
Syntax help:
397+
</p>
398+
<div className="flex flex-row gap-0.5 items-center">
399+
<KeyboardShortcutHint shortcut="⌘" />
400+
<KeyboardShortcutHint shortcut="/" />
398401
</div>
399-
</>
400-
)}
402+
</div>
403+
{isFocused && (
404+
<span className="flex flex-row gap-1.5 items-center">
405+
<KeyboardShortcutHint shortcut="↵" />
406+
<span className="text-muted-foreground text-sm font-medium">
407+
to select
408+
</span>
409+
</span>
410+
)}
411+
</div>
401412
</div>
402413
)
403414
});
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
'use client';
2+
3+
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
4+
import { Separator } from "@/components/ui/separator";
5+
import {
6+
Table,
7+
TableBody,
8+
TableCell,
9+
TableHead,
10+
TableHeader,
11+
TableRow,
12+
} from "@/components/ui/table";
13+
import clsx from "clsx";
14+
import Link from "next/link";
15+
import { useCallback, useRef, useState } from "react";
16+
import { useHotkeys } from "react-hotkeys-hook";
17+
18+
const LINGUIST_LINK = "https://github.com/github-linguist/linguist/blob/main/lib/linguist/languages.yml";
19+
const CTAGS_LINK = "https://ctags.io/";
20+
21+
export const SyntaxReferenceGuide = () => {
22+
const [isOpen, setIsOpen] = useState(false);
23+
const previousFocusedElement = useRef<HTMLElement | null>(null);
24+
25+
const openDialog = useCallback(() => {
26+
previousFocusedElement.current = document.activeElement as HTMLElement;
27+
setIsOpen(true);
28+
}, []);
29+
30+
const closeDialog = useCallback(() => {
31+
setIsOpen(false);
32+
33+
// @note: Without requestAnimationFrame, focus was not being returned
34+
// to codemirror elements for some reason.
35+
requestAnimationFrame(() => {
36+
previousFocusedElement.current?.focus();
37+
});
38+
}, []);
39+
40+
const handleOpenChange = useCallback((isOpen: boolean) => {
41+
if (isOpen) {
42+
openDialog();
43+
} else {
44+
closeDialog();
45+
}
46+
}, [closeDialog, openDialog]);
47+
48+
useHotkeys("mod+/", (event) => {
49+
event.preventDefault();
50+
handleOpenChange(!isOpen);
51+
}, {
52+
enableOnFormTags: true,
53+
enableOnContentEditable: true,
54+
description: "Open Syntax Reference Guide",
55+
});
56+
57+
return (
58+
<Dialog
59+
open={isOpen}
60+
onOpenChange={handleOpenChange}
61+
>
62+
<DialogContent
63+
className="max-h-[80vh] max-w-[700px] overflow-scroll"
64+
>
65+
<DialogHeader>
66+
<DialogTitle>Syntax Reference Guide</DialogTitle>
67+
<DialogDescription className="text-sm text-foreground">
68+
Queries consist of space-seperated regular expressions. Wrapping expressions in <Code>{`""`}</Code> combines them. By default, a file must have at least one match for each expression to be included.
69+
</DialogDescription>
70+
</DialogHeader>
71+
<Table>
72+
<TableHeader>
73+
<TableRow>
74+
<TableHead className="py-2">Example</TableHead>
75+
<TableHead className="py-2">Explanation</TableHead>
76+
</TableRow>
77+
</TableHeader>
78+
<TableBody>
79+
<TableRow>
80+
<TableCell className="py-2"><Code>foo</Code></TableCell>
81+
<TableCell className="py-2">Match files with regex <Code>/foo/</Code></TableCell>
82+
</TableRow>
83+
<TableRow>
84+
<TableCell className="py-2"><Code>foo bar</Code></TableCell>
85+
<TableCell className="py-2">Match files with regex <Code>/foo/</Code> <b>and</b> <Code>/bar/</Code></TableCell>
86+
</TableRow>
87+
<TableRow>
88+
<TableCell className="py-2"><Code>{`"foo bar"`}</Code></TableCell>
89+
<TableCell className="py-2">Match files with regex <Code>/foo bar/</Code></TableCell>
90+
</TableRow>
91+
</TableBody>
92+
</Table>
93+
94+
<Separator className="my-2"/>
95+
<p className="text-sm">
96+
{`Multiple expressions can be or'd together with `}<Code>or</Code>, negated with <Code>-</Code>, or grouped with <Code>()</Code>.
97+
</p>
98+
<Table>
99+
<TableHeader>
100+
<TableRow>
101+
<TableHead className="py-2">Example</TableHead>
102+
<TableHead className="py-2">Explanation</TableHead>
103+
</TableRow>
104+
</TableHeader>
105+
<TableBody>
106+
<TableRow>
107+
<TableCell className="py-2"><Code>foo <Highlight>or</Highlight> bar</Code></TableCell>
108+
<TableCell className="py-2">Match files with regex <Code>/foo/</Code> <b>or</b> <Code>/bar/</Code></TableCell>
109+
</TableRow>
110+
<TableRow>
111+
<TableCell className="py-2"><Code>foo -bar</Code></TableCell>
112+
<TableCell className="py-2">Match files with regex <Code>/foo/</Code> but <b>not</b> <Code>/bar/</Code></TableCell>
113+
</TableRow>
114+
<TableRow>
115+
<TableCell className="py-2"><Code>foo (bar <Highlight>or</Highlight> baz)</Code></TableCell>
116+
<TableCell className="py-2">Match files with regex <Code>/foo/</Code> <b>and</b> either <Code>/bar/</Code> <b>or</b> <Code>/baz/</Code></TableCell>
117+
</TableRow>
118+
</TableBody>
119+
</Table>
120+
121+
<Separator className="my-2"/>
122+
<p className="text-sm">
123+
Expressions can be prefixed with certain keywords to modify search behavior. Some keywords can be negated using the <Code>-</Code> prefix.
124+
</p>
125+
126+
<Table>
127+
<TableHeader>
128+
<TableRow>
129+
<TableHead className="py-2">Prefix</TableHead>
130+
<TableHead className="py-2">Description</TableHead>
131+
<TableHead className="py-2 w-[175px]">Example</TableHead>
132+
</TableRow>
133+
</TableHeader>
134+
<TableBody>
135+
<TableRow>
136+
<TableCell className="py-2"><Code><Highlight>file:</Highlight></Code></TableCell>
137+
<TableCell className="py-2">Filter results from filepaths that match the regex. By default all files are searched.</TableCell>
138+
<TableCell className="py-2">
139+
<div className="flex flex-col gap-1">
140+
<Code
141+
title="Filter results to filepaths that match regex /README/"
142+
>
143+
<Highlight>file:</Highlight>README
144+
</Code>
145+
<Code
146+
title="Filter results to filepaths that match regex /my file/"
147+
>
148+
<Highlight>file:</Highlight>{`"my file"`}
149+
</Code>
150+
<Code
151+
title="Ignore results from filepaths match regex /test\.ts$/"
152+
>
153+
<Highlight>-file:</Highlight>test\.ts$
154+
</Code>
155+
</div>
156+
</TableCell>
157+
</TableRow>
158+
<TableRow>
159+
<TableCell className="py-2"><Code><Highlight>repo:</Highlight></Code></TableCell>
160+
<TableCell className="py-2">Filter results from repos that match the regex. By default all repos are searched.</TableCell>
161+
<TableCell className="py-2">
162+
<div className="flex flex-col gap-1">
163+
<Code
164+
title="Filter results to repos that match regex /linux/"
165+
>
166+
<Highlight>repo:</Highlight>linux
167+
</Code>
168+
<Code
169+
title="Ignore results from repos that match regex /^web\/.*/"
170+
>
171+
<Highlight>-repo:</Highlight>^web/.*
172+
</Code>
173+
</div>
174+
</TableCell>
175+
</TableRow>
176+
<TableRow>
177+
<TableCell className="py-2"><Code><Highlight>rev:</Highlight></Code></TableCell>
178+
<TableCell className="py-2">Filter results from a specific branch or tag. By default <b>only</b> the default branch is searched.</TableCell>
179+
<TableCell className="py-2">
180+
<div className="flex flex-col gap-1">
181+
<Code
182+
title="Filter results to branches that match regex /beta/"
183+
>
184+
<Highlight>rev:</Highlight>beta
185+
</Code>
186+
</div>
187+
</TableCell>
188+
</TableRow>
189+
<TableRow>
190+
<TableCell className="py-2"><Code><Highlight>lang:</Highlight></Code></TableCell>
191+
<TableCell className="py-2">Filter results by language (as defined by <Link className="text-blue-500" href={LINGUIST_LINK}>linguist</Link>). By default all languages are searched.</TableCell>
192+
<TableCell className="py-2">
193+
<div className="flex flex-col gap-1">
194+
<Code
195+
title="Filter results to TypeScript files"
196+
>
197+
<Highlight>lang:</Highlight>TypeScript
198+
</Code>
199+
<Code
200+
title="Ignore results from YAML files"
201+
>
202+
<Highlight>-lang:</Highlight>YAML
203+
</Code>
204+
</div>
205+
</TableCell>
206+
</TableRow>
207+
<TableRow>
208+
<TableCell className="py-2"><Code><Highlight>sym:</Highlight></Code></TableCell>
209+
<TableCell className="py-2">Match symbol definitions created by <Link className="text-blue-500" href={CTAGS_LINK}>universal ctags</Link> at index time.</TableCell>
210+
<TableCell className="py-2">
211+
<div className="flex flex-col gap-1">
212+
<Code
213+
title="Filter results to symbols that match regex /\bmain\b/"
214+
>
215+
<Highlight>sym:</Highlight>\bmain\b
216+
</Code>
217+
</div>
218+
</TableCell>
219+
</TableRow>
220+
</TableBody>
221+
</Table>
222+
</DialogContent>
223+
</Dialog>
224+
)
225+
}
226+
227+
const Code = ({ children, className, title }: { children: React.ReactNode, className?: string, title?: string }) => {
228+
return (
229+
<code
230+
className={clsx("bg-gray-100 dark:bg-gray-700 w-fit rounded-md font-mono px-2 py-0.5", className)}
231+
title={title}
232+
>
233+
{children}
234+
</code>
235+
)
236+
}
237+
238+
const Highlight = ({ children }: { children: React.ReactNode }) => {
239+
return (
240+
<span className="text-highlight">
241+
{children}
242+
</span>
243+
)
244+
}

packages/web/src/app/[domain]/layout.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { cookies, headers } from "next/headers";
1010
import { getSelectorsByUserAgent } from "react-device-detect";
1111
import { MobileUnsupportedSplashScreen } from "./components/mobileUnsupportedSplashScreen";
1212
import { MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME } from "@/lib/constants";
13+
import { SyntaxReferenceGuide } from "./components/syntaxReferenceGuide";
1314

1415
interface LayoutProps {
1516
children: React.ReactNode,
@@ -79,5 +80,10 @@ export default async function Layout({
7980
<MobileUnsupportedSplashScreen />
8081
)
8182
}
82-
return children;
83+
return (
84+
<>
85+
{children}
86+
<SyntaxReferenceGuide />
87+
</>
88+
)
8389
}

0 commit comments

Comments
 (0)