a tool for shared writing and social publishing
at feature/analytics 113 lines 3.7 kB view raw
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};