Skip to content

Commit cf41914

Browse files
disnetclaude
andauthored
Fix scroll position lost when returning from full-screen reader (#118)
* Fix scroll position lost when returning from full-screen reader Replace {#if}/{:else} conditional rendering with simultaneous rendering and CSS hiding in FeedListView and SavedListView. The reader overlay is position:fixed and fully covers the viewport, so the list DOM doesn't need to be destroyed. Using visibility:hidden + position:fixed keeps the list in the DOM, preserving scroll position. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix scroll position by saving/restoring window.scrollY The previous fix kept the DOM alive with visibility:hidden + position:fixed, but position:fixed removes the element from flow, causing window.scrollY to reset to 0. Now we explicitly save scrollY on reader open and restore it via requestAnimationFrame on reader close. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 125fbb8 commit cf41914

2 files changed

Lines changed: 229 additions & 211 deletions

File tree

src/lib/components/feed/FeedListView.svelte

Lines changed: 143 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,18 @@
2020
2121
// Reader overlay state
2222
let readerItem = $state<FeedDisplayItem | null>(null);
23+
let savedScrollY = 0;
2324
2425
function openReader(item: FeedDisplayItem) {
26+
savedScrollY = window.scrollY;
2527
readerItem = item;
2628
}
2729
2830
function closeReader() {
2931
readerItem = null;
32+
requestAnimationFrame(() => {
33+
window.scrollTo(0, savedScrollY);
34+
});
3035
}
3136
3237
export function openSelectedReader() {
@@ -164,150 +169,152 @@
164169
? handleReaderShare
165170
: undefined}
166171
/>
167-
{:else}
168-
<div class="article-list">
169-
{#each feedViewStore.currentItems as displayItem, index (displayItem.key)}
170-
<div bind:this={articleElements[index]}>
171-
{#if displayItem.type === 'article'}
172-
{@const article = displayItem.item}
173-
{@const sub = subscriptionsStore.subscriptions.find(
174-
(s) => s.id === article.subscriptionId
175-
)}
176-
<ArticleCard
177-
{article}
178-
siteUrl={sub?.siteUrl || sub?.feedUrl}
179-
feedTitle={sub?.customTitle || sub?.title}
180-
feedId={sub?.id}
181-
isRead={itemLabelsStore.isRead(article.guid)}
182-
isSaved={itemLabelsStore.isSaved(article.guid)}
183-
isShared={sharesStore.isShared(article.guid)}
184-
shareNote={sharesStore.getShareNote(article.guid)}
185-
selected={preferences.expandAllItems || feedViewStore.selectedIndex === index}
186-
expanded={feedViewStore.expandedIndex === index}
187-
highlighted={feedViewStore.selectedIndex === index}
188-
onToggleSave={() => onToggleSave(article)}
189-
onToggleRead={() => handleToggleRead(article)}
190-
onShare={() => sub && onShare(article, sub)}
191-
onUnshare={() => onUnshare(article.guid)}
192-
onSelect={() => handleSelect(index)}
193-
onExpand={() => handleExpand(index)}
194-
onOpenFullscreen={() => openReader(displayItem)}
195-
/>
196-
{:else if displayItem.type === 'share'}
197-
{@const share = displayItem.item}
198-
{@const localArticle = feedViewStore.getArticleForShare(share)}
199-
<ArticleCard
200-
{share}
201-
{localArticle}
202-
isRead={itemLabelsStore.isSocialRead(share.recordUri)}
203-
isSaved={itemLabelsStore.isSaved(share.recordUri)}
204-
selected={preferences.expandAllItems || feedViewStore.selectedIndex === index}
205-
expanded={feedViewStore.expandedIndex === index}
206-
highlighted={feedViewStore.selectedIndex === index}
207-
onToggleSave={() =>
208-
itemLabelsStore.toggleSave(share.recordUri, 'share', share.itemUrl, share.itemTitle, {
172+
{/if}
173+
174+
<div class="article-list" class:hidden-behind-reader={readerItem !== null}>
175+
{#each feedViewStore.currentItems as displayItem, index (displayItem.key)}
176+
<div bind:this={articleElements[index]}>
177+
{#if displayItem.type === 'article'}
178+
{@const article = displayItem.item}
179+
{@const sub = subscriptionsStore.subscriptions.find((s) => s.id === article.subscriptionId)}
180+
<ArticleCard
181+
{article}
182+
siteUrl={sub?.siteUrl || sub?.feedUrl}
183+
feedTitle={sub?.customTitle || sub?.title}
184+
feedId={sub?.id}
185+
isRead={itemLabelsStore.isRead(article.guid)}
186+
isSaved={itemLabelsStore.isSaved(article.guid)}
187+
isShared={sharesStore.isShared(article.guid)}
188+
shareNote={sharesStore.getShareNote(article.guid)}
189+
selected={preferences.expandAllItems || feedViewStore.selectedIndex === index}
190+
expanded={feedViewStore.expandedIndex === index}
191+
highlighted={feedViewStore.selectedIndex === index}
192+
onToggleSave={() => onToggleSave(article)}
193+
onToggleRead={() => handleToggleRead(article)}
194+
onShare={() => sub && onShare(article, sub)}
195+
onUnshare={() => onUnshare(article.guid)}
196+
onSelect={() => handleSelect(index)}
197+
onExpand={() => handleExpand(index)}
198+
onOpenFullscreen={() => openReader(displayItem)}
199+
/>
200+
{:else if displayItem.type === 'share'}
201+
{@const share = displayItem.item}
202+
{@const localArticle = feedViewStore.getArticleForShare(share)}
203+
<ArticleCard
204+
{share}
205+
{localArticle}
206+
isRead={itemLabelsStore.isSocialRead(share.recordUri)}
207+
isSaved={itemLabelsStore.isSaved(share.recordUri)}
208+
selected={preferences.expandAllItems || feedViewStore.selectedIndex === index}
209+
expanded={feedViewStore.expandedIndex === index}
210+
highlighted={feedViewStore.selectedIndex === index}
211+
onToggleSave={() =>
212+
itemLabelsStore.toggleSave(share.recordUri, 'share', share.itemUrl, share.itemTitle, {
213+
type: 'share',
214+
recordUri: share.recordUri,
215+
itemUrl: share.itemUrl,
216+
itemTitle: share.itemTitle,
217+
itemAuthor: share.itemAuthor,
218+
itemDescription: share.itemDescription,
219+
itemImage: share.itemImage,
220+
itemPublishedAt: share.itemPublishedAt,
221+
})}
222+
onToggleRead={() => {
223+
if (itemLabelsStore.isSocialRead(share.recordUri)) {
224+
itemLabelsStore.markSocialAsUnread(share.recordUri);
225+
} else {
226+
feedViewStore.trackSeenThisSession({
209227
type: 'share',
210-
recordUri: share.recordUri,
211-
itemUrl: share.itemUrl,
212-
itemTitle: share.itemTitle,
213-
itemAuthor: share.itemAuthor,
214-
itemDescription: share.itemDescription,
215-
itemImage: share.itemImage,
216-
itemPublishedAt: share.itemPublishedAt,
217-
})}
218-
onToggleRead={() => {
219-
if (itemLabelsStore.isSocialRead(share.recordUri)) {
220-
itemLabelsStore.markSocialAsUnread(share.recordUri);
221-
} else {
222-
feedViewStore.trackSeenThisSession({
223-
type: 'share',
224-
item: share,
225-
key: share.recordUri,
226-
});
227-
itemLabelsStore.markSocialAsRead(
228-
'share',
229-
share.recordUri,
230-
share.authorDid,
231-
share.itemUrl,
232-
share.itemTitle
233-
);
228+
item: share,
229+
key: share.recordUri,
230+
});
231+
itemLabelsStore.markSocialAsRead(
232+
'share',
233+
share.recordUri,
234+
share.authorDid,
235+
share.itemUrl,
236+
share.itemTitle
237+
);
238+
}
239+
}}
240+
onSelect={() => handleSelect(index)}
241+
onExpand={() => handleExpand(index)}
242+
onOpenFullscreen={() => openReader(displayItem)}
243+
/>
244+
{:else if displayItem.type === 'userShare'}
245+
{@const share = displayItem.item}
246+
{@const article = displayItem.article}
247+
{@const sub = subscriptionsStore.subscriptions.find((s) => s.id === article.subscriptionId)}
248+
<ArticleCard
249+
{article}
250+
siteUrl={sub?.siteUrl || sub?.feedUrl}
251+
feedTitle={sub?.customTitle || sub?.title}
252+
feedId={sub?.id}
253+
isRead={itemLabelsStore.isRead(article.guid)}
254+
isSaved={itemLabelsStore.isSaved(article.guid)}
255+
isShared={true}
256+
shareNote={share.note}
257+
reshareCount={share.reshareCount || 0}
258+
selected={preferences.expandAllItems || feedViewStore.selectedIndex === index}
259+
expanded={feedViewStore.expandedIndex === index}
260+
highlighted={feedViewStore.selectedIndex === index}
261+
onToggleSave={() => onToggleSave(article)}
262+
onToggleRead={() => handleToggleRead(article)}
263+
onUnshare={() => onUnshare(share.articleGuid)}
264+
onSelect={() => handleSelect(index)}
265+
onExpand={() => handleExpand(index)}
266+
onOpenFullscreen={() => openReader(displayItem)}
267+
/>
268+
{:else if displayItem.type === 'document'}
269+
{@const doc = displayItem.item}
270+
<ArticleCard
271+
document={doc}
272+
isRead={itemLabelsStore.isSocialRead(doc.recordUri)}
273+
isSaved={itemLabelsStore.isSaved(doc.recordUri)}
274+
selected={preferences.expandAllItems || feedViewStore.selectedIndex === index}
275+
expanded={feedViewStore.expandedIndex === index}
276+
highlighted={feedViewStore.selectedIndex === index}
277+
onToggleSave={() =>
278+
itemLabelsStore.toggleSave(
279+
doc.recordUri,
280+
'document',
281+
doc.canonicalUrl || doc.path || '',
282+
doc.title,
283+
{
284+
type: 'document',
285+
recordUri: doc.recordUri,
286+
url: doc.canonicalUrl || doc.path || '',
287+
title: doc.title,
288+
description: doc.description,
289+
publishedAt: doc.publishedAt,
234290
}
235-
}}
236-
onSelect={() => handleSelect(index)}
237-
onExpand={() => handleExpand(index)}
238-
onOpenFullscreen={() => openReader(displayItem)}
239-
/>
240-
{:else if displayItem.type === 'userShare'}
241-
{@const share = displayItem.item}
242-
{@const article = displayItem.article}
243-
{@const sub = subscriptionsStore.subscriptions.find(
244-
(s) => s.id === article.subscriptionId
245-
)}
246-
<ArticleCard
247-
{article}
248-
siteUrl={sub?.siteUrl || sub?.feedUrl}
249-
feedTitle={sub?.customTitle || sub?.title}
250-
feedId={sub?.id}
251-
isRead={itemLabelsStore.isRead(article.guid)}
252-
isSaved={itemLabelsStore.isSaved(article.guid)}
253-
isShared={true}
254-
shareNote={share.note}
255-
reshareCount={share.reshareCount || 0}
256-
selected={preferences.expandAllItems || feedViewStore.selectedIndex === index}
257-
expanded={feedViewStore.expandedIndex === index}
258-
highlighted={feedViewStore.selectedIndex === index}
259-
onToggleSave={() => onToggleSave(article)}
260-
onToggleRead={() => handleToggleRead(article)}
261-
onUnshare={() => onUnshare(share.articleGuid)}
262-
onSelect={() => handleSelect(index)}
263-
onExpand={() => handleExpand(index)}
264-
onOpenFullscreen={() => openReader(displayItem)}
265-
/>
266-
{:else if displayItem.type === 'document'}
267-
{@const doc = displayItem.item}
268-
<ArticleCard
269-
document={doc}
270-
isRead={itemLabelsStore.isSocialRead(doc.recordUri)}
271-
isSaved={itemLabelsStore.isSaved(doc.recordUri)}
272-
selected={preferences.expandAllItems || feedViewStore.selectedIndex === index}
273-
expanded={feedViewStore.expandedIndex === index}
274-
highlighted={feedViewStore.selectedIndex === index}
275-
onToggleSave={() =>
276-
itemLabelsStore.toggleSave(
277-
doc.recordUri,
278-
'document',
279-
doc.canonicalUrl || doc.path || '',
280-
doc.title,
281-
{
282-
type: 'document',
283-
recordUri: doc.recordUri,
284-
url: doc.canonicalUrl || doc.path || '',
285-
title: doc.title,
286-
description: doc.description,
287-
publishedAt: doc.publishedAt,
288-
}
289-
)}
290-
onToggleRead={() => handleToggleDocumentRead(doc)}
291-
onSelect={() => handleSelect(index)}
292-
onExpand={() => handleExpand(index)}
293-
onOpenFullscreen={() => openReader(displayItem)}
294-
/>
295-
{/if}
296-
</div>
297-
{/each}
291+
)}
292+
onToggleRead={() => handleToggleDocumentRead(doc)}
293+
onSelect={() => handleSelect(index)}
294+
onExpand={() => handleExpand(index)}
295+
onOpenFullscreen={() => openReader(displayItem)}
296+
/>
297+
{/if}
298+
</div>
299+
{/each}
298300

299-
<InfiniteScrollSentinel
300-
hasMore={feedViewStore.hasMore}
301-
isLoading={feedViewStore.isLoadingMore}
302-
onLoadMore={() => feedViewStore.loadMore()}
303-
/>
304-
</div>
305-
{/if}
301+
<InfiniteScrollSentinel
302+
hasMore={feedViewStore.hasMore}
303+
isLoading={feedViewStore.isLoadingMore}
304+
onLoadMore={() => feedViewStore.loadMore()}
305+
/>
306+
</div>
306307

307308
<style>
308309
.article-list {
309310
display: flex;
310311
flex-direction: column;
311312
gap: 0.5rem;
312313
}
314+
315+
.hidden-behind-reader {
316+
visibility: hidden;
317+
position: fixed;
318+
pointer-events: none;
319+
}
313320
</style>

0 commit comments

Comments
 (0)