A calm place to write long-form, and publish it to the open social web. skypress.blog/
0

Configure Feed

Select the types of activity you want to include in your feed.

Register skypress/mention rich-text format

+96
+36
src/lib/editor/mention-format.test.ts
··· 1 + import { describe, expect, it, beforeAll } from 'vitest'; 2 + import { create, toHTMLString, applyFormat } from '@wordpress/rich-text'; 3 + import { registerMentionFormat, MENTION_FORMAT } from './mention-format'; 4 + 5 + beforeAll( () => { 6 + registerMentionFormat(); 7 + } ); 8 + 9 + describe( 'skypress/mention format', () => { 10 + it( 'round-trips an anchor carrying href + data-did, keeping the text', () => { 11 + // Build a value "@alice.bsky.social" and apply the mention format across it. 12 + const value = create( { text: '@alice.bsky.social' } ); 13 + const formatted = applyFormat( 14 + value, 15 + // The bundled `RichTextFormat` type only models `{ type }`; the runtime 16 + // `attributes` map is real, so cast to satisfy the incomplete type. 17 + { 18 + type: MENTION_FORMAT, 19 + attributes: { 20 + url: 'https://bsky.app/profile/alice.bsky.social', 21 + did: 'did:plc:alice', 22 + }, 23 + } as unknown as Parameters< typeof applyFormat >[ 1 ], 24 + 0, 25 + value.text.length 26 + ); 27 + const html = toHTMLString( { value: formatted } ); 28 + expect( html ).toContain( 'href="https://bsky.app/profile/alice.bsky.social"' ); 29 + expect( html ).toContain( 'data-did="did:plc:alice"' ); 30 + expect( html ).toContain( '@alice.bsky.social' ); 31 + } ); 32 + 33 + it( 'is idempotent — calling register twice does not throw', () => { 34 + expect( () => registerMentionFormat() ).not.toThrow(); 35 + } ); 36 + } );
+60
src/lib/editor/mention-format.ts
··· 1 + import { registerFormatType, store as richTextStore } from '@wordpress/rich-text'; 2 + import { select } from '@wordpress/data'; 3 + 4 + export const MENTION_FORMAT = 'skypress/mention'; 5 + 6 + /** 7 + * Settings accepted by `registerFormatType` at runtime. The bundled 8 + * `@wordpress/rich-text@7.24.0` `WPFormat` type is not re-exported from the package 9 + * index and omits the documented `attributes` map (HTML-attribute → format-attribute 10 + * key), so model it locally and pass it through a cast at the registration call below. 11 + */ 12 + type MentionFormatSettings = { 13 + title: string; 14 + tagName: string; 15 + className: string; 16 + attributes: Record< string, string >; 17 + edit: () => null; 18 + }; 19 + 20 + /** Minimal shape of the rich-text store selector we depend on. */ 21 + type RichTextSelectors = { 22 + getFormatType: ( name: string ) => unknown; 23 + }; 24 + 25 + /** 26 + * Register the `skypress/mention` rich-text format. It serializes to 27 + * `<a class="skypress-mention" href="{profile}" data-did="{did}">@{handle}</a>`. 28 + * The `class` survives the reader's sanitizer (so it can be styled and identified); 29 + * `data-did` is the publish-time marker (`collectMentions`) and is stripped from public 30 + * HTML by `sanitize.ts`. Inserted by the `@` autocompleter, not a toolbar button. 31 + */ 32 + export function registerMentionFormat(): void { 33 + // `@wordpress/rich-text@7.24.0` does not re-export `getFormatType` from its 34 + // index, so query the public store selector directly to stay idempotent 35 + // without triggering registerFormatType's "already registered" console.error. 36 + const selectors = select( 37 + richTextStore as unknown as Parameters< typeof select >[ 0 ] 38 + ) as unknown as RichTextSelectors; 39 + if ( selectors.getFormatType( MENTION_FORMAT ) ) { 40 + return; 41 + } 42 + 43 + const settings: MentionFormatSettings = { 44 + title: 'Mention', 45 + tagName: 'a', 46 + className: 'skypress-mention', 47 + attributes: { 48 + url: 'href', 49 + did: 'data-did', 50 + }, 51 + edit() { 52 + return null; 53 + }, 54 + }; 55 + 56 + registerFormatType( 57 + MENTION_FORMAT, 58 + settings as unknown as Parameters< typeof registerFormatType >[ 1 ] 59 + ); 60 + }