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 triggerClassName="absolute left-0"
62 results={commandResults.map((r) => r.name)}
63 highlighted={highlighted}
64 setHighlighted={setHighlighted}
65 onSelect={async () => {
66 let command = commandResults.find((c) => c.name === highlighted);
67 if (!command || !rep) return;
68 undoManager.startGroup();
69 await command.onSelect(
70 rep,
71 { ...props, entity_set: entity_set.set },
72 undoManager,
73 );
74 undoManager.endGroup();
75 }}
76 onOpenChange={() => clearCommandSearchText()}
77 >
78 {commandResults.length === 0 ? (
79 <div className="w-full text-tertiary text-center italic py-2 px-2 ">
80 No blocks found
81 </div>
82 ) : (
83 commandResults.map((result, index) => (
84 <div key={index} className="contents">
85 <ComboboxResult
86 className="pl-1!"
87 result={result.name}
88 onSelect={() => {
89 rep &&
90 result.onSelect(
91 rep,
92 { ...props, entity_set: entity_set.set },
93 undoManager,
94 );
95 }}
96 highlighted={highlighted}
97 setHighlighted={setHighlighted}
98 >
99 <div className="text-tertiary w-8 shrink-0 flex justify-center">
100 {result.icon}
101 </div>
102 {result.name}
103 </ComboboxResult>
104 {commandResults[index + 1] &&
105 result.type !== commandResults[index + 1].type && (
106 <hr className="mx-2 my-0.5 border-border" />
107 )}
108 </div>
109 ))
110 )}
111 </Combobox>
112 );
113};