Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 45 additions & 21 deletions frontend/src/Pages/Read.elm
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ import Route.Path
import Shared
import Shared.Model
import Shared.Msg
import Tailwind.Utilities as Tw
import Tailwind.Theme exposing (Color)
import Tailwind.Utilities as Tw
import Translations.Read
import Translations.Sidebar
import Ui.ShortNote as ShortNote exposing (ShortNotesViewData)
Expand All @@ -51,17 +51,17 @@ toLayout : Shared.Model -> Model -> Layouts.Layout Msg
toLayout shared model =
let
topPart =
Categories.new
{ model = model.categories
, toMsg = CategoriesSent
, onSelect = CategorySelected
, equals = \category1 category2 -> category1 == category2
, image = categoryImage
, categories = availableCategories shared.nostr shared.loginStatus shared.browserEnv.translations
, browserEnv = shared.browserEnv
, theme = shared.theme
}
|> Categories.view
Categories.new
{ model = model.categories
, toMsg = CategoriesSent
, onSelect = CategorySelected
, equals = \category1 category2 -> category1 == category2
, image = categoryImage
, categories = availableCategories shared.nostr shared.loginStatus shared.browserEnv.translations
, browserEnv = shared.browserEnv
, theme = shared.theme
}
|> Categories.view
in
Layouts.Sidebar.new
{ theme = shared.theme
Expand Down Expand Up @@ -177,6 +177,14 @@ init shared route () =

else
Effect.none

restoreScrollEffect =
if shared.readPageScrollPosition > 0 then
Ports.restoreScrollPosition shared.readPageScrollPosition
|> Effect.sendCmd

else
Effect.none
in
( { categories = Categories.init { selected = correctedCategory }
, path = route.path
Expand All @@ -186,6 +194,7 @@ init shared route () =
[ changeCategoryEffect
, requestArticlesEffect shared correctedCategory False
, signUpEffect
, restoreScrollEffect
]
)

Expand All @@ -199,8 +208,10 @@ type Msg
| CategoriesSent (Categories.Msg Category Msg)
| BookmarkButtonMsg EventId BookmarkButton.Msg
| LoadMoreArticles
| ScrollCaptured Float
| NoOp


update : Shared.Model -> Msg -> Model -> ( Model, Effect Msg )
update shared msg model =
case msg of
Expand Down Expand Up @@ -228,6 +239,12 @@ update shared msg model =
LoadMoreArticles ->
( model, requestArticlesEffect shared (Categories.selected model.categories) True )

ScrollCaptured scrollPosition ->
( model
, Shared.Msg.SetReadPageScrollPosition scrollPosition
|> Effect.sendSharedMsg
)

NoOp ->
( model, Effect.none )

Expand All @@ -241,14 +258,13 @@ updateModelWithCategory shared model category =
]
)


requestArticlesEffect : Shared.Model -> Category -> Bool -> Effect Msg
requestArticlesEffect shared category loadMore =
RequestArticlesFeed loadMore [ filterForCategory shared category ]
|> Nostr.createRequest shared.nostr "Long-form articles" [ KindUserMetadata, KindEventDeletionRequest ]
|> Shared.Msg.RequestNostrEvents
|> Effect.sendSharedMsg


|> Nostr.createRequest shared.nostr "Long-form articles" [ KindUserMetadata, KindEventDeletionRequest ]
|> Shared.Msg.RequestNostrEvents
|> Effect.sendSharedMsg


filterForCategory : Shared.Model -> Category -> EventFilter
Expand Down Expand Up @@ -328,10 +344,18 @@ userFollowsList nostr loginStatus =

subscriptions : Model -> Sub Msg
subscriptions model =
model.bookmarkButtons
|> Dict.toList
|> List.map (\(eventId, bookmarkButton) -> BookmarkButton.subscriptions bookmarkButton |> Sub.map (BookmarkButtonMsg eventId))
|> Sub.batch
let
bookmarkButtonsSubs =
model.bookmarkButtons
|> Dict.toList
|> List.map (\( eventId, bookmarkButton ) -> BookmarkButton.subscriptions bookmarkButton |> Sub.map (BookmarkButtonMsg eventId))
|> Sub.batch

scrollSub =
Ports.receiveScrollPosition ScrollCaptured
in
Sub.batch [ bookmarkButtonsSubs, scrollSub ]



-- VIEW
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/Ports.elm
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ port sendCommand : OutgoingCommand -> Cmd msg
port receiveMessage : (IncomingMessage -> msg) -> Sub msg


port restoreScrollPosition : Float -> Cmd msg


port receiveScrollPosition : (Float -> msg) -> Sub msg


connect : List String -> Cmd msg
connect relays =
sendCommand
Expand Down
13 changes: 10 additions & 3 deletions frontend/src/Shared.elm
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ contentId =
"content-container"



-- FLAGS


