Skip to content

Commit 823d8dd

Browse files
authored
Merge pull request #46 from logseq/logseq/weekly-page-314f
Add a /weekly page for recent Logseq work
2 parents 55f999a + 517692b commit 823d8dd

6 files changed

Lines changed: 359 additions & 2 deletions

File tree

‎src/App.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { scrollToTop } from './components/utils'
1717
import cx from 'classnames'
1818
import { TermsPage } from './pages/User/Terms'
1919
import { PrivacyPolicyPage } from './pages/User/PrivacyPolicy'
20+
import { WeeklyPage } from './pages/Weekly'
2021

2122
export function App() {
2223
const appState = useAppState()
@@ -82,6 +83,7 @@ export function App() {
8283
<Route path={'/downloads'} element={<DownloadsPage/>}/>
8384
<Route path={'/terms'} element={<TermsPage/>}></Route>
8485
<Route path={'/privacy-policy'} element={<PrivacyPolicyPage/>}></Route>
86+
<Route path={'/weekly'} element={<WeeklyPage/>}/>
8587
</Routes>
8688

8789
{/* modals */}

‎src/pages/Landing/LandingFooterNav.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,9 @@ export function LandingFooterNav () {
228228

229229
const links = (
230230
<div className="links flex flex-col justify-center">
231-
<p className="flex space-x-4 text-xs text-gray-300/90 pb-1">
231+
<p className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-gray-300/90 pb-1">
232232
<a href="https://logseq.io/p/NX4mc_ggEV" target="_blank" rel="noreferrer">Roadmap</a>
233+
<Link to={"/weekly"}>Weekly</Link>
233234
<Link to={"/privacy-policy"}>Privacy</Link>
234235
<Link to={"/terms"}>Terms</Link>
235236
<a href="mailto:hi@logseq.com">Contact Us</a>

‎src/pages/Weekly/README.md‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Weekly highlights
2+
3+
`weekly.json` is the only file to edit when refreshing [/weekly](https://logseq.com/weekly).
4+
5+
Source is merged pull requests in [`logseq/logseq`](https://github.com/logseq/logseq) only. Do not include other Logseq repos, issues, or invented PR numbers.
6+
7+
## Shape
8+
9+
`weeks` is an array, newest first. Keep about three months at most. The page defaults to the latest week and shows at most 10 items, in the order written.
10+
11+
Each week:
12+
13+
| Field | Required | Notes |
14+
| --- | --- | --- |
15+
| `weekStart` | yes | Inclusive `YYYY-MM-DD`. Also the dropdown value. |
16+
| `weekEnd` | yes | Inclusive `YYYY-MM-DD`. |
17+
| `updatedAt` | yes | ISO-8601 datetime for the “Updated …” line. |
18+
| `items` | yes | Ordered array, max 10 shown. |
19+
20+
Each item:
21+
22+
| Field | Required | Notes |
23+
| --- | --- | --- |
24+
| `title` | yes | Short. |
25+
| `summary` | no | One sentence. |
26+
| `link` | no | One real `logseq/logseq` pull request URL. |
27+
| `tags` | no | Optional. Not shown as badges. |
28+
| `category` | no | Optional. Not shown as badges. |
29+
| `priority` | no | Optional metadata. Display order is array order. |
30+
31+
A week with fewer than 5 items still renders as-is.

‎src/pages/Weekly/index.tsx‎

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
import { useSearchParams } from 'react-router-dom'
2+
import { ArrowSquareOut } from '@phosphor-icons/react'
3+
import { LandingFooterNav } from '../Landing'
4+
import weeklyData from './weekly.json'
5+
6+
const MAX_ITEMS = 10
7+
8+
type WeeklyItem = {
9+
title?: string
10+
summary?: string
11+
link?: string
12+
tags?: string[]
13+
category?: string
14+
priority?: number
15+
}
16+
17+
type WeeklyWeek = {
18+
weekStart?: string
19+
weekEnd?: string
20+
updatedAt?: string
21+
items?: WeeklyItem[]
22+
}
23+
24+
type WeeklyFile = {
25+
weeks?: WeeklyWeek[]
26+
weekStart?: string
27+
weekEnd?: string
28+
updatedAt?: string
29+
items?: WeeklyItem[]
30+
}
31+
32+
const data = weeklyData as WeeklyFile
33+
34+
function parseYmd (value?: string): Date | null {
35+
if (!value) return null
36+
const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(value.trim())
37+
if (!match) return null
38+
const year = Number(match[1])
39+
const month = Number(match[2])
40+
const day = Number(match[3])
41+
const date = new Date(year, month - 1, day)
42+
if (
43+
date.getFullYear() !== year ||
44+
date.getMonth() !== month - 1 ||
45+
date.getDate() !== day
46+
) return null
47+
return date
48+
}
49+
50+
function formatWeekRange (startIso?: string, endIso?: string): string {
51+
const start = parseYmd(startIso)
52+
const end = parseYmd(endIso)
53+
const monthDay = new Intl.DateTimeFormat('en', { month: 'long', day: 'numeric' })
54+
const monthDayYear = new Intl.DateTimeFormat('en', {
55+
month: 'long', day: 'numeric', year: 'numeric',
56+
})
57+
58+
if (start && end) {
59+
const sameYear = start.getFullYear() === end.getFullYear()
60+
const sameMonth = sameYear && start.getMonth() === end.getMonth()
61+
if (sameMonth) {
62+
return `${monthDay.format(start)}–${end.getDate()}, ${end.getFullYear()}`
63+
}
64+
if (sameYear) {
65+
return `${monthDay.format(start)} – ${monthDayYear.format(end)}`
66+
}
67+
return `${monthDayYear.format(start)} – ${monthDayYear.format(end)}`
68+
}
69+
70+
if (start) return monthDayYear.format(start)
71+
if (end) return monthDayYear.format(end)
72+
return 'Unknown week'
73+
}
74+
75+
function formatUpdated (iso?: string): string {
76+
if (!iso) return ''
77+
const date = new Date(iso)
78+
if (Number.isNaN(date.getTime())) return ''
79+
return new Intl.DateTimeFormat('en', {
80+
month: 'long',
81+
day: 'numeric',
82+
year: 'numeric',
83+
timeZone: 'UTC',
84+
}).format(date)
85+
}
86+
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+
104+
function visibleHref (link?: string): string | null {
105+
if (!link) return null
106+
const href = link.trim()
107+
if (!href || href === '#') return null
108+
return href
109+
}
110+
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)
116+
}
117+
118+
function WeeklyItemRow (props: { item: WeeklyItem }) {
119+
const title = props.item.title?.trim() || ''
120+
const summary = props.item.summary?.trim()
121+
const href = visibleHref(props.item.link)
122+
const external = !!href && /^https?:\/\//i.test(href)
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>
141+
)}
142+
</>
143+
)
144+
145+
if (!href) {
146+
return <li className="border-t border-white/10 py-5">{body}</li>
147+
}
148+
149+
return (
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>
159+
</li>
160+
)
161+
}
162+
163+
export function WeeklyPage () {
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+
}
178+
179+
return (
180+
<div className="app-page pt-20">
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
185+
</h1>
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>
208+
)}
209+
210+
{updated && (
211+
<p className="mt-4 text-sm text-logseq-100/70">
212+
Updated {updated}
213+
</p>
214+
)}
215+
216+
{items.length > 0 ? (
217+
<ul className="mt-8 border-b border-white/10">
218+
{items.map((item, index) => (
219+
<WeeklyItemRow item={item} key={`${item.title}-${index}`}/>
220+
))}
221+
</ul>
222+
) : (
223+
<p className="mt-10 text-base text-logseq-50/75">
224+
{range ? `Nothing from ${range}.` : 'Nothing here yet.'}
225+
</p>
226+
)}
227+
</div>
228+
</article>
229+
230+
<div className="page-inner-full-wrap b relative">
231+
<div className="page-inner footer-nav">
232+
<div className="page-inner">
233+
<LandingFooterNav/>
234+
</div>
235+
</div>
236+
</div>
237+
</div>
238+
)
239+
}

