Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/improsocial/impro
An extensible Bluesky client for web impro.social
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140import { html } from "/js/lib/lit-html.js";import { sanitizeUri } from "/js/utils.js";import { tokenizeRichText, isEmojiOnlyTokens } from "/js/richTextHelpers.js";import { linkToHashtag, linkToProfileByDid } from "/js/navigation.js";
const KNOWN_UNSUPPORTED_FACET_TYPES = [ "blue.poll.post.facet#option", "blue.poll.post.facet#question", "blue.moji.richtext.facet",];
// Matches social-app behaviorexport function truncateUrl(url) { try { const urlp = new URL(url); if (urlp.protocol !== "http:" && urlp.protocol !== "https:") { return url; } const path = (urlp.pathname === "/" ? "" : urlp.pathname) + urlp.search + urlp.hash; if (path.length > 15) { return urlp.host + path.slice(0, 13) + "..."; } return urlp.host + path; } catch { return url; }}
function facetTemplate({ facet, wrappedText, truncateUrls }) { // only support 1 feature for now const feature = facet.features[0]; if (!feature) { console.warn("no feature found for facet", facet); return wrappedText; } switch (feature.$type) { case "app.bsky.richtext.facet#link": const uri = feature.uri; return html`<a href="${sanitizeUri(uri)}" >${truncateUrls ? truncateUrl(wrappedText) : wrappedText}</a >`; case "app.bsky.richtext.facet#tag": const tag = feature.tag; return html`<a href="${linkToHashtag(tag)}">${wrappedText}</a>`; case "app.bsky.richtext.facet#mention": const did = feature.did; // Handle unresolved mentions return html`<a href="${did ? linkToProfileByDid(did) : "#"}" data-hover-did=${did || ""} >${wrappedText}</a >`; default: if (!KNOWN_UNSUPPORTED_FACET_TYPES.includes(feature.$type)) { console.warn("unknown facet type " + feature.$type, feature); } return wrappedText; }}
// tokens: ({ type: "text" } / { type: "facet" } / { type: "inline" } / { type: "block" })// placeholderFacetTypes: Set of facet feature $types to render as// invisible-but-space-preserving spans (used while a rich-text transform// that claims the type is still pending, to avoid flashing plaintext).export function richTextTokensTemplate({ tokens, truncateUrls = false, renderNodeToken = () => null, placeholderFacetTypes = null,}) { const isEmojiOnly = isEmojiOnlyTokens(tokens); const parts = []; tokens.forEach((token, index) => { switch (token.type) { case "text": { let value = token.value; // Trim the newlines adjoining a block token so blocks don't render // with double gaps (the text is displayed white-space: pre-wrap). if (tokens[index - 1]?.type === "block" && value.startsWith("\n")) { value = value.slice(1); } if (tokens[index + 1]?.type === "block" && value.endsWith("\n")) { value = value.slice(0, -1); } parts.push(value); break; } case "facet": { if ( placeholderFacetTypes && token.facet.features?.some((feature) => placeholderFacetTypes.has(feature.$type), ) ) { parts.push( html`<span class="rich-text-facet-pending" aria-hidden="true" >${token.text}</span >`, ); break; } parts.push( facetTemplate({ facet: token.facet, wrappedText: token.text, truncateUrls, }), ); break; } case "inline": case "block": { const element = renderNodeToken(token) ?? null; if (!element) break; parts.push( token.type === "block" ? html`<div class="rich-text-block">${element}</div>` : element, ); break; } } }); if (isEmojiOnly) { // prettier-ignore return html`<div class="rich-text rich-text-emoji-only" data-testid="rich-text" data-teststate="emoji-only">${parts}</div>`; } // prettier-ignore return html`<div class="rich-text" data-testid="rich-text">${parts}</div>`;}
export function richTextTemplate({ text, facets = [], truncateUrls = false }) { const tokens = tokenizeRichText({ text, facets }); return richTextTokensTemplate({ tokens, truncateUrls, });}