Expand Down Expand Up @@ -114,6 +115,7 @@ init flagsResult _ =
, role = ClientReader
, theme = ParetoTheme
, alertTimerMessage = AlertTimerMessage.init
, readPageScrollPosition = 0
}
, Effect.batch
[ Effect.sendCmd <| Cmd.map Shared.Msg.BrowserEnvMsg browserEnvCmd
Expand Down Expand Up @@ -143,6 +145,7 @@ init flagsResult _ =
, role = ClientReader
, theme = ParetoTheme
, alertTimerMessage = AlertTimerMessage.init
, readPageScrollPosition = 0
}
, Effect.none
)
Expand Down Expand Up @@ -321,6 +324,11 @@ update route msg model =
ChangeLocale locale ->
update route (BrowserEnvMsg (BrowserEnv.UpdateLocale locale)) model

SetReadPageScrollPosition scrollPosition ->
( { model | readPageScrollPosition = scrollPosition }
, Effect.none
)


updateWithPortMessage : Model -> IncomingMessage -> ( Model, Effect Msg )
updateWithPortMessage model portMessage =
Expand Down Expand Up @@ -417,9 +425,8 @@ createFollowersEffect nostr maybePubKey =
loggedIn : Model -> Bool
loggedIn model =
loggedInPubKey model.loginStatus
|> Maybe.map (\_ -> True)
|> Maybe.withDefault False

|> Maybe.map (\_ -> True)
|> Maybe.withDefault False


pubkeyDecoder : Json.Decode.Decoder PubKey
Expand Down
1 change: 1 addition & 0 deletions frontend/src/Shared/Model.elm
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type alias Model =
, role : ClientRole
, theme : Theme
, alertTimerMessage : AlertTimerMessage.Model
, readPageScrollPosition : Float
}


Expand Down
5 changes: 3 additions & 2 deletions frontend/src/Shared/Msg.elm
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ module Shared.Msg exposing (Msg(..))

{-| -}

import BrowserEnv exposing (TestMode)
import Browser.Dom
import BrowserEnv exposing (TestMode)
import Components.AlertTimerMessage as AlertTimerMessage
import Nostr
import Nostr.ConfigCheck as ConfigCheck
Expand Down Expand Up @@ -40,4 +40,5 @@ type Msg
| AlertSent AlertTimerMessage.Msg
| ScrollContentToTop
| DomError (Result Browser.Dom.Error ())
| ChangeLocale String
| ChangeLocale String
| SetReadPageScrollPosition Float
71 changes: 71 additions & 0 deletions frontend/src/interop.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,77 @@ export const onReady = ({ app, env }) => {
}
});

// Scroll position tracking for Read page only
let lastScrollPosition = 0;
const scrollThreshold = 50;

function isReadPage() {
const pathname = window.location.pathname;
// Check if current path is /read or / (root which shows Read page by default)
return pathname === '/read' || pathname === '/' || pathname.startsWith('/read?');
}

// Get the content container element
const contentContainer = document.getElementById('content-container');

app.ports.restoreScrollPosition.subscribe((scrollPosition) => {
if (contentContainer && isReadPage()) {
// Polling approach: keep trying to restore scroll until it succeeds
// This handles cases where Elm is still rendering
let attempts = 0;
const maxAttempts = 50; // ~5 seconds with 100ms intervals

const attemptScroll = () => {
attempts++;
const currentScrollTop = contentContainer.scrollTop;
const scrollHeight = contentContainer.scrollHeight;

// Try to set scroll position
contentContainer.scrollTop = scrollPosition;

// Verify it was set
const newScrollTop = contentContainer.scrollTop;

// If scroll was applied successfully or we've exceeded max attempts, stop
if (newScrollTop === scrollPosition || attempts >= maxAttempts) {
if (attempts >= maxAttempts) {
console.warn('max attempts reached, stopping scroll restoration');
} else {
console.log('scroll restoration successful');
}
lastScrollPosition = scrollPosition;
return;
}

// Schedule next attempt
setTimeout(attemptScroll, 100);
};

// Start attempting to restore scroll
attemptScroll();
}
});

// Listen to scroll events on the content-container element
if (contentContainer) {
contentContainer.addEventListener('scroll', function() {
// Only send scroll position updates when on the Read page
if (!isReadPage()) {
return;
}

const currentScrollPosition = contentContainer.scrollTop;

// Only send update if scroll position changed by at least 50px
if (Math.abs(currentScrollPosition - lastScrollPosition) >= scrollThreshold) {
lastScrollPosition = currentScrollPosition;
app.ports.receiveScrollPosition.send(currentScrollPosition);
}
}, { passive: true });
} else {
console.log('WARNING: content-container element not found!');
}

window.onload = function () {
// make sure to load Nostr-Login after browser extensions had a chance to create window.nostr
loadNostrLogin();
Expand Down