‎src/pages/Weekly/weekly.json‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
{
2+
"weeks": [
3+
{
4+
"weekStart": "2026-09-15",
5+
"weekEnd": "2026-09-21",
6+
"updatedAt": "2026-09-22T15:30:00Z",
7+
"items": [
8+
{
9+
"title": "Faster DB graph scans",
10+
"summary": "Cut several O(graph) paths in views, journals, refs, and startup revision checks so larger graphs stay responsive.",
11+
"link": "https://github.com/logseq/logseq/pull/13347",
12+
"priority": 1
13+
},
14+
{
15+
"title": "Snappier block editing",
16+
"summary": "Stopped re-rendering the editor box on every keystroke and sized the textarea with CSS field-sizing where supported.",
17+
"link": "https://github.com/logseq/logseq/pull/13211",
18+
"priority": 2
19+
},
20+
{
21+
"title": "More reliable DB worker",
22+
"summary": "Moved lock ownership to SQLite and keep the worker healthy across system sleep so graphs do not get stuck after idle.",
23+
"link": "https://github.com/logseq/logseq/pull/13302",
24+
"priority": 3
25+
},
26+
{
27+
"title": "Library and sidebar polish",
28+
"summary": "Hide library blocks, collapse nested pages more cleanly, and refresh favorites after drag-and-drop reorder.",
29+
"link": "https://github.com/logseq/logseq/pull/13349",
30+
"priority": 4
31+
},
32+
{
33+
"title": "Property paste and undo fixes",
34+
"summary": "Copy and paste text property values as blocks, restore property values after undo, and keep child page content on selection delete.",
35+
"link": "https://github.com/logseq/logseq/pull/13324",
36+
"priority": 5
37+
},
38+
{
39+
"title": "Query and table view fixes",
40+
"summary": "Fixed query result views and set-literal editing, restored property columns, and sped up DB table scrolling.",
41+
"link": "https://github.com/logseq/logseq/pull/13318",
42+
"priority": 6
43+
},
44+
{
45+
"title": "CLI and Windows build fixes",
46+
"summary": "Added force-remove page, username/password login, and fixed Windows vite/bundle and state-phase build issues.",
47+
"link": "https://github.com/logseq/logseq/pull/13331",
48+
"priority": 7
49+
},
50+
{
51+
"title": "Import and publishing fixes",
52+
"summary": "Convert markdown headings into DB heading metadata on import, and honor default home in HTML export.",
53+
"link": "https://github.com/logseq/logseq/pull/13275",
54+
"priority": 8
55+
}
56+
]
57+
},
58+
{
59+
"weekStart": "2026-09-08",
60+
"weekEnd": "2026-09-14",
61+
"updatedAt": "2026-09-22T15:30:00Z",
62+
"items": [
63+
{
64+
"title": "Faster file-graph import",
65+
"summary": "Optimized the file graph import path and preserved mention timestamps, icons, and PDF annotations more reliably.",
66+
"link": "https://github.com/logseq/logseq/pull/13130",
67+
"priority": 1
68+
},
69+
{
70+
"title": "Page rename and delete edge cases",
71+
"summary": "Update linking page mirrors on rename, tag Recycle as Page so File→DB deletes work, and load breadcrumbs on demand.",
72+
"link": "https://github.com/logseq/logseq/pull/13147",
73+
"priority": 2
74+
},
75+
{
76+
"title": "Property and shortcut polish",
77+
"summary": "URL property editor UX, default Task status icons, shared default shortcuts when resetting, and Contents from the right-sidebar menu.",
78+
"link": "https://github.com/logseq/logseq/pull/13186",
79+
"priority": 3
80+
}
81+
]
82+
}
83+
]
84+
}

‎tsconfig.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
4040
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
4141
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
42-
// "resolveJsonModule": true, /* Enable importing .json files. */
42+
"resolveJsonModule": true, /* Enable importing .json files. */
4343
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
4444

4545
/* JavaScript Support */

0 commit comments

Comments
 (0)