a tool for shared writing and social publishing

use iframely for urls in embed block editor

+65 -16
+65 -16
components/Blocks/EmbedBlock.tsx
··· 10 import { Input } from "components/Input"; 11 import { isUrl } from "src/utils/isURL"; 12 import { elementId } from "src/utils/elementId"; 13 - import { deleteBlock } from "./DeleteBlock"; 14 import { focusBlock } from "src/utils/focusBlock"; 15 import { useDrag } from "src/hooks/useDrag"; 16 import { BlockEmbedSmall } from "components/Icons/BlockEmbedSmall"; 17 import { CheckTiny } from "components/Icons/CheckTiny"; 18 19 export const EmbedBlock = (props: BlockProps & { preview?: boolean }) => { 20 let { permissions } = useEntitySetContext(); ··· 132 133 let entity_set = useEntitySetContext(); 134 let [linkValue, setLinkValue] = useState(""); 135 let { rep } = useReplicache(); 136 let submit = async () => { 137 let entity = props.entityID; ··· 149 } 150 let link = linkValue; 151 if (!linkValue.startsWith("http")) link = `https://${linkValue}`; 152 - // these mutations = simpler subset of addLinkBlock 153 if (!rep) return; 154 - await rep.mutate.assertFact({ 155 - entity: entity, 156 - attribute: "block/type", 157 - data: { type: "block-type-union", value: "embed" }, 158 - }); 159 - await rep?.mutate.assertFact({ 160 - entity: entity, 161 - attribute: "embed/url", 162 - data: { 163 - type: "string", 164 - value: link, 165 - }, 166 - }); 167 }; 168 let smoker = useSmoker(); 169 ··· 171 <form 172 onSubmit={(e) => { 173 e.preventDefault(); 174 let rect = document 175 .getElementById("embed-block-submit") 176 ?.getBoundingClientRect(); ··· 212 <button 213 type="submit" 214 id="embed-block-submit" 215 className={`p-1 ${isSelected && !isLocked ? "text-accent-contrast" : "text-border"}`} 216 onMouseDown={(e) => { 217 e.preventDefault(); 218 if (!linkValue || linkValue === "") { 219 smoker({ 220 error: true, ··· 234 submit(); 235 }} 236 > 237 - <CheckTiny /> 238 </button> 239 </div> 240 </form>
··· 10 import { Input } from "components/Input"; 11 import { isUrl } from "src/utils/isURL"; 12 import { elementId } from "src/utils/elementId"; 13 import { focusBlock } from "src/utils/focusBlock"; 14 import { useDrag } from "src/hooks/useDrag"; 15 import { BlockEmbedSmall } from "components/Icons/BlockEmbedSmall"; 16 import { CheckTiny } from "components/Icons/CheckTiny"; 17 + import { DotLoader } from "components/utils/DotLoader"; 18 + import { 19 + LinkPreviewBody, 20 + LinkPreviewMetadataResult, 21 + } from "app/api/link_previews/route"; 22 23 export const EmbedBlock = (props: BlockProps & { preview?: boolean }) => { 24 let { permissions } = useEntitySetContext(); ··· 136 137 let entity_set = useEntitySetContext(); 138 let [linkValue, setLinkValue] = useState(""); 139 + let [loading, setLoading] = useState(false); 140 let { rep } = useReplicache(); 141 let submit = async () => { 142 let entity = props.entityID; ··· 154 } 155 let link = linkValue; 156 if (!linkValue.startsWith("http")) link = `https://${linkValue}`; 157 if (!rep) return; 158 + 159 + // Try to get embed URL from iframely, fallback to direct URL 160 + setLoading(true); 161 + try { 162 + let res = await fetch("/api/link_previews", { 163 + headers: { "Content-Type": "application/json" }, 164 + method: "POST", 165 + body: JSON.stringify({ url: link, type: "meta" } as LinkPreviewBody), 166 + }); 167 + 168 + let embedUrl = link; 169 + let embedHeight = 360; 170 + 171 + if (res.status === 200) { 172 + let data = await (res.json() as LinkPreviewMetadataResult); 173 + if (data.success && data.data.links?.player?.[0]) { 174 + let embed = data.data.links.player[0]; 175 + embedUrl = embed.href; 176 + embedHeight = embed.media?.height || 300; 177 + } 178 + } 179 + 180 + await rep.mutate.assertFact([ 181 + { 182 + entity: entity, 183 + attribute: "embed/url", 184 + data: { 185 + type: "string", 186 + value: embedUrl, 187 + }, 188 + }, 189 + { 190 + entity: entity, 191 + attribute: "embed/height", 192 + data: { 193 + type: "number", 194 + value: embedHeight, 195 + }, 196 + }, 197 + ]); 198 + } catch { 199 + // On any error, fallback to using the URL directly 200 + await rep.mutate.assertFact([ 201 + { 202 + entity: entity, 203 + attribute: "embed/url", 204 + data: { 205 + type: "string", 206 + value: link, 207 + }, 208 + }, 209 + ]); 210 + } finally { 211 + setLoading(false); 212 + } 213 }; 214 let smoker = useSmoker(); 215 ··· 217 <form 218 onSubmit={(e) => { 219 e.preventDefault(); 220 + if (loading) return; 221 let rect = document 222 .getElementById("embed-block-submit") 223 ?.getBoundingClientRect(); ··· 259 <button 260 type="submit" 261 id="embed-block-submit" 262 + disabled={loading} 263 className={`p-1 ${isSelected && !isLocked ? "text-accent-contrast" : "text-border"}`} 264 onMouseDown={(e) => { 265 e.preventDefault(); 266 + if (loading) return; 267 if (!linkValue || linkValue === "") { 268 smoker({ 269 error: true, ··· 283 submit(); 284 }} 285 > 286 + {loading ? <DotLoader /> : <CheckTiny />} 287 </button> 288 </div> 289 </form>