a tool for shared writing and social publishing

handling delete in the block options

+111 -15
+51 -5
components/Blocks/Block.tsx
··· 37 37 import { ArrowDownTiny } from "components/Icons/ArrowDownTiny"; 38 38 import { Separator } from "components/Layout"; 39 39 import { moveBlockUp, moveBlockDown } from "src/utils/moveBlock"; 40 + import { deleteBlock } from "src/utils/deleteBlock"; 40 41 41 42 export type Block = { 42 43 factID: string; ··· 395 396 hasBackground?: "accent" | "page"; 396 397 borderOnHover?: boolean; 397 398 hasAlignment?: boolean; 399 + areYouSure?: boolean; 400 + setAreYouSure?: (value: boolean) => void; 398 401 }) => { 399 402 // this is used to wrap non-text blocks in consistent selected styling, spacing, and top level options like delete 400 403 return ( ··· 417 420 > 418 421 {props.children} 419 422 </div> 420 - {props.isSelected && <NonTextBlockOptions />} 423 + {props.isSelected && ( 424 + <NonTextBlockOptions 425 + areYouSure={props.areYouSure} 426 + setAreYouSure={props.setAreYouSure} 427 + /> 428 + )} 421 429 </div> 422 430 ); 423 431 }; 424 432 425 - const NonTextBlockOptions = (props: { isCanvas?: boolean }) => { 433 + let debounced: null | number = null; 434 + 435 + const NonTextBlockOptions = (props: { 436 + isCanvas?: boolean; 437 + areYouSure?: boolean; 438 + setAreYouSure?: (value: boolean) => void; 439 + }) => { 426 440 let { rep } = useReplicache(); 427 441 let entity_set = useEntitySetContext(); 428 442 let focusedEntity = useUIState((s) => s.focusedEntity); ··· 443 457 {focusedEntityType?.data.value !== "canvas" && ( 444 458 <> 445 459 <button 446 - onClick={async () => { 460 + onClick={async (e) => { 461 + e.stopPropagation(); 462 + 447 463 if (!rep) return; 448 464 await moveBlockDown(rep, entity_set.set); 449 465 }} ··· 451 467 <ArrowDownTiny /> 452 468 </button> 453 469 <button 454 - onClick={async () => { 470 + onClick={async (e) => { 471 + e.stopPropagation(); 472 + 455 473 if (!rep) return; 456 474 await moveBlockUp(rep); 457 475 }} ··· 461 479 <Separator classname="border-bg-page! h-4! mx-0.5" /> 462 480 </> 463 481 )} 464 - <button onClick={() => {}}> 482 + <button 483 + onClick={async (e) => { 484 + e.stopPropagation(); 485 + if (!rep || !focusedEntity) return; 486 + 487 + if (props.areYouSure !== undefined && props.setAreYouSure) { 488 + if (!props.areYouSure) { 489 + props.setAreYouSure(true); 490 + debounced = window.setTimeout(() => { 491 + debounced = null; 492 + }, 300); 493 + return; 494 + } 495 + 496 + if (props.areYouSure) { 497 + if (debounced) { 498 + window.clearTimeout(debounced); 499 + debounced = window.setTimeout(() => { 500 + debounced = null; 501 + }, 300); 502 + return; 503 + } 504 + await deleteBlock([focusedEntity.entityID], rep); 505 + } 506 + } else { 507 + await deleteBlock([focusedEntity.entityID], rep); 508 + } 509 + }} 510 + > 465 511 <DeleteTiny /> 466 512 </button> 467 513 </div>
+18 -3
components/Blocks/MailboxBlock.tsx
··· 26 26 import { ArrowDownTiny } from "components/Icons/ArrowDownTiny"; 27 27 import { InfoSmall } from "components/Icons/InfoSmall"; 28 28 29 - export const MailboxBlock = (props: BlockProps) => { 29 + export const MailboxBlock = ( 30 + props: BlockProps & { 31 + areYouSure?: boolean; 32 + setAreYouSure?: (value: boolean) => void; 33 + }, 34 + ) => { 30 35 let isSubscribed = useSubscriptionStatus(props.entityID); 31 36 let isSelected = useUIState((s) => 32 37 s.selectedBlocks.find((b) => b.value === props.entityID), ··· 41 46 let subscriber_count = useEntity(props.entityID, "mailbox/subscriber-count"); 42 47 if (!permission) 43 48 return ( 44 - <MailboxReaderView entityID={props.entityID} parent={props.parent} /> 49 + <MailboxReaderView 50 + entityID={props.entityID} 51 + parent={props.parent} 52 + /> 45 53 ); 46 54 47 55 return ( ··· 49 57 <BlockLayout 50 58 isSelected={!!isSelected} 51 59 hasBackground={"accent"} 60 + areYouSure={props.areYouSure} 61 + setAreYouSure={props.setAreYouSure} 52 62 className="flex gap-2 items-center justify-center" 53 63 > 54 64 <ButtonPrimary ··· 120 130 ); 121 131 }; 122 132 123 - const MailboxReaderView = (props: { entityID: string; parent: string }) => { 133 + const MailboxReaderView = (props: { 134 + entityID: string; 135 + parent: string; 136 + 137 + }) => { 124 138 let isSubscribed = useSubscriptionStatus(props.entityID); 125 139 let isSelected = useUIState((s) => 126 140 s.selectedBlocks.find((b) => b.value === props.entityID), ··· 133 147 <BlockLayout 134 148 isSelected={!!isSelected} 135 149 hasBackground={"accent"} 150 + 136 151 className="`h-full flex flex-col gap-2 items-center justify-center" 137 152 > 138 153 {!isSubscribed?.confirmed ? (
+9 -1
components/Blocks/PageLinkBlock.tsx
··· 13 13 import { CardThemeProvider } from "components/ThemeManager/ThemeProvider"; 14 14 import { useCardBorderHidden } from "components/Pages/useCardBorderHidden"; 15 15 16 - export function PageLinkBlock(props: BlockProps & { preview?: boolean }) { 16 + export function PageLinkBlock( 17 + props: BlockProps & { 18 + preview?: boolean; 19 + areYouSure?: boolean; 20 + setAreYouSure?: (value: boolean) => void; 21 + }, 22 + ) { 17 23 let page = useEntity(props.entityID, "block/card"); 18 24 let type = 19 25 useEntity(page?.data.value || null, "page/type")?.data.value || "doc"; ··· 32 38 <BlockLayout 33 39 hasBackground="page" 34 40 isSelected={!!isSelected} 41 + areYouSure={props.areYouSure} 42 + setAreYouSure={props.setAreYouSure} 35 43 className={`cursor-pointer 36 44 pageLinkBlockWrapper relative group/pageLinkBlock 37 45 flex overflow-clip p-0!
+14 -2
components/Blocks/PollBlock/index.tsx
··· 20 20 import { PublicationPollBlock } from "../PublicationPollBlock"; 21 21 import { usePollBlockUIState } from "./pollBlockState"; 22 22 23 - export const PollBlock = (props: BlockProps) => { 23 + export const PollBlock = ( 24 + props: BlockProps & { 25 + areYouSure?: boolean; 26 + setAreYouSure?: (value: boolean) => void; 27 + }, 28 + ) => { 24 29 let { data: pub } = useLeafletPublicationData(); 25 30 if (!pub) return <LeafletPollBlock {...props} />; 26 31 return <PublicationPollBlock {...props} />; 27 32 }; 28 33 29 - export const LeafletPollBlock = (props: BlockProps) => { 34 + export const LeafletPollBlock = ( 35 + props: BlockProps & { 36 + areYouSure?: boolean; 37 + setAreYouSure?: (value: boolean) => void; 38 + }, 39 + ) => { 30 40 let isSelected = useUIState((s) => 31 41 s.selectedBlocks.find((b) => b.value === props.entityID), 32 42 ); ··· 64 74 <BlockLayout 65 75 isSelected={!!isSelected} 66 76 hasBackground={"accent"} 77 + areYouSure={props.areYouSure} 78 + setAreYouSure={props.setAreYouSure} 67 79 className="poll flex flex-col gap-2 w-full" 68 80 > 69 81 {pollState === "editing" ? (
+8 -1
components/Blocks/PublicationPollBlock.tsx
··· 21 21 * It allows adding/editing options when the poll hasn't been published yet, 22 22 * but disables adding new options once the poll record exists (indicated by pollUri). 23 23 */ 24 - export const PublicationPollBlock = (props: BlockProps) => { 24 + export const PublicationPollBlock = ( 25 + props: BlockProps & { 26 + areYouSure?: boolean; 27 + setAreYouSure?: (value: boolean) => void; 28 + }, 29 + ) => { 25 30 let { data: publicationData } = useLeafletPublicationData(); 26 31 let isSelected = useUIState((s) => 27 32 s.selectedBlocks.find((b) => b.value === props.entityID), ··· 57 62 className="poll flex flex-col gap-2" 58 63 hasBackground={"accent"} 59 64 isSelected={!!isSelected} 65 + areYouSure={props.areYouSure} 66 + setAreYouSure={props.setAreYouSure} 60 67 > 61 68 <EditPollForPublication 62 69 entityID={props.entityID}
+8 -1
components/Blocks/RSVPBlock/index.tsx
··· 24 24 } 25 25 | { state: "contact_details"; status: RSVP_Status }; 26 26 27 - export function RSVPBlock(props: BlockProps) { 27 + export function RSVPBlock( 28 + props: BlockProps & { 29 + areYouSure?: boolean; 30 + setAreYouSure?: (value: boolean) => void; 31 + }, 32 + ) { 28 33 let isSelected = useUIState((s) => 29 34 s.selectedBlocks.find((b) => b.value === props.entityID), 30 35 ); ··· 32 37 <BlockLayout 33 38 isSelected={!!isSelected} 34 39 hasBackground={"accent"} 40 + areYouSure={props.areYouSure} 41 + setAreYouSure={props.setAreYouSure} 35 42 className="rsvp relative flex flex-col gap-1 w-full rounded-lg place-items-center justify-center" 36 43 > 37 44 <RSVPForm entityID={props.entityID} />
+3 -2
components/Blocks/useBlockKeyboardHandlers.ts
··· 154 154 if ((el as HTMLInputElement).value !== "") return; 155 155 } 156 156 157 - // if the block is a card or mailbox... 157 + // if the block is a card, mailbox, rsvp, or poll... 158 158 if ( 159 159 props.type === "card" || 160 160 props.type === "mailbox" || 161 - props.type === "rsvp" 161 + props.type === "rsvp" || 162 + props.type === "poll" 162 163 ) { 163 164 // ...and areYouSure state is false, set it to true 164 165 if (!areYouSure) {