a tool for shared writing and social publishing

initial delete button but no wiring yet

+106 -83
+50 -30
components/Blocks/Block.tsx
··· 33 33 import { deepEquals } from "src/utils/deepEquals"; 34 34 import { isTextBlock } from "src/utils/isTextBlock"; 35 35 import { focusPage } from "src/utils/focusPage"; 36 + import { DeleteTiny } from "components/Icons/DeleteTiny"; 36 37 37 38 export type Block = { 38 39 factID: string; ··· 85 86 let selected = useUIState( 86 87 (s) => !!s.selectedBlocks.find((b) => b.value === props.entityID), 87 88 ); 89 + let alignment = useEntity(props.value, "block/text-alignment")?.data.value; 90 + 91 + let alignmentStyle = 92 + props.type === "button" || props.type === "image" 93 + ? "justify-center" 94 + : "justify-start"; 95 + 96 + if (alignment) 97 + alignmentStyle = { 98 + left: "justify-start", 99 + right: "justify-end", 100 + center: "justify-center", 101 + justify: "justify-start", 102 + }[alignment]; 88 103 89 104 let [areYouSure, setAreYouSure] = useState(false); 90 105 useEffect(() => { ··· 120 135 blockWrapper relative 121 136 flex flex-row gap-2 122 137 px-3 sm:px-4 138 + z-1 w-full 139 + ${alignmentStyle} 123 140 ${ 124 141 !props.nextBlock 125 142 ? "pb-3 sm:pb-4" ··· 258 275 ) => { 259 276 // BaseBlock renders the actual block content, delete states, controls spacing between block and list markers 260 277 let BlockTypeComponent = BlockTypeComponents[props.type]; 261 - let alignment = useEntity(props.value, "block/text-alignment")?.data.value; 262 - 263 - let alignmentStyle = 264 - props.type === "button" || props.type === "image" 265 - ? "justify-center" 266 - : "justify-start"; 267 - 268 - if (alignment) 269 - alignmentStyle = { 270 - left: "justify-start", 271 - right: "justify-end", 272 - center: "justify-center", 273 - justify: "justify-start", 274 - }[alignment]; 275 278 276 279 if (!BlockTypeComponent) return <div>unknown block</div>; 277 280 return ( 278 - <div 279 - className={`blockContentWrapper w-full grow flex gap-2 z-1 ${alignmentStyle}`} 280 - > 281 + <> 281 282 {props.listData && <ListMarker {...props} />} 282 283 {props.areYouSure ? ( 283 284 <AreYouSure ··· 290 291 ) : ( 291 292 <BlockTypeComponent {...props} preview={props.preview} /> 292 293 )} 293 - </div> 294 + </> 294 295 ); 295 296 }; 296 297 ··· 393 394 className?: string; 394 395 hasBackground?: "accent" | "page"; 395 396 borderOnHover?: boolean; 397 + hasAlignment?: boolean; 396 398 }) => { 399 + // this is used to wrap non-text blocks in consistent selected styling, spacing, and top level options like delete 397 400 return ( 398 - <div 399 - className={`block ${props.className} p-2 sm:p-3 w-full overflow-hidden 401 + <div className={`relative ${props.hasAlignment ? "w-fit" : "w-full"}`}> 402 + <div 403 + className={`nonTextBlock ${props.className} p-2 sm:p-3 overflow-hidden 404 + ${props.hasAlignment ? "w-fit" : "w-full"} 400 405 ${props.isSelected ? "block-border-selected " : "block-border"} 401 406 ${props.borderOnHover && "hover:border-accent-contrast! hover:outline-accent-contrast! focus-within:border-accent-contrast! focus-within:outline-accent-contrast!"}`} 402 - style={{ 403 - backgroundColor: 404 - props.hasBackground === "accent" 405 - ? "var(--accent-light)" 406 - : props.hasBackground === "page" 407 - ? "rgb(var(--bg-page))" 408 - : "transparent", 409 - }} 410 - > 411 - {props.children} 407 + style={{ 408 + backgroundColor: 409 + props.hasBackground === "accent" 410 + ? "var(--accent-light)" 411 + : props.hasBackground === "page" 412 + ? "rgb(var(--bg-page))" 413 + : "transparent", 414 + }} 415 + > 416 + {props.children} 417 + </div> 418 + {props.isSelected && <DeleteBlock />} 419 + </div> 420 + ); 421 + }; 422 + 423 + const DeleteBlock = () => { 424 + return ( 425 + <div className="absolute -top-4 right-2"> 426 + <button 427 + className="bg-border border-2 border-bg-page rounded-full p-1 text-bg-page" 428 + onClick={() => {}} 429 + > 430 + <DeleteTiny /> 431 + </button> 412 432 </div> 413 433 ); 414 434 };
+51 -52
components/Blocks/CodeBlock.tsx
··· 68 68 }, []); 69 69 return ( 70 70 <div className="codeBlock w-full flex flex-col rounded-md gap-0.5 "> 71 + <BlockLayout 72 + isSelected={focusedBlock} 73 + hasBackground="accent" 74 + borderOnHover 75 + className="p-0! min-h-[48px]" 76 + > 77 + {focusedBlock && permissions.write ? ( 78 + <BaseTextareaBlock 79 + placeholder="write some code…" 80 + data-editable-block 81 + data-entityid={props.entityID} 82 + id={elementId.block(props.entityID).input} 83 + block={props} 84 + rep={rep} 85 + permissionSet={entity_set.set} 86 + spellCheck={false} 87 + autoCapitalize="none" 88 + autoCorrect="off" 89 + className="codeBlockEditor whitespace-nowrap! overflow-auto! font-mono p-2 sm:p-3" 90 + value={content?.data.value} 91 + onChange={async (e) => { 92 + // Update the entity with the new value 93 + await rep?.mutate.assertFact({ 94 + attribute: "block/code", 95 + entity: props.entityID, 96 + data: { type: "string", value: e.target.value }, 97 + }); 98 + }} 99 + /> 100 + ) : !html ? ( 101 + <pre 102 + onClick={onClick} 103 + onMouseDown={(e) => e.stopPropagation()} 104 + className="codeBlockRendered overflow-auto! font-mono p-2 sm:p-3 w-full h-full" 105 + > 106 + {content?.data.value === "" || content?.data.value === undefined ? ( 107 + <div className="text-tertiary italic">write some code…</div> 108 + ) : ( 109 + content?.data.value 110 + )} 111 + </pre> 112 + ) : ( 113 + <div 114 + onMouseDown={(e) => e.stopPropagation()} 115 + onClick={onClick} 116 + data-lang={lang} 117 + className="contents" 118 + dangerouslySetInnerHTML={{ __html: html || "" }} 119 + /> 120 + )} 121 + </BlockLayout> 71 122 {permissions.write && ( 72 123 <div className="text-sm text-tertiary flex justify-between"> 73 124 <div className="flex gap-1"> ··· 119 170 </select> 120 171 </div> 121 172 )} 122 - 123 - <BlockLayout 124 - isSelected={focusedBlock} 125 - hasBackground="accent" 126 - borderOnHover 127 - className="p-0! min-h-[48px]" 128 - > 129 - {focusedBlock && permissions.write ? ( 130 - <BaseTextareaBlock 131 - placeholder="write some code…" 132 - data-editable-block 133 - data-entityid={props.entityID} 134 - id={elementId.block(props.entityID).input} 135 - block={props} 136 - rep={rep} 137 - permissionSet={entity_set.set} 138 - spellCheck={false} 139 - autoCapitalize="none" 140 - autoCorrect="off" 141 - className="codeBlockEditor whitespace-nowrap! overflow-auto! font-mono p-2 sm:p-3" 142 - value={content?.data.value} 143 - onChange={async (e) => { 144 - // Update the entity with the new value 145 - await rep?.mutate.assertFact({ 146 - attribute: "block/code", 147 - entity: props.entityID, 148 - data: { type: "string", value: e.target.value }, 149 - }); 150 - }} 151 - /> 152 - ) : !html ? ( 153 - <pre 154 - onClick={onClick} 155 - onMouseDown={(e) => e.stopPropagation()} 156 - className="codeBlockRendered overflow-auto! font-mono p-2 sm:p-3 w-full h-full" 157 - > 158 - {content?.data.value === "" || content?.data.value === undefined ? ( 159 - <div className="text-tertiary italic">write some code…</div> 160 - ) : ( 161 - content?.data.value 162 - )} 163 - </pre> 164 - ) : ( 165 - <div 166 - onMouseDown={(e) => e.stopPropagation()} 167 - onClick={onClick} 168 - data-lang={lang} 169 - className="contents" 170 - dangerouslySetInnerHTML={{ __html: html || "" }} 171 - /> 172 - )} 173 - </BlockLayout> 174 173 </div> 175 174 ); 176 175 }
+5 -1
components/Blocks/ImageBlock.tsx
··· 150 150 `; 151 151 152 152 return ( 153 - <BlockLayout isSelected={!!isSelected} className={blockClassName}> 153 + <BlockLayout 154 + hasAlignment 155 + isSelected={!!isSelected} 156 + className={blockClassName} 157 + > 154 158 {isLocalUpload || image.data.local ? ( 155 159 <img 156 160 loading="lazy"