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 website blocks

+197 -5
+28 -2
actions/publishToPublication.ts
··· 13 13 PubLeafletDocument, 14 14 PubLeafletPagesLinearDocument, 15 15 PubLeafletRichtextFacet, 16 + PubLeafletBlocksWebsite, 16 17 } from "lexicons/api"; 17 18 import { Block } from "components/Blocks/Block"; 18 19 import { TID } from "@atproto/common"; ··· 73 74 let images = blocks 74 75 .filter((b) => b.type === "image") 75 76 .map((b) => scan.eav(b.value, "block/image")[0]); 77 + let links = blocks 78 + .filter((b) => b.type == "link") 79 + .map((b) => scan.eav(b.value, "link/preview")[0]); 76 80 let imageMap = new Map<string, BlobRef>(); 77 81 await Promise.all( 78 - images.map(async (b) => { 82 + [...links, ...images].map(async (b) => { 79 83 let data = await fetch(b.data.src); 80 84 if (data.status !== 200) return; 81 85 let binary = await data.blob(); ··· 210 214 let facets = YJSFragmentToFacets(nodes[0]); 211 215 return [stringValue, facets] as const; 212 216 }; 213 - if (b.type !== "text" && b.type !== "heading" && b.type !== "image") return; 217 + if ( 218 + b.type !== "text" && 219 + b.type !== "heading" && 220 + b.type !== "image" && 221 + b.type !== "link" 222 + ) 223 + return; 214 224 let alignmentValue = 215 225 scan.eav(b.value, "block/text-alignment")[0]?.data.value || "left"; 216 226 ··· 248 258 height: image.data.height, 249 259 width: image.data.width, 250 260 }, 261 + }; 262 + return block; 263 + } 264 + if (b.type === "link") { 265 + let [previewImage] = scan.eav(b.value, "link/preview"); 266 + let [description] = scan.eav(b.value, "link/description"); 267 + let [src] = scan.eav(b.value, "link/url"); 268 + if (!src) return; 269 + let blobref = imageMap.get(previewImage.data.src); 270 + let [title] = scan.eav(b.value, "link/title"); 271 + let block: $Typed<PubLeafletBlocksWebsite.Main> = { 272 + $type: "pub.leaflet.blocks.website", 273 + previewImage: blobref, 274 + src: src.data.value, 275 + description: description.data.value, 276 + title: title.data.value, 251 277 }; 252 278 return block; 253 279 }
+56 -2
app/lish/[did]/[publication]/[rkey]/page.tsx
··· 6 6 PubLeafletBlocksHeader, 7 7 PubLeafletBlocksImage, 8 8 PubLeafletBlocksText, 9 + PubLeafletBlocksWebsite, 9 10 PubLeafletBlocksUnorderedList, 10 11 PubLeafletDocument, 11 12 PubLeafletPagesLinearDocument, ··· 14 15 import { getPublicationURL } from "app/lish/createPub/getPublicationURL"; 15 16 import { TextBlock } from "./TextBlock"; 16 17 import { ThemeProvider } from "components/ThemeManager/ThemeProvider"; 17 - import { BskyAgent } from "@atproto/api"; 18 + import { BlobRef, BskyAgent } from "@atproto/api"; 18 19 import { SubscribeWithBluesky } from "app/lish/Subscribe"; 19 20 20 21 export async function generateMetadata(props: { ··· 176 177 </ul> 177 178 ); 178 179 } 180 + case PubLeafletBlocksWebsite.isMain(b.block): { 181 + return ( 182 + <a 183 + href={b.block.src} 184 + target="_blank" 185 + className={` 186 + externalLinkBlock flex relative group/linkBlock 187 + h-[104px] w-full bg-bg-page overflow-hidden text-primary hover:no-underline no-underline 188 + hover:border-accent-contrast shadow-sm 189 + block-border 190 + `} 191 + > 192 + <div className="pt-2 pb-2 px-3 grow min-w-0"> 193 + <div className="flex flex-col w-full min-w-0 h-full grow "> 194 + <div 195 + className={`linkBlockTitle bg-transparent -mb-0.5 border-none text-base font-bold outline-none resize-none align-top border h-[24px] line-clamp-1`} 196 + style={{ 197 + overflow: "hidden", 198 + textOverflow: "ellipsis", 199 + wordBreak: "break-all", 200 + }} 201 + > 202 + {b.block.title} 203 + </div> 204 + 205 + <div 206 + className={`linkBlockDescription text-sm bg-transparent border-none outline-none resize-none align-top grow line-clamp-2`} 207 + > 208 + {b.block.description} 209 + </div> 210 + <div 211 + style={{ wordBreak: "break-word" }} // better than tailwind break-all! 212 + className={`min-w-0 w-full line-clamp-1 text-xs italic group-hover/linkBlock:text-accent-contrast text-tertiary`} 213 + > 214 + {b.block.src} 215 + </div> 216 + </div> 217 + </div> 218 + {b.block.previewImage && ( 219 + <div 220 + className={`linkBlockPreview w-[120px] m-2 -mb-2 bg-cover shrink-0 rounded-t-md border border-border rotate-[4deg] origin-center`} 221 + style={{ 222 + backgroundImage: `url(${blobRefToSrc(b.block.previewImage?.ref, did)})`, 223 + backgroundPosition: "center", 224 + }} 225 + /> 226 + )} 227 + </a> 228 + ); 229 + } 179 230 case PubLeafletBlocksImage.isMain(b.block): { 180 231 return ( 181 232 <img 182 233 height={b.block.aspectRatio?.height} 183 234 width={b.block.aspectRatio?.width} 184 235 className={`!pt-3 sm:!pt-4 ${className}`} 185 - src={`/api/atproto_images?did=${did}&cid=${(b.block.image.ref as unknown as { $link: string })["$link"]}`} 236 + src={blobRefToSrc(b.block.image.ref, did)} 186 237 /> 187 238 ); 188 239 } ··· 223 274 return null; 224 275 } 225 276 }; 277 + 278 + const blobRefToSrc = (b: BlobRef["ref"], did: string) => 279 + `/api/atproto_images?did=${did}&cid=${(b as unknown as { $link: string })["$link"]}`; 226 280 227 281 function ListItem(props: { 228 282 item: PubLeafletBlocksUnorderedList.ListItem;
+2
lexicons/api/index.ts
··· 11 11 import * as PubLeafletBlocksImage from './types/pub/leaflet/blocks/image' 12 12 import * as PubLeafletBlocksText from './types/pub/leaflet/blocks/text' 13 13 import * as PubLeafletBlocksUnorderedList from './types/pub/leaflet/blocks/unorderedList' 14 + import * as PubLeafletBlocksWebsite from './types/pub/leaflet/blocks/website' 14 15 import * as PubLeafletGraphSubscription from './types/pub/leaflet/graph/subscription' 15 16 import * as PubLeafletPagesLinearDocument from './types/pub/leaflet/pages/linearDocument' 16 17 import * as PubLeafletRichtextFacet from './types/pub/leaflet/richtext/facet' ··· 35 36 export * as PubLeafletBlocksImage from './types/pub/leaflet/blocks/image' 36 37 export * as PubLeafletBlocksText from './types/pub/leaflet/blocks/text' 37 38 export * as PubLeafletBlocksUnorderedList from './types/pub/leaflet/blocks/unorderedList' 39 + export * as PubLeafletBlocksWebsite from './types/pub/leaflet/blocks/website' 38 40 export * as PubLeafletGraphSubscription from './types/pub/leaflet/graph/subscription' 39 41 export * as PubLeafletPagesLinearDocument from './types/pub/leaflet/pages/linearDocument' 40 42 export * as PubLeafletRichtextFacet from './types/pub/leaflet/richtext/facet'
+29
lexicons/api/lexicons.ts
··· 223 223 }, 224 224 }, 225 225 }, 226 + PubLeafletBlocksWebsite: { 227 + lexicon: 1, 228 + id: 'pub.leaflet.blocks.website', 229 + defs: { 230 + main: { 231 + type: 'object', 232 + required: ['src'], 233 + properties: { 234 + previewImage: { 235 + type: 'blob', 236 + accept: ['image/*'], 237 + maxSize: 1000000, 238 + }, 239 + title: { 240 + type: 'string', 241 + }, 242 + description: { 243 + type: 'string', 244 + }, 245 + src: { 246 + type: 'string', 247 + format: 'uri', 248 + }, 249 + }, 250 + }, 251 + }, 252 + }, 226 253 PubLeafletGraphSubscription: { 227 254 lexicon: 1, 228 255 id: 'pub.leaflet.graph.subscription', ··· 271 298 'lex:pub.leaflet.blocks.header', 272 299 'lex:pub.leaflet.blocks.image', 273 300 'lex:pub.leaflet.blocks.unorderedList', 301 + 'lex:pub.leaflet.blocks.website', 274 302 ], 275 303 }, 276 304 alignment: { ··· 1427 1455 PubLeafletBlocksImage: 'pub.leaflet.blocks.image', 1428 1456 PubLeafletBlocksText: 'pub.leaflet.blocks.text', 1429 1457 PubLeafletBlocksUnorderedList: 'pub.leaflet.blocks.unorderedList', 1458 + PubLeafletBlocksWebsite: 'pub.leaflet.blocks.website', 1430 1459 PubLeafletGraphSubscription: 'pub.leaflet.graph.subscription', 1431 1460 PubLeafletPagesLinearDocument: 'pub.leaflet.pages.linearDocument', 1432 1461 PubLeafletRichtextFacet: 'pub.leaflet.richtext.facet',
+29
lexicons/api/types/pub/leaflet/blocks/website.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { ValidationResult, BlobRef } from '@atproto/lexicon' 5 + import { CID } from 'multiformats/cid' 6 + import { validate as _validate } from '../../../../lexicons' 7 + import { $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' 8 + 9 + const is$typed = _is$typed, 10 + validate = _validate 11 + const id = 'pub.leaflet.blocks.website' 12 + 13 + export interface Main { 14 + $type?: 'pub.leaflet.blocks.website' 15 + previewImage?: BlobRef 16 + title?: string 17 + description?: string 18 + src: string 19 + } 20 + 21 + const hashMain = 'main' 22 + 23 + export function isMain<V>(v: V) { 24 + return is$typed(v, id, hashMain) 25 + } 26 + 27 + export function validateMain<V>(v: V) { 28 + return validate<Main & V>(v, id, hashMain) 29 + }
+2
lexicons/api/types/pub/leaflet/pages/linearDocument.ts
··· 9 9 import type * as PubLeafletBlocksHeader from '../blocks/header' 10 10 import type * as PubLeafletBlocksImage from '../blocks/image' 11 11 import type * as PubLeafletBlocksUnorderedList from '../blocks/unorderedList' 12 + import type * as PubLeafletBlocksWebsite from '../blocks/website' 12 13 13 14 const is$typed = _is$typed, 14 15 validate = _validate ··· 36 37 | $Typed<PubLeafletBlocksHeader.Main> 37 38 | $Typed<PubLeafletBlocksImage.Main> 38 39 | $Typed<PubLeafletBlocksUnorderedList.Main> 40 + | $Typed<PubLeafletBlocksWebsite.Main> 39 41 | { $type: string } 40 42 alignment?: 41 43 | 'lex:pub.leaflet.pages.linearDocument#textAlignLeft'
+31
lexicons/pub/leaflet/blocks/website.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "pub.leaflet.blocks.website", 4 + "defs": { 5 + "main": { 6 + "type": "object", 7 + "required": [ 8 + "src" 9 + ], 10 + "properties": { 11 + "previewImage": { 12 + "type": "blob", 13 + "accept": [ 14 + "image/*" 15 + ], 16 + "maxSize": 1000000 17 + }, 18 + "title": { 19 + "type": "string" 20 + }, 21 + "description": { 22 + "type": "string" 23 + }, 24 + "src": { 25 + "type": "string", 26 + "format": "uri" 27 + } 28 + } 29 + } 30 + } 31 + }
+2 -1
lexicons/pub/leaflet/pages/linearDocument.json
··· 26 26 "pub.leaflet.blocks.text", 27 27 "pub.leaflet.blocks.header", 28 28 "pub.leaflet.blocks.image", 29 - "pub.leaflet.blocks.unorderedList" 29 + "pub.leaflet.blocks.unorderedList", 30 + "pub.leaflet.blocks.website" 30 31 ] 31 32 }, 32 33 "alignment": {
+18
lexicons/src/blocks.ts
··· 19 19 }, 20 20 }; 21 21 22 + export const PubLeafletBlocksWebsite: LexiconDoc = { 23 + lexicon: 1, 24 + id: "pub.leaflet.blocks.website", 25 + defs: { 26 + main: { 27 + type: "object", 28 + required: ["src"], 29 + properties: { 30 + previewImage: { type: "blob", accept: ["image/*"], maxSize: 1000000 }, 31 + title: { type: "string" }, 32 + description: { type: "string" }, 33 + src: { type: "string", format: "uri" }, 34 + }, 35 + }, 36 + }, 37 + }; 38 + 22 39 export const PubLeafletBlocksHeader: LexiconDoc = { 23 40 lexicon: 1, 24 41 id: "pub.leaflet.blocks.header", ··· 101 118 PubLeafletBlocksHeader, 102 119 PubLeafletBlocksImage, 103 120 PubLeafletBlocksUnorderedList, 121 + PubLeafletBlocksWebsite, 104 122 ]; 105 123 export const BlockUnion: LexRefUnion = { 106 124 type: "union",