a tool for shared writing and social publishing
at test/unknown-marks 187 lines 6.0 kB view raw
1import { useUIState } from "src/useUIState"; 2import { BlockProps } from "./Block"; 3import { useMemo } from "react"; 4import { focusElement, AsyncValueInput } from "components/Input"; 5import { useEntitySetContext } from "components/EntitySetProvider"; 6import { useEntity, useReplicache } from "src/replicache"; 7import { v7 } from "uuid"; 8import { elementId } from "src/utils/elementId"; 9import { CloseTiny } from "components/Icons/CloseTiny"; 10import { useLeafletPublicationData } from "components/PageSWRDataProvider"; 11import { 12 PubLeafletBlocksPoll, 13 PubLeafletDocument, 14 PubLeafletPagesLinearDocument, 15} from "lexicons/api"; 16import { ids } from "lexicons/api/lexicons"; 17 18/** 19 * PublicationPollBlock is used for editing polls in publication documents. 20 * It allows adding/editing options when the poll hasn't been published yet, 21 * but disables adding new options once the poll record exists (indicated by pollUri). 22 */ 23export const PublicationPollBlock = (props: BlockProps) => { 24 let { data: publicationData } = useLeafletPublicationData(); 25 let isSelected = useUIState((s) => 26 s.selectedBlocks.find((b) => b.value === props.entityID), 27 ); 28 // Check if this poll has been published in a publication document 29 const isPublished = useMemo(() => { 30 if (!publicationData?.documents?.data) return false; 31 32 const docRecord = publicationData.documents 33 .data as PubLeafletDocument.Record; 34 console.log(docRecord); 35 36 // Search through all pages and blocks to find if this poll entity has been published 37 for (const page of docRecord.pages || []) { 38 if (page.$type === "pub.leaflet.pages.linearDocument") { 39 const linearPage = page as PubLeafletPagesLinearDocument.Main; 40 for (const blockWrapper of linearPage.blocks || []) { 41 if (blockWrapper.block?.$type === ids.PubLeafletBlocksPoll) { 42 const pollBlock = blockWrapper.block as PubLeafletBlocksPoll.Main; 43 console.log(pollBlock); 44 // Check if this poll's rkey matches our entity ID 45 const rkey = pollBlock.pollRef.uri.split("/").pop(); 46 if (rkey === props.entityID) { 47 return true; 48 } 49 } 50 } 51 } 52 } 53 return false; 54 }, [publicationData, props.entityID]); 55 56 return ( 57 <div 58 className={`poll flex flex-col gap-2 p-3 w-full 59 ${isSelected ? "block-border-selected " : "block-border"}`} 60 style={{ 61 backgroundColor: 62 "color-mix(in oklab, rgb(var(--accent-1)), rgb(var(--bg-page)) 85%)", 63 }} 64 > 65 <EditPollForPublication 66 entityID={props.entityID} 67 isPublished={isPublished} 68 /> 69 </div> 70 ); 71}; 72 73const EditPollForPublication = (props: { 74 entityID: string; 75 isPublished: boolean; 76}) => { 77 let pollOptions = useEntity(props.entityID, "poll/options"); 78 let { rep } = useReplicache(); 79 let permission_set = useEntitySetContext(); 80 81 return ( 82 <> 83 {props.isPublished && ( 84 <div className="text-sm italic text-tertiary"> 85 This poll has been published. You can't edit the options. 86 </div> 87 )} 88 89 {pollOptions.length === 0 && !props.isPublished && ( 90 <div className="text-center italic text-tertiary text-sm"> 91 no options yet... 92 </div> 93 )} 94 95 {pollOptions.map((p) => ( 96 <EditPollOptionForPublication 97 key={p.id} 98 entityID={p.data.value} 99 pollEntity={props.entityID} 100 disabled={props.isPublished} 101 canDelete={!props.isPublished} 102 /> 103 ))} 104 105 {!props.isPublished && permission_set.permissions.write && ( 106 <button 107 className="pollAddOption w-fit flex gap-2 items-center justify-start text-sm text-accent-contrast" 108 onClick={async () => { 109 let pollOptionEntity = v7(); 110 await rep?.mutate.addPollOption({ 111 pollEntity: props.entityID, 112 pollOptionEntity, 113 pollOptionName: "", 114 permission_set: permission_set.set, 115 factID: v7(), 116 }); 117 118 focusElement( 119 document.getElementById( 120 elementId.block(props.entityID).pollInput(pollOptionEntity), 121 ) as HTMLInputElement | null, 122 ); 123 }} 124 > 125 Add an Option 126 </button> 127 )} 128 </> 129 ); 130}; 131 132const EditPollOptionForPublication = (props: { 133 entityID: string; 134 pollEntity: string; 135 disabled: boolean; 136 canDelete: boolean; 137}) => { 138 let { rep } = useReplicache(); 139 let { permissions } = useEntitySetContext(); 140 let optionName = useEntity(props.entityID, "poll-option/name")?.data.value; 141 142 return ( 143 <div className="flex gap-2 items-center"> 144 <AsyncValueInput 145 id={elementId.block(props.pollEntity).pollInput(props.entityID)} 146 type="text" 147 className="pollOptionInput w-full input-with-border" 148 placeholder="Option here..." 149 disabled={props.disabled || !permissions.write} 150 value={optionName || ""} 151 onChange={async (e) => { 152 await rep?.mutate.assertFact([ 153 { 154 entity: props.entityID, 155 attribute: "poll-option/name", 156 data: { type: "string", value: e.currentTarget.value }, 157 }, 158 ]); 159 }} 160 onKeyDown={(e) => { 161 if ( 162 props.canDelete && 163 e.key === "Backspace" && 164 !e.currentTarget.value 165 ) { 166 e.preventDefault(); 167 rep?.mutate.removePollOption({ optionEntity: props.entityID }); 168 } 169 }} 170 /> 171 172 {permissions.write && props.canDelete && ( 173 <button 174 tabIndex={-1} 175 className="text-accent-contrast" 176 onMouseDown={async () => { 177 await rep?.mutate.removePollOption({ 178 optionEntity: props.entityID, 179 }); 180 }} 181 > 182 <CloseTiny /> 183 </button> 184 )} 185 </div> 186 ); 187};