11import { useEffect , useRef , useState } from 'preact/hooks' ;
22import type { FeedRecord } from '../api/contracts' ;
3+ import { DominantField } from './DominantField' ;
34
45interface ResultDisplayProps {
56 result : FeedRecord ;
@@ -8,6 +9,7 @@ interface ResultDisplayProps {
89
910export function ResultDisplay ( { result, onCreateAnother } : ResultDisplayProps ) {
1011 const [ copyNotice , setCopyNotice ] = useState ( '' ) ;
12+ const [ previewItems , setPreviewItems ] = useState < string [ ] > ( [ ] ) ;
1113 const copyResetRef = useRef < number | undefined > ( undefined ) ;
1214
1315 const fullUrl = result . public_url . startsWith ( 'http' )
@@ -20,6 +22,44 @@ export function ResultDisplay({ result, onCreateAnother }: ResultDisplayProps) {
2022 } ;
2123 } , [ ] ) ;
2224
25+ useEffect ( ( ) => {
26+ let isCancelled = false ;
27+
28+ const loadPreview = async ( ) => {
29+ try {
30+ const response = await window . fetch ( fullUrl ) ;
31+ const xml = await response . text ( ) ;
32+ const document = new DOMParser ( ) . parseFromString ( xml , 'application/xml' ) ;
33+ const explicitTitles = Array . from ( document . querySelectorAll ( 'item > title, entry > title' ) ) . map (
34+ ( node ) => node . textContent ?. trim ( )
35+ ) ;
36+ const derivedDescriptions = Array . from ( document . querySelectorAll ( 'item > description' ) )
37+ . map ( ( node ) => node . textContent ?. trim ( ) )
38+ . filter ( ( description ) : description is string => Boolean ( description ) )
39+ . filter ( ( description ) => / ^ \d + \. \s + / . test ( description ) )
40+ . map ( ( description ) =>
41+ description
42+ . replace ( / ^ \d + \. \s + / , '' )
43+ . replace ( / \s + \( [ ^ ) ] * \) \s * $ / , '' )
44+ . trim ( )
45+ ) ;
46+ const itemTitles = [ ...explicitTitles , ...derivedDescriptions ]
47+ . filter ( ( title ) : title is string => Boolean ( title ) )
48+ . slice ( 0 , 3 ) ;
49+
50+ if ( ! isCancelled ) setPreviewItems ( itemTitles ) ;
51+ } catch {
52+ if ( ! isCancelled ) setPreviewItems ( [ ] ) ;
53+ }
54+ } ;
55+
56+ void loadPreview ( ) ;
57+
58+ return ( ) => {
59+ isCancelled = true ;
60+ } ;
61+ } , [ fullUrl ] ) ;
62+
2363 const copyToClipboard = async ( text : string ) => {
2464 try {
2565 await navigator . clipboard . writeText ( text ) ;
@@ -34,40 +74,44 @@ export function ResultDisplay({ result, onCreateAnother }: ResultDisplayProps) {
3474 return (
3575 < section class = "result-shell" aria-live = "polite" >
3676 < div class = "result-copy" >
37- < p class = "result-title" > Feed URL ready</ p >
3877 < p class = "result-meta" > { result . name } </ p >
3978 </ div >
4079
41- < label class = "field-block field-block--primary field-block--hero result-url" htmlFor = "feed-url" >
42- < span class = "field-label field-label--ghost" > Feed URL</ span >
43- < input
44- id = "feed-url"
45- name = "feed-url"
46- type = "text"
47- value = { fullUrl }
48- readOnly
49- class = "input input--mono input--hero"
50- />
51- </ label >
80+ < DominantField
81+ id = "feed-url"
82+ label = "Feed URL"
83+ value = { fullUrl }
84+ readOnly
85+ actionLabel = "Copy feed URL"
86+ actionText = "Copy"
87+ onAction = { ( ) => void copyToClipboard ( fullUrl ) }
88+ />
5289
53- < div class = "result-actions" >
54- < button type = "button" class = "btn btn--primary btn--hero" onClick = { ( ) => copyToClipboard ( fullUrl ) } >
55- Copy feed URL
56- </ button >
90+ { previewItems . length > 0 && (
91+ < section class = "result-preview" aria-label = "Feed preview" >
92+ < p class = "result-preview__label" > Latest items</ p >
93+ < ul class = "result-preview__list" >
94+ { previewItems . map ( ( item ) => (
95+ < li key = { item } > { item } </ li >
96+ ) ) }
97+ </ ul >
98+ </ section >
99+ ) }
100+
101+ < div class = "result-actions result-actions--quiet" >
57102 < a href = { fullUrl } class = "btn btn--ghost btn--linkish" target = "_blank" rel = "noopener noreferrer" >
58103 Open feed
59104 </ a >
105+ < button type = "button" class = "btn btn--quiet btn--linkish" onClick = { onCreateAnother } >
106+ Create another feed
107+ </ button >
60108 </ div >
61109
62110 { copyNotice && (
63111 < div class = "notice notice--success" role = "status" >
64112 < p > { copyNotice } </ p >
65113 </ div >
66114 ) }
67-
68- < button type = "button" class = "btn btn--quiet btn--linkish" onClick = { onCreateAnother } >
69- Create another feed
70- </ button >
71115 </ section >
72116 ) ;
73117}
0 commit comments