a tool for shared writing and social publishing
0
fork

Configure Feed

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

add link block adder to toolbar

+152 -67
+152 -67
components/Toolbar/index.tsx
··· 16 16 BlockSmall, 17 17 StrikethroughSmall, 18 18 HighlightSmall, 19 + CheckTiny, 19 20 } from "components/Icons"; 20 21 import { create } from "zustand"; 21 22 import { combine } from "zustand/middleware"; ··· 40 41 import { useEntitySetContext } from "components/EntitySetProvider"; 41 42 import { generateKeyBetween } from "fractional-indexing"; 42 43 import { focusCard } from "components/Cards"; 44 + import { addLinkBlock } from "src/utils/addLinkBlock"; 43 45 44 46 export const TextToolbar = (props: { cardID: string; blockID: string }) => { 45 47 let { rep } = useReplicache(); 46 48 47 49 let [toolbarState, setToolbarState] = useState< 48 - "default" | "highlight" | "link" | "header" | "list" | "block" 50 + "default" | "highlight" | "link" | "header" | "list" | "block" | "linkBlock" 49 51 >("default"); 50 52 51 53 let [lastUsedHighlight, setlastUsedHighlight] = useState<"1" | "2" | "3">( ··· 133 135 <TextBlockTypeButtons onClose={() => setToolbarState("default")} /> 134 136 ) : toolbarState === "block" ? ( 135 137 <BlockToolbar 138 + setToolbarState={setToolbarState} 136 139 onClose={() => { 137 140 if (blockEmpty) 138 141 useUIState.setState(() => ({ ··· 145 148 setToolbarState("default"); 146 149 }} 147 150 /> 151 + ) : toolbarState === "linkBlock" ? ( 152 + <LinkBlockToolbar 153 + onClose={() => { 154 + setToolbarState("block"); 155 + }} 156 + /> 148 157 ) : null} 149 158 </div> 150 159 <button 151 160 onClick={() => { 152 - if (toolbarState === "default") { 161 + if (toolbarState === "linkBlock") { 162 + setToolbarState("link"); 163 + } else if (toolbarState === "default") { 153 164 useUIState.setState(() => ({ 154 165 focusedBlock: { 155 166 type: "card", ··· 168 179 ); 169 180 }; 170 181 182 + const LinkBlockToolbar = (props: { onClose: () => void }) => { 183 + let entity_set = useEntitySetContext(); 184 + let [linkValue, setLinkValue] = useState(""); 185 + let focusedBlock = useUIState((s) => s.focusedBlock); 186 + let { rep } = useReplicache(); 187 + let submit = async () => { 188 + if (!focusedBlock || !rep) return []; 189 + let entity, position; 190 + let parent = 191 + focusedBlock.type === "card" 192 + ? focusedBlock.entityID 193 + : focusedBlock.parent; 194 + let children = await rep.query((tx) => 195 + scanIndex(tx).eav(parent, "card/block"), 196 + ); 197 + if (focusedBlock.type === "card") { 198 + entity = v7(); 199 + position = 200 + children.sort((a, b) => (a.data.position > b.data.position ? 1 : -1))[ 201 + children.length - 1 202 + ]?.data.position || null; 203 + await rep?.mutate.addBlock({ 204 + parent: focusedBlock.entityID, 205 + permission_set: entity_set.set, 206 + type: "text", 207 + position: generateKeyBetween(position, null), 208 + newEntityID: entity, 209 + }); 210 + } else { 211 + entity = focusedBlock.entityID; 212 + } 213 + addLinkBlock(linkValue, entity, rep); 214 + props.onClose(); 215 + }; 216 + 217 + return ( 218 + <div className={`max-w-sm flex gap-2 rounded-md "text-secondary`}> 219 + <> 220 + <input 221 + autoFocus 222 + id="block-link-input" 223 + type="url" 224 + className="w-full grow border-none outline-none bg-transparent " 225 + placeholder="www.leaflet.pub" 226 + value={linkValue} 227 + onChange={(e) => setLinkValue(e.target.value)} 228 + onKeyDown={(e) => { 229 + if (e.key === "Enter") { 230 + submit(); 231 + } 232 + }} 233 + /> 234 + <div className="flex items-center gap-3 "> 235 + <button 236 + className="hover:text-accent" 237 + onMouseDown={(e) => { 238 + e.preventDefault(); 239 + submit(); 240 + }} 241 + > 242 + <CheckTiny /> 243 + </button> 244 + </div> 245 + </> 246 + </div> 247 + ); 248 + }; 249 + 171 250 const HighlightToolbar = (props: { 172 251 onClose: () => void; 173 252 lastUsedHighlight: "1" | "2" | "3"; ··· 229 308 ); 230 309 }; 231 310 232 - const BlockToolbar = (props: { onClose: () => void }) => { 311 + const BlockToolbar = (props: { 312 + onClose: () => void; 313 + setToolbarState: (s: "linkBlock") => void; 314 + }) => { 315 + let [state, setState] = useState<"normal" | "link">("normal"); 233 316 let { rep } = useReplicache(); 234 317 let entity_set = useEntitySetContext(); 235 318 let focusedBlock = useUIState((s) => s.focusedBlock); ··· 261 344 } 262 345 return [entity, parent]; 263 346 }, [focusedBlock, rep, entity_set]); 264 - return ( 265 - <div className="flex w-full justify-between items-center gap-4"> 266 - <div className="flex items-center gap-[6px]"> 267 - <ToolbarButton onClick={() => props.onClose()}> 268 - <BlockSmall /> 269 - </ToolbarButton> 270 - <Separator /> 271 - <ToolbarButton> 272 - <label 273 - className="blockOptionsImage hover:cursor-pointer flex place-items-center" 274 - onMouseDown={(e) => e.preventDefault()} 347 + if (state === "normal") 348 + return ( 349 + <div className="flex w-full justify-between items-center gap-4"> 350 + <div className="flex items-center gap-[6px]"> 351 + <ToolbarButton onClick={() => props.onClose()}> 352 + <BlockSmall /> 353 + </ToolbarButton> 354 + <Separator /> 355 + <ToolbarButton> 356 + <label 357 + className="blockOptionsImage hover:cursor-pointer flex place-items-center" 358 + onMouseDown={(e) => e.preventDefault()} 359 + > 360 + <div className="text-tertiary hover:text-accent "> 361 + <BlockImageSmall /> 362 + </div> 363 + <div className="hidden"> 364 + <input 365 + type="file" 366 + accept="image/*" 367 + onChange={async (e) => { 368 + let file = e.currentTarget.files?.[0]; 369 + if (!file || !rep || !focusedBlock) return; 370 + let [entity, parent] = await getEntity(); 371 + await rep.mutate.assertFact({ 372 + entity, 373 + attribute: "block/type", 374 + data: { type: "block-type-union", value: "image" }, 375 + }); 376 + await addImage(file, rep, { 377 + entityID: entity, 378 + attribute: "block/image", 379 + }); 380 + }} 381 + /> 382 + </div> 383 + </label> 384 + </ToolbarButton> 385 + <ToolbarButton onClick={() => props.setToolbarState("linkBlock")}> 386 + <BlockLinkSmall /> 387 + </ToolbarButton> 388 + <ToolbarButton 389 + onClick={async () => { 390 + if (!focusedBlock || !rep) return; 391 + let [entity, parent] = await getEntity(); 392 + await rep?.mutate.assertFact({ 393 + entity: entity, 394 + attribute: "block/type", 395 + data: { type: "block-type-union", value: "card" }, 396 + }); 397 + useUIState.getState().openCard(parent, entity); 398 + let entityID = v7(); 399 + await rep?.mutate.addBlock({ 400 + parent: entity, 401 + position: "a0", 402 + newEntityID: entityID, 403 + type: "text", 404 + permission_set: entity_set.set, 405 + }); 406 + focusCard(entity, rep, "focusFirstBlock"); 407 + }} 275 408 > 276 - <div className="text-tertiary hover:text-accent "> 277 - <BlockImageSmall /> 278 - </div> 279 - <div className="hidden"> 280 - <input 281 - type="file" 282 - accept="image/*" 283 - onChange={async (e) => { 284 - let file = e.currentTarget.files?.[0]; 285 - if (!file || !rep || !focusedBlock) return; 286 - let [entity, parent] = await getEntity(); 287 - await rep.mutate.assertFact({ 288 - entity, 289 - attribute: "block/type", 290 - data: { type: "block-type-union", value: "image" }, 291 - }); 292 - await addImage(file, rep, { 293 - entityID: entity, 294 - attribute: "block/image", 295 - }); 296 - }} 297 - /> 298 - </div> 299 - </label> 300 - </ToolbarButton> 301 - <ToolbarButton> 302 - <BlockLinkSmall /> 303 - </ToolbarButton> 304 - <ToolbarButton 305 - onClick={async () => { 306 - if (!focusedBlock || !rep) return; 307 - let [entity, parent] = await getEntity(); 308 - await rep?.mutate.assertFact({ 309 - entity: entity, 310 - attribute: "block/type", 311 - data: { type: "block-type-union", value: "card" }, 312 - }); 313 - useUIState.getState().openCard(parent, entity); 314 - let entityID = v7(); 315 - await rep?.mutate.addBlock({ 316 - parent: entity, 317 - position: "a0", 318 - newEntityID: entityID, 319 - type: "text", 320 - permission_set: entity_set.set, 321 - }); 322 - focusCard(entity, rep, "focusFirstBlock"); 323 - }} 324 - > 325 - <BlockCardSmall /> 326 - </ToolbarButton> 409 + <BlockCardSmall /> 410 + </ToolbarButton> 411 + </div> 327 412 </div> 328 - </div> 329 - ); 413 + ); 414 + return; 330 415 }; 331 416 332 417 export const ToolbarButton = (props: {