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.
5.2 kB · 170 lines
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171import { render } from "/js/lib/lit-html.js";import { Component } from "/js/components/component.js";import { Signal, effect } from "/js/signals.js";import { shallowEquals } from "/js/utils.js";import { tokenizeRichText, tokensHaveFacetType } from "/js/richTextHelpers.js";import { richTextTokensTemplate } from "/js/templates/richText.template.js";
class PluginRichText extends Component { static placeholderFallbackMs = 500;
static get observedAttributes() { return ["truncate-urls"]; }
set text(value) { this._text = value; this.$text?.set(value); }
set facets(value) { this._facets = value; this.$facets?.set(value); }
set transformContext(value) { this._transformContext = value; this.$transformContext?.set(value); }
connectedCallback() { if (this.initialized) return; this.initialized = true; if (!this.pluginService) { throw new Error("pluginService is required"); } this.$text = new Signal.State(this._text ?? ""); this.$facets = new Signal.State(this._facets ?? null); this.$transformContext = new Signal.State(this._transformContext ?? null, { equals: shallowEquals, }); this.$truncateUrls = new Signal.State(this.hasAttribute("truncate-urls")); this.disposeRender = effect(() => this.renderContent()); }
attributeChangedCallback(name, oldValue, newValue) { if (!this.initialized || oldValue === newValue) return; if (name === "truncate-urls") { this.$truncateUrls.set(newValue !== null); } }
disconnectedCallback() { if (!this.initialized) return; this.disposeRender?.(); this.disposeRender = null; this._currentRequest = null; if (this._claimedFacetFallbackTimer) { clearTimeout(this._claimedFacetFallbackTimer); this._claimedFacetFallbackTimer = null; } this.initialized = false; }
renderContent() { const text = this.$text.get() ?? ""; const facets = this.$facets.get() ?? []; const transformContext = this.$transformContext.get(); const truncateUrls = this.$truncateUrls.get(); const pluginService = this.pluginService; const baseTokens = tokenizeRichText({ text, facets });
// Subscribe to the version signal so registering/unregistering a transform will re-render pluginService.$richTextTransformsVersion.get();
if (this._claimedFacetFallbackTimer) { clearTimeout(this._claimedFacetFallbackTimer); this._claimedFacetFallbackTimer = null; }
// Request transformed tokens from pluginService if (!transformContext) { this._currentRequest = null; this._renderTokens({ tokens: baseTokens, truncateUrls, placeholderFacetTypes: null, }); return; } const request = pluginService.transformRichTextTokens(baseTokens, { numberOfLines: null, ...transformContext, source: { text, facets }, }); this._currentRequest = request;
// Hide facet tokens whose feature $type is claimed by a pending transform, // so shortcodes don't briefly flash as plaintext before the transform swaps // them for their rendered form (e.g. bluemoji). const claimedFacetTypes = pluginService.getClaimedFacetTypes(); this._renderTokens({ tokens: baseTokens, truncateUrls, placeholderFacetTypes: claimedFacetTypes, });
// Fallback + null-resolve re-render only matter when something was hidden. const hasClaimedFacets = tokensHaveFacetType(baseTokens, claimedFacetTypes);
if (hasClaimedFacets) { this._claimedFacetFallbackTimer = setTimeout(() => { this._claimedFacetFallbackTimer = null; if (this._currentRequest !== request || !this.initialized) return; this._renderTokens({ tokens: baseTokens, truncateUrls, placeholderFacetTypes: null, }); }, PluginRichText.placeholderFallbackMs); }
request.then( (transformed) => { if ( // Ignore results that resolved after a newer render pass this._currentRequest !== request || !this.initialized ) return; if (this._claimedFacetFallbackTimer) { clearTimeout(this._claimedFacetFallbackTimer); this._claimedFacetFallbackTimer = null; } if (!transformed) { if (hasClaimedFacets) { this._renderTokens({ tokens: baseTokens, truncateUrls, placeholderFacetTypes: null, }); } return; } this._renderTokens({ tokens: transformed, truncateUrls, placeholderFacetTypes: null, }); }, (error) => { console.error("Rich text transform request failed", error); }, ); }
_renderTokens({ tokens, truncateUrls, placeholderFacetTypes }) { render( richTextTokensTemplate({ tokens, truncateUrls, placeholderFacetTypes, renderNodeToken: (token) => this.pluginService.renderRichTextNodeToken(token, this), }), this, ); }}
PluginRichText.register();