a tool for shared writing and social publishing
1import { useState } from "react";
2import { blockCommands } from "./BlockCommands";
3import { useReplicache } from "src/replicache";
4import { useEntitySetContext } from "components/EntitySetProvider";
5import { useLeafletPublicationData } from "components/PageSWRDataProvider";
6import { setEditorState, useEditorStates } from "src/state/useEditorState";
7import { Combobox, ComboboxResult } from "components/Combobox";
8
9type Props = {
10 parent: string;
11 entityID: string | null;
12 position: string | null;
13 nextPosition: string | null;
14 factID?: string | undefined;
15 first?: boolean;
16 className?: string;
17};
18
19export const BlockCommandBar = ({
20 props,
21 searchValue,
22}: {
23 props: Props;
24 searchValue: string;
25}) => {
26 let [highlighted, setHighlighted] = useState<string | undefined>(undefined);
27
28 let { rep, undoManager } = useReplicache();
29 let entity_set = useEntitySetContext();
30 let { data: pub } = useLeafletPublicationData();
31
32 // This clears '/' AND anything typed after it
33 const clearCommandSearchText = () => {
34 if (!props.entityID) return;
35 const entityID = props.entityID;
36
37 const existingState = useEditorStates.getState().editorStates[entityID];
38 if (!existingState) return;
39
40 const tr = existingState.editor.tr;
41 tr.deleteRange(1, tr.doc.content.size - 1);
42 setEditorState(entityID, { editor: existingState.editor.apply(tr) });
43 };
44
45 let commandResults = blockCommands.filter((command) => {
46 const lowerSearchValue = searchValue.toLocaleLowerCase();
47 const matchesName = command.name
48 .toLocaleLowerCase()
49 .includes(lowerSearchValue);
50 const matchesAlternate =
51 command.alternateNames?.some((altName) =>
52 altName.toLocaleLowerCase().includes(lowerSearchValue),
53 ) ?? false;
54 const matchesSearch = matchesName || matchesAlternate;
55 const isVisible = !pub || !command.hiddenInPublication;
56 return matchesSearch && isVisible;
57 });
58
59 return (
60 <Combobox
61 open
62 triggerClassName="absolute left-0"
63 results={commandResults.map((r) => r.name)}
64 highlighted={highlighted}
65 setHighlighted={setHighlighted}
66 onSelect={async () => {
67 let command = commandResults.find((c) => c.name === highlighted);
68 if (!command || !rep) return;
69 undoManager.startGroup();
70 await command.onSelect(
71 rep,
72 { ...props, entity_set: entity_set.set },
73 undoManager,
74 );
75 undoManager.endGroup();
76 }}
77 onOpenChange={() => clearCommandSearchText()}
78 >
79 {commandResults.length === 0 ? (
80 <div className="w-full text-tertiary text-center italic py-2 px-2 ">
81 No blocks found
82 </div>
83 ) : (
84 commandResults.map((result, index) => (
85 <div key={index} className="contents">
86 <ComboboxResult
87 className="pl-0!"
88 result={result.name}
89 onSelect={() => {
90 rep &&
91 result.onSelect(
92 rep,
93 { ...props, entity_set: entity_set.set },
94 undoManager,
95 );
96 }}
97 highlighted={highlighted}
98 setHighlighted={setHighlighted}
99 >
100 <div className="text-tertiary w-8 shrink-0 flex justify-center">
101 {result.icon}
102 </div>
103 {result.name}
104 </ComboboxResult>
105 {commandResults[index + 1] &&
106 result.type !== commandResults[index + 1].type && (
107 <hr className="mx-2 my-0.5 border-border" />
108 )}
109 </div>
110 ))
111 )}
112 </Combobox>
113 );
114};