@@ -9,6 +9,32 @@ const DISCOGS_API_URL = "https://api.discogs.com/";
99const DISCOGS_TOKEN = import . meta. env . VITE_DISCOGS_TOKEN || "" ;
1010const USER_AGENT = "Beardify/1.0.0 (https://github.com/BeardedBear/beardify)" ;
1111
12+ /**
13+ * Discogs markup parsing configuration
14+ */
15+ const DISCOGS_BASE_URL = "https://www.discogs.com" ;
16+ const LINK_ATTRS = 'target="_blank" rel="noopener noreferrer" class="discogs-link"' ;
17+
18+ /**
19+ * Discogs entity types configuration
20+ * Maps entity type letter to URL path and display text
21+ */
22+ const DISCOGS_ENTITIES : Record < string , { path : string ; searchType : string ; text : string } > = {
23+ a : { path : "artist" , searchType : "artist" , text : "artist" } ,
24+ l : { path : "label" , searchType : "label" , text : "label" } ,
25+ m : { path : "master" , searchType : "master" , text : "release" } ,
26+ r : { path : "release" , searchType : "release" , text : "release" } ,
27+ } ;
28+
29+ /**
30+ * Text formatting tags configuration
31+ */
32+ const TEXT_FORMATS : Array < { pattern : RegExp ; replacement : string } > = [
33+ { pattern : / \[ b \] ( .* ?) \[ \/ b \] / gi, replacement : "<strong>$1</strong>" } ,
34+ { pattern : / \[ i \] ( .* ?) \[ \/ i \] / gi, replacement : "<em>$1</em>" } ,
35+ { pattern : / \[ u \] ( .* ?) \[ \/ u \] / gi, replacement : "<u>$1</u>" } ,
36+ ] ;
37+
1238/**
1339 * Creates a Discogs API client instance
1440 */
@@ -46,3 +72,62 @@ export async function getDiscogsArtist(discogsId: string): Promise<DiscogsArtist
4672 return null ;
4773 }
4874}
75+
76+ /**
77+ * Parse Discogs markup and convert to HTML
78+ * Supported tags:
79+ * - [i], [b], [u] -> text formatting
80+ * - [a], [l], [r], [m] -> Discogs entity links (by ID or name)
81+ * - [url] -> external links
82+ * @param text - The Discogs markup text to parse
83+ * @returns HTML string with converted markup
84+ */
85+ export function parseDiscogsMarkup ( text : string ) : string {
86+ let result = text ;
87+
88+ // Escape HTML to prevent XSS
89+ result = result . replace ( / & / g, "&" ) . replace ( / < / g, "<" ) . replace ( / > / g, ">" ) ;
90+
91+ // Apply text formatting
92+ for ( const { pattern, replacement } of TEXT_FORMATS ) {
93+ result = result . replace ( pattern , replacement ) ;
94+ }
95+
96+ // [x123456] or [x=123456] -> link to Discogs entity by ID
97+ result = result . replace ( / \[ ( [ a l m r ] ) = ? ( \d + ) \] / gi, ( _ , type , id ) => createDiscogsLinkById ( type , id ) ) ;
98+
99+ // [x=Name] -> link to Discogs search by name (non-numeric)
100+ result = result . replace ( / \[ ( [ a l ] ) = ( [ ^ \] ] + ) \] / gi, ( _ , type , name ) => createDiscogsSearchLink ( type , name ) ) ;
101+
102+ // [url=http://...]text[/url] -> <a href="...">text</a>
103+ result = result . replace (
104+ / \[ u r l = ( .* ?) \] ( .* ?) \[ \/ u r l \] / gi,
105+ '<a href="$1" target="_blank" rel="noopener noreferrer">$2</a>' ,
106+ ) ;
107+
108+ // [url]http://...[/url] -> <a href="...">...</a>
109+ result = result . replace ( / \[ u r l \] ( .* ?) \[ \/ u r l \] / gi, '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>' ) ;
110+
111+ // Convert newlines to <br> tags
112+ result = result . replace ( / \n / g, "<br>" ) ;
113+
114+ return result ;
115+ }
116+
117+ /**
118+ * Create a Discogs link by ID
119+ */
120+ function createDiscogsLinkById ( type : string , id : string ) : string {
121+ const entity = DISCOGS_ENTITIES [ type . toLowerCase ( ) ] ;
122+ if ( ! entity ) return `[${ type } ${ id } ]` ;
123+ return `<a href="${ DISCOGS_BASE_URL } /${ entity . path } /${ id } " ${ LINK_ATTRS } >${ entity . text } </a>` ;
124+ }
125+
126+ /**
127+ * Create a Discogs search link by name
128+ */
129+ function createDiscogsSearchLink ( type : string , name : string ) : string {
130+ const entity = DISCOGS_ENTITIES [ type . toLowerCase ( ) ] ;
131+ if ( ! entity ) return `[${ type } =${ name } ]` ;
132+ return `<a href="${ DISCOGS_BASE_URL } /search/?q=${ encodeURIComponent ( name ) } &type=${ entity . searchType } " ${ LINK_ATTRS } >${ name } </a>` ;
133+ }
0 commit comments