a tool for shared writing and social publishing

use iframely for web metadata + embeds (#198)

* use iframely for web metadata + embeds

* use hash again

authored by awarm.space and committed by

GitHub 969c975d 9321d0a6

+196 -111
+5 -34
components/Blocks/TextBlock/index.tsx
··· 36 36 import { BlockImageSmall } from "components/Icons/BlockImageSmall"; 37 37 import { isIOS } from "src/utils/isDevice"; 38 38 import { useLeafletPublicationData } from "components/PageSWRDataProvider"; 39 + import { DotLoader } from "components/utils/DotLoader"; 39 40 40 41 const HeadingStyle = { 41 42 1: "text-xl font-bold", ··· 434 435 entityID: string; 435 436 editorState: EditorState | undefined; 436 437 }) => { 438 + let [loading, setLoading] = useState(false); 437 439 let { editorState } = props; 438 440 let rep = useReplicache(); 439 441 let smoker = useSmoker(); ··· 458 460 onClick={async (e) => { 459 461 if (!rep.rep) return; 460 462 rep.undoManager.startGroup(); 461 - function extractYoutubeId(url: string) { 462 - const pattern = 463 - /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/; 464 - const match = url.match(pattern); 465 - return match ? match[1] : null; 466 - } 467 - let videoId = extractYoutubeId(editorState.doc.textContent); 468 - if (videoId) { 469 - await rep.rep.mutate.assertFact([ 470 - { 471 - entity: props.entityID, 472 - attribute: "block/type", 473 - data: { type: "block-type-union", value: "embed" }, 474 - }, 475 - { 476 - entity: props.entityID, 477 - attribute: "embed/url", 478 - data: { 479 - type: "string", 480 - value: `https://youtube.com/embed/${videoId}`, 481 - }, 482 - }, 483 - { 484 - entity: props.entityID, 485 - attribute: "embed/height", 486 - data: { 487 - type: "number", 488 - value: 315, 489 - }, 490 - }, 491 - ]); 492 - return; 493 - } 494 463 if (isBlueskyPost) { 495 464 let success = await addBlueskyPostBlock( 496 465 editorState.doc.textContent, ··· 507 476 }, 508 477 }); 509 478 } else { 479 + setLoading(true); 510 480 await addLinkBlock( 511 481 editorState.doc.textContent, 512 482 props.entityID, 513 483 rep.rep, 514 484 ); 485 + setLoading(false); 515 486 } 516 487 rep.undoManager.endGroup(); 517 488 }} 518 489 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 " 519 490 > 520 - embed 491 + {loading ? <DotLoader /> : "embed"} 521 492 </button> 522 493 ); 523 494 } else return null;
+100 -39
src/utils/addLinkBlock.ts
··· 15 15 ) { 16 16 if (!rep) return; 17 17 18 + let res = await fetch("/api/link_previews", { 19 + headers: { "Content-Type": "application/json" }, 20 + method: "POST", 21 + body: JSON.stringify({ url, type: "meta" } as LinkPreviewBody), 22 + }); 23 + if (res.status !== 200) { 24 + await rep?.mutate.assertFact([ 25 + { 26 + entity: entityID, 27 + attribute: "link/url", 28 + data: { 29 + type: "string", 30 + value: url, 31 + }, 32 + }, 33 + { 34 + entity: entityID, 35 + attribute: "block/type", 36 + data: { type: "block-type-union", value: "link" }, 37 + }, 38 + ]); 39 + return; 40 + } 41 + let data = await (res.json() as LinkPreviewMetadataResult); 42 + if (!data.success) { 43 + await rep?.mutate.assertFact([ 44 + { 45 + entity: entityID, 46 + attribute: "link/url", 47 + data: { 48 + type: "string", 49 + value: url, 50 + }, 51 + }, 52 + { 53 + entity: entityID, 54 + attribute: "block/type", 55 + data: { type: "block-type-union", value: "link" }, 56 + }, 57 + ]); 58 + return; 59 + } 60 + 61 + if (data.data.links?.player?.[0]) { 62 + console.log(data.data.links.player); 63 + let embed = data.data.links?.player?.[0]; 64 + await rep.mutate.assertFact([ 65 + { 66 + entity: entityID, 67 + attribute: "block/type", 68 + data: { type: "block-type-union", value: "embed" }, 69 + }, 70 + { 71 + entity: entityID, 72 + attribute: "embed/url", 73 + data: { 74 + type: "string", 75 + value: embed.href, 76 + }, 77 + }, 78 + { 79 + entity: entityID, 80 + attribute: "embed/height", 81 + data: { 82 + type: "number", 83 + value: embed.media?.height || 300, 84 + }, 85 + }, 86 + ]); 87 + return; 88 + } 18 89 await rep?.mutate.assertFact([ 19 90 { 20 91 entity: entityID, ··· 29 100 attribute: "block/type", 30 101 data: { type: "block-type-union", value: "link" }, 31 102 }, 103 + { 104 + entity: entityID, 105 + attribute: "link/title", 106 + data: { 107 + type: "string", 108 + value: data.data.meta?.title || "", 109 + }, 110 + }, 111 + { 112 + entity: entityID, 113 + attribute: "link/description", 114 + data: { 115 + type: "string", 116 + value: data.data.meta?.description || "", 117 + }, 118 + }, 32 119 ]); 33 - fetch("/api/link_previews", { 120 + let imageRes = await fetch("/api/link_previews", { 34 121 headers: { "Content-Type": "application/json" }, 35 122 method: "POST", 36 - body: JSON.stringify({ url, type: "meta" } as LinkPreviewBody), 37 - }).then(async (res) => { 38 - let data = await (res.json() as LinkPreviewMetadataResult); 39 - if (data.success) { 40 - await rep?.mutate.assertFact({ 41 - entity: entityID, 42 - attribute: "link/title", 43 - data: { 44 - type: "string", 45 - value: data.data.data.title || "", 46 - }, 47 - }); 48 - await rep?.mutate.assertFact({ 49 - entity: entityID, 50 - attribute: "link/description", 51 - data: { 52 - type: "string", 53 - value: data.data.data.description || "", 54 - }, 55 - }); 56 - } 123 + body: JSON.stringify({ url, type: "image" } as LinkPreviewBody), 57 124 }); 58 125 59 - fetch("/api/link_previews", { 60 - headers: { "Content-Type": "application/json" }, 61 - method: "POST", 62 - body: JSON.stringify({ url, type: "image" } as LinkPreviewBody), 63 - }).then(async (res) => { 64 - let data = await (res.json() as LinkPreviewImageResult); 126 + let image_data = await (imageRes.json() as LinkPreviewImageResult); 65 127 66 - await rep?.mutate.assertFact({ 67 - entity: entityID, 68 - attribute: "link/preview", 69 - data: { 70 - fallback: "", 71 - type: "image", 72 - src: data.url, 73 - width: data.width, 74 - height: data.height, 75 - }, 76 - }); 128 + await rep?.mutate.assertFact({ 129 + entity: entityID, 130 + attribute: "link/preview", 131 + data: { 132 + fallback: "", 133 + type: "image", 134 + src: image_data.url, 135 + width: image_data.width, 136 + height: image_data.height, 137 + }, 77 138 }); 78 139 } 79 140