@@ -5,6 +5,7 @@ import { Section, SectionDescription } from '@tanstack/devtools-ui'
55
66const TITLE_MAX_WIDTH_PX = 600
77const DESCRIPTION_MAX_WIDTH_PX = 960
8+ const DESCRIPTION_MOBILE_MAX_LINES = 3
89const ELLIPSIS = '...'
910
1011function truncateToWidth (
@@ -64,12 +65,10 @@ function getSerpFromHead(): SerpData {
6465 return { title, description, siteName, favicon, url }
6566}
6667
67- type SerpOverflow = {
68- titleOverflow : boolean
69- descriptionOverflow : boolean
70- }
71-
72- function getSerpReports ( data : SerpData , overflow : SerpOverflow ) : string [ ] {
68+ function getSerpReportsDesktop (
69+ data : SerpData ,
70+ overflow : { titleOverflow : boolean ; descriptionOverflow : boolean } ,
71+ ) : string [ ] {
7372 const issues : string [ ] = [ ]
7473 if ( ! data . title ?. trim ( ) ) {
7574 issues . push ( 'No title tag set on the page.' )
@@ -93,10 +92,42 @@ function getSerpReports(data: SerpData, overflow: SerpOverflow): string[] {
9392 return issues
9493}
9594
95+ function getSerpReportsMobile (
96+ data : SerpData ,
97+ overflow : {
98+ titleOverflow : boolean
99+ descriptionOverflowMobile : boolean
100+ } ,
101+ ) : string [ ] {
102+ const issues : string [ ] = [ ]
103+ if ( ! data . title ?. trim ( ) ) {
104+ issues . push ( 'No title tag set on the page.' )
105+ }
106+ if ( ! data . description ?. trim ( ) ) {
107+ issues . push ( 'No meta description set on the page.' )
108+ }
109+ if ( ! data . favicon ) {
110+ issues . push ( 'No favicon or icon set on the page.' )
111+ }
112+ if ( overflow . titleOverflow ) {
113+ issues . push (
114+ 'The title is wider than 600px and it may not be displayed in full length.' ,
115+ )
116+ }
117+ if ( overflow . descriptionOverflowMobile ) {
118+ issues . push (
119+ 'Description exceeds the 3-line limit for mobile view. Please shorten your text to fit within 3 lines.' ,
120+ )
121+ }
122+ return issues
123+ }
124+
96125export function SerpPreviewSection ( ) {
97126 const [ serp , setSerp ] = createSignal < SerpData > ( getSerpFromHead ( ) )
98127 const [ titleOverflow , setTitleOverflow ] = createSignal ( false )
99128 const [ descriptionOverflow , setDescriptionOverflow ] = createSignal ( false )
129+ const [ descriptionOverflowMobile , setDescriptionOverflowMobile ] =
130+ createSignal ( false )
100131 const [ displayTitle , setDisplayTitle ] = createSignal ( '' )
101132 const [ displayDescription , setDisplayDescription ] = createSignal ( '' )
102133 const [ titleMeasureEl , setTitleMeasureEl ] = createSignal <
@@ -105,6 +136,9 @@ export function SerpPreviewSection() {
105136 const [ descMeasureEl , setDescMeasureEl ] = createSignal <
106137 HTMLDivElement | undefined
107138 > ( undefined )
139+ const [ descMeasureMobileEl , setDescMeasureMobileEl ] = createSignal <
140+ HTMLDivElement | undefined
141+ > ( undefined )
108142 const styles = useStyles ( )
109143
110144 useHeadChanges ( ( ) => {
@@ -114,6 +148,7 @@ export function SerpPreviewSection() {
114148 createEffect ( ( ) => {
115149 const titleEl = titleMeasureEl ( )
116150 const descEl = descMeasureEl ( )
151+ const descMobileEl = descMeasureMobileEl ( )
117152 const data = serp ( )
118153 if ( ! titleEl || ! descEl ) return
119154
@@ -135,15 +170,33 @@ export function SerpPreviewSection() {
135170 )
136171 setDisplayDescription ( truncatedDesc )
137172 setDescriptionOverflow ( truncatedDesc !== descText )
173+
174+ if ( descMobileEl && descText ) {
175+ descMobileEl . textContent = descText
176+ const lineHeight = parseFloat (
177+ getComputedStyle ( descMobileEl ) . lineHeight ,
178+ ) || 20
179+ const lines = Math . ceil ( descMobileEl . scrollHeight / lineHeight )
180+ setDescriptionOverflowMobile ( lines > DESCRIPTION_MOBILE_MAX_LINES )
181+ } else {
182+ setDescriptionOverflowMobile ( false )
183+ }
138184 } )
139185
140- const reports = createMemo ( ( ) =>
141- getSerpReports ( serp ( ) , {
186+ const reportsDesktop = createMemo ( ( ) =>
187+ getSerpReportsDesktop ( serp ( ) , {
142188 titleOverflow : titleOverflow ( ) ,
143189 descriptionOverflow : descriptionOverflow ( ) ,
144190 } ) ,
145191 )
146192
193+ const reportsMobile = createMemo ( ( ) =>
194+ getSerpReportsMobile ( serp ( ) , {
195+ titleOverflow : titleOverflow ( ) ,
196+ descriptionOverflowMobile : descriptionOverflowMobile ( ) ,
197+ } ) ,
198+ )
199+
147200 const data = serp ( )
148201
149202 return (
@@ -189,17 +242,64 @@ export function SerpPreviewSection() {
189242 aria-hidden = "true"
190243 />
191244 </ div >
245+ { reportsDesktop ( ) . length > 0 ? (
246+ < div class = { styles ( ) . seoMissingTagsSection } >
247+ < strong > Missing issues for Desktop preview:</ strong >
248+ < ul class = { styles ( ) . serpErrorList } >
249+ < For each = { reportsDesktop ( ) } >
250+ { ( issue ) => (
251+ < li class = { styles ( ) . serpReportItem } > { issue } </ li >
252+ ) }
253+ </ For >
254+ </ ul >
255+ </ div >
256+ ) : null }
192257 </ div >
193- { reports ( ) . length > 0 ? (
194- < div class = { styles ( ) . seoMissingTagsSection } >
195- < strong > SERP preview issues:</ strong >
196- < ul class = { styles ( ) . serpErrorList } >
197- < For each = { reports ( ) } >
198- { ( issue ) => < li class = { styles ( ) . serpReportItem } > { issue } </ li > }
199- </ For >
200- </ ul >
258+ < div class = { styles ( ) . serpPreviewBlock } >
259+ < div class = { styles ( ) . serpPreviewLabel } > Mobile preview</ div >
260+ < div class = { styles ( ) . serpSnippetMobile } >
261+ < div class = { styles ( ) . serpSnippetTopRow } >
262+ { data . favicon ? (
263+ < img
264+ src = { data . favicon }
265+ alt = ""
266+ class = { styles ( ) . serpSnippetFavicon }
267+ />
268+ ) : (
269+ < div class = { styles ( ) . serpSnippetDefaultFavicon } />
270+ ) }
271+ < div class = { styles ( ) . serpSnippetSiteColumn } >
272+ < span class = { styles ( ) . serpSnippetSiteName } >
273+ { data . siteName || data . url }
274+ </ span >
275+ < span class = { styles ( ) . serpSnippetSiteUrl } > { data . url } </ span >
276+ </ div >
277+ </ div >
278+ < div class = { styles ( ) . serpSnippetTitle } >
279+ { displayTitle ( ) || data . title || 'No title' }
280+ </ div >
281+ < div class = { styles ( ) . serpSnippetDescMobile } >
282+ { displayDescription ( ) || data . description || 'No meta description.' }
283+ </ div >
284+ < div
285+ ref = { setDescMeasureMobileEl }
286+ class = { `${ styles ( ) . serpSnippetDesc } ${ styles ( ) . serpMeasureHiddenMobile } ` }
287+ aria-hidden = "true"
288+ />
201289 </ div >
202- ) : null }
290+ { reportsMobile ( ) . length > 0 ? (
291+ < div class = { styles ( ) . seoMissingTagsSection } >
292+ < strong > Missing issues for Mobile preview:</ strong >
293+ < ul class = { styles ( ) . serpErrorList } >
294+ < For each = { reportsMobile ( ) } >
295+ { ( issue ) => (
296+ < li class = { styles ( ) . serpReportItem } > { issue } </ li >
297+ ) }
298+ </ For >
299+ </ ul >
300+ </ div >
301+ ) : null }
302+ </ div >
203303 </ Section >
204304 )
205305}
0 commit comments