1+ import { useSearchParams } from 'react-router-dom'
12import { ArrowSquareOut } from '@phosphor-icons/react'
23import { LandingFooterNav } from '../Landing'
34import weeklyData from './weekly.json'
@@ -13,8 +14,15 @@ type WeeklyItem = {
1314 priority ?: number
1415}
1516
17+ type WeeklyWeek = {
18+ weekStart ?: string
19+ weekEnd ?: string
20+ updatedAt ?: string
21+ items ?: WeeklyItem [ ]
22+ }
23+
1624type WeeklyFile = {
17- _note ?: string
25+ weeks ?: WeeklyWeek [ ]
1826 weekStart ?: string
1927 weekEnd ?: string
2028 updatedAt ?: string
@@ -61,7 +69,7 @@ function formatWeekRange (startIso?: string, endIso?: string): string {
6169
6270 if ( start ) return monthDayYear . format ( start )
6371 if ( end ) return monthDayYear . format ( end )
64- return ''
72+ return 'Unknown week '
6573}
6674
6775function formatUpdated ( iso ?: string ) : string {
@@ -76,107 +84,144 @@ function formatUpdated (iso?: string): string {
7684 } ) . format ( date )
7785}
7886
87+ function weekKey ( week : WeeklyWeek ) : string {
88+ return ( week . weekStart || week . weekEnd || '' ) . slice ( 0 , 10 )
89+ }
90+
91+ function weeksFromFile ( file : WeeklyFile ) : WeeklyWeek [ ] {
92+ const source = Array . isArray ( file . weeks )
93+ ? file . weeks
94+ : ( file . weekStart || file . weekEnd || file . items )
95+ ? [ file ]
96+ : [ ]
97+
98+ return source
99+ . filter ( week => week && ( week . weekStart || week . weekEnd ) )
100+ . slice ( )
101+ . sort ( ( a , b ) => weekKey ( b ) . localeCompare ( weekKey ( a ) ) )
102+ }
103+
79104function visibleHref ( link ?: string ) : string | null {
80105 if ( ! link ) return null
81106 const href = link . trim ( )
82107 if ( ! href || href === '#' ) return null
83108 return href
84109}
85110
86- function labelsFor ( item : WeeklyItem ) : string [ ] {
87- const labels : string [ ] = [ ]
88- if ( item . category ?. trim ( ) ) labels . push ( item . category . trim ( ) )
89- for ( const tag of item . tags || [ ] ) {
90- const text = tag ?. trim ( )
91- if ( text && ! labels . includes ( text ) ) labels . push ( text )
92- }
93- return labels
111+ function itemsFor ( week ?: WeeklyWeek ) : WeeklyItem [ ] {
112+ if ( ! week || ! Array . isArray ( week . items ) ) return [ ]
113+ return week . items
114+ . filter ( item => item && typeof item . title === 'string' && item . title . trim ( ) )
115+ . slice ( 0 , MAX_ITEMS )
94116}
95117
96118function WeeklyItemRow ( props : { item : WeeklyItem } ) {
97- const { item } = props
98- const href = visibleHref ( item . link )
119+ const title = props . item . title ?. trim ( ) || ''
120+ const summary = props . item . summary ?. trim ( )
121+ const href = visibleHref ( props . item . link )
99122 const external = ! ! href && / ^ h t t p s ? : \/ \/ / i. test ( href )
100- const labels = labelsFor ( item )
101- const title = item . title ?. trim ( ) || ''
102-
103- const titleNode = href ? (
104- < a
105- href = { href }
106- className = "inline-flex items-center gap-2 hover:text-white"
107- { ...( external ? { target : '_blank' , rel : 'noreferrer' } : { } ) }
108- >
109- { title }
110- { external && (
111- < >
112- < ArrowSquareOut size = { 16 } className = "opacity-50" aria-hidden = "true" />
113- < span className = "sr-only" > (opens a new window)</ span >
114- </ >
123+
124+ const body = (
125+ < >
126+ < span className = "block text-lg font-medium leading-snug text-gray-100" >
127+ { title }
128+ { external && (
129+ < ArrowSquareOut
130+ size = { 15 }
131+ weight = "bold"
132+ className = "ml-1.5 inline-block translate-y-px opacity-40"
133+ aria-hidden = "true"
134+ />
135+ ) }
136+ </ span >
137+ { summary && (
138+ < span className = "mt-1.5 block text-base font-normal leading-relaxed text-logseq-50/80" >
139+ { summary }
140+ </ span >
115141 ) }
116- </ a >
117- ) : title
142+ </ >
143+ )
144+
145+ if ( ! href ) {
146+ return < li className = "border-t border-white/10 py-5" > { body } </ li >
147+ }
118148
119149 return (
120- < li className = "py-8 first:pt-0" >
121- { labels . length > 0 && (
122- < p className = "text-xs tracking-wide text-logseq-100/80" >
123- { labels . join ( ' · ' ) }
124- </ p >
125- ) }
126- < h2 className = "mt-2 text-xl font-medium tracking-wide text-gray-100 sm:text-2xl" >
127- { titleNode }
128- </ h2 >
129- { item . summary ?. trim ( ) && (
130- < p className = "mt-2 max-w-xl text-base leading-relaxed text-logseq-50/80" >
131- { item . summary . trim ( ) }
132- </ p >
133- ) }
150+ < li className = "border-t border-white/10" >
151+ < a
152+ href = { href }
153+ className = "block py-5 hover:text-white"
154+ { ...( external ? { target : '_blank' , rel : 'noreferrer' } : { } ) }
155+ >
156+ { body }
157+ { external && < span className = "sr-only" > (opens a new window)</ span > }
158+ </ a >
134159 </ li >
135160 )
136161}
137162
138163export function WeeklyPage ( ) {
139- const items = ( Array . isArray ( data . items ) ? data . items : [ ] )
140- . filter ( item => item && typeof item . title === 'string' && item . title . trim ( ) )
141- . slice ( 0 , MAX_ITEMS )
142- const range = formatWeekRange ( data . weekStart , data . weekEnd )
143- const updated = formatUpdated ( data . updatedAt )
164+ const [ searchParams , setSearchParams ] = useSearchParams ( )
165+ const weeks = weeksFromFile ( data )
166+ const requested = searchParams . get ( 'week' ) || ''
167+ const selected = weeks . find ( week => weekKey ( week ) === requested ) || weeks [ 0 ]
168+ const items = itemsFor ( selected )
169+ const range = selected ? formatWeekRange ( selected . weekStart , selected . weekEnd ) : ''
170+ const updated = formatUpdated ( selected ?. updatedAt )
171+
172+ const onWeekChange = ( key : string ) => {
173+ const next = new URLSearchParams ( searchParams )
174+ if ( ! key || key === weekKey ( weeks [ 0 ] || { } ) ) next . delete ( 'week' )
175+ else next . set ( 'week' , key )
176+ setSearchParams ( next , { replace : true } )
177+ }
144178
145179 return (
146180 < div className = "app-page pt-20" >
147- < article className = "page-inner w-full px-6 pb-4 sm:px-10" >
148- < div className = "mx-auto max-w-2xl pb-16 pt-10 sm:pb-24 sm:pt-20" >
149- < p className = "text-sm tracking-wide text-logseq-100/80" > Weekly</ p >
150- < h1 className = "mt-3 text-4xl font-semibold tracking-wide text-logseq-50 sm:text-5xl sm:leading-tight" >
151- What we worked on
181+ < article className = "page-inner w-full px-5 pb-8 sm:px-10" >
182+ < div className = "mx-auto max-w-2xl pb-12 pt-8 sm:pb-20 sm:pt-16" >
183+ < h1 className = "text-3xl font-semibold tracking-wide text-logseq-50" >
184+ Weekly
152185 </ h1 >
153- < p className = "mt-6 max-w-xl text-lg leading-relaxed text-logseq-50/80" >
154- A short list from the Logseq team. Not everything — just the few things worth knowing.
155- </ p >
156-
157- { ( range || updated ) && (
158- < p className = "mt-8 text-sm text-logseq-100/80" >
159- { range && < span > { range } </ span > }
160- { range && updated && < span className = "px-2 opacity-50" > ·</ span > }
161- { updated && < span > Updated { updated } </ span > }
162- </ p >
186+
187+ { weeks . length > 0 && (
188+ < div className = "mt-8" >
189+ < label className = "block text-sm text-logseq-100/80" htmlFor = "weekly-week" >
190+ Week
191+ </ label >
192+ < select
193+ id = "weekly-week"
194+ className = "mt-2 w-full max-w-full rounded-md border border-logseq-500/70 bg-logseq-800 px-3 py-3 text-base text-gray-100 sm:max-w-sm [color-scheme:dark]"
195+ value = { selected ? weekKey ( selected ) : '' }
196+ onChange = { ( event ) => onWeekChange ( event . target . value ) }
197+ >
198+ { weeks . map ( week => {
199+ const key = weekKey ( week )
200+ return (
201+ < option key = { key } value = { key } >
202+ { formatWeekRange ( week . weekStart , week . weekEnd ) }
203+ </ option >
204+ )
205+ } ) }
206+ </ select >
207+ </ div >
163208 ) }
164209
165- { data . _note && (
166- < p className = "mt-6 max-w-xl text-sm leading-relaxed text-logseq-100/70" >
167- Sample page. These notes are placeholders, not a real week of work.
210+ { updated && (
211+ < p className = "mt-4 text-sm text-logseq-100/70" >
212+ Updated { updated }
168213 </ p >
169214 ) }
170215
171216 { items . length > 0 ? (
172- < ul className = "mt-12 divide-y divide-logseq-600/60 " >
217+ < ul className = "mt-8 border-b border-white/10 " >
173218 { items . map ( ( item , index ) => (
174219 < WeeklyItemRow item = { item } key = { `${ item . title } -${ index } ` } />
175220 ) ) }
176221 </ ul >
177222 ) : (
178- < p className = "mt-14 text-base text-logseq-50/70 " >
179- Nothing highlighted this week. Check back after the next update.
223+ < p className = "mt-10 text-base text-logseq-50/75 " >
224+ { range ? `Nothing from ${ range } .` : 'Nothing here yet.' }
180225 </ p >
181226 ) }
182227 </ div >
0 commit comments