a tool for shared writing and social publishing
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

added full bleed images

celine bebf868d c8246e21

+161 -30
+6 -4
components/Blocks/Block.tsx
··· 92 92 flex flex-row gap-2 93 93 px-3 sm:px-4 94 94 ${ 95 - props.type === "heading" || 96 - (props.listData && props.nextBlock?.listData) 97 - ? "pb-0" 98 - : "pb-2" 95 + !props.nextBlock 96 + ? "pb-3 sm:pb-4" 97 + : props.type === "heading" || 98 + (props.listData && props.nextBlock?.listData) 99 + ? "pb-0" 100 + : "pb-2" 99 101 } 100 102 ${ 101 103 !props.previousBlock
+36 -6
components/Blocks/ImageBlock.tsx
··· 21 21 s.selectedBlocks.find((b) => b.value === props.value), 22 22 ); 23 23 let isLocked = useEntity(props.value, "block/is-locked")?.data.value; 24 + let isFullBleed = useEntity(props.value, "image/full-bleed")?.data.value; 25 + let isFirst = props.previousBlock === null; 26 + let isLast = props.nextBlock === null; 27 + 28 + let nextIsFullBleed = useEntity( 29 + props.nextBlock && props.nextBlock.value, 30 + "image/full-bleed", 31 + )?.data.value; 32 + let prevIsFullBleed = useEntity( 33 + props.previousBlock && props.previousBlock.value, 34 + "image/full-bleed", 35 + )?.data.value; 24 36 25 37 useEffect(() => { 26 38 if (props.preview) return; ··· 39 51 <label 40 52 className={` 41 53 group/image-block 42 - w-full h-[104px] p-2 hover:cursor-pointer 54 + w-full h-[104px] hover:cursor-pointer p-2 43 55 text-tertiary hover:text-accent-contrast hover:font-bold 44 56 flex flex-col items-center justify-center 45 57 hover:border-2 border-dashed hover:border-accent-contrast rounded-lg ··· 92 104 ); 93 105 } 94 106 95 - let className = isSelected 96 - ? "block-border-selected !border-transparent " 97 - : "block-border !border-transparent"; 107 + let className = isFullBleed 108 + ? "" 109 + : isSelected 110 + ? "block-border-selected !border-transparent " 111 + : "block-border !border-transparent"; 112 + 98 113 let isLocalUpload = localImages.get(image.data.src); 114 + 99 115 return ( 100 - <div className="relative group/image"> 116 + <div 117 + className={`relative group/image 118 + ${className} 119 + ${isFullBleed && "-mx-3 sm:-mx-4"} 120 + ${isFullBleed ? (isFirst ? "-mt-3 sm:-mt-4" : prevIsFullBleed ? "-mt-1" : "") : ""} 121 + ${isFullBleed ? (isLast ? "-mb-4" : nextIsFullBleed ? "-mb-2" : "") : ""} `} 122 + > 123 + {isFullBleed && isSelected ? <FullBleedSelectionIndicator /> : null} 101 124 {isLocalUpload || image.data.local ? ( 102 125 <img 103 126 loading="lazy" ··· 106 129 src={isLocalUpload ? image.data.src + "?local" : image.data.fallback} 107 130 height={image?.data.height} 108 131 width={image?.data.width} 109 - className={className} 110 132 /> 111 133 ) : ( 112 134 <Image ··· 120 142 </div> 121 143 ); 122 144 } 145 + 146 + export const FullBleedSelectionIndicator = () => { 147 + return ( 148 + <div 149 + className={`absolute top-3 sm:top-4 bottom-3 sm:bottom-4 left-3 sm:left-4 right-3 sm:right-4 border-2 border-bg-page rounded-lg outline-offset-1 outline outline-2 outline-tertiary`} 150 + /> 151 + ); 152 + };
+1 -1
components/Blocks/index.tsx
··· 221 221 let { rep } = useReplicache(); 222 222 let entity_set = useEntitySetContext(); 223 223 224 - if (!entity_set.permissions.write) return <div className="h-4 min-h-4" />; 224 + if (!entity_set.permissions.write) return; 225 225 return ( 226 226 <div 227 227 className="blockListClickableBottomArea shrink-0 h-[50vh]"
+38
components/Icons.tsx
··· 1278 1278 </svg> 1279 1279 ); 1280 1280 }; 1281 + export const ImageFullBleedOffSmall = (props: Props) => { 1282 + return ( 1283 + <svg 1284 + width="24" 1285 + height="24" 1286 + viewBox="0 0 24 24" 1287 + fill="none" 1288 + xmlns="http://www.w3.org/2000/svg" 1289 + {...props} 1290 + > 1291 + <path 1292 + fillRule="evenodd" 1293 + clipRule="evenodd" 1294 + d="M1.82922 8.93921C1.41501 8.93921 1.07922 8.60342 1.07922 8.18921V6.18921C1.07922 4.11814 2.75816 2.43921 4.82922 2.43921H6.82922C7.24344 2.43921 7.57922 2.775 7.57922 3.18921C7.57922 3.60342 7.24344 3.93921 6.82922 3.93921L4.82922 3.93921C3.58658 3.93921 2.57922 4.94657 2.57922 6.18921L2.57922 8.18921C2.57922 8.60342 2.24344 8.93921 1.82922 8.93921ZM17.1708 2.43921C16.7566 2.43921 16.4208 2.775 16.4208 3.18921C16.4208 3.60342 16.7566 3.93921 17.1708 3.93921H19.1708C20.4134 3.93921 21.4208 4.94657 21.4208 6.18921V8.18921C21.4208 8.60342 21.7566 8.93921 22.1708 8.93921C22.585 8.93921 22.9208 8.60342 22.9208 8.18921V6.18921C22.9208 4.11814 21.2418 2.43921 19.1708 2.43921H17.1708ZM17.1708 21.5608C16.7566 21.5608 16.4208 21.225 16.4208 20.8108C16.4208 20.3966 16.7566 20.0608 17.1708 20.0608H19.1708C20.4134 20.0608 21.4208 19.0534 21.4208 17.8108V15.8108C21.4208 15.3966 21.7566 15.0608 22.1708 15.0608C22.585 15.0608 22.9208 15.3966 22.9208 15.8108V17.8108C22.9208 19.8819 21.2418 21.5608 19.1708 21.5608H17.1708ZM1.07922 15.8108C1.07922 15.3966 1.41501 15.0608 1.82922 15.0608C2.24344 15.0608 2.57922 15.3966 2.57922 15.8108L2.57922 17.8108C2.57922 19.0534 3.58658 20.0608 4.82922 20.0608H6.82922C7.24344 20.0608 7.57922 20.3966 7.57922 20.8108C7.57922 21.225 7.24344 21.5608 6.82922 21.5608H4.82922C2.75816 21.5608 1.07922 19.8819 1.07922 17.8108V15.8108ZM7.37924 12.5162L9.08548 13.6549L8.56422 14.1568C8.38144 14.3328 8.09583 14.3439 7.89985 14.1828L7.63847 13.9678C7.21682 13.6211 6.64007 13.5308 6.13263 13.7321L5.92453 13.8146L7.37924 12.5162ZM10.8169 14.8104L9.93703 14.2232L9.2578 14.8772C8.70944 15.4051 7.8526 15.4386 7.26468 14.9552L7.0033 14.7402C6.86275 14.6246 6.6705 14.5945 6.50135 14.6616L4.95422 15.2753V17.0608C4.95422 17.406 5.23405 17.6858 5.57922 17.6858H18.3533C18.6985 17.6858 18.9783 17.406 18.9783 17.0608V12.526L18.5888 12.2077L18.091 13.0482C17.7153 13.6825 16.9345 13.9498 16.249 13.6788L15.1568 13.2471C15.0216 13.1937 14.8698 13.2017 14.741 13.2692L14.2876 13.5067C13.809 13.7574 13.2325 13.7313 12.7785 13.4384L12.4416 13.2211L11.5777 14.1178L11.782 14.2556C12.011 14.4099 12.0714 14.7207 11.9171 14.9497C11.7627 15.1786 11.4519 15.2391 11.223 15.0847L10.8341 14.8225C10.8282 14.8186 10.8225 14.8145 10.8169 14.8104ZM17.8056 11.5677L17.2306 12.5386C17.1053 12.7501 16.8451 12.8392 16.6166 12.7488L15.5244 12.3171C15.1188 12.1568 14.6634 12.181 14.277 12.3834L13.8236 12.6209C13.6641 12.7044 13.4719 12.6957 13.3206 12.5981L13.1486 12.4871L15.6953 9.84339L17.8056 11.5677ZM16.328 9.06903L18.9783 11.2346V6.93921C18.9783 6.59403 18.6985 6.31421 18.3533 6.31421H5.57922C5.23405 6.31421 4.95422 6.59403 4.95422 6.93921V13.3403L6.71336 11.7702C7.05329 11.4668 7.55535 11.4315 7.93434 11.6845L10.7337 13.5526L14.9751 9.14962C15.3363 8.77464 15.9248 8.73958 16.328 9.06903ZM5.57922 5.06421C4.54369 5.06421 3.70422 5.90368 3.70422 6.93921V17.0608C3.70422 18.0963 4.54369 18.9358 5.57922 18.9358H18.3533C19.3889 18.9358 20.2283 18.0963 20.2283 17.0608V6.93921C20.2283 5.90368 19.3889 5.06421 18.3533 5.06421H5.57922ZM8.87793 8.73889C8.87793 8.4316 9.12704 8.1825 9.43433 8.1825C9.74162 8.1825 9.99073 8.4316 9.99073 8.73889C9.99073 9.04619 9.74162 9.29529 9.43433 9.29529C9.12704 9.29529 8.87793 9.04619 8.87793 8.73889ZM9.43433 7.1825C8.57475 7.1825 7.87793 7.87932 7.87793 8.73889C7.87793 9.59847 8.57475 10.2953 9.43433 10.2953C10.2939 10.2953 10.9907 9.59847 10.9907 8.73889C10.9907 7.87932 10.2939 7.1825 9.43433 7.1825Z" 1295 + fill="currentColor" 1296 + /> 1297 + </svg> 1298 + ); 1299 + }; 1300 + export const ImageFullBleedOnSmall = (props: Props) => { 1301 + return ( 1302 + <svg 1303 + width="24" 1304 + height="24" 1305 + viewBox="0 0 24 24" 1306 + fill="none" 1307 + xmlns="http://www.w3.org/2000/svg" 1308 + {...props} 1309 + > 1310 + <path 1311 + fillRule="evenodd" 1312 + clipRule="evenodd" 1313 + d="M0.712524 5.68921C0.712524 3.96332 2.11163 2.56421 3.83752 2.56421H20.1624C21.8883 2.56421 23.2874 3.96332 23.2874 5.68921V18.3108C23.2874 20.0367 21.8883 21.4358 20.1624 21.4358H3.83752C2.11164 21.4358 0.712524 20.0367 0.712524 18.3108V5.68921ZM3.83752 3.81421C2.80199 3.81421 1.96252 4.65368 1.96252 5.68921V14.1901L4.82906 11.6316C5.21149 11.2902 5.7763 11.2506 6.20267 11.5351L10.2892 14.2624L16.3336 7.98778C16.573 7.73919 16.9687 7.73179 17.2173 7.97126C17.4659 8.21074 17.4733 8.6064 17.2338 8.85499L13.5626 12.6661L14.0542 13.0054C14.1688 13.0845 14.3178 13.0936 14.4412 13.0289L15.5068 12.4708C15.8425 12.2949 16.2291 12.242 16.5997 12.321L18.8672 12.8047C18.9866 12.8301 19.1109 12.7959 19.2004 12.7129L20.5274 11.4827C20.4554 11.282 20.4879 11.0496 20.6334 10.8732C20.8531 10.607 21.247 10.5692 21.5133 10.7889L22.0374 11.2213V5.68921C22.0374 4.65368 21.198 3.81421 20.1624 3.81421H3.83752ZM21.4356 12.3453L20.0502 13.6296C19.6624 13.9892 19.1237 14.1375 18.6065 14.0272L16.339 13.5435C16.2535 13.5253 16.1642 13.5375 16.0868 13.5781L15.0212 14.1362C14.4866 14.4163 13.8408 14.377 13.3441 14.0341L12.6838 13.5783L11.3433 14.9699L11.6378 15.1684C11.9241 15.3613 11.9997 15.7498 11.8068 16.036C11.6139 16.3222 11.2255 16.3979 10.9392 16.205L10.0299 15.5921L10.0276 15.5905L9.07582 14.9554L7.84586 16.0297C7.58589 16.2568 7.19106 16.2301 6.96399 15.9702C6.73691 15.7102 6.76357 15.3154 7.02354 15.0883L7.99868 14.2365L5.58925 12.6286L2.3289 15.5385C2.22302 15.6331 2.09388 15.6849 1.96252 15.6953V18.3108C1.96252 19.3463 2.80199 20.1858 3.83752 20.1858H20.1624C21.198 20.1858 22.0374 19.3463 22.0374 18.3108V12.8418L21.4356 12.3453ZM9.71952 6.20252C9.39162 6.0947 9.03839 6.27312 8.93057 6.60102C8.82275 6.92893 9.00116 7.28216 9.32907 7.38998C9.67978 7.5053 9.84306 7.66604 9.92838 7.80233C10.02 7.94875 10.0598 8.12899 10.0598 8.33053C10.0598 8.79991 9.67925 9.18042 9.20987 9.18042C9.06221 9.18042 8.79851 9.10649 8.58819 8.92328C8.40267 8.76167 8.25172 8.51136 8.28005 8.10042C8.3038 7.75606 8.04389 7.45765 7.69953 7.4339C7.35517 7.41015 7.05676 7.67006 7.03302 8.01442C6.97619 8.83849 7.30377 9.46218 7.76714 9.86582C8.20571 10.2479 8.76126 10.4304 9.20987 10.4304C10.3696 10.4304 11.3098 9.49027 11.3098 8.33053C11.3098 7.97485 11.2407 7.54299 10.9879 7.13909C10.7287 6.72507 10.3118 6.39728 9.71952 6.20252ZM16.1204 5.922C16.1204 5.50778 16.4561 5.172 16.8704 5.172H19.3704C20.0607 5.172 20.6204 5.73164 20.6204 6.422V8.922C20.6204 9.33621 20.2846 9.672 19.8704 9.672C19.4561 9.672 19.1204 9.33621 19.1204 8.922V6.672H16.8704C16.4561 6.672 16.1204 6.33621 16.1204 5.922ZM16.8704 18.8224C16.4561 18.8224 16.1204 18.4866 16.1204 18.0724C16.1204 17.6582 16.4561 17.3224 16.8704 17.3224H19.1204V15.0724C19.1204 14.6582 19.4561 14.3224 19.8704 14.3224C20.2846 14.3224 20.6204 14.6582 20.6204 15.0724V17.5724C20.6204 18.2627 20.0607 18.8224 19.3704 18.8224H16.8704ZM7.84949 5.9248C7.84949 5.51059 7.5137 5.1748 7.09949 5.1748H4.59949C3.90913 5.1748 3.34949 5.73445 3.34949 6.4248V8.9248C3.34949 9.33902 3.68527 9.6748 4.09949 9.6748C4.5137 9.6748 4.84949 9.33902 4.84949 8.9248V6.6748H7.09949C7.5137 6.6748 7.84949 6.33902 7.84949 5.9248ZM7.09949 18.8252C7.5137 18.8252 7.84949 18.4894 7.84949 18.0752C7.84949 17.661 7.5137 17.3252 7.09949 17.3252H4.84949V15.0752C4.84949 14.661 4.5137 14.3252 4.09949 14.3252C3.68527 14.3252 3.34949 14.661 3.34949 15.0752V17.5752C3.34949 18.2656 3.90913 18.8252 4.59949 18.8252H7.09949Z" 1314 + fill="currentColor" 1315 + /> 1316 + </svg> 1317 + ); 1318 + }; 1281 1319 1282 1320 export const ListUnorderedSmall = (props: Props) => { 1283 1321 return (
+19 -11
components/Toolbar/BlockToolbar.tsx
··· 7 7 import { useUIState } from "src/useUIState"; 8 8 import { LockBlockButton } from "./LockBlockButton"; 9 9 import { TextAlignmentButton } from "./TextAlignmentToolbar"; 10 + import { ImageFullBleedButton } from "./ImageToolbar"; 10 11 11 12 export const BlockToolbar = (props: { 12 13 setToolbarState: (state: "areYouSure" | "block" | "text-alignment") => void; ··· 35 36 <DeleteSmall /> 36 37 </ToolbarButton> 37 38 <Separator classname="h-6" /> 38 - {focusedEntityType?.data.value !== "canvas" ? ( 39 + <MoveBlockButtons /> 40 + {blockType === "image" && ( 39 41 <> 40 - <MoveBlockButtons /> 41 - <Separator classname="h-6" /> 42 - {(blockType === "image" || 43 - blockType === "button" || 44 - blockType === "datetime") && ( 45 - <> 46 - <TextAlignmentButton setToolbarState={props.setToolbarState} /> 47 - <Separator classname="h-6" /> 48 - </> 42 + <TextAlignmentButton setToolbarState={props.setToolbarState} /> 43 + <ImageFullBleedButton /> 44 + {focusedEntityType?.data.value !== "canvas" && ( 45 + <Separator classname="h-6" /> 49 46 )} 50 47 </> 51 - ) : null} 48 + )} 49 + {(blockType === "button" || blockType === "datetime") && ( 50 + <> 51 + <TextAlignmentButton setToolbarState={props.setToolbarState} /> 52 + {focusedEntityType?.data.value !== "canvas" && ( 53 + <Separator classname="h-6" /> 54 + )} 55 + </> 56 + )} 52 57 53 58 <LockBlockButton /> 54 59 </div> ··· 72 77 return ( 73 78 <> 74 79 <ToolbarButton 80 + hiddenOnCanvas 75 81 onClick={async () => { 76 82 let [sortedBlocks, siblings] = await getSortedSelection(); 77 83 if (sortedBlocks.length > 1) return; ··· 128 134 </ToolbarButton> 129 135 130 136 <ToolbarButton 137 + hiddenOnCanvas 131 138 onClick={async () => { 132 139 let [sortedBlocks, siblings] = await getSortedSelection(); 133 140 if (sortedBlocks.length > 1) return; ··· 173 180 > 174 181 <MoveBlockDown /> 175 182 </ToolbarButton> 183 + <Separator classname="h-6" /> 176 184 </> 177 185 ); 178 186 };
+34
components/Toolbar/ImageToolbar.tsx
··· 1 + import { 2 + ImageFullBleedOffSmall, 3 + ImageFullBleedOnSmall, 4 + LinkSmall, 5 + } from "components/Icons"; 6 + import { ToolbarButton } from "."; 7 + import { useEntity, useReplicache } from "src/replicache"; 8 + import { useUIState } from "src/useUIState"; 9 + 10 + export const ImageFullBleedButton = (props: {}) => { 11 + let { rep } = useReplicache(); 12 + let focusedBlock = useUIState((s) => s.focusedEntity)?.entityID || null; 13 + let isFullBleed = useEntity(focusedBlock, "image/full-bleed")?.data.value; 14 + 15 + return ( 16 + <ToolbarButton 17 + hiddenOnCanvas 18 + active={isFullBleed} 19 + onClick={async (e) => { 20 + e.preventDefault(); 21 + if (rep && focusedBlock) { 22 + await rep.mutate.assertFact({ 23 + entity: focusedBlock, 24 + attribute: "image/full-bleed", 25 + data: { type: "boolean", value: !isFullBleed }, 26 + }); 27 + } 28 + }} 29 + tooltipContent={<div className="">Toggle Full Bleed</div>} 30 + > 31 + {isFullBleed ? <ImageFullBleedOnSmall /> : <ImageFullBleedOffSmall />} 32 + </ToolbarButton> 33 + ); 34 + };
+5 -4
components/Toolbar/TextAlignmentToolbar.tsx
··· 26 26 <> 27 27 <ToolbarButton 28 28 onClick={() => setAlignment("left")} 29 - tooltipContent="Align Text Left" 29 + tooltipContent="Align Left" 30 30 > 31 31 <AlignLeftSmall /> 32 32 </ToolbarButton> 33 33 <ToolbarButton 34 34 onClick={() => setAlignment("center")} 35 - tooltipContent="Align Text Center" 35 + tooltipContent="Align Center" 36 36 > 37 37 <AlignCenterSmall /> 38 38 </ToolbarButton> 39 39 <ToolbarButton 40 40 onClick={() => setAlignment("right")} 41 - tooltipContent="Align Text Right" 41 + tooltipContent="Align Right" 42 42 > 43 43 <AlignRightSmall /> 44 44 </ToolbarButton> ··· 56 56 .value || "left"; 57 57 return ( 58 58 <ToolbarButton 59 - tooltipContent={<div>Align Text</div>} 59 + hiddenOnCanvas 60 + tooltipContent={<div>Align</div>} 60 61 className={`${props.className}`} 61 62 onClick={() => { 62 63 props.setToolbarState("text-alignment");
+14 -4
components/Toolbar/index.tsx
··· 23 23 export type ToolbarTypes = 24 24 | "areYouSure" 25 25 | "default" 26 + | "block" 27 + | "multiselect" 26 28 | "highlight" 27 29 | "link" 28 30 | "heading" 29 31 | "text-alignment" 30 32 | "list" 31 33 | "linkBlock" 32 - | "block" 33 - | "multiselect"; 34 + | "img-alt-text"; 34 35 35 36 export const Toolbar = (props: { pageID: string; blockID: string }) => { 36 37 let { rep } = useReplicache(); ··· 185 186 children: React.ReactNode; 186 187 active?: boolean; 187 188 disabled?: boolean; 189 + hiddenOnCanvas?: boolean; 188 190 }) => { 189 - let focusedBlock = useUIState((s) => s.focusedEntity); 190 - let isLocked = useEntity(focusedBlock?.entityID || null, "block/is-locked"); 191 + let focusedEntity = useUIState((s) => s.focusedEntity); 192 + let isLocked = useEntity(focusedEntity?.entityID || null, "block/is-locked"); 191 193 let isDisabled = 192 194 props.disabled === undefined ? !!isLocked?.data.value : props.disabled; 193 195 196 + let focusedEntityType = useEntity( 197 + focusedEntity?.entityType === "page" 198 + ? focusedEntity.entityID 199 + : focusedEntity?.parent || null, 200 + "page/type", 201 + ); 202 + if (focusedEntityType?.data.value === "canvas" && props.hiddenOnCanvas) 203 + return; 194 204 return ( 195 205 <TooltipButton 196 206 onMouseDown={(e) => {
+8
src/replicache/attributes.ts
··· 134 134 }, 135 135 } as const; 136 136 137 + const ImageBlockAttributes = { 138 + "image/full-bleed": { 139 + type: "boolean", 140 + cardinality: "one", 141 + }, 142 + } as const; 143 + 137 144 export const ThemeAttributes = { 138 145 "theme/page-leaflet-watermark": { 139 146 type: "boolean", ··· 202 209 ...MailboxAttributes, 203 210 ...EmbedBlockAttributes, 204 211 ...ButtonBlockAttributes, 212 + ...ImageBlockAttributes, 205 213 }; 206 214 type Attribute = typeof Attributes; 207 215 export type Data<A extends keyof typeof Attributes> = {