From d5d6103327a71c0f96b86b3b1f5fa8a906383e39 Mon Sep 17 00:00:00 2001 From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com> Date: Mon, 29 Apr 2024 22:32:46 +0100 Subject: [PATCH 01/15] Fix currency symbol being hardcoded to $ --- internal/assets/templates/stocks.html | 2 +- internal/feed/primitives.go | 26 ++++++++++++++++++++++++++ internal/feed/yahoo.go | 14 +++++++++++--- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/internal/assets/templates/stocks.html b/internal/assets/templates/stocks.html index 2bdb02e1..f8ea9b2a 100644 --- a/internal/assets/templates/stocks.html +++ b/internal/assets/templates/stocks.html @@ -15,7 +15,7 @@
{{ printf "%+.2f" .PercentChange }}%
-
${{ .Price | formatPrice }}
+
{{ .Currency }}{{ .Price | formatPrice }}
{{ end }} diff --git a/internal/feed/primitives.go b/internal/feed/primitives.go index 7f4f4642..99d67634 100644 --- a/internal/feed/primitives.go +++ b/internal/feed/primitives.go @@ -59,9 +59,35 @@ type Video struct { type Videos []Video +var currencyToSymbol = map[string]string{ + "USD": "$", + "EUR": "€", + "JPY": "¥", + "CAD": "C$", + "AUD": "A$", + "GBP": "£", + "CHF": "Fr", + "NZD": "N$", + "INR": "₹", + "BRL": "R$", + "RUB": "₽", + "TRY": "₺", + "ZAR": "R", + "CNY": "¥", + "KRW": "₩", + "HKD": "HK$", + "SGD": "S$", + "SEK": "kr", + "NOK": "kr", + "DKK": "kr", + "PLN": "zł", + "PHP": "₱", +} + type Stock struct { Name string Symbol string + Currency string Price float64 PercentChange float64 SvgChartPoints string diff --git a/internal/feed/yahoo.go b/internal/feed/yahoo.go index 94126655..a8106d74 100644 --- a/internal/feed/yahoo.go +++ b/internal/feed/yahoo.go @@ -10,6 +10,7 @@ type stockResponseJson struct { Chart struct { Result []struct { Meta struct { + Currency string `json:"currency"` Symbol string `json:"symbol"` RegularMarketPrice float64 `json:"regularMarketPrice"` ChartPreviousClose float64 `json:"chartPreviousClose"` @@ -78,10 +79,17 @@ func FetchStocksDataFromYahoo(stockRequests []StockRequest) (Stocks, error) { points := SvgPolylineCoordsFromYValues(100, 50, maybeCopySliceWithoutZeroValues(prices)) + currency, exists := currencyToSymbol[response.Chart.Result[0].Meta.Currency] + + if !exists { + currency = response.Chart.Result[0].Meta.Currency + } + stocks = append(stocks, Stock{ - Name: stockRequests[i].Name, - Symbol: response.Chart.Result[0].Meta.Symbol, - Price: response.Chart.Result[0].Meta.RegularMarketPrice, + Name: stockRequests[i].Name, + Symbol: response.Chart.Result[0].Meta.Symbol, + Price: response.Chart.Result[0].Meta.RegularMarketPrice, + Currency: currency, PercentChange: percentChange( response.Chart.Result[0].Meta.RegularMarketPrice, previous, From 44a153d30abd1553d68c6977eb5d799bcac23e14 Mon Sep 17 00:00:00 2001 From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com> Date: Wed, 1 May 2024 19:08:25 +0100 Subject: [PATCH 02/15] Allow using alternative links for YT, HN and reddit --- docs/configuration.md | 45 ++++++++++++++++++++++++++++++++++ internal/feed/hacker-news.go | 17 ++++++++++--- internal/feed/reddit.go | 16 ++++++++++-- internal/feed/youtube.go | 19 ++++++++++++-- internal/widget/hacker-news.go | 11 +++++---- internal/widget/reddit.go | 15 ++++++------ internal/widget/videos.go | 11 +++++---- 7 files changed, 109 insertions(+), 25 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 0c14525b..a46569a3 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -434,6 +434,7 @@ Preview: | ---- | ---- | -------- | ------- | | channels | array | yes | | | limit | integer | no | 25 | +| video-url-template | string | no | https://www.youtube.com/watch?v={VIDEO-ID} | ##### `channels` A list of channel IDs. One way of getting the ID of a channel is going to the channel's page and clicking on its description: @@ -447,6 +448,17 @@ Then scroll down and click on "Share channel", then "Copy channel ID": ##### `limit` The maximum number of videos to show. +##### `video-url-template` +Used to replace the default link for videos. Useful when you're running your own YouTube front-end. Example: + +```yaml +video-url-template: https://invidious.your-domain.com/watch?v={VIDEO-ID} +``` + +Placeholders: + +`{VIDEO-ID}` - the ID of the video + ### Hacker News Display a list of posts from [Hacker News](https://news.ycombinator.com/). @@ -466,6 +478,19 @@ Preview: | ---- | ---- | -------- | ------- | | limit | integer | no | 15 | | collapse-after | integer | no | 5 | +| comments-url-template | string | no | https://news.ycombinator.com/item?id={POST-ID} | + +##### `comments-url-template` +Used to replace the default link for post comments. Useful if you want to use an alternative front-end. Example: + +```yaml +comments-url-template: https://www.hckrnws.com/stories/{POST-PATH} +``` + +Placeholders: + +`{POST-ID}` - the ID of the post + ### Reddit Display a list of posts from a specific subreddit. @@ -488,6 +513,7 @@ Example: | style | string | no | vertical-list | | limit | integer | no | 15 | | collapse-after | integer | no | 5 | +| comments-url-template | string | no | https://www.reddit.com/{POST-PATH} | ##### `subreddit` The subreddit for which to fetch the posts from. @@ -513,6 +539,25 @@ The maximum number of posts to show. ##### `collapse-after` How many posts are visible before the "SHOW MORE" button appears. Set to `-1` to never collapse. Not available when using the `vertical-cards` and `horizontal-cards` styles. +##### `comments-url-template` +Used to replace the default link for post comments. Useful if you want to use the old Reddit design or any other 3rd party front-end. Example: + +```yaml +comments-url-template: https://old.reddit.com/{POST-PATH} +``` + +Placeholders: + +`{POST-PATH}` - the full path to the post, such as: + +``` +r/selfhosted/comments/bsp01i/welcome_to_rselfhosted_please_read_this_first/ +``` + +`{POST-ID}` - the ID that comes after `/comments/` + +`{SUBREDDIT}` - the subreddit name + ### Weather Display weather information for a specific location. The data is provided by https://open-meteo.com/. diff --git a/internal/feed/hacker-news.go b/internal/feed/hacker-news.go index 8e7ad736..baacdf27 100644 --- a/internal/feed/hacker-news.go +++ b/internal/feed/hacker-news.go @@ -5,6 +5,7 @@ import ( "log/slog" "net/http" "strconv" + "strings" "time" ) @@ -28,7 +29,7 @@ func getHackerNewsTopPostIds() ([]int, error) { return response, nil } -func getHackerNewsPostsFromIds(postIds []int) (ForumPosts, error) { +func getHackerNewsPostsFromIds(postIds []int, commentsUrlTemplate string) (ForumPosts, error) { requests := make([]*http.Request, len(postIds)) for i, id := range postIds { @@ -52,9 +53,17 @@ func getHackerNewsPostsFromIds(postIds []int) (ForumPosts, error) { continue } + var commentsUrl string + + if commentsUrlTemplate == "" { + commentsUrl = "https://news.ycombinator.com/item?id=" + strconv.Itoa(results[i].Id) + } else { + commentsUrl = strings.ReplaceAll(commentsUrlTemplate, "{POST-ID}", strconv.Itoa(results[i].Id)) + } + posts = append(posts, ForumPost{ Title: results[i].Title, - DiscussionUrl: "https://news.ycombinator.com/item?id=" + strconv.Itoa(results[i].Id), + DiscussionUrl: commentsUrl, TargetUrl: results[i].TargetUrl, TargetUrlDomain: extractDomainFromUrl(results[i].TargetUrl), CommentCount: results[i].CommentCount, @@ -74,7 +83,7 @@ func getHackerNewsPostsFromIds(postIds []int) (ForumPosts, error) { return posts, nil } -func FetchHackerNewsTopPosts(limit int) (ForumPosts, error) { +func FetchHackerNewsTopPosts(limit int, commentsUrlTemplate string) (ForumPosts, error) { postIds, err := getHackerNewsTopPostIds() if err != nil { @@ -85,5 +94,5 @@ func FetchHackerNewsTopPosts(limit int) (ForumPosts, error) { postIds = postIds[:limit] } - return getHackerNewsPostsFromIds(postIds) + return getHackerNewsPostsFromIds(postIds, commentsUrlTemplate) } diff --git a/internal/feed/reddit.go b/internal/feed/reddit.go index b8449f90..c8ff4c63 100644 --- a/internal/feed/reddit.go +++ b/internal/feed/reddit.go @@ -5,6 +5,7 @@ import ( "html" "net/http" "net/url" + "strings" "time" ) @@ -12,6 +13,7 @@ type subredditResponseJson struct { Data struct { Children []struct { Data struct { + Id string `json:"id"` Title string `json:"title"` Upvotes int `json:"ups"` Url string `json:"url"` @@ -28,7 +30,7 @@ type subredditResponseJson struct { } `json:"data"` } -func FetchSubredditPosts(subreddit string) (ForumPosts, error) { +func FetchSubredditPosts(subreddit string, commentsUrlTemplate string) (ForumPosts, error) { requestUrl := fmt.Sprintf("https://www.reddit.com/r/%s/hot.json", url.QueryEscape(subreddit)) request, err := http.NewRequest("GET", requestUrl, nil) @@ -57,9 +59,19 @@ func FetchSubredditPosts(subreddit string) (ForumPosts, error) { continue } + var commentsUrl string + + if commentsUrlTemplate == "" { + commentsUrl = "https://www.reddit.com" + post.Permalink + } else { + commentsUrl = strings.ReplaceAll(commentsUrlTemplate, "{SUBREDDIT}", subreddit) + commentsUrl = strings.ReplaceAll(commentsUrl, "{POST-ID}", post.Id) + commentsUrl = strings.ReplaceAll(commentsUrl, "{POST-PATH}", strings.TrimLeft(post.Permalink, "/")) + } + forumPost := ForumPost{ Title: html.UnescapeString(post.Title), - DiscussionUrl: "https://www.reddit.com" + post.Permalink, + DiscussionUrl: commentsUrl, TargetUrlDomain: post.Domain, CommentCount: post.CommentsCount, Score: post.Upvotes, diff --git a/internal/feed/youtube.go b/internal/feed/youtube.go index e22b8bb1..e478259d 100644 --- a/internal/feed/youtube.go +++ b/internal/feed/youtube.go @@ -4,6 +4,7 @@ import ( "fmt" "log/slog" "net/http" + "net/url" "strings" "time" ) @@ -38,7 +39,7 @@ func parseYoutubeFeedTime(t string) time.Time { return parsedTime } -func FetchYoutubeChannelUploads(channelIds []string) (Videos, error) { +func FetchYoutubeChannelUploads(channelIds []string, videoUrlTemplate string) (Videos, error) { requests := make([]*http.Request, 0, len(channelIds)) for i := range channelIds { @@ -75,10 +76,24 @@ func FetchYoutubeChannelUploads(channelIds []string) (Videos, error) { continue } + var videoUrl string + + if videoUrlTemplate == "" { + videoUrl = video.Link.Href + } else { + parsedUrl, err := url.Parse(video.Link.Href) + + if err == nil { + videoUrl = strings.ReplaceAll(videoUrlTemplate, "{VIDEO-ID}", parsedUrl.Query().Get("v")) + } else { + videoUrl = "#" + } + } + videos = append(videos, Video{ ThumbnailUrl: video.Group.Thumbnail.Url, Title: video.Title, - Url: video.Link.Href, + Url: videoUrl, Author: response.Channel, AuthorUrl: response.ChannelLink.Href + "/videos", TimePosted: parseYoutubeFeedTime(video.Published), diff --git a/internal/widget/hacker-news.go b/internal/widget/hacker-news.go index 9870e742..1025d061 100644 --- a/internal/widget/hacker-news.go +++ b/internal/widget/hacker-news.go @@ -10,10 +10,11 @@ import ( ) type HackerNews struct { - widgetBase `yaml:",inline"` - Posts feed.ForumPosts `yaml:"-"` - Limit int `yaml:"limit"` - CollapseAfter int `yaml:"collapse-after"` + widgetBase `yaml:",inline"` + Posts feed.ForumPosts `yaml:"-"` + Limit int `yaml:"limit"` + CollapseAfter int `yaml:"collapse-after"` + CommentsUrlTemplate string `yaml:"comments-url-template"` } func (widget *HackerNews) Initialize() error { @@ -31,7 +32,7 @@ func (widget *HackerNews) Initialize() error { } func (widget *HackerNews) Update(ctx context.Context) { - posts, err := feed.FetchHackerNewsTopPosts(40) + posts, err := feed.FetchHackerNewsTopPosts(40, widget.CommentsUrlTemplate) if !widget.canContinueUpdateAfterHandlingErr(err) { return diff --git a/internal/widget/reddit.go b/internal/widget/reddit.go index 3287fcf6..b884ac6e 100644 --- a/internal/widget/reddit.go +++ b/internal/widget/reddit.go @@ -11,12 +11,13 @@ import ( ) type Reddit struct { - widgetBase `yaml:",inline"` - Posts feed.ForumPosts `yaml:"-"` - Subreddit string `yaml:"subreddit"` - Style string `yaml:"style"` - Limit int `yaml:"limit"` - CollapseAfter int `yaml:"collapse-after"` + widgetBase `yaml:",inline"` + Posts feed.ForumPosts `yaml:"-"` + Subreddit string `yaml:"subreddit"` + Style string `yaml:"style"` + CommentsUrlTemplate string `yaml:"comments-url-template"` + Limit int `yaml:"limit"` + CollapseAfter int `yaml:"collapse-after"` } func (widget *Reddit) Initialize() error { @@ -38,7 +39,7 @@ func (widget *Reddit) Initialize() error { } func (widget *Reddit) Update(ctx context.Context) { - posts, err := feed.FetchSubredditPosts(widget.Subreddit) + posts, err := feed.FetchSubredditPosts(widget.Subreddit, widget.CommentsUrlTemplate) if !widget.canContinueUpdateAfterHandlingErr(err) { return diff --git a/internal/widget/videos.go b/internal/widget/videos.go index 26527685..a472f0ed 100644 --- a/internal/widget/videos.go +++ b/internal/widget/videos.go @@ -10,10 +10,11 @@ import ( ) type Videos struct { - widgetBase `yaml:",inline"` - Videos feed.Videos `yaml:"-"` - Channels []string `yaml:"channels"` - Limit int `yaml:"limit"` + widgetBase `yaml:",inline"` + Videos feed.Videos `yaml:"-"` + VideoUrlTemplate string `yaml:"video-url-template"` + Channels []string `yaml:"channels"` + Limit int `yaml:"limit"` } func (widget *Videos) Initialize() error { @@ -27,7 +28,7 @@ func (widget *Videos) Initialize() error { } func (widget *Videos) Update(ctx context.Context) { - videos, err := feed.FetchYoutubeChannelUploads(widget.Channels) + videos, err := feed.FetchYoutubeChannelUploads(widget.Channels, widget.VideoUrlTemplate) if !widget.canContinueUpdateAfterHandlingErr(err) { return From 58967ab758352be8f62c640cd60822120a87f11e Mon Sep 17 00:00:00 2001 From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com> Date: Wed, 1 May 2024 22:06:40 +0100 Subject: [PATCH 03/15] Update doc --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index a46569a3..3b0ebf98 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -484,7 +484,7 @@ Preview: Used to replace the default link for post comments. Useful if you want to use an alternative front-end. Example: ```yaml -comments-url-template: https://www.hckrnws.com/stories/{POST-PATH} +comments-url-template: https://www.hckrnws.com/stories/{POST-ID} ``` Placeholders: From 948c117f5b067e33e15a42a9d6791e98443665e3 Mon Sep 17 00:00:00 2001 From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com> Date: Wed, 1 May 2024 22:28:59 +0100 Subject: [PATCH 04/15] Retain stocks order by default --- docs/configuration.md | 4 ++++ internal/widget/stocks.go | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 3b0ebf98..77da5b56 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -831,6 +831,7 @@ Preview: | Name | Type | Required | | ---- | ---- | -------- | | stocks | array | yes | +| sort | string | no | ##### `stocks` An array of stocks for which to display information about. @@ -849,6 +850,9 @@ The symbol, as seen in Yahoo Finance. The name that will be displayed under the symbol. +##### `sort-by` +By default the stocks are displayed in the order they were defined. You can customize their ordering by setting the `sort-by` property to `absolute-change` for descending order based on the stock's absolute price change. + ### Twitch Channels Display a list of channels from Twitch. diff --git a/internal/widget/stocks.go b/internal/widget/stocks.go index afba0e41..18638757 100644 --- a/internal/widget/stocks.go +++ b/internal/widget/stocks.go @@ -12,6 +12,7 @@ import ( type Stocks struct { widgetBase `yaml:",inline"` Stocks feed.Stocks `yaml:"-"` + Sort string `yaml:"sort-by"` Tickers []feed.StockRequest `yaml:"stocks"` } @@ -28,7 +29,10 @@ func (widget *Stocks) Update(ctx context.Context) { return } - stocks.SortByAbsChange() + if widget.Sort == "absolute-change" { + stocks.SortByAbsChange() + } + widget.Stocks = stocks } From f8af2025e895c7e07e1e9dac65bf3ea731b245c0 Mon Sep 17 00:00:00 2001 From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com> Date: Wed, 1 May 2024 23:47:14 +0100 Subject: [PATCH 05/15] Rename flex-grow to grow --- internal/assets/static/main.css | 2 +- internal/assets/templates/page.html | 2 +- internal/assets/templates/reddit-horizontal-cards.html | 2 +- internal/assets/templates/rss-cards.html | 2 +- internal/assets/templates/videos.html | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/assets/static/main.css b/internal/assets/static/main.css index 4824536c..31c49c05 100644 --- a/internal/assets/static/main.css +++ b/internal/assets/static/main.css @@ -1011,7 +1011,7 @@ body { .justify-center { justify-content: center; } .justify-end { justify-content: end; } .uppercase { text-transform: uppercase; } -.flex-grow { flex-grow: 1; } +.grow { flex-grow: 1; } .flex-column { flex-direction: column; } .items-center { align-items: center; } .items-start { align-items: start; } diff --git a/internal/assets/templates/page.html b/internal/assets/templates/page.html index e2a6dd91..61fad9e3 100644 --- a/internal/assets/templates/page.html +++ b/internal/assets/templates/page.html @@ -29,7 +29,7 @@
- diff --git a/internal/assets/templates/reddit-horizontal-cards.html b/internal/assets/templates/reddit-horizontal-cards.html index 5744cea7..4159bcc9 100644 --- a/internal/assets/templates/reddit-horizontal-cards.html +++ b/internal/assets/templates/reddit-horizontal-cards.html @@ -12,7 +12,7 @@
{{ end }} -
+
{{ if ne "" .TargetUrl }} {{ .TargetUrlDomain }} {{ else }} diff --git a/internal/assets/templates/rss-cards.html b/internal/assets/templates/rss-cards.html index 1b5e721e..75ffa5ee 100644 --- a/internal/assets/templates/rss-cards.html +++ b/internal/assets/templates/rss-cards.html @@ -14,7 +14,7 @@ {{ end }} -
+
{{ .Title }} diff --git a/internal/widget/hacker-news.go b/internal/widget/hacker-news.go index 1025d061..d36081c2 100644 --- a/internal/widget/hacker-news.go +++ b/internal/widget/hacker-news.go @@ -15,6 +15,7 @@ type HackerNews struct { Limit int `yaml:"limit"` CollapseAfter int `yaml:"collapse-after"` CommentsUrlTemplate string `yaml:"comments-url-template"` + Thumbnails bool `yaml:"-"` } func (widget *HackerNews) Initialize() error { diff --git a/internal/widget/reddit.go b/internal/widget/reddit.go index b884ac6e..2d0e944f 100644 --- a/internal/widget/reddit.go +++ b/internal/widget/reddit.go @@ -15,6 +15,7 @@ type Reddit struct { Posts feed.ForumPosts `yaml:"-"` Subreddit string `yaml:"subreddit"` Style string `yaml:"style"` + Thumbnails bool `yaml:"thumbnails"` CommentsUrlTemplate string `yaml:"comments-url-template"` Limit int `yaml:"limit"` CollapseAfter int `yaml:"collapse-after"` From 9c5b6045aff755ac95f3f7aa24ae04dbd2d328c4 Mon Sep 17 00:00:00 2001 From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com> Date: Thu, 2 May 2024 17:14:27 +0100 Subject: [PATCH 09/15] Don't request unused wind speed data --- internal/feed/openmeteo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/feed/openmeteo.go b/internal/feed/openmeteo.go index 71438f09..b3bbe494 100644 --- a/internal/feed/openmeteo.go +++ b/internal/feed/openmeteo.go @@ -94,7 +94,7 @@ func FetchWeatherForPlace(place *PlaceJson, units string) (*Weather, error) { query.Add("timeformat", "unixtime") query.Add("timezone", place.Timezone) query.Add("forecast_days", "1") - query.Add("current", "temperature_2m,apparent_temperature,weather_code,wind_speed_10m") + query.Add("current", "temperature_2m,apparent_temperature,weather_code") query.Add("hourly", "temperature_2m,precipitation_probability") query.Add("daily", "sunrise,sunset") query.Add("temperature_unit", temperatureUnit) From c97171883cecce96302feb3cd8919a185fff2855 Mon Sep 17 00:00:00 2001 From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com> Date: Thu, 2 May 2024 18:45:15 +0100 Subject: [PATCH 10/15] Rename thumbnails to show-thumbnails --- docs/configuration.md | 4 ++-- internal/assets/templates/forum-posts.html | 2 +- internal/widget/hacker-news.go | 2 +- internal/widget/reddit.go | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 1e3997d3..b8db5ba3 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -511,7 +511,7 @@ Example: | ---- | ---- | -------- | ------- | | subreddit | string | yes | | | style | string | no | vertical-list | -| thumbnails | boolean | no | false | +| show-thumbnails | boolean | no | false | | limit | integer | no | 15 | | collapse-after | integer | no | 5 | | comments-url-template | string | no | https://www.reddit.com/{POST-PATH} | @@ -534,7 +534,7 @@ Used to change the appearance of the widget. Possible values are `vertical-list` ![](images/reddit-widget-vertical-cards-preview.png) -##### `thumbnails` +##### `show-thumbnails` Shows or hides thumbnails next to the post. This only works if the `style` is `vertical-list`. Preview: ![](images/reddit-widget-vertical-list-thumbnails.png) diff --git a/internal/assets/templates/forum-posts.html b/internal/assets/templates/forum-posts.html index aaa377eb..7c9d1886 100644 --- a/internal/assets/templates/forum-posts.html +++ b/internal/assets/templates/forum-posts.html @@ -5,7 +5,7 @@ {{ range $i, $post := .Posts }}
  • - {{ if $.Thumbnails }} + {{ if $.ShowThumbnails }} {{ if ne $post.ThumbnailUrl "" }} {{ else if $post.HasTargetUrl }} diff --git a/internal/widget/hacker-news.go b/internal/widget/hacker-news.go index d36081c2..ff9ece05 100644 --- a/internal/widget/hacker-news.go +++ b/internal/widget/hacker-news.go @@ -15,7 +15,7 @@ type HackerNews struct { Limit int `yaml:"limit"` CollapseAfter int `yaml:"collapse-after"` CommentsUrlTemplate string `yaml:"comments-url-template"` - Thumbnails bool `yaml:"-"` + ShowThumbnails bool `yaml:"-"` } func (widget *HackerNews) Initialize() error { diff --git a/internal/widget/reddit.go b/internal/widget/reddit.go index 2d0e944f..ceb3b821 100644 --- a/internal/widget/reddit.go +++ b/internal/widget/reddit.go @@ -15,7 +15,7 @@ type Reddit struct { Posts feed.ForumPosts `yaml:"-"` Subreddit string `yaml:"subreddit"` Style string `yaml:"style"` - Thumbnails bool `yaml:"thumbnails"` + ShowThumbnails bool `yaml:"show-thumbnails"` CommentsUrlTemplate string `yaml:"comments-url-template"` Limit int `yaml:"limit"` CollapseAfter int `yaml:"collapse-after"` From ad06146784689f3ec671c57fd91160f79ff9fe8e Mon Sep 17 00:00:00 2001 From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com> Date: Thu, 2 May 2024 18:45:58 +0100 Subject: [PATCH 11/15] Add hide-on-mobile utility class --- internal/assets/static/main.css | 2 +- internal/assets/templates/forum-posts.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/assets/static/main.css b/internal/assets/static/main.css index 29ea47f2..920e2a89 100644 --- a/internal/assets/static/main.css +++ b/internal/assets/static/main.css @@ -995,7 +995,7 @@ body { flex-flow: row-reverse; } - svg.forum-post-list-thumbnail { + .hide-on-mobile { display: none } } diff --git a/internal/assets/templates/forum-posts.html b/internal/assets/templates/forum-posts.html index 7c9d1886..32f7076e 100644 --- a/internal/assets/templates/forum-posts.html +++ b/internal/assets/templates/forum-posts.html @@ -9,11 +9,11 @@ {{ if ne $post.ThumbnailUrl "" }} {{ else if $post.HasTargetUrl }} - + {{ else }} - + {{ end }} From d8d66254788bf480d1356c3c502287fe90b92e96 Mon Sep 17 00:00:00 2001 From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com> Date: Thu, 2 May 2024 19:54:20 +0100 Subject: [PATCH 12/15] Allow specifying state in weather location --- docs/configuration.md | 23 +++++++++++ internal/assets/templates/weather.html | 2 +- internal/feed/openmeteo.go | 56 +++++++++++++++++++++++++- internal/widget/weather.go | 1 + 4 files changed, 79 insertions(+), 3 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index b8db5ba3..b00933eb 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -579,6 +579,15 @@ Example: location: London, United Kingdom ``` +> [!NOTE] +> +> US cities which have common names can have their state specified as the second parameter like such: +> +> * Greenville, North Carolina, United States +> * Greenville, South Carolina, United States +> * Greenville, Mississippi, United States + + Preview: ![](images/weather-widget-preview.png) @@ -592,6 +601,7 @@ Each bar represents a 2 hour interval. The yellow background represents sunrise | location | string | yes | | | units | string | no | metric | | hide-location | boolean | no | false | +| show-area-name | boolean | no | false | ##### `location` The name of the city and country to fetch weather information for. Attempting to launch the applcation with an invalid location will result in an error. You can use the [gecoding API page](https://open-meteo.com/en/docs/geocoding-api) to search for your specific location. Glance will use the first result from the list if there are multiple. @@ -602,6 +612,19 @@ Whether to show the temperature in celsius or fahrenheit, possible values are `m ##### `hide-location` Optionally don't display the location name on the widget. +##### `show-area-name` +Whether to display the state/administrative area in the location name. If set to `true` the location will be displayed as: + +``` +Greenville, North Carolina, United States +``` + +Otherwise, if set to `false` (which is the default) it'll be displayed as: + +``` +Greenville, United States +``` + ### Monitor Display a list of sites and whether they are reachable (online) or not. This is determined by sending a HEAD request to the specified URL, if the response is 200 then the site is OK. The time it took to receive a response is also shown in milliseconds. diff --git a/internal/assets/templates/weather.html b/internal/assets/templates/weather.html index b2511868..40fdcb62 100644 --- a/internal/assets/templates/weather.html +++ b/internal/assets/templates/weather.html @@ -23,7 +23,7 @@ {{ if not .HideLocation }}
    -
    {{ .Place.Name }}, {{ .Place.Country }}
    +
    {{ .Place.Name }},{{ if .ShowAreaName }} {{ .Place.Area }},{{ end }} {{ .Place.Country }}
    {{ end }} {{ end }} diff --git a/internal/feed/openmeteo.go b/internal/feed/openmeteo.go index b3bbe494..2a8dfa62 100644 --- a/internal/feed/openmeteo.go +++ b/internal/feed/openmeteo.go @@ -6,6 +6,7 @@ import ( "net/http" "net/url" "slices" + "strings" "time" _ "time/tzdata" @@ -17,6 +18,7 @@ type PlacesResponseJson struct { type PlaceJson struct { Name string + Area string `json:"admin1"` Latitude float64 Longitude float64 Timezone string @@ -48,8 +50,41 @@ type weatherColumn struct { HasPrecipitation bool } +var commonCountryAbbreviations = map[string]string{ + "US": "United States", + "USA": "United States", + "UK": "United Kingdom", +} + +func expandCountryAbbreviations(name string) string { + if expanded, ok := commonCountryAbbreviations[strings.TrimSpace(name)]; ok { + return expanded + } + + return name +} + +// Separates the location that Open Meteo accepts from the administrative area +// which can then be used to filter to the correct place after the list of places +// has been retrieved. Also expands abbreviations since Open Meteo does not accept +// country names like "US", "USA" and "UK" +func parsePlaceName(name string) (string, string) { + parts := strings.Split(name, ",") + + if len(parts) == 1 { + return name, "" + } + + if len(parts) == 2 { + return parts[0] + ", " + expandCountryAbbreviations(parts[1]), "" + } + + return parts[0] + ", " + expandCountryAbbreviations(parts[2]), strings.TrimSpace(parts[1]) +} + func FetchPlaceFromName(location string) (*PlaceJson, error) { - requestUrl := fmt.Sprintf("https://geocoding-api.open-meteo.com/v1/search?name=%s&count=1&language=en&format=json", url.QueryEscape(location)) + location, area := parsePlaceName(location) + requestUrl := fmt.Sprintf("https://geocoding-api.open-meteo.com/v1/search?name=%s&count=10&language=en&format=json", url.QueryEscape(location)) request, _ := http.NewRequest("GET", requestUrl, nil) responseJson, err := decodeJsonFromRequest[PlacesResponseJson](defaultClient, request) @@ -61,7 +96,24 @@ func FetchPlaceFromName(location string) (*PlaceJson, error) { return nil, fmt.Errorf("no places found for %s", location) } - place := &responseJson.Results[0] + var place *PlaceJson + + if area != "" { + area = strings.ToLower(area) + + for i := range responseJson.Results { + if strings.ToLower(responseJson.Results[i].Area) == area { + place = &responseJson.Results[i] + break + } + } + + if place == nil { + return nil, fmt.Errorf("no place found for %s in %s", location, area) + } + } else { + place = &responseJson.Results[0] + } loc, err := time.LoadLocation(place.Timezone) diff --git a/internal/widget/weather.go b/internal/widget/weather.go index a59b9b93..9d90e03c 100644 --- a/internal/widget/weather.go +++ b/internal/widget/weather.go @@ -12,6 +12,7 @@ import ( type Weather struct { widgetBase `yaml:",inline"` Location string `yaml:"location"` + ShowAreaName bool `yaml:"show-area-name"` HideLocation bool `yaml:"hide-location"` Units string `yaml:"units"` Place *feed.PlaceJson `yaml:"-"` From 24ef8f6e2a8f438c26597516b3368b334772dddc Mon Sep 17 00:00:00 2001 From: Jaryl Chng Date: Tue, 30 Apr 2024 09:21:33 +0800 Subject: [PATCH 13/15] Added a request-url-template option for Reddit requests Reddit blocks known datacenter IPs from accessing their endpoints unless you request them to unblock it. Even so it has a low chance of success. The new option will allow users to specify a request URL to handle Reddit requests. --- docs/configuration.md | 13 +++++++++++++ internal/feed/reddit.go | 8 ++++++-- internal/widget/reddit.go | 10 +++++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 1e3997d3..31c0a624 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -515,6 +515,7 @@ Example: | limit | integer | no | 15 | | collapse-after | integer | no | 5 | | comments-url-template | string | no | https://www.reddit.com/{POST-PATH} | +| request-url-template | string | no | | ##### `subreddit` The subreddit for which to fetch the posts from. @@ -568,6 +569,18 @@ r/selfhosted/comments/bsp01i/welcome_to_rselfhosted_please_read_this_first/ `{SUBREDDIT}` - the subreddit name +##### `request-url-template` +A custom request url that will be used to fetch the data instead. This is useful when you're hosting Glance on a VPS and Reddit is blocking the requests, and you want to route it through an HTTP proxy. + +Placeholders: + +`{REQUEST-URL}` - will be templated and replaced with the expanded request URL (i.e. https://www.reddit.com/r/selfhosted/hot.json). Example: + +``` +https://proxy/{REQUEST-URL} +https://your.proxy/?url={REQUEST-URL} +``` + ### Weather Display weather information for a specific location. The data is provided by https://open-meteo.com/. diff --git a/internal/feed/reddit.go b/internal/feed/reddit.go index c8ff4c63..3ac5deef 100644 --- a/internal/feed/reddit.go +++ b/internal/feed/reddit.go @@ -30,8 +30,12 @@ type subredditResponseJson struct { } `json:"data"` } -func FetchSubredditPosts(subreddit string, commentsUrlTemplate string) (ForumPosts, error) { - requestUrl := fmt.Sprintf("https://www.reddit.com/r/%s/hot.json", url.QueryEscape(subreddit)) +func FetchSubredditPosts(subreddit string, commentsUrlTemplate string, requestUrlTemplate string) (ForumPosts, error) { + subreddit = url.QueryEscape(subreddit) + requestUrl := fmt.Sprintf("https://www.reddit.com/r/%s/hot.json", subreddit) + if requestUrlTemplate != "" { + requestUrl = strings.ReplaceAll(requestUrlTemplate, "{REQUEST-URL}", requestUrl) + } request, err := http.NewRequest("GET", requestUrl, nil) if err != nil { diff --git a/internal/widget/reddit.go b/internal/widget/reddit.go index 2d0e944f..9226b53e 100644 --- a/internal/widget/reddit.go +++ b/internal/widget/reddit.go @@ -4,6 +4,7 @@ import ( "context" "errors" "html/template" + "strings" "time" "github.com/glanceapp/glance/internal/assets" @@ -19,6 +20,7 @@ type Reddit struct { CommentsUrlTemplate string `yaml:"comments-url-template"` Limit int `yaml:"limit"` CollapseAfter int `yaml:"collapse-after"` + RequestUrlTemplate string `yaml:"request-url-template"` } func (widget *Reddit) Initialize() error { @@ -34,13 +36,19 @@ func (widget *Reddit) Initialize() error { widget.CollapseAfter = 5 } + if widget.RequestUrlTemplate != "" { + if !strings.Contains(widget.RequestUrlTemplate, "{REQUEST-URL}") { + return errors.New("no `{REQUEST-URL}` placeholder specified") + } + } + widget.withTitle("/r/" + widget.Subreddit).withCacheDuration(30 * time.Minute) return nil } func (widget *Reddit) Update(ctx context.Context) { - posts, err := feed.FetchSubredditPosts(widget.Subreddit, widget.CommentsUrlTemplate) + posts, err := feed.FetchSubredditPosts(widget.Subreddit, widget.CommentsUrlTemplate, widget.RequestUrlTemplate) if !widget.canContinueUpdateAfterHandlingErr(err) { return From 3523562c3a0c35381e70d2704675d9dc0c72d8aa Mon Sep 17 00:00:00 2001 From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com> Date: Fri, 3 May 2024 05:38:40 +0100 Subject: [PATCH 14/15] Additions to monitor and bookmarks widgets --- docs/configuration.md | 51 +++++++++++++++++++----- internal/assets/static/main.css | 26 +++++++++++- internal/assets/templates/bookmarks.html | 9 ++++- internal/assets/templates/monitor.html | 2 +- internal/widget/bookmarks.go | 24 ++++++++++- internal/widget/monitor.go | 1 + 6 files changed, 96 insertions(+), 17 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 1c5a19f7..0022c827 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -491,7 +491,6 @@ Placeholders: `{POST-ID}` - the ID of the post - ### Reddit Display a list of posts from a specific subreddit. @@ -682,11 +681,12 @@ You can hover over the "ERROR" text to view more information. Properties for each site: -| Name | Type | Required | -| ---- | ---- | -------- | -| title | string | yes | -| url | string | yes | -| icon | string | no | +| Name | Type | Required | Default | +| ---- | ---- | -------- | ------- | +| title | string | yes | | +| url | string | yes | | +| icon | string | no | | +| same-tab | boolean | no | false | `title` @@ -700,6 +700,10 @@ The URL which will be requested and its response will determine the status of th Optional URL to an image which will be used as the icon for the site. Can be an external URL or internal via [server configured assets](#assets-path). +`same-tab` + +Whether to open the link in the same or a new tab. + ### Releases Display a list of releases for specific repositories on Github. Draft releases and prereleases will not be shown. @@ -816,14 +820,39 @@ An array of groups which can optionally have a title and a custom color. | Name | Type | Required | Default | | ---- | ---- | -------- | ------- | | title | string | no | | -| color | HSL | no | the primary theme color | +| color | HSL | no | the primary color of the theme | | links | array | yes | | ###### Properties for each link -| Name | Type | Required | -| ---- | ---- | -------- | -| title | string | yes | -| url | string | yes | +| Name | Type | Required | Default | +| ---- | ---- | -------- | ------- | +| title | string | yes | | +| url | string | yes | | +| icon | string | no | | +| same-tab | boolean | no | false | +| hide-arrow | boolean | no | false | + +`icon` + +URL pointing to an image. You can also directly use [Simple Icons](https://simpleicons.org/) via a `si:` prefix: + +```yaml +icon: si:gmail +icon: si:youtube +icon: si:reddit +``` + +> [!WARNING] +> +> Simple Icons are loaded externally and are hosted on `cdnjs.cloudflare.com`, if you do not wish to depend on a 3rd party you are free to download the icons individually and host them locally. + +`same-tab` + +Whether to open the link in the same tab or a new one. + +`hide-arrow` + +Whether to hide the colored arrow on each link. ### Calendar Display a calendar. diff --git a/internal/assets/static/main.css b/internal/assets/static/main.css index 920e2a89..3008ee4a 100644 --- a/internal/assets/static/main.css +++ b/internal/assets/static/main.css @@ -33,6 +33,7 @@ --color-widget-background: hsl(var(--color-widget-background-hsl-values)); --color-separator: hsl(var(--bghs), calc(var(--scheme) ((var(--scheme) var(--bgl)) + 4% * var(--cm)))); --color-widget-content-border: hsl(var(--bghs), calc(var(--scheme) (var(--scheme) var(--bgl) + 4%))); + --color-widget-background-highlight: hsl(var(--bghs), calc(var(--scheme) (var(--scheme) var(--bgl) + 4%))); --ths: var(--bgh), calc(var(--bgs) * var(--tsm)); --color-text-base: hsl(var(--ths), calc(var(--scheme) var(--cm) * 58%)); @@ -80,7 +81,7 @@ .visited-indicator:not(.text-truncate)::after, .visited-indicator.text-truncate::before, -.bookmarks-link::after { +.bookmarks-link:not(.bookmarks-link-no-arrow)::after { content: '↗'; margin-left: 0.5em; display: inline-block; @@ -590,10 +591,31 @@ body { color: var(--bookmarks-group-color); } -.bookmarks-link::after { +.bookmarks-group .bookmarks-link::after { color: var(--bookmarks-group-color); } +.bookmarks-icon-container { + margin-block: 0.1rem; + background-color: var(--color-widget-background-highlight); + border-radius: var(--border-radius); + padding: 0.5rem; +} + +.bookmarks-icon { + width: 20px; + height: 20px; + opacity: 0.8; +} + +.simple-icon { + opacity: 0.7; +} + +:root:not(.light-scheme) .simple-icon { + filter: invert(1); +} + .calendar-day { width: calc(100% / 7); text-align: center; diff --git a/internal/assets/templates/bookmarks.html b/internal/assets/templates/bookmarks.html index 92421e1e..068ab4d2 100644 --- a/internal/assets/templates/bookmarks.html +++ b/internal/assets/templates/bookmarks.html @@ -7,7 +7,14 @@ {{ if ne .Title "" }}
    {{ .Title }}
    {{ end }}
  • diff --git a/internal/assets/templates/monitor.html b/internal/assets/templates/monitor.html index 602c3345..fa973025 100644 --- a/internal/assets/templates/monitor.html +++ b/internal/assets/templates/monitor.html @@ -8,7 +8,7 @@ {{ end }}
    - {{ .Title }} + {{ .Title }}
      {{ if not .Status.Error }}
    • {{ .StatusText }}
    • diff --git a/internal/widget/bookmarks.go b/internal/widget/bookmarks.go index a3e3d283..afa8a052 100644 --- a/internal/widget/bookmarks.go +++ b/internal/widget/bookmarks.go @@ -2,6 +2,7 @@ package widget import ( "html/template" + "strings" "github.com/glanceapp/glance/internal/assets" ) @@ -13,14 +14,33 @@ type Bookmarks struct { Title string `yaml:"title"` Color *HSLColorField `yaml:"color"` Links []struct { - Title string `yaml:"title"` - URL string `yaml:"url"` + Title string `yaml:"title"` + URL string `yaml:"url"` + Icon string `yaml:"icon"` + IsSimpleIcon bool `yaml:"-"` + SameTab bool `yaml:"same-tab"` + HideArrow bool `yaml:"hide-arrow"` } `yaml:"links"` } `yaml:"groups"` } func (widget *Bookmarks) Initialize() error { widget.withTitle("Bookmarks").withError(nil) + + for g := range widget.Groups { + for l := range widget.Groups[g].Links { + if widget.Groups[g].Links[l].Icon == "" { + continue + } + + if strings.HasPrefix(widget.Groups[g].Links[l].Icon, "si:") { + icon := strings.TrimPrefix(widget.Groups[g].Links[l].Icon, "si:") + widget.Groups[g].Links[l].IsSimpleIcon = true + widget.Groups[g].Links[l].Icon = "https://cdnjs.cloudflare.com/ajax/libs/simple-icons/11.14.0/" + icon + ".svg" + } + } + } + widget.cachedHTML = widget.render(widget, assets.BookmarksTemplate) return nil diff --git a/internal/widget/monitor.go b/internal/widget/monitor.go index 7c964e54..3375ae5c 100644 --- a/internal/widget/monitor.go +++ b/internal/widget/monitor.go @@ -49,6 +49,7 @@ type Monitor struct { Title string `yaml:"title"` Url string `yaml:"url"` IconUrl string `yaml:"icon"` + SameTab bool `yaml:"same-tab"` Status *feed.SiteStatus `yaml:"-"` StatusText string `yaml:"-"` StatusStyle string `yaml:"-"` From f317b1b1892e7fefc7cdbc8437c626a7b656b8ee Mon Sep 17 00:00:00 2001 From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com> Date: Fri, 3 May 2024 05:43:08 +0100 Subject: [PATCH 15/15] Adjust the height and position of reddit thumbnails --- internal/assets/static/main.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/assets/static/main.css b/internal/assets/static/main.css index 3008ee4a..b77a60f6 100644 --- a/internal/assets/static/main.css +++ b/internal/assets/static/main.css @@ -576,11 +576,11 @@ body { .forum-post-list-thumbnail { flex-shrink: 0; width: 6rem; - height: 4rem; + height: 4.1rem; border-radius: var(--border-radius); object-fit: cover; border: 1px solid var(--color-separator); - margin-top: 0.2rem; + margin-top: 0.1rem; } .bookmarks-group {