2023-03-22 07:31:38 +01:00
|
|
|
import React, {useState, useEffect, useMemo} from 'react'
|
2023-03-21 07:19:45 +01:00
|
|
|
import {
|
2023-03-22 07:31:38 +01:00
|
|
|
KBarProvider,
|
|
|
|
KBarPortal,
|
|
|
|
KBarPositioner,
|
|
|
|
KBarAnimator,
|
|
|
|
KBarSearch,
|
|
|
|
KBarResults,
|
|
|
|
useMatches,
|
|
|
|
useRegisterActions,
|
|
|
|
useKBar,
|
|
|
|
createAction
|
2023-03-21 07:19:45 +01:00
|
|
|
} from "kbar";
|
|
|
|
import classNames from 'classnames';
|
|
|
|
|
|
|
|
const searchStyle = {
|
2023-03-22 07:31:38 +01:00
|
|
|
padding: "12px 16px",
|
|
|
|
fontSize: "16px",
|
|
|
|
width: "100%",
|
|
|
|
boxSizing: "border-box",
|
2023-03-22 23:17:52 +01:00
|
|
|
outline: "none"
|
2023-03-21 07:19:45 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
const animatorStyle = {
|
2023-03-22 07:31:38 +01:00
|
|
|
maxWidth: "600px",
|
|
|
|
width: "100%",
|
|
|
|
borderRadius: "8px",
|
|
|
|
overflow: "hidden"
|
2023-03-21 07:19:45 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
const groupNameStyle = {
|
2023-03-22 07:31:38 +01:00
|
|
|
padding: "8px 16px",
|
|
|
|
fontSize: "10px",
|
|
|
|
textTransform: "uppercase",
|
|
|
|
opacity: 0.5,
|
2023-03-21 07:19:45 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
function RenderResults() {
|
2023-03-22 07:31:38 +01:00
|
|
|
const { results, rootActionId } = useMatches();
|
2023-03-21 07:19:45 +01:00
|
|
|
|
2023-03-22 07:31:38 +01:00
|
|
|
return (
|
|
|
|
<KBarResults
|
|
|
|
items={results}
|
|
|
|
onRender={({ item, active }) =>
|
|
|
|
typeof item === "string" ? (
|
|
|
|
<div style={groupNameStyle}>{item}</div>
|
|
|
|
) : (
|
|
|
|
<ResultItem
|
|
|
|
action={item}
|
|
|
|
active={active}
|
|
|
|
currentRootActionId={rootActionId}
|
|
|
|
/>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
);
|
2023-03-21 07:19:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const ResultItem = React.forwardRef(
|
2023-03-22 07:31:38 +01:00
|
|
|
(
|
|
|
|
{
|
|
|
|
action,
|
|
|
|
active,
|
|
|
|
currentRootActionId,
|
|
|
|
},
|
|
|
|
ref
|
|
|
|
) => {
|
|
|
|
const ancestors = React.useMemo(() => {
|
|
|
|
if (!currentRootActionId) return action.ancestors;
|
|
|
|
const index = action.ancestors.findIndex(
|
|
|
|
(ancestor) => ancestor.id === currentRootActionId
|
|
|
|
);
|
|
|
|
// +1 removes the currentRootAction; e.g.
|
|
|
|
// if we are on the "Set theme" parent action,
|
|
|
|
// the UI should not display "Set theme… > Dark"
|
|
|
|
// but rather just "Dark"
|
|
|
|
return action.ancestors.slice(index + 1);
|
|
|
|
}, [action.ancestors, currentRootActionId]);
|
2023-03-21 07:19:45 +01:00
|
|
|
|
2023-03-22 07:31:38 +01:00
|
|
|
return (
|
|
|
|
<div
|
|
|
|
ref={ref}
|
|
|
|
className={classNames(
|
2023-03-22 23:17:52 +01:00
|
|
|
"flex justify-between items-center cursor-pointer px-4 py-3 dark:text-white",
|
2023-03-22 07:31:38 +01:00
|
|
|
{
|
2023-03-22 23:17:52 +01:00
|
|
|
"bg-emerald-50 dark:bg-emerald-900": active,
|
2023-03-22 07:31:38 +01:00
|
|
|
"border-l-2 border-emerald-300": active
|
|
|
|
})}
|
|
|
|
>
|
|
|
|
<div className="flex gap-3 items-center text-sm">
|
|
|
|
{action.icon && action.icon}
|
|
|
|
<div className="flex flex-col">
|
|
|
|
<div>
|
|
|
|
{ancestors.length > 0 &&
|
|
|
|
ancestors.map((ancestor) => (
|
|
|
|
<React.Fragment key={ancestor.id}>
|
|
|
|
<span
|
|
|
|
style={{
|
|
|
|
opacity: 0.5,
|
|
|
|
marginRight: 8,
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{ancestor.name}
|
|
|
|
</span>
|
|
|
|
<span
|
|
|
|
style={{
|
|
|
|
marginRight: 8,
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
›
|
|
|
|
</span>
|
|
|
|
</React.Fragment>
|
|
|
|
))}
|
|
|
|
<span>{action.name}</span>
|
|
|
|
</div>
|
|
|
|
{action.subtitle && (
|
|
|
|
<span style={{ fontSize: 12 }}>{action.subtitle}</span>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{action.shortcut?.length ? (
|
|
|
|
<div
|
|
|
|
aria-hidden
|
|
|
|
style={{ display: "grid", gridAutoFlow: "column", gap: "4px" }}
|
|
|
|
>
|
|
|
|
{action.shortcut.map((sc) => (
|
|
|
|
<kbd
|
|
|
|
key={sc}
|
|
|
|
style={{
|
|
|
|
padding: "4px 6px",
|
|
|
|
background: "rgba(0 0 0 / .1)",
|
|
|
|
borderRadius: "4px",
|
|
|
|
fontSize: 14,
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{sc}
|
|
|
|
</kbd>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
) : null}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2023-03-21 07:19:45 +01:00
|
|
|
);
|
|
|
|
|
2023-03-22 07:31:38 +01:00
|
|
|
const staticActions = [
|
|
|
|
{
|
|
|
|
id: "user",
|
|
|
|
name: "User",
|
|
|
|
shortcut: ["u"],
|
|
|
|
keywords: "profile",
|
|
|
|
perform: () => (window.location.pathname = "user"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "user.edit",
|
|
|
|
name: "Edit User",
|
|
|
|
shortcut: ["u e"],
|
|
|
|
keywords: "profile edit settings",
|
|
|
|
perform: () => (window.location.pathname = "user/settings"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "admin",
|
|
|
|
name: "Admin",
|
|
|
|
shortcut: ["a"],
|
|
|
|
keywords: "home",
|
|
|
|
perform: () => (window.location.pathname = "admin"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "notes",
|
|
|
|
name: "Notes",
|
|
|
|
shortcut: ["n"],
|
|
|
|
keywords: "posts",
|
|
|
|
perform: () => (window.location.pathname = "admin/notes"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "notes.new",
|
|
|
|
name: "New Note",
|
|
|
|
shortcut: ["n n"],
|
|
|
|
keywords: "create new",
|
|
|
|
perform: () => (window.location.pathname = "admin/notes/new"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "channels",
|
|
|
|
name: "Channels",
|
|
|
|
shortcut: ["c"],
|
|
|
|
keywords: "channels",
|
|
|
|
perform: () => (window.location.pathname = "admin/channels"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "channels.new",
|
|
|
|
name: "New Channel",
|
|
|
|
shortcut: ["n c"],
|
|
|
|
keywords: "create new",
|
|
|
|
perform: () => (window.location.pathname = "admin/channels/new"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "identities",
|
|
|
|
name: "Identities",
|
|
|
|
shortcut: ["i"],
|
|
|
|
keywords: "identities",
|
|
|
|
perform: () => (window.location.pathname = "admin/identities"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "identities.new",
|
|
|
|
name: "New Identity",
|
|
|
|
shortcut: ["n i"],
|
|
|
|
keywords: "create new",
|
|
|
|
perform: () => (window.location.pathname = "admin/identities/new"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "settings",
|
|
|
|
name: "Settings",
|
|
|
|
shortcut: ["s"],
|
|
|
|
keywords: "settings",
|
|
|
|
perform: () => (window.location.pathname = "admin/settings"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "settings.edit",
|
|
|
|
name: "Edit Settings",
|
|
|
|
shortcut: ["s e"],
|
|
|
|
keywords: "settings edit",
|
|
|
|
perform: () => (window.location.pathname = "admin/settings/edit"),
|
|
|
|
}
|
2023-03-22 23:17:52 +01:00
|
|
|
].map(function(a) {
|
|
|
|
return {...a, section: "Pages"}
|
|
|
|
})
|
2023-03-21 07:19:45 +01:00
|
|
|
|
2023-03-22 07:31:38 +01:00
|
|
|
function DynamicResultsProvider() {
|
|
|
|
const [actions, setActions] = useState([])
|
|
|
|
const [notes, setNotes] = useState([])
|
|
|
|
const [rerender, setRerender] = useState(true)
|
2023-03-21 07:19:45 +01:00
|
|
|
|
2023-03-22 07:31:38 +01:00
|
|
|
useEffect(() => {
|
|
|
|
fetch("/api/admin/notes")
|
|
|
|
.then(resp => resp.json())
|
|
|
|
.then(json => setNotes(json.notes))
|
|
|
|
}, [rerender])
|
2023-03-21 07:19:45 +01:00
|
|
|
|
2023-03-22 23:17:52 +01:00
|
|
|
|
2023-03-22 07:31:38 +01:00
|
|
|
const noteActions = useMemo(() => notes.map(note => createAction({
|
|
|
|
id: note.slug,
|
|
|
|
name: note.name,
|
2023-03-22 23:17:52 +01:00
|
|
|
subtitle: note.channels.map(c => c.name).join(", "),
|
|
|
|
section: "Notes",
|
2023-03-22 07:31:38 +01:00
|
|
|
keywords: note.channels.map(c => c.name),
|
|
|
|
perform: () => (window.location.pathname = `/admin/notes/${note.id}`),
|
|
|
|
})), [notes])
|
2023-03-21 07:19:45 +01:00
|
|
|
|
2023-03-22 07:31:38 +01:00
|
|
|
useRegisterActions([...staticActions, ...noteActions], [noteActions])
|
2023-03-21 07:19:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export default function KBar() {
|
2023-03-22 07:31:38 +01:00
|
|
|
return (
|
|
|
|
<KBarProvider actions={staticActions}>
|
|
|
|
<DynamicResultsProvider />
|
|
|
|
<KBarPortal>
|
|
|
|
<KBarPositioner>
|
2023-03-22 23:17:52 +01:00
|
|
|
<KBarAnimator style={animatorStyle} className="bg-gray-50 border border-gray-100 shadow-sm dark:bg-gray-900 dark:border-gray-800">
|
|
|
|
<KBarSearch style={searchStyle} className="bg-gray-50 dark:bg-gray-900 border-b border-gray-100" />
|
2023-03-22 07:31:38 +01:00
|
|
|
<RenderResults />
|
|
|
|
</KBarAnimator>
|
|
|
|
</KBarPositioner>
|
|
|
|
</KBarPortal>
|
|
|
|
</KBarProvider>
|
|
|
|
)
|
2023-03-21 07:19:45 +01:00
|
|
|
}
|