a tool for shared writing and social publishing
0
fork

Configure Feed

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

delete blockoptions component

has been replaced by / commands!

-312
-311
components/Blocks/BlockOptions.tsx
··· 1 - import { useEntity, useReplicache } from "src/replicache"; 2 - import { useUIState } from "src/useUIState"; 3 - import { 4 - BlockDocPageSmall, 5 - BlockImageSmall, 6 - CheckTiny, 7 - CloseTiny, 8 - LinkSmall, 9 - BlockMailboxSmall, 10 - } from "components/Icons"; 11 - import { generateKeyBetween } from "fractional-indexing"; 12 - import { addImage } from "src/utils/addImage"; 13 - import { focusPage } from "components/Pages"; 14 - import { useState } from "react"; 15 - import { Separator } from "components/Layout"; 16 - import { addLinkBlock } from "src/utils/addLinkBlock"; 17 - import { useEntitySetContext } from "components/EntitySetProvider"; 18 - import { v7 } from "uuid"; 19 - import { Input } from "components/Input"; 20 - import { ToolbarButton } from "components/Toolbar"; 21 - import * as Tooltip from "@radix-ui/react-tooltip"; 22 - import { 23 - TextBlockTypeButton, 24 - TextBlockTypeToolbar, 25 - } from "components/Toolbar/TextBlockTypeToolbar"; 26 - import { isUrl } from "src/utils/isURL"; 27 - import { useSmoker, useToaster } from "components/Toast"; 28 - 29 - type Props = { 30 - parent: string; 31 - entityID: string; 32 - position: string | null; 33 - nextPosition: string | null; 34 - factID?: string | undefined; 35 - first?: boolean; 36 - className?: string; 37 - }; 38 - export function BlockOptions(props: Props) { 39 - let { rep } = useReplicache(); 40 - let entity_set = useEntitySetContext(); 41 - let [blockMenuState, setblockMenuState] = useState< 42 - "default" | "link" | "heading" 43 - >("default"); 44 - 45 - let focusedElement = useUIState((s) => s.focusedEntity); 46 - let focusedPageID = 47 - focusedElement?.entityType === "page" 48 - ? focusedElement.entityID 49 - : focusedElement?.parent; 50 - 51 - let type = useEntity(props.entityID, "block/type"); 52 - 53 - return ( 54 - <Tooltip.Provider> 55 - <div 56 - className={`blockOptionsWrapper w-fit hidden sm:group-hover/text:flex group-focus-within/text:flex place-items-center ${props.className}`} 57 - > 58 - <div className="blockOptionsdefaultContent flex gap-1 items-center"> 59 - {blockMenuState === "default" && ( 60 - <> 61 - <ToolbarButton 62 - tooltipContent="Add an Image" 63 - className="text-tertiary h-6" 64 - > 65 - <label onMouseDown={(e) => e.preventDefault()}> 66 - <BlockImageSmall /> 67 - <div className="hidden"> 68 - <input 69 - type="file" 70 - accept="image/*" 71 - onChange={async (e) => { 72 - let file = e.currentTarget.files?.[0]; 73 - if (!file || !rep) return; 74 - if (props.factID) 75 - await rep.mutate.retractFact({ 76 - factID: props.factID, 77 - }); 78 - let entity = props.entityID; 79 - if (!entity) { 80 - entity = v7(); 81 - await rep?.mutate.addBlock({ 82 - parent: props.parent, 83 - factID: v7(), 84 - permission_set: entity_set.set, 85 - type: "text", 86 - position: generateKeyBetween( 87 - props.position, 88 - props.nextPosition, 89 - ), 90 - newEntityID: entity, 91 - }); 92 - } 93 - await rep.mutate.assertFact({ 94 - entity, 95 - attribute: "block/type", 96 - data: { type: "block-type-union", value: "image" }, 97 - }); 98 - await addImage(file, rep, { 99 - entityID: entity, 100 - attribute: "block/image", 101 - }); 102 - }} 103 - /> 104 - </div> 105 - </label> 106 - </ToolbarButton> 107 - <ToolbarButton 108 - tooltipContent="Add a page" 109 - className="text-tertiary h-6" 110 - onClick={async () => { 111 - let entity; 112 - if (!props.entityID) { 113 - entity = v7(); 114 - 115 - await rep?.mutate.addBlock({ 116 - permission_set: entity_set.set, 117 - factID: v7(), 118 - parent: props.parent, 119 - type: "card", 120 - position: generateKeyBetween( 121 - props.position, 122 - props.nextPosition, 123 - ), 124 - newEntityID: entity, 125 - }); 126 - } else { 127 - entity = props.entityID; 128 - await rep?.mutate.assertFact({ 129 - entity, 130 - attribute: "block/type", 131 - data: { type: "block-type-union", value: "card" }, 132 - }); 133 - } 134 - let newPage = v7(); 135 - await rep?.mutate.addPageLinkBlock({ 136 - type: "doc", 137 - blockEntity: entity, 138 - firstBlockFactID: v7(), 139 - firstBlockEntity: v7(), 140 - pageEntity: newPage, 141 - permission_set: entity_set.set, 142 - }); 143 - useUIState.getState().openPage(props.parent, newPage); 144 - if (rep) focusPage(newPage, rep, "focusFirstBlock"); 145 - }} 146 - > 147 - <BlockDocPageSmall /> 148 - </ToolbarButton> 149 - <ToolbarButton 150 - tooltipContent="Add a Link" 151 - className="text-tertiary h-6" 152 - onClick={() => { 153 - setblockMenuState("link"); 154 - }} 155 - > 156 - <LinkSmall /> 157 - </ToolbarButton> 158 - 159 - <ToolbarButton 160 - tooltipContent="Add a Mailbox" 161 - className="text-tertiary h-6" 162 - onClick={async () => { 163 - let entity; 164 - if (!props.entityID) { 165 - entity = v7(); 166 - await rep?.mutate.addBlock({ 167 - parent: props.parent, 168 - factID: v7(), 169 - permission_set: entity_set.set, 170 - type: "mailbox", 171 - position: generateKeyBetween( 172 - props.position, 173 - props.nextPosition, 174 - ), 175 - newEntityID: entity, 176 - }); 177 - } else { 178 - entity = props.entityID; 179 - await rep?.mutate.assertFact({ 180 - entity, 181 - attribute: "block/type", 182 - data: { type: "block-type-union", value: "mailbox" }, 183 - }); 184 - } 185 - }} 186 - > 187 - <BlockMailboxSmall /> 188 - </ToolbarButton> 189 - <Separator classname="h-6" /> 190 - <TextBlockTypeButton 191 - className="hover:text-primary text-tertiary h-6" 192 - setToolbarState={() => setblockMenuState("heading")} 193 - /> 194 - </> 195 - )} 196 - {blockMenuState === "heading" && ( 197 - <> 198 - <TextBlockTypeToolbar 199 - className="bg-transparent hover:text-primary text-tertiary " 200 - onClose={() => setblockMenuState("default")} 201 - /> 202 - <Separator classname="h-6" /> 203 - <button 204 - className="hover:text-accent-contrast" 205 - onClick={() => setblockMenuState("default")} 206 - > 207 - <CloseTiny /> 208 - </button> 209 - </> 210 - )} 211 - {blockMenuState === "link" && ( 212 - <> 213 - <BlockLinkInput 214 - onClose={() => { 215 - setblockMenuState("default"); 216 - }} 217 - {...props} 218 - /> 219 - </> 220 - )} 221 - </div> 222 - </div> 223 - </Tooltip.Provider> 224 - ); 225 - } 226 - 227 - const BlockLinkInput = (props: { onClose: () => void } & Props) => { 228 - let entity_set = useEntitySetContext(); 229 - let [linkValue, setLinkValue] = useState(""); 230 - let { rep } = useReplicache(); 231 - let submit = async () => { 232 - let entity = props.entityID; 233 - if (!entity) { 234 - entity = v7(); 235 - 236 - await rep?.mutate.addBlock({ 237 - permission_set: entity_set.set, 238 - factID: v7(), 239 - parent: props.parent, 240 - type: "card", 241 - position: generateKeyBetween(props.position, props.nextPosition), 242 - newEntityID: entity, 243 - }); 244 - } 245 - let link = linkValue; 246 - if (!linkValue.startsWith("http")) link = `https://${linkValue}`; 247 - addLinkBlock(link, entity, rep); 248 - props.onClose(); 249 - }; 250 - let smoke = useSmoker(); 251 - 252 - return ( 253 - <div className={`max-w-sm flex gap-2 rounded-md text-secondary`}> 254 - <> 255 - <LinkSmall className="shrink-0" /> 256 - <Separator /> 257 - <Input 258 - autoFocus 259 - type="url" 260 - className="w-full grow border-none outline-none bg-transparent " 261 - placeholder="www.example.com" 262 - value={linkValue} 263 - onChange={(e) => setLinkValue(e.target.value)} 264 - onBlur={() => props.onClose()} 265 - onKeyDown={(e) => { 266 - if (e.key === "Enter") { 267 - if (!linkValue) return; 268 - if (!isUrl(linkValue)) { 269 - let rect = e.currentTarget.getBoundingClientRect(); 270 - smoke({ 271 - error: true, 272 - text: "invalid url!", 273 - position: { x: rect.left, y: rect.top - 8 }, 274 - }); 275 - return; 276 - } 277 - submit(); 278 - } 279 - }} 280 - /> 281 - <div className="flex items-center gap-3 "> 282 - <button 283 - disabled={!linkValue || linkValue === ""} 284 - className="hover:text-accent-contrast disabled:text-border" 285 - onMouseDown={(e) => { 286 - e.preventDefault(); 287 - if (!linkValue) return; 288 - if (!isUrl(linkValue)) { 289 - smoke({ 290 - error: true, 291 - text: "invalid url!", 292 - position: { x: e.clientX, y: e.clientY }, 293 - }); 294 - return; 295 - } 296 - submit(); 297 - }} 298 - > 299 - <CheckTiny /> 300 - </button> 301 - <button 302 - className="hover:text-accent-contrast" 303 - onClick={() => props.onClose()} 304 - > 305 - <CloseTiny /> 306 - </button> 307 - </div> 308 - </> 309 - </div> 310 - ); 311 - };
-1
components/Blocks/index.tsx
··· 13 13 import { generateKeyBetween } from "fractional-indexing"; 14 14 import { v7 } from "uuid"; 15 15 16 - import { BlockOptions } from "./BlockOptions"; 17 16 import { Block } from "./Block"; 18 17 import { useEffect } from "react"; 19 18 import { addShortcut } from "src/shortcuts";