a tool for shared writing and social publishing
0
fork

Configure Feed

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

Feature/link transformer (#127)

* text aligned the placeholder in empty text block and moved cmd options into separate component

* added button to turn link into block

* changed button text

* changed button text again lol

* support for if Locked

authored by

celine and committed by
GitHub
19a78e04 90b6bebf

+153 -91
+13
app/globals.css
··· 177 177 background-color: transparent; 178 178 } 179 179 180 + .selected-outline { 181 + @apply border; 182 + @apply focus:outline; 183 + @apply focus:outline-2; 184 + @apply focus:outline-offset-1; 185 + @apply focus-within:outline; 186 + @apply focus-within:outline-2; 187 + @apply focus-within:outline-offset-1; 188 + @apply hover:outline; 189 + @apply hover:outline-2; 190 + @apply hover:outline-offset-1; 191 + } 192 + 180 193 .input-with-border { 181 194 @apply border; 182 195 @apply border-border;
-1
components/Blocks/ExternalLinkBlock.tsx
··· 30 30 useEffect(() => { 31 31 if (props.preview) return; 32 32 let input = document.getElementById(elementId.block(props.entityID).input); 33 - console.log(isSelected, input); 34 33 if (isSelected) { 35 34 setTimeout(() => { 36 35 let input = document.getElementById(
+140 -90
components/Blocks/TextBlock/index.tsx
··· 53 53 import { v7 } from "uuid"; 54 54 import { focusPage } from "components/Pages"; 55 55 import { blockCommands } from "../BlockCommands"; 56 + import { CommandPage } from "twilio/lib/rest/wireless/v1/command"; 57 + import { betterIsUrl, isUrl } from "src/utils/isURL"; 56 58 57 59 export function TextBlock( 58 60 props: BlockProps & { className?: string; preview?: boolean }, ··· 349 351 window.clearTimeout(actionTimeout.current); 350 352 actionTimeout.current = null; 351 353 } 352 - if (editorState.doc.textContent.startsWith("http")) { 353 - await addLinkBlock( 354 - editorState.doc.textContent, 355 - props.entityID, 356 - rep.rep, 357 - ); 358 - } 354 + // if (editorState.doc.textContent.startsWith("http")) { 355 + // await addLinkBlock( 356 + // editorState.doc.textContent, 357 + // props.entityID, 358 + // rep.rep, 359 + // ); 360 + // } 359 361 }} 360 362 onFocus={() => { 361 363 setTimeout(() => { ··· 386 388 props.nextBlock === null ? ( 387 389 // if this is the only block on the page and is empty or is a canvas, show placeholder 388 390 <div 389 - className={`${props.className} pointer-events-none absolute top-0 left-0 italic text-tertiary flex flex-col`} 391 + className={`${props.className} ${alignmentClass} w-full pointer-events-none absolute top-0 left-0 italic text-tertiary flex flex-col`} 390 392 > 391 393 {props.type === "text" 392 394 ? "write something..." ··· 401 403 </div> 402 404 ) : editorState.doc.textContent.length === 0 && focused ? ( 403 405 // if not the only block on page but is the block is empty and selected, but NOT multiselected show add button 404 - <div 405 - className={`absolute top-0 right-0 w-fit flex gap-[6px] items-center font-bold rounded-md text-sm text-border ${props.pageType === "canvas" && "mr-[6px]"}`} 406 - > 407 - <TooltipButton 408 - className={props.className} 409 - onMouseDown={async () => { 410 - let command = blockCommands.find((f) => f.name === "Image"); 411 - if (!rep.rep) return; 412 - await command?.onSelect( 413 - rep.rep, 414 - { ...props, entity_set: entity_set.set }, 415 - rep.undoManager, 416 - ); 417 - }} 418 - side="bottom" 419 - tooltipContent={ 420 - <div className="flex gap-1 font-bold">Add an Image</div> 421 - } 422 - > 423 - <BlockImageSmall className="hover:text-accent-contrast text-border" /> 424 - </TooltipButton> 425 - 426 - <TooltipButton 427 - className={props.className} 428 - onMouseDown={async () => { 429 - let command = blockCommands.find((f) => f.name === "New Page"); 430 - if (!rep.rep) return; 431 - await command?.onSelect( 432 - rep.rep, 433 - { ...props, entity_set: entity_set.set }, 434 - rep.undoManager, 435 - ); 436 - }} 437 - side="bottom" 438 - tooltipContent={ 439 - <div className="flex gap-1 font-bold">Add a Subpage</div> 440 - } 441 - > 442 - <BlockDocPageSmall className="hover:text-accent-contrast text-border" /> 443 - </TooltipButton> 444 - 445 - <TooltipButton 446 - className={props.className} 447 - onMouseDown={(e) => { 448 - e.preventDefault(); 449 - let editor = 450 - useEditorStates.getState().editorStates[props.entityID]; 451 - 452 - let editorState = editor?.editor; 453 - if (editorState) { 454 - editor?.view?.focus(); 455 - let tr = editorState.tr.insertText("/", 1); 456 - tr.setSelection(TextSelection.create(tr.doc, 2)); 457 - useEditorStates.setState((s) => ({ 458 - editorStates: { 459 - ...s.editorStates, 460 - [props.entityID]: { 461 - ...s.editorStates[props.entityID]!, 462 - editor: editorState!.apply(tr), 463 - }, 464 - }, 465 - })); 466 - } 467 - focusBlock( 468 - { 469 - type: props.type, 470 - value: props.entityID, 471 - parent: props.parent, 472 - }, 473 - { type: "end" }, 474 - ); 475 - }} 476 - side="bottom" 477 - tooltipContent={ 478 - <div className="flex gap-1 font-bold">Add More!</div> 479 - } 480 - > 481 - <div className="w-6 h-6 flex place-items-center justify-center"> 482 - <AddTiny className="text-accent-contrast" /> 483 - </div> 484 - </TooltipButton> 485 - </div> 406 + <CommandOptions {...props} className={props.className} /> 486 407 ) : null} 487 408 488 409 {editorState.doc.textContent.startsWith("/") && selected && ( ··· 492 413 /> 493 414 )} 494 415 </div> 416 + <BlockifyLink entityID={props.entityID} /> 495 417 <SyncView entityID={props.entityID} parentID={props.parent} /> 496 418 <CommandHandler entityID={props.entityID} /> 497 419 </ProseMirror> 498 420 ); 499 421 } 422 + 423 + const BlockifyLink = (props: { entityID: string }) => { 424 + let rep = useReplicache(); 425 + let isLocked = useEntity(props.entityID, "block/is-locked"); 426 + let focused = useUIState((s) => s.focusedEntity?.entityID === props.entityID); 427 + let editorState = useEditorStates( 428 + (s) => s.editorStates[props.entityID], 429 + )?.editor; 430 + 431 + let isBlueskyPost = 432 + editorState?.doc.textContent.includes("bsky.app/") && 433 + editorState?.doc.textContent.includes("post"); 434 + // only if the line stats with http or https and doesn't have other content 435 + // if its bluesky, change text to embed post 436 + if ( 437 + !isLocked && 438 + focused && 439 + editorState && 440 + betterIsUrl(editorState.doc.textContent) && 441 + !editorState.doc.textContent.includes(" ") 442 + ) { 443 + return ( 444 + <button 445 + onClick={async () => { 446 + if (isBlueskyPost) { 447 + console.log("bluesky embed coming soon!"); 448 + } 449 + rep.undoManager.startGroup(); 450 + await addLinkBlock( 451 + editorState.doc.textContent, 452 + props.entityID, 453 + rep.rep, 454 + ); 455 + rep.undoManager.endGroup(); 456 + }} 457 + className="absolute right-0 top-0 px-1 py-0.5 text-xs text-tertiary sm:hover:text-accent-contrast border border-border-light sm:hover:border-accent-contrast sm:outline-accent-tertiary rounded-md bg-bg-page selected-outline " 458 + > 459 + embed 460 + </button> 461 + ); 462 + } else return null; 463 + }; 464 + 465 + const CommandOptions = (props: BlockProps & { className?: string }) => { 466 + let rep = useReplicache(); 467 + let entity_set = useEntitySetContext(); 468 + return ( 469 + <div 470 + className={`absolute top-0 right-0 w-fit flex gap-[6px] items-center font-bold rounded-md text-sm text-border ${props.pageType === "canvas" && "mr-[6px]"}`} 471 + > 472 + <TooltipButton 473 + className={props.className} 474 + onMouseDown={async () => { 475 + let command = blockCommands.find((f) => f.name === "Image"); 476 + if (!rep.rep) return; 477 + await command?.onSelect( 478 + rep.rep, 479 + { ...props, entity_set: entity_set.set }, 480 + rep.undoManager, 481 + ); 482 + }} 483 + side="bottom" 484 + tooltipContent={ 485 + <div className="flex gap-1 font-bold">Add an Image</div> 486 + } 487 + > 488 + <BlockImageSmall className="hover:text-accent-contrast text-border" /> 489 + </TooltipButton> 490 + 491 + <TooltipButton 492 + className={props.className} 493 + onMouseDown={async () => { 494 + let command = blockCommands.find((f) => f.name === "New Page"); 495 + if (!rep.rep) return; 496 + await command?.onSelect( 497 + rep.rep, 498 + { ...props, entity_set: entity_set.set }, 499 + rep.undoManager, 500 + ); 501 + }} 502 + side="bottom" 503 + tooltipContent={ 504 + <div className="flex gap-1 font-bold">Add a Subpage</div> 505 + } 506 + > 507 + <BlockDocPageSmall className="hover:text-accent-contrast text-border" /> 508 + </TooltipButton> 509 + 510 + <TooltipButton 511 + className={props.className} 512 + onMouseDown={(e) => { 513 + e.preventDefault(); 514 + let editor = useEditorStates.getState().editorStates[props.entityID]; 515 + 516 + let editorState = editor?.editor; 517 + if (editorState) { 518 + editor?.view?.focus(); 519 + let tr = editorState.tr.insertText("/", 1); 520 + tr.setSelection(TextSelection.create(tr.doc, 2)); 521 + useEditorStates.setState((s) => ({ 522 + editorStates: { 523 + ...s.editorStates, 524 + [props.entityID]: { 525 + ...s.editorStates[props.entityID]!, 526 + editor: editorState!.apply(tr), 527 + }, 528 + }, 529 + })); 530 + } 531 + focusBlock( 532 + { 533 + type: props.type, 534 + value: props.entityID, 535 + parent: props.parent, 536 + }, 537 + { type: "end" }, 538 + ); 539 + }} 540 + side="bottom" 541 + tooltipContent={<div className="flex gap-1 font-bold">Add More!</div>} 542 + > 543 + <div className="w-6 h-6 flex place-items-center justify-center"> 544 + <AddTiny className="text-accent-contrast" /> 545 + </div> 546 + </TooltipButton> 547 + </div> 548 + ); 549 + }; 500 550 501 551 function CommandHandler(props: { entityID: string }) { 502 552 let cb = useEditorEventCallback(