Skip to content

Commit

Permalink
feat(term): add feed detail widget
Browse files Browse the repository at this point in the history
  • Loading branch information
ymgyt committed Feb 23, 2024
1 parent 3f0f8b4 commit 836258d
Show file tree
Hide file tree
Showing 12 changed files with 190 additions and 14 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ futures-util = { version = "0.3.30" }
graphql_client = { version = "0.13.0", default-features = false }
headers = { version = "0.4.0" }
http = { version = "0.2" } # request use 0.2
itertools = { version = "0.12", default-features = false, features = ["use_std"] }
kvsd = { version = "0.1.2", default-features = false }
moka = { version = "0.12.4", features = ["future"] }
opentelemetry = { version = "0.21.0" }
Expand Down
1 change: 1 addition & 0 deletions crates/synd_term/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ edit = "0.1.5"
futures-util = "0.3.30"
graphql_client = { workspace = true }
html2text = { version = "0.12" }
itertools = { workspace = true }
open = "5.0.1"
ratatui = { version = "0.26.0" }
reqwest = { workspace = true }
Expand Down
5 changes: 5 additions & 0 deletions crates/synd_term/gql/mutation.gql
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ mutation UnsubscribeFeed($input: UnsubscribeFeedInput!) {

fragment Feed on Feed {
id
type
title
url
updated
websiteUrl
description
generator
entries(first: 10) {
nodes {
...EntryMeta
Expand All @@ -51,6 +53,9 @@ fragment Feed on Feed {
...Link
}
}
authors {
nodes
}
}

fragment EntryMeta on Entry {
Expand Down
5 changes: 5 additions & 0 deletions crates/synd_term/gql/query.gql
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ query Subscription($after: String, $first: Int) {

fragment Feed on Feed {
id
type
title
url
updated
websiteUrl
description
generator
entries(first: 10) {
nodes {
...EntryMeta
Expand All @@ -28,6 +30,9 @@ fragment Feed on Feed {
...Link
}
}
authors {
nodes
}
}

fragment EntryMeta on Entry {
Expand Down
12 changes: 12 additions & 0 deletions crates/synd_term/gql/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,18 @@
"name": "String",
"ofType": null
}
},
{
"args": [],
"deprecationReason": null,
"description": null,
"isDeprecated": false,
"name": "generator",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
],
"inputFields": null,
Expand Down
46 changes: 44 additions & 2 deletions crates/synd_term/src/client/mutation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub mod subscribe_feed {
#![allow(dead_code)]
use std::result::Result;
pub const OPERATION_NAME: &str = "SubscribeFeed";
pub const QUERY : & str = "mutation SubscribeFeed($input: SubscribeFeedInput!) {\n subscribeFeed(input: $input) {\n __typename\n ... on SubscribeFeedSuccess {\n feed {\n ...Feed\n }\n status {\n code\n }\n }\n ... on SubscribeFeedError {\n status {\n code\n }\n message\n }\n }\n}\n\nmutation UnsubscribeFeed($input: UnsubscribeFeedInput!) {\n unsubscribeFeed(input: $input) {\n __typename\n ... on UnsubscribeFeedSuccess {\n status {\n code\n }\n }\n ... on UnsubscribeFeedError {\n status {\n code\n }\n }\n }\n}\n\nfragment Feed on Feed {\n id\n title\n url\n updated\n websiteUrl\n description\n entries(first: 10) {\n nodes {\n ...EntryMeta\n }\n }\n links {\n nodes {\n ...Link\n }\n }\n}\n\nfragment EntryMeta on Entry {\n title,\n published,\n updated,\n summary,\n}\n\nfragment Link on Link {\n href\n rel\n mediaType\n title \n}\n" ;
pub const QUERY : & str = "mutation SubscribeFeed($input: SubscribeFeedInput!) {\n subscribeFeed(input: $input) {\n __typename\n ... on SubscribeFeedSuccess {\n feed {\n ...Feed\n }\n status {\n code\n }\n }\n ... on SubscribeFeedError {\n status {\n code\n }\n message\n }\n }\n}\n\nmutation UnsubscribeFeed($input: UnsubscribeFeedInput!) {\n unsubscribeFeed(input: $input) {\n __typename\n ... on UnsubscribeFeedSuccess {\n status {\n code\n }\n }\n ... on UnsubscribeFeedError {\n status {\n code\n }\n }\n }\n}\n\nfragment Feed on Feed {\n id\n type\n title\n url\n updated\n websiteUrl\n description\n generator\n entries(first: 10) {\n nodes {\n ...EntryMeta\n }\n }\n links {\n nodes {\n ...Link\n }\n }\n authors {\n nodes\n }\n}\n\nfragment EntryMeta on Entry {\n title,\n published,\n updated,\n summary,\n}\n\nfragment Link on Link {\n href\n rel\n mediaType\n title \n}\n" ;
use super::*;
use serde::{Deserialize, Serialize};
#[allow(dead_code)]
Expand All @@ -17,6 +17,40 @@ pub mod subscribe_feed {
type ID = String;
type Rfc3339Time = crate::client::scalar::Rfc3339Time;
#[derive(Debug)]
pub enum FeedType {
ATOM,
RSS1,
RSS2,
RSS0,
JSON,
Other(String),
}
impl ::serde::Serialize for FeedType {
fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_str(match *self {
FeedType::ATOM => "ATOM",
FeedType::RSS1 => "RSS1",
FeedType::RSS2 => "RSS2",
FeedType::RSS0 => "RSS0",
FeedType::JSON => "JSON",
FeedType::Other(ref s) => &s,
})
}
}
impl<'de> ::serde::Deserialize<'de> for FeedType {
fn deserialize<D: ::serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s: String = ::serde::Deserialize::deserialize(deserializer)?;
match s.as_str() {
"ATOM" => Ok(FeedType::ATOM),
"RSS1" => Ok(FeedType::RSS1),
"RSS2" => Ok(FeedType::RSS2),
"RSS0" => Ok(FeedType::RSS0),
"JSON" => Ok(FeedType::JSON),
_ => Ok(FeedType::Other(s)),
}
}
}
#[derive(Debug)]
pub enum ResponseCode {
OK,
UNAUTHORIZED,
Expand Down Expand Up @@ -59,14 +93,18 @@ pub mod subscribe_feed {
#[derive(Deserialize, Debug)]
pub struct Feed {
pub id: ID,
#[serde(rename = "type")]
pub type_: FeedType,
pub title: Option<String>,
pub url: String,
pub updated: Option<Rfc3339Time>,
#[serde(rename = "websiteUrl")]
pub website_url: Option<String>,
pub description: Option<String>,
pub generator: Option<String>,
pub entries: FeedEntries,
pub links: FeedLinks,
pub authors: FeedAuthors,
}
#[derive(Deserialize, Debug)]
pub struct FeedEntries {
Expand All @@ -79,6 +117,10 @@ pub mod subscribe_feed {
}
pub type FeedLinksNodes = Link;
#[derive(Deserialize, Debug)]
pub struct FeedAuthors {
pub nodes: Vec<String>,
}
#[derive(Deserialize, Debug)]
pub struct EntryMeta {
pub title: Option<String>,
pub published: Option<Rfc3339Time>,
Expand Down Expand Up @@ -140,7 +182,7 @@ pub mod unsubscribe_feed {
#![allow(dead_code)]
use std::result::Result;
pub const OPERATION_NAME: &str = "UnsubscribeFeed";
pub const QUERY : & str = "mutation SubscribeFeed($input: SubscribeFeedInput!) {\n subscribeFeed(input: $input) {\n __typename\n ... on SubscribeFeedSuccess {\n feed {\n ...Feed\n }\n status {\n code\n }\n }\n ... on SubscribeFeedError {\n status {\n code\n }\n message\n }\n }\n}\n\nmutation UnsubscribeFeed($input: UnsubscribeFeedInput!) {\n unsubscribeFeed(input: $input) {\n __typename\n ... on UnsubscribeFeedSuccess {\n status {\n code\n }\n }\n ... on UnsubscribeFeedError {\n status {\n code\n }\n }\n }\n}\n\nfragment Feed on Feed {\n id\n title\n url\n updated\n websiteUrl\n description\n entries(first: 10) {\n nodes {\n ...EntryMeta\n }\n }\n links {\n nodes {\n ...Link\n }\n }\n}\n\nfragment EntryMeta on Entry {\n title,\n published,\n updated,\n summary,\n}\n\nfragment Link on Link {\n href\n rel\n mediaType\n title \n}\n" ;
pub const QUERY : & str = "mutation SubscribeFeed($input: SubscribeFeedInput!) {\n subscribeFeed(input: $input) {\n __typename\n ... on SubscribeFeedSuccess {\n feed {\n ...Feed\n }\n status {\n code\n }\n }\n ... on SubscribeFeedError {\n status {\n code\n }\n message\n }\n }\n}\n\nmutation UnsubscribeFeed($input: UnsubscribeFeedInput!) {\n unsubscribeFeed(input: $input) {\n __typename\n ... on UnsubscribeFeedSuccess {\n status {\n code\n }\n }\n ... on UnsubscribeFeedError {\n status {\n code\n }\n }\n }\n}\n\nfragment Feed on Feed {\n id\n type\n title\n url\n updated\n websiteUrl\n description\n generator\n entries(first: 10) {\n nodes {\n ...EntryMeta\n }\n }\n links {\n nodes {\n ...Link\n }\n }\n authors {\n nodes\n }\n}\n\nfragment EntryMeta on Entry {\n title,\n published,\n updated,\n summary,\n}\n\nfragment Link on Link {\n href\n rel\n mediaType\n title \n}\n" ;
use super::*;
use serde::{Deserialize, Serialize};
#[allow(dead_code)]
Expand Down
46 changes: 44 additions & 2 deletions crates/synd_term/src/client/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub mod subscription {
#![allow(dead_code)]
use std::result::Result;
pub const OPERATION_NAME: &str = "Subscription";
pub const QUERY : & str = "query Subscription($after: String, $first: Int) {\n output: subscription {\n feeds(after: $after, first: $first) {\n nodes {\n ...Feed\n }\n pageInfo {\n ...PageInfo\n }\n }\n }\n}\n\nfragment Feed on Feed {\n id\n title\n url\n updated\n websiteUrl\n description\n entries(first: 10) {\n nodes {\n ...EntryMeta\n }\n }\n links {\n nodes {\n ...Link\n }\n }\n}\n\nfragment EntryMeta on Entry {\n title,\n published,\n updated,\n summary,\n}\n\nfragment Link on Link {\n href\n rel\n mediaType\n title \n}\n\nquery Entries($after: String, $first: Int!) {\n output: subscription {\n entries(after: $after, first: $first) {\n nodes {\n ...Entry\n }\n pageInfo {\n ...PageInfo\n }\n }\n }\n}\n\nfragment Entry on Entry {\n title\n published\n updated\n summary\n websiteUrl\n feed {\n ...FeedMeta\n }\n}\n\nfragment FeedMeta on FeedMeta {\n title\n url\n}\n\nfragment PageInfo on PageInfo {\n hasNextPage\n endCursor\n}\n" ;
pub const QUERY : & str = "query Subscription($after: String, $first: Int) {\n output: subscription {\n feeds(after: $after, first: $first) {\n nodes {\n ...Feed\n }\n pageInfo {\n ...PageInfo\n }\n }\n }\n}\n\nfragment Feed on Feed {\n id\n type\n title\n url\n updated\n websiteUrl\n description\n generator\n entries(first: 10) {\n nodes {\n ...EntryMeta\n }\n }\n links {\n nodes {\n ...Link\n }\n }\n authors {\n nodes\n }\n}\n\nfragment EntryMeta on Entry {\n title,\n published,\n updated,\n summary,\n}\n\nfragment Link on Link {\n href\n rel\n mediaType\n title \n}\n\nquery Entries($after: String, $first: Int!) {\n output: subscription {\n entries(after: $after, first: $first) {\n nodes {\n ...Entry\n }\n pageInfo {\n ...PageInfo\n }\n }\n }\n}\n\nfragment Entry on Entry {\n title\n published\n updated\n summary\n websiteUrl\n feed {\n ...FeedMeta\n }\n}\n\nfragment FeedMeta on FeedMeta {\n title\n url\n}\n\nfragment PageInfo on PageInfo {\n hasNextPage\n endCursor\n}\n" ;
use super::*;
use serde::{Deserialize, Serialize};
#[allow(dead_code)]
Expand All @@ -16,6 +16,40 @@ pub mod subscription {
#[allow(dead_code)]
type ID = String;
type Rfc3339Time = crate::client::scalar::Rfc3339Time;
#[derive(Debug)]
pub enum FeedType {
ATOM,
RSS1,
RSS2,
RSS0,
JSON,
Other(String),
}
impl ::serde::Serialize for FeedType {
fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_str(match *self {
FeedType::ATOM => "ATOM",
FeedType::RSS1 => "RSS1",
FeedType::RSS2 => "RSS2",
FeedType::RSS0 => "RSS0",
FeedType::JSON => "JSON",
FeedType::Other(ref s) => &s,
})
}
}
impl<'de> ::serde::Deserialize<'de> for FeedType {
fn deserialize<D: ::serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s: String = ::serde::Deserialize::deserialize(deserializer)?;
match s.as_str() {
"ATOM" => Ok(FeedType::ATOM),
"RSS1" => Ok(FeedType::RSS1),
"RSS2" => Ok(FeedType::RSS2),
"RSS0" => Ok(FeedType::RSS0),
"JSON" => Ok(FeedType::JSON),
_ => Ok(FeedType::Other(s)),
}
}
}
#[derive(Serialize, Debug)]
pub struct Variables {
pub after: Option<String>,
Expand All @@ -25,14 +59,18 @@ pub mod subscription {
#[derive(Deserialize, Debug)]
pub struct Feed {
pub id: ID,
#[serde(rename = "type")]
pub type_: FeedType,
pub title: Option<String>,
pub url: String,
pub updated: Option<Rfc3339Time>,
#[serde(rename = "websiteUrl")]
pub website_url: Option<String>,
pub description: Option<String>,
pub generator: Option<String>,
pub entries: FeedEntries,
pub links: FeedLinks,
pub authors: FeedAuthors,
}
#[derive(Deserialize, Debug)]
pub struct FeedEntries {
Expand All @@ -45,6 +83,10 @@ pub mod subscription {
}
pub type FeedLinksNodes = Link;
#[derive(Deserialize, Debug)]
pub struct FeedAuthors {
pub nodes: Vec<String>,
}
#[derive(Deserialize, Debug)]
pub struct EntryMeta {
pub title: Option<String>,
pub published: Option<Rfc3339Time>,
Expand Down Expand Up @@ -99,7 +141,7 @@ pub mod entries {
#![allow(dead_code)]
use std::result::Result;
pub const OPERATION_NAME: &str = "Entries";
pub const QUERY : & str = "query Subscription($after: String, $first: Int) {\n output: subscription {\n feeds(after: $after, first: $first) {\n nodes {\n ...Feed\n }\n pageInfo {\n ...PageInfo\n }\n }\n }\n}\n\nfragment Feed on Feed {\n id\n title\n url\n updated\n websiteUrl\n description\n entries(first: 10) {\n nodes {\n ...EntryMeta\n }\n }\n links {\n nodes {\n ...Link\n }\n }\n}\n\nfragment EntryMeta on Entry {\n title,\n published,\n updated,\n summary,\n}\n\nfragment Link on Link {\n href\n rel\n mediaType\n title \n}\n\nquery Entries($after: String, $first: Int!) {\n output: subscription {\n entries(after: $after, first: $first) {\n nodes {\n ...Entry\n }\n pageInfo {\n ...PageInfo\n }\n }\n }\n}\n\nfragment Entry on Entry {\n title\n published\n updated\n summary\n websiteUrl\n feed {\n ...FeedMeta\n }\n}\n\nfragment FeedMeta on FeedMeta {\n title\n url\n}\n\nfragment PageInfo on PageInfo {\n hasNextPage\n endCursor\n}\n" ;
pub const QUERY : & str = "query Subscription($after: String, $first: Int) {\n output: subscription {\n feeds(after: $after, first: $first) {\n nodes {\n ...Feed\n }\n pageInfo {\n ...PageInfo\n }\n }\n }\n}\n\nfragment Feed on Feed {\n id\n type\n title\n url\n updated\n websiteUrl\n description\n generator\n entries(first: 10) {\n nodes {\n ...EntryMeta\n }\n }\n links {\n nodes {\n ...Link\n }\n }\n authors {\n nodes\n }\n}\n\nfragment EntryMeta on Entry {\n title,\n published,\n updated,\n summary,\n}\n\nfragment Link on Link {\n href\n rel\n mediaType\n title \n}\n\nquery Entries($after: String, $first: Int!) {\n output: subscription {\n entries(after: $after, first: $first) {\n nodes {\n ...Entry\n }\n pageInfo {\n ...PageInfo\n }\n }\n }\n}\n\nfragment Entry on Entry {\n title\n published\n updated\n summary\n websiteUrl\n feed {\n ...FeedMeta\n }\n}\n\nfragment FeedMeta on FeedMeta {\n title\n url\n}\n\nfragment PageInfo on PageInfo {\n hasNextPage\n endCursor\n}\n" ;
use super::*;
use serde::{Deserialize, Serialize};
#[allow(dead_code)]
Expand Down
24 changes: 24 additions & 0 deletions crates/synd_term/src/types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use chrono::DateTime;
use synd_feed::types::FeedType;

use crate::client::{mutation, query};

Expand Down Expand Up @@ -78,39 +79,62 @@ impl EntryMeta {

#[derive(Debug)]
pub struct Feed {
pub r#type: Option<FeedType>,
pub title: Option<String>,
pub url: String,
pub updated: Option<Time>,
pub links: Vec<Link>,
pub website_url: Option<String>,
pub description: Option<String>,
pub generator: Option<String>,
pub entries: Vec<EntryMeta>,
pub authors: Vec<String>,
}

impl From<query::subscription::Feed> for Feed {
fn from(f: query::subscription::Feed) -> Self {
Self {
r#type: match f.type_ {
query::subscription::FeedType::ATOM => Some(FeedType::Atom),
query::subscription::FeedType::RSS1 => Some(FeedType::RSS1),
query::subscription::FeedType::RSS2 => Some(FeedType::RSS2),
query::subscription::FeedType::RSS0 => Some(FeedType::RSS0),
query::subscription::FeedType::JSON => Some(FeedType::JSON),
query::subscription::FeedType::Other(_) => None,
},
title: f.title,
url: f.url,
updated: f.updated.map(parse_time),
links: f.links.nodes.into_iter().map(From::from).collect(),
website_url: f.website_url,
description: f.description,
generator: f.generator,
entries: f.entries.nodes.into_iter().map(From::from).collect(),
authors: f.authors.nodes,
}
}
}

impl From<mutation::subscribe_feed::Feed> for Feed {
fn from(f: mutation::subscribe_feed::Feed) -> Self {
Self {
r#type: match f.type_ {
mutation::subscribe_feed::FeedType::ATOM => Some(FeedType::Atom),
mutation::subscribe_feed::FeedType::RSS1 => Some(FeedType::RSS1),
mutation::subscribe_feed::FeedType::RSS2 => Some(FeedType::RSS2),
mutation::subscribe_feed::FeedType::RSS0 => Some(FeedType::RSS0),
mutation::subscribe_feed::FeedType::JSON => Some(FeedType::JSON),
mutation::subscribe_feed::FeedType::Other(_) => None,
},
title: f.title,
url: f.url,
updated: f.updated.map(parse_time),
links: f.links.nodes.into_iter().map(From::from).collect(),
website_url: f.website_url,
description: f.description,
generator: f.generator,
entries: f.entries.nodes.into_iter().map(From::from).collect(),
authors: f.authors.nodes,
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/synd_term/src/ui/components/entries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ impl Entries {

let scrollbar_area = Rect {
y: area.y + 2, // table header
height: area.height - 3,
height: area.height.saturating_sub(3),
..area
};

Expand Down

0 comments on commit 836258d

Please sign in to comment.