diff of a87d76b84ccff6e737fc47b0ac66850c32bd400b
a87d76b84ccff6e737fc47b0ac66850c32bd400b
diff --git a/elm-frontti/src/Ajax_cmds.elm b/elm-frontti/src/Ajax_cmds.elm
index ed1aea4..c637693 100644
--- a/elm-frontti/src/Ajax_cmds.elm
+++ b/elm-frontti/src/Ajax_cmds.elm
@@ -150,3 +150,9 @@ addFeed newFeed =
{ url = "/api/user/feeds"
, body = Http.jsonBody (Feeds.newFeedEncoder newFeed)
, expect = Http.expectWhatever FeedAdded }
+
+markFeedItemRead feed_id item_id =
+ Http.post
+ { url = "/api/user/feeds/" ++ feed_id ++ "/" ++ item_id ++ "/mark-read"
+ , body = Http.emptyBody
+ , expect = Http.expectWhatever FeedItemReadResponse}
diff --git a/elm-frontti/src/FeedView.elm b/elm-frontti/src/FeedView.elm
index 074dc85..1a0e794 100644
--- a/elm-frontti/src/FeedView.elm
+++ b/elm-frontti/src/FeedView.elm
@@ -16,35 +16,43 @@ import Tab exposing (tabs)
import Random
feed_item time_format zone item =
- li [] [ h1 [] [ a [ href item.link ] [ text item.title] ]
- , (if item.title == "" then
- h4 [] [ a [ href item.link] [ text (formatDateTime time_format zone item.pubdate) ]]
- else
- h4 [] [ text (formatDateTime time_format zone item.pubdate)])
- , div [ class "feed-author"] [ text <| "By " ++ item.author]
- , div [ class "feed-item"
- , dangerouslySetInnerHTML item.description] []]
+ case item.feed_id of
+ Just feed_id ->
+ li [] [ h1 [] [ a [ href item.link ] [ text item.title] ]
+ , (if item.title == "" then
+ h4 [] [ a [ href item.link] [ text (formatDateTime time_format zone item.pubdate) ]]
+ else
+ h4 [] [ text (formatDateTime time_format zone item.pubdate)])
+ , div [ class "feed-read"] [
+ let is_read = item.is_read in
+ button [ onClick <| ReadFeedItem feed_id item.id (not is_read) ] [ text "Mark as read"]]
+ , div [ class "feed-author"] [ text <| "By " ++ item.author]
+ , div [ class "feed-item"
+ , dangerouslySetInnerHTML item.description] []]
+ Nothing ->
+ li [] [ text "Unknown feed" ]
-correctlySortedFeedItemList settings zone items =
+correctlySortedFeedItemList show_archived settings zone items =
( items
|> List.sortBy (Time.posixToMillis << .pubdate)
|> List.reverse
+ |> List.filter (\item -> (not item.is_read) || show_archived)
|> List.map (feed_item settings.time_format zone))
-- fs = feeds, elm sucks balls at shadowing
-perFeedView settings zone fs new_feed_state =
+perFeedView show_archived settings zone fs new_feed_state =
ul [ class "feed-list" ]
(List.map (\feed ->
li [ class "feed" ]
[ header [] [ text feed.name ]
, a [ href feed.url ] [ text feed.url ]
, ul [ class "feed-items" ]
- (correctlySortedFeedItemList settings zone feed.items)]) fs)
+ (correctlySortedFeedItemList show_archived settings zone <| List.map (\i -> { i | feed_id = Just feed.id}) feed.items)]) fs)
-singleFeedView settings zone fs =
- let feed = List.concatMap .items fs in
+singleFeedView show_archived settings zone fs =
+ let final_feed = List.concatMap (\feed -> List.map (\item -> { item | feed_id = Just feed.id}) feed.items) fs in
ul [ class "feed-items" ]
- (correctlySortedFeedItemList settings zone feed)
+ (correctlySortedFeedItemList show_archived settings zone final_feed)
readerState_str state =
@@ -52,15 +60,19 @@ readerState_str state =
PerFeed -> "PerFeed"
SingleFeed -> "SingleFeed"
-feeds feedReaderState settings zone fs new_feed =
+feeds feedReaderState show_archived settings zone fs new_feed =
let new_feed_state = Maybe.withDefault (NewFeed "" "") new_feed
in
div [ id "feeds" ]
- [ tabs "rss-feed-tab" (readerState_str feedReaderState)
+ [ label [] [ input [ type_ "checkbox"
+ , checked show_archived
+ , onClick <| ShowArchivedFeedItems (not show_archived)] []
+ , text "Show read items"]
+ , tabs "rss-feed-tab" (readerState_str feedReaderState)
(Dict.fromList [ ("PerFeed", "Group by feed")
, ("SingleFeed", "Show all in a feed")])
- (Dict.fromList [ ("PerFeed", perFeedView settings zone fs new_feed_state)
- , ("SingleFeed", singleFeedView settings zone fs)])
+ (Dict.fromList [ ("PerFeed", perFeedView show_archived settings zone fs new_feed_state)
+ , ("SingleFeed", singleFeedView show_archived settings zone fs)])
, h3 [] [ text "Add new feed?"]
, label [ for "name" ] [ text "Feed name" ]
diff --git a/elm-frontti/src/Feeds.elm b/elm-frontti/src/Feeds.elm
index 7c70631..620efdc 100644
--- a/elm-frontti/src/Feeds.elm
+++ b/elm-frontti/src/Feeds.elm
@@ -20,7 +20,10 @@ type alias Item =
, description: String
, link: String
, author: String
- , pubdate: Time.Posix}
+ , pubdate: Time.Posix
+ , is_read: Bool
+ -- parent id that's required for ui to function correctly
+ , feed_id: Maybe UUID}
type alias Feed =
{ id: UUID
@@ -45,6 +48,7 @@ descriptionDecoder = Decode.field "description" Decode.string
linkDecoder = Decode.field "link" Decode.string
authorDecoder = Decode.field "author" Decode.string
pubdateDecoder = Decode.field "pubdate" Extra.iso8601
+is_readDecoder = Decode.field "is_read" Decode.bool
itemDecoder =
Decode.succeed Item
@@ -55,6 +59,8 @@ itemDecoder =
|> decodeApply linkDecoder
|> decodeApply authorDecoder
|> decodeApply pubdateDecoder
+ |> decodeApply is_readDecoder
+ |> decodeApply (Decode.succeed Nothing)
itemsDecoder =
Decode.field "items" (Decode.list itemDecoder)
diff --git a/elm-frontti/src/Main.elm b/elm-frontti/src/Main.elm
index b317c59..7d95970 100644
--- a/elm-frontti/src/Main.elm
+++ b/elm-frontti/src/Main.elm
@@ -571,7 +571,7 @@ update msg model =
FeedsReceived result ->
case result of
Ok feeds ->
- ( { model | view_state = Feeds feeds}
+ ( { model | view_state = Feeds feeds False}
, Cmd.none)
Err error ->
( { model | view_state = ShowError (errToString error) }
@@ -629,6 +629,38 @@ update msg model =
, Cmd.none)
_ -> ( model
, alert <| "Unknown tab " ++ tab_id)
+ ReadFeedItem feed_id item_id is_read ->
+ case model.view_state of
+ Feeds feeds show_archived ->
+ let new_feeds = ( feeds
+ |> List.map (\f -> if f.id == feed_id then
+ {f | items = ( f.items
+ |> List.map (\item ->
+ if item.id == item_id then
+ {item | is_read = is_read}
+ else
+ item ))}
+ else
+ f))
+ in
+ ({ model | view_state = Feeds new_feeds show_archived}
+ , markFeedItemRead (UUID.toString feed_id) (UUID.toString item_id))
+ _ -> ( model
+ , Cmd.none)
+ ShowArchivedFeedItems showArchived ->
+ case model.view_state of
+ Feeds feeds _ ->
+ ({ model
+ | view_state = Feeds feeds showArchived}
+ , Cmd.none)
+ _ -> ( model
+ , Cmd.none)
+ FeedItemReadResponse result ->
+ case result of
+ Ok _ -> ( model
+ , Cmd.none)
+ Err error -> ( { model | view_state = ShowError (errToString error) }
+ , Cmd.none)
doGoHome_ model other_cmds =
@@ -722,7 +754,7 @@ view model =
Nothing -> [ div [] [ text "No post loaded" ]]
MediaList -> [ medialist model.loadedImages model.medialist_state ]
SettingsEditor -> [ SettingsEditor.editor settings]
- Feeds feeds -> [ FeedView.feeds model.feedReaderState settings model.zone feeds model.new_feed ])
+ Feeds feeds show_archived -> [ FeedView.feeds model.feedReaderState show_archived settings model.zone feeds model.new_feed ])
, div [id "sidebar"] [ User.loginView model.loginState
, (sidebarHistory model.titles )
, (case model.view_state of
diff --git a/elm-frontti/src/Message.elm b/elm-frontti/src/Message.elm
index ec3cc0c..634b344 100644
--- a/elm-frontti/src/Message.elm
+++ b/elm-frontti/src/Message.elm
@@ -30,7 +30,7 @@ type ViewState
| MediaList -- list all the image blobs in db
| TaggedPostsView (List Article.Article)
| SettingsEditor
- | Feeds (List Feeds.Feed)
+ | Feeds (List Feeds.Feed) Bool -- <- show_archived?
type alias User =
{ username : String
@@ -163,6 +163,9 @@ type Msg
| FeedAdded (Result Http.Error ())
| SetPerFeedView
| SelectTab String String
+ | ReadFeedItem UUID UUID Bool
+ | ShowArchivedFeedItems Bool
+ | FeedItemReadResponse (Result Http.Error ())
-- ports
port reallySetupAce : String -> Cmd msg
diff --git a/resources/sql/021-more-rss-reader-stuff.sql b/resources/sql/021-more-rss-reader-stuff.sql
new file mode 100644
index 0000000..0e26653
--- /dev/null
+++ b/resources/sql/021-more-rss-reader-stuff.sql
@@ -0,0 +1,2 @@
+ALTER TABLE blog.feed_item
+ADD COLUMN read_at TIMESTAMP NULL DEFAULT NULL;
diff --git a/resources/sql/reader-fns.sql b/resources/sql/reader-fns.sql
index 7c058e1..581f69a 100644
--- a/resources/sql/reader-fns.sql
+++ b/resources/sql/reader-fns.sql
@@ -32,3 +32,13 @@ INSERT INTO blog.feed_subscription(name, url, owner) VALUES ($1, $2, $3);
-- name: insert-feed-item @execute
INSERT INTO blog.feed_item(title, link, description, author, pubdate, feed)
VALUES ($1, $2, $3, $4, to_timestamp($5), $6);
+
+-- name: mark-as-read
+UPDATE blog.feed_item fi
+SET read_at = now()
+FROM blog.feed_subscription fs
+WHERE fs.id = fi.feed
+ AND fi.id = $1
+ AND fi.feed = $2
+ AND fs.owner = $3
+RETURNING *;
diff --git a/src/migration-list.lisp b/src/migration-list.lisp
index 411322b..fb68da0 100644
--- a/src/migration-list.lisp
+++ b/src/migration-list.lisp
@@ -25,6 +25,7 @@
(defmigration "018-previously")
(defmigration "019-rss-settings")
(defmigration "020-rss-reader-stuff")
+(defmigration "021-more-rss-reader-stuff")
(defun prepare-e2e-migration ()
(postmodern:execute "DELETE FROM blog.Users")
diff --git a/src/routes/rss-reader-routes.lisp b/src/routes/rss-reader-routes.lisp
index f316875..f5c5bbf 100644
--- a/src/routes/rss-reader-routes.lisp
+++ b/src/routes/rss-reader-routes.lisp
@@ -31,6 +31,13 @@
(setf (hunchentoot:return-code*) 204)
""))
+(defroute mark-as-read ("/api/user/feeds/:feed-id/:item-id/mark-read" :method :post
+ :decorators (@transaction
+ @authenticated)) ()
+ (murja.rss.reader-db:mark-as-read item-id feed-id (gethash "id" *user*))
+ (setf (hunchentoot:return-code*) 204)
+ "")
+
;; This will be called by cron/curl
(defroute update-feeds-rotue ("/api/rss/update" :method :get
:decorators (@transaction)) ()
diff --git a/src/rss/reader-db.lisp b/src/rss/reader-db.lisp
index 650dd17..d320a86 100644
--- a/src/rss/reader-db.lisp
+++ b/src/rss/reader-db.lisp
@@ -3,7 +3,7 @@
(:import-from :halisql :defqueries)
(:import-from :lisp-fixup :partial :compose)
(:import-from :cl-date-time-parser :parse-date-time)
- (:export :get-user-feeds :subscribe-to-feed))
+ (:export :get-user-feeds :subscribe-to-feed :mark-as-read))
(in-package :murja.rss.reader-db)
@@ -15,10 +15,22 @@
hashmap)
(defun get-user-feeds (user-id)
- (let ((feeds (coerce (get-user-feeds* user-id) 'list)))
- (mapcar (compose (partial #'parse "items")
- (partial #'parse "creator"))
- feeds)))
+ (let* ((feeds (coerce (get-user-feeds* user-id) 'list))
+ (fixed-feeds
+ (mapcar (compose (partial #'parse "items")
+ (partial #'parse "creator"))
+ feeds)))
+ (dolist (feed fixed-feeds)
+ (setf (gethash "items" feed)
+ (coerce (gethash "items" feed) 'list))
+ (dolist (item (gethash "items" feed))
+ (setf (gethash "is_read" item)
+ (not (equalp 'NULL (gethash "read_at" item))))
+
+ ;; frontend doesn't need this
+ (remhash "read_at" item)))
+
+ fixed-feeds))
(defun subscribe-to-feed (feed-name feed-url owner)
(insert-feed feed-name feed-url (gethash "id" owner)))