Skip to content

Commit a1d478c

Browse files
committed
chore(ci): fix bookmark pagination patch
1 parent 6b91ff4 commit a1d478c

1 file changed

Lines changed: 119 additions & 121 deletions

File tree

.github/workflows/apply-library-pagination.yml

Lines changed: 119 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -26,31 +26,38 @@ jobs:
2626
run: |
2727
python <<'PY'
2828
from pathlib import Path
29+
from textwrap import dedent
30+
31+
def replace_once(source: str, old: str, new: str, label: str) -> str:
32+
count = source.count(old)
33+
if count != 1:
34+
raise SystemExit(f'{label}: expected 1 match, found {count}')
35+
return source.replace(old, new, 1)
2936
3037
page = Path('src/pages/BookmarkLibrary.tsx')
3138
source = page.read_text(encoding='utf-8')
3239
33-
replacements = [
34-
(
35-
" useDeferredValue,\n useMemo,\n useState,\n",
36-
" useDeferredValue,\n useEffect,\n useMemo,\n useRef,\n useState,\n",
37-
),
38-
(
39-
" ArrowUpDown,\n Clock3,\n",
40-
" ArrowUpDown,\n ChevronLeft,\n ChevronRight,\n Clock3,\n",
41-
),
42-
(
43-
" List,\n Lock,\n",
44-
" List,\n Lock,\n MoreHorizontal,\n",
45-
),
46-
]
47-
for old, new in replacements:
48-
if source.count(old) != 1:
49-
raise SystemExit(f'BookmarkLibrary import anchor mismatch: {old!r}')
50-
source = source.replace(old, new, 1)
51-
52-
constants_anchor = "const LIBRARY_VIEW_KEY = 'nowen-library-view-v1'\nconst LIBRARY_SORT_KEY = 'nowen-library-sort-v1'\n"
53-
constants_replacement = """const LIBRARY_VIEW_KEY = 'nowen-library-view-v1'
40+
source = replace_once(
41+
source,
42+
" useDeferredValue,\n useMemo,\n useState,\n",
43+
" useDeferredValue,\n useEffect,\n useMemo,\n useRef,\n useState,\n",
44+
'react imports',
45+
)
46+
source = replace_once(
47+
source,
48+
" ArrowUpDown,\n Clock3,\n",
49+
" ArrowUpDown,\n ChevronLeft,\n ChevronRight,\n Clock3,\n",
50+
'chevron imports',
51+
)
52+
source = replace_once(
53+
source,
54+
" List,\n Lock,\n",
55+
" List,\n Lock,\n MoreHorizontal,\n",
56+
'ellipsis import',
57+
)
58+
59+
constants = dedent('''
60+
const LIBRARY_VIEW_KEY = 'nowen-library-view-v1'
5461
const LIBRARY_SORT_KEY = 'nowen-library-sort-v1'
5562
const LIBRARY_PAGE_SIZE_KEY = 'nowen-library-page-size-v1'
5663
const LIBRARY_PAGE_SIZE_OPTIONS = [20, 40, 80] as const
@@ -94,26 +101,28 @@ jobs:
94101
items.push(totalPages)
95102
return items
96103
}
97-
"""
98-
if source.count(constants_anchor) != 1:
99-
raise SystemExit('BookmarkLibrary constants anchor mismatch')
100-
source = source.replace(constants_anchor, constants_replacement, 1)
104+
''').lstrip()
105+
source = replace_once(
106+
source,
107+
"const LIBRARY_VIEW_KEY = 'nowen-library-view-v1'\nconst LIBRARY_SORT_KEY = 'nowen-library-sort-v1'\n",
108+
constants,
109+
'library constants',
110+
)
101111
102-
state_anchor = """ const [sortMode, setSortMode] = useState<SortMode>(() =>
103-
readStoredValue(LIBRARY_SORT_KEY, ['custom', 'title', 'updated'] as const, 'custom'),
112+
state_anchor = (
113+
" const [sortMode, setSortMode] = useState<SortMode>(() =>\n"
114+
" readStoredValue(LIBRARY_SORT_KEY, ['custom', 'title', 'updated'] as const, 'custom'),\n"
115+
" )\n"
104116
)
105-
"""
106-
state_replacement = state_anchor + """ const [pageSize, setPageSize] = useState(readStoredPageSize)
107-
const [currentPage, setCurrentPage] = useState(1)
108-
const contentRef = useRef<HTMLElement>(null)
109-
"""
110-
if source.count(state_anchor) != 1:
111-
raise SystemExit('BookmarkLibrary state anchor mismatch')
112-
source = source.replace(state_anchor, state_replacement, 1)
113-
114-
logic_anchor = """ const activeCollectionLabel = useMemo(() => {
115-
"""
116-
logic_block = """ const totalPages = Math.max(1, Math.ceil(filteredBookmarks.length / pageSize))
117+
state_insert = state_anchor + (
118+
" const [pageSize, setPageSize] = useState(readStoredPageSize)\n"
119+
" const [currentPage, setCurrentPage] = useState(1)\n"
120+
" const contentRef = useRef<HTMLElement>(null)\n"
121+
)
122+
source = replace_once(source, state_anchor, state_insert, 'pagination state')
123+
124+
pagination_logic = dedent('''
125+
const totalPages = Math.max(1, Math.ceil(filteredBookmarks.length / pageSize))
117126
const safeCurrentPage = Math.min(currentPage, totalPages)
118127
const pageStart = (safeCurrentPage - 1) * pageSize
119128
const paginatedBookmarks = useMemo(
@@ -136,29 +145,33 @@ jobs:
136145
setCurrentPage((previousPage) => Math.min(previousPage, totalPages))
137146
}, [totalPages])
138147
139-
""" + logic_anchor
140-
if source.count(logic_anchor) != 1:
141-
raise SystemExit('BookmarkLibrary pagination logic anchor mismatch')
142-
source = source.replace(logic_anchor, logic_block, 1)
143-
144-
handlers_anchor = """ const clearFilters = useCallback(() => {
145-
setQuery('')
146-
if (activeTag) {
147-
onSelectTag(null)
148-
} else if (activeCollection !== 'all') {
149-
onSelectCollection('all')
150-
}
151-
}, [activeCollection, activeTag, onSelectCollection, onSelectTag])
152-
153-
"""
154-
handlers_replacement = handlers_anchor + """ const scrollToResults = useCallback(() => {
155-
const scroll = () => contentRef.current?.scrollIntoView?.({ behavior: 'smooth', block: 'start' })
156-
if (typeof window.requestAnimationFrame === 'function') {
157-
window.requestAnimationFrame(scroll)
158-
} else {
159-
scroll()
160-
}
161-
}, [])
148+
''')
149+
source = replace_once(
150+
source,
151+
" const activeCollectionLabel = useMemo(() => {\n",
152+
pagination_logic + " const activeCollectionLabel = useMemo(() => {\n",
153+
'pagination calculations',
154+
)
155+
156+
clear_filters = (
157+
" const clearFilters = useCallback(() => {\n"
158+
" setQuery('')\n"
159+
" if (activeTag) {\n"
160+
" onSelectTag(null)\n"
161+
" } else if (activeCollection !== 'all') {\n"
162+
" onSelectCollection('all')\n"
163+
" }\n"
164+
" }, [activeCollection, activeTag, onSelectCollection, onSelectTag])\n\n"
165+
)
166+
pagination_handlers = dedent('''
167+
const scrollToResults = useCallback(() => {
168+
const scroll = () => contentRef.current?.scrollIntoView?.({ behavior: 'smooth', block: 'start' })
169+
if (typeof window.requestAnimationFrame === 'function') {
170+
window.requestAnimationFrame(scroll)
171+
} else {
172+
scroll()
173+
}
174+
}, [])
162175
163176
const changePage = useCallback((nextPage: number) => {
164177
const clampedPage = Math.min(Math.max(nextPage, 1), totalPages)
@@ -174,39 +187,22 @@ jobs:
174187
scrollToResults()
175188
}, [scrollToResults])
176189
177-
"""
178-
if source.count(handlers_anchor) != 1:
179-
raise SystemExit('BookmarkLibrary handlers anchor mismatch')
180-
source = source.replace(handlers_anchor, handlers_replacement, 1)
181-
182-
main_anchor = '<main className="bookmark-library__content">'
183-
if source.count(main_anchor) != 1:
184-
raise SystemExit('BookmarkLibrary main anchor mismatch')
185-
source = source.replace(main_anchor, '<main ref={contentRef} className="bookmark-library__content">', 1)
186-
187-
results_anchor = """ {filteredBookmarks.map((bookmark) => (
188-
"""
189-
if source.count(results_anchor) != 1:
190-
raise SystemExit('BookmarkLibrary result map anchor mismatch')
191-
source = source.replace(results_anchor, """ {paginatedBookmarks.map((bookmark) => (
192-
""", 1)
193-
194-
footer_anchor = """ ))}
195-
</div>
196-
) : (
197-
"""
198-
footer_replacement = """ ))}
199-
</div>
200-
) : (
201-
"""
202-
if source.count(footer_anchor) != 1:
203-
raise SystemExit('BookmarkLibrary result footer anchor mismatch')
204-
205-
# Insert the pagination after the result/empty conditional so the footer stays stable.
206-
conditional_end_anchor = """ )}
207-
</main>
208-
"""
209-
pagination_markup = """ )}
190+
''')
191+
source = replace_once(source, clear_filters, clear_filters + pagination_handlers, 'pagination handlers')
192+
source = replace_once(
193+
source,
194+
'<main className="bookmark-library__content">',
195+
'<main ref={contentRef} className="bookmark-library__content">',
196+
'content ref',
197+
)
198+
source = replace_once(
199+
source,
200+
' {filteredBookmarks.map((bookmark) => (\n',
201+
' {paginatedBookmarks.map((bookmark) => (\n',
202+
'paginated result map',
203+
)
204+
205+
pagination_markup = dedent('''
210206
211207
{showPagination && (
212208
<footer className="bookmark-library__pagination">
@@ -274,18 +270,19 @@ jobs:
274270
</nav>
275271
</footer>
276272
)}
277-
</main>
278-
"""
279-
if source.count(conditional_end_anchor) != 1:
280-
raise SystemExit('BookmarkLibrary conditional end anchor mismatch')
281-
source = source.replace(conditional_end_anchor, pagination_markup, 1)
282-
273+
''')
274+
source = replace_once(
275+
source,
276+
" )}\n </main>\n",
277+
" )}" + pagination_markup + " </main>\n",
278+
'pagination footer',
279+
)
283280
page.write_text(source, encoding='utf-8')
284281
285282
css = Path('src/styles/bookmark-library.css')
286283
css_source = css.read_text(encoding='utf-8')
287-
css_anchor = '.bookmark-library__empty {\n'
288-
css_block = r'''.bookmark-library__pagination {
284+
css_block = dedent('''
285+
.bookmark-library__pagination {
289286
display: flex;
290287
align-items: center;
291288
justify-content: space-between;
@@ -420,24 +417,27 @@ jobs:
420417
}
421418
}
422419
423-
'''
424-
if css_source.count(css_anchor) != 1:
425-
raise SystemExit('BookmarkLibrary CSS anchor mismatch')
426-
css_source = css_source.replace(css_anchor, css_block + css_anchor, 1)
420+
''')
421+
css_source = replace_once(
422+
css_source,
423+
'.bookmark-library__empty {\n',
424+
css_block + '.bookmark-library__empty {\n',
425+
'pagination styles',
426+
)
427427
css.write_text(css_source, encoding='utf-8')
428428
429429
test = Path('src/pages/__tests__/BookmarkLibrary.test.tsx')
430430
test_source = test.read_text(encoding='utf-8')
431-
test_anchor = """ it('persists the preferred list view', async () => {
432-
const { getByTitle } = render(<BookmarkLibrary {...baseProps} />)
433-
fireEvent.click(getByTitle('列表视图'))
434-
435-
await waitFor(() => {
436-
expect(window.localStorage.getItem('nowen-library-view-v1')).toBe('list')
437-
})
438-
})
439-
"""
440-
test_block = test_anchor + """
431+
test_anchor = (
432+
" it('persists the preferred list view', async () => {\n"
433+
" const { getByTitle } = render(<BookmarkLibrary {...baseProps} />)\n"
434+
" fireEvent.click(getByTitle('列表视图'))\n\n"
435+
" await waitFor(() => {\n"
436+
" expect(window.localStorage.getItem('nowen-library-view-v1')).toBe('list')\n"
437+
" })\n"
438+
" })\n"
439+
)
440+
test_case = dedent('''
441441
442442
it('paginates large result sets and keeps page-size controls available', async () => {
443443
const manyBookmarks: Bookmark[] = Array.from({ length: 25 }, (_, index) => ({
@@ -474,10 +474,8 @@ jobs:
474474
expect(window.localStorage.getItem('nowen-library-page-size-v1')).toBe('40')
475475
})
476476
})
477-
"""
478-
if test_source.count(test_anchor) != 1:
479-
raise SystemExit('BookmarkLibrary test anchor mismatch')
480-
test_source = test_source.replace(test_anchor, test_block, 1)
477+
''')
478+
test_source = replace_once(test_source, test_anchor, test_anchor + test_case, 'pagination test')
481479
test.write_text(test_source, encoding='utf-8')
482480
PY
483481

0 commit comments

Comments
 (0)