Просмотр исходного кода

Merge branch 'release/v0.6.0' into features

Svilen Markov 11 месяцев назад
Родитель
Сommit
776fdcc6ce

+ 6 - 14
docs/configuration.md

@@ -1044,13 +1044,13 @@ You can hover over the "ERROR" text to view more information.
 
 #### Properties
 
-| Name | Type | Required |
-| ---- | ---- | -------- |
-| sites | array | yes |
-| style | string | no |
+| Name | Type | Required | Default |
+| ---- | ---- | -------- | ------- |
+| sites | array | yes | |
+| show-failing-only | boolean | no | false |
 
-##### `style`
-To make the widget scale appropriately in a `full` size column, set the style to the experimental `dynamic-columns-experimental` option.
+##### `show-failing-only`
+Shows only a list of failing sites when set to `true`.
 
 ##### `sites`
 
@@ -1333,14 +1333,10 @@ Preview:
 | Name | Type | Required |
 | ---- | ---- | -------- |
 | groups | array | yes |
-| style | string | no |
 
 ##### `groups`
 An array of groups which can optionally have a title and a custom color.
 
-##### `style`
-To make the widget scale appropriately in a `full` size column, set the style to the experimental `dynamic-columns-experimental` option.
-
 ###### Properties for each group
 | Name | Type | Required | Default |
 | ---- | ---- | -------- | ------- |
@@ -1518,7 +1514,6 @@ Preview:
 | ---- | ---- | -------- |
 | markets | array | yes |
 | sort-by | string | no |
-| style | string | no |
 
 ##### `markets`
 An array of markets for which to display information about.
@@ -1526,9 +1521,6 @@ An array of markets for which to display information about.
 ##### `sort-by`
 By default the markets 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.
 
-##### `style`
-To make the widget scale appropriately in a `full` size column, set the style to the experimental `dynamic-columns-experimental` option.
-
 ###### Properties for each stock
 | Name | Type | Required |
 | ---- | ---- | -------- |

+ 70 - 27
internal/assets/static/main.css

@@ -121,15 +121,6 @@
     color: var(--color-primary);
 }
 
-.list        { --list-half-gap: 0rem; }
-.list-gap-2  { --list-half-gap: 0.1rem; }
-.list-gap-4  { --list-half-gap: 0.2rem; }
-.list-gap-10 { --list-half-gap: 0.5rem; }
-.list-gap-14 { --list-half-gap: 0.7rem; }
-.list-gap-20 { --list-half-gap: 1rem; }
-.list-gap-24 { --list-half-gap: 1.2rem; }
-.list-gap-34 { --list-half-gap: 1.7rem; }
-
 .page-columns-transitioned .list-with-transition > * { animation: collapsibleItemReveal .25s backwards; }
 .list-with-transition > *:nth-child(2) { animation-delay: 30ms; }
 .list-with-transition > *:nth-child(3) { animation-delay: 60ms; }
@@ -143,7 +134,7 @@
     margin-top: calc(var(--list-half-gap) * 2);
 }
 
-.list-with-separator > *:not(:first-child) {
+.list.list-with-separator > *:not(:first-child) {
     margin-top: var(--list-half-gap);
     border-top: 1px solid var(--color-separator);
     padding-top: var(--list-half-gap);
@@ -570,17 +561,30 @@ details[open] .summary::after {
     max-width: 1100px;
 }
 
+.page-center-vertically .page {
+    display: flex;
+    justify-content: center;
+    flex-direction: column;
+}
+
+/* TODO: refactor, otherwise I hope I never have to change dynamic columns again */
 .dynamic-columns {
-    gap: calc(var(--widget-content-vertical-padding) / 2);
+    --list-half-gap: 0.5rem;
+    gap: var(--widget-content-vertical-padding) var(--widget-content-horizontal-padding);
     display: grid;
     grid-template-columns: repeat(var(--columns-per-row), 1fr);
-    margin: calc(0px - var(--widget-content-vertical-padding) / 2) calc(0px - var(--widget-content-horizontal-padding) / 2);
 }
 
 .dynamic-columns > * {
-    padding: calc(var(--widget-content-vertical-padding) / 2) calc(var(--widget-content-horizontal-padding) / 1.5);
-    background-color: var(--color-background);
-    border-radius: var(--border-radius);
+    padding-left: var(--widget-content-horizontal-padding);
+    border-left: 1px solid var(--color-separator);
+    min-width: 0;
+}
+
+.dynamic-columns > *:first-child {
+    padding-top: 0;
+    border-top: none;
+    border-left: none;
 }
 
 .dynamic-columns:has(> :nth-child(1)) { --columns-per-row: 1; }
@@ -589,23 +593,49 @@ details[open] .summary::after {
 .dynamic-columns:has(> :nth-child(4)) { --columns-per-row: 4; }
 .dynamic-columns:has(> :nth-child(5)) { --columns-per-row: 5; }
 
-@container widget (max-width: 1500px) {
+@container widget (max-width: 599px) {
+    .dynamic-columns { gap: 0; }
     .dynamic-columns:has(> :nth-child(1)) { --columns-per-row: 1; }
-    .dynamic-columns:has(> :nth-child(2)) { --columns-per-row: 2; }
-    .dynamic-columns:has(> :nth-child(3)) { --columns-per-row: 3; }
-    .dynamic-columns:has(> :nth-child(4)) { --columns-per-row: 4; }
+    .dynamic-columns > * {
+        border-left: none;
+        padding-left: 0;
+    }
+    .dynamic-columns > *:not(:first-child) {
+        margin-top: calc(var(--list-half-gap) * 2);
+    }
+    .dynamic-columns.list-with-separator > *:not(:first-child) {
+        margin-top: var(--list-half-gap);
+        border-top: 1px solid var(--color-separator);
+        padding-top: var(--list-half-gap);
+    }
 }
-@container widget (max-width: 1250px) {
-    .dynamic-columns:has(> :nth-child(1)) { --columns-per-row: 1; }
+@container widget (min-width: 600px) and (max-width: 849px) {
     .dynamic-columns:has(> :nth-child(2)) { --columns-per-row: 2; }
+    .dynamic-columns > :nth-child(2n-1) {
+        border-left: none;
+        padding-left: 0;
+    }
+}
+@container widget (min-width: 850px) and (max-width: 1249px) {
     .dynamic-columns:has(> :nth-child(3)) { --columns-per-row: 3; }
+    .dynamic-columns > :nth-child(3n+1) {
+        border-left: none;
+        padding-left: 0;
+    }
 }
-@container widget (max-width: 850px) {
-    .dynamic-columns:has(> :nth-child(1)) { --columns-per-row: 1; }
-    .dynamic-columns:has(> :nth-child(2)) { --columns-per-row: 2; }
+@container widget (min-width: 1250px) and (max-width: 1499px) {
+    .dynamic-columns:has(> :nth-child(4)) { --columns-per-row: 4; }
+    .dynamic-columns > :nth-child(4n+1) {
+        border-left: none;
+        padding-left: 0;
+    }
 }
-@container widget (max-width: 550px) {
-    .dynamic-columns:has(> :nth-child(1)) { --columns-per-row: 1; }
+@container widget (min-width: 1500px) {
+    .dynamic-columns:has(> :nth-child(5)) { --columns-per-row: 5; }
+    .dynamic-columns > :nth-child(5n+1) {
+        border-left: none;
+        padding-left: 0;
+    }
 }
 
 .cards-vertical {
@@ -839,6 +869,7 @@ details[open] .summary::after {
 .market-chart {
     margin-left: auto;
     width: 6.5rem;
+    flex-shrink: 0;
 }
 
 .market-chart svg {
@@ -1325,10 +1356,13 @@ details[open] .summary::after {
 }
 
 .monitor-site:hover .monitor-site-icon {
-    filter: grayscale(0);
     opacity: 1;
 }
 
+.monitor-site:hover .monitor-site-icon:not(.simple-icon) {
+    filter: grayscale(0);
+}
+
 .monitor-site-status-icon {
     flex-shrink: 0;
     margin-left: auto;
@@ -1764,4 +1798,13 @@ details[open] .summary::after {
 .margin-bottom-10   { margin-bottom: 1rem; }
 .margin-bottom-15   { margin-bottom: 1.5rem; }
 .margin-bottom-auto { margin-bottom: auto; }
+.padding-block-5    { padding-block: 0.5rem; }
 .scale-half         { transform: scale(0.5); }
+.list               { --list-half-gap: 0rem; }
+.list-gap-2         { --list-half-gap: 0.1rem; }
+.list-gap-4         { --list-half-gap: 0.2rem; }
+.list-gap-10        { --list-half-gap: 0.5rem; }
+.list-gap-14        { --list-half-gap: 0.7rem; }
+.list-gap-20        { --list-half-gap: 1rem; }
+.list-gap-24        { --list-half-gap: 1.2rem; }
+.list-gap-34        { --list-half-gap: 1.7rem; }

+ 14 - 28
internal/assets/templates/bookmarks.html

@@ -1,37 +1,23 @@
 {{ template "widget-base.html" . }}
 
 {{ define "widget-content" }}
-{{ if ne .Style "dynamic-columns-experimental" }}
-<ul class="list list-gap-24 list-with-separator">
-    {{ range .Groups }}
-    <li class="bookmarks-group"{{ if .Color }} style="--bookmarks-group-color: {{ .Color.AsCSSValue }}"{{ end }}>
-        {{ template "group" . }}
-    </li>
-    {{ end }}
-</ul>
-{{ else }}
-<div class="dynamic-columns">
+<div class="dynamic-columns list-gap-24 list-with-separator">
     {{ range .Groups }}
     <div class="bookmarks-group"{{ if .Color }} style="--bookmarks-group-color: {{ .Color.AsCSSValue }}"{{ end }}>
-        {{ template "group" . }}
+        {{ if ne .Title "" }}<div class="bookmarks-group-title size-h3 margin-bottom-3">{{ .Title }}</div>{{ end }}
+        <ul class="list list-gap-2">
+        {{ range .Links }}
+        <li class="flex items-center gap-10">
+            {{ if ne "" .Icon }}
+            <div class="bookmarks-icon-container">
+                <img class="bookmarks-icon{{ if .IsSimpleIcon }} simple-icon{{ end }}" src="{{ .Icon }}" alt="" loading="lazy">
+            </div>
+            {{ end }}
+            <a href="{{ .URL }}" class="bookmarks-link {{ if .HideArrow }}bookmarks-link-no-arrow {{ end }}color-highlight size-h4" {{ if not .SameTab }}target="_blank"{{ end }} rel="noreferrer">{{ .Title }}</a>
+        </li>
+        {{ end }}
+        </ul>
     </div>
     {{ end }}
 </div>
 {{ end }}
-{{ end }}
-
-{{ define "group" }}
-{{ if ne .Title "" }}<div class="bookmarks-group-title size-h3 margin-bottom-3">{{ .Title }}</div>{{ end }}
-<ul class="list list-gap-2">
-{{ range .Links }}
-<li class="flex items-center gap-10">
-    {{ if ne "" .Icon }}
-    <div class="bookmarks-icon-container">
-        <img class="bookmarks-icon{{ if .IsSimpleIcon }} simple-icon{{ end }}" src="{{ .Icon }}" alt="" loading="lazy">
-    </div>
-    {{ end }}
-    <a href="{{ .URL }}" class="bookmarks-link {{ if .HideArrow }}bookmarks-link-no-arrow {{ end }}color-highlight size-h4" {{ if not .SameTab }}target="_blank"{{ end }} rel="noreferrer">{{ .Title }}</a>
-</li>
-{{ end }}
-</ul>
-{{ end }}

+ 16 - 30
internal/assets/templates/markets.html

@@ -1,39 +1,25 @@
 {{ template "widget-base.html" . }}
 
 {{ define "widget-content" }}
-{{ if ne .Style "dynamic-columns-experimental" }}
-<ul class="list list-gap-20 list-with-separator">
-    {{ range .Markets }}
-    <li class="flex items-center gap-15">
-        {{ template "market" . }}
-    </li>
-    {{ end }}
-</ul>
-{{ else }}
-<div class="dynamic-columns">
+<div class="dynamic-columns list-gap-20 list-with-separator">
     {{ range .Markets }}
     <div class="flex items-center gap-15">
-        {{ template "market" . }}
-    </div>
-    {{ end }}
-</div>
-{{ end }}
-{{ end }}
-
-{{ define "market" }}
-<div class="min-width-0">
-    <a{{ if ne "" .SymbolLink }} href="{{ .SymbolLink }}" target="_blank" rel="noreferrer"{{ end }} class="color-highlight size-h3 block text-truncate">{{ .Symbol }}</a>
-    <div class="text-truncate">{{ .Name }}</div>
-</div>
+        <div class="min-width-0">
+            <a{{ if ne "" .SymbolLink }} href="{{ .SymbolLink }}" target="_blank" rel="noreferrer"{{ end }} class="color-highlight size-h3 block text-truncate">{{ .Symbol }}</a>
+            <div class="text-truncate">{{ .Name }}</div>
+        </div>
 
-<a class="market-chart" {{ if ne "" .ChartLink }} href="{{ .ChartLink }}" target="_blank" rel="noreferrer"{{ end }}>
-    <svg class="market-chart shrink-0" viewBox="0 0 100 50">
-        <polyline fill="none" stroke="var(--color-text-subdue)" stroke-width="1.5px" points="{{ .SvgChartPoints }}" vector-effect="non-scaling-stroke"></polyline>
-    </svg>
-</a>
+        <a class="market-chart" {{ if ne "" .ChartLink }} href="{{ .ChartLink }}" target="_blank" rel="noreferrer"{{ end }}>
+            <svg class="market-chart shrink-0" viewBox="0 0 100 50">
+                <polyline fill="none" stroke="var(--color-text-subdue)" stroke-width="1.5px" points="{{ .SvgChartPoints }}" vector-effect="non-scaling-stroke"></polyline>
+            </svg>
+        </a>
 
-<div class="market-values shrink-0">
-    <div class="size-h3 text-right {{ if eq .PercentChange 0.0 }}{{ else if gt .PercentChange 0.0 }}color-positive{{ else }}color-negative{{ end }}">{{ printf "%+.2f" .PercentChange }}%</div>
-    <div class="text-right">{{ .Currency }}{{ .Price | formatPrice }}</div>
+        <div class="market-values shrink-0">
+            <div class="size-h3 text-right {{ if eq .PercentChange 0.0 }}{{ else if gt .PercentChange 0.0 }}color-positive{{ else }}color-negative{{ end }}">{{ printf "%+.2f" .PercentChange }}%</div>
+            <div class="text-right">{{ .Currency }}{{ .Price | formatPrice }}</div>
+        </div>
+    </div>
+    {{ end }}
 </div>
 {{ end }}

+ 13 - 13
internal/assets/templates/monitor.html

@@ -1,22 +1,22 @@
 {{ template "widget-base.html" . }}
 
 {{ define "widget-content" }}
-{{ if ne .Style "dynamic-columns-experimental" }}
-<ul class="list list-gap-20 list-with-separator">
+{{ if not (and .ShowFailingOnly (not .HasFailing)) }}
+<ul class="dynamic-columns list-gap-20 list-with-separator">
     {{ range .Sites }}
-    <li class="monitor-site flex items-center gap-15">
-        {{ template "site" . }}
-    </li>
-    {{ end }}
-</ul>
-{{ else }}
-<ul class="dynamic-columns">
-    {{ range .Sites }}
-    <div class="flex items-center gap-15">
+    {{ if and $.ShowFailingOnly (eq .StatusStyle "ok" ) }} {{ continue }} {{ end }}
+    <div class="monitor-site flex items-center gap-15">
         {{ template "site" . }}
     </div>
     {{ end }}
 </ul>
+{{ else }}
+<div class="flex items-center justify-center gap-10 padding-block-5">
+    <p>All sites are online</p>
+    <svg class="shrink-0" style="width: 1.7rem;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="var(--color-positive)">
+        <path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z" clip-rule="evenodd" />
+    </svg>
+</div>
 {{ end }}
 {{ end }}
 
@@ -24,8 +24,8 @@
 {{ if .IconUrl }}
 <img class="monitor-site-icon{{ if .IsSimpleIcon }} simple-icon{{ end }}" src="{{ .IconUrl }}" alt="" loading="lazy">
 {{ end }}
-<div>
-    <a class="size-h3 color-highlight" href="{{ .URL }}" {{ if not .SameTab }}target="_blank"{{ end }} rel="noreferrer">{{ .Title }}</a>
+<div class="min-width-0">
+    <a class="size-h3 color-highlight text-truncate block" href="{{ .URL }}" {{ if not .SameTab }}target="_blank"{{ end }} rel="noreferrer">{{ .Title }}</a>
     <ul class="list-horizontal-text">
         {{ if not .Status.Error }}
         <li title="{{ .Status.Code }}">{{ .StatusText }}</li>

+ 1 - 1
internal/assets/templates/page.html

@@ -11,7 +11,7 @@
 </script>
 {{ end }}
 
-{{ define "document-root-attrs" }}class="{{ if .App.Config.Theme.Light }}light-scheme {{ end }}{{ if ne "" .Page.Width }}page-width-{{ .Page.Width }}{{ end }}"{{ end }}
+{{ define "document-root-attrs" }}class="{{ if .App.Config.Theme.Light }}light-scheme {{ end }}{{ if ne "" .Page.Width }}page-width-{{ .Page.Width }} {{ end }}{{ if .Page.CenterVertically }}page-center-vertically{{ end }}"{{ end }}
 
 {{ define "document-head-after" }}
 {{ template "page-style-overrides.gotmpl" . }}

+ 1 - 7
internal/feed/github.go

@@ -38,16 +38,10 @@ func fetchLatestGithubRelease(request *ReleaseRequest) (*AppRelease, error) {
 		return nil, err
 	}
 
-	version := response.TagName
-
-	if len(version) > 0 && version[0] != 'v' {
-		version = "v" + version
-	}
-
 	return &AppRelease{
 		Source:       ReleaseSourceGithub,
 		Name:         request.Repository,
-		Version:      version,
+		Version:      normalizeVersionFormat(response.TagName),
 		NotesUrl:     response.HtmlUrl,
 		TimeReleased: parseRFC3339Time(response.PublishedAt),
 		Downvotes:    response.Reactions.Downvotes,

+ 1 - 7
internal/feed/gitlab.go

@@ -38,16 +38,10 @@ func fetchLatestGitLabRelease(request *ReleaseRequest) (*AppRelease, error) {
 		return nil, err
 	}
 
-	version := response.TagName
-
-	if len(version) > 0 && version[0] != 'v' {
-		version = "v" + version
-	}
-
 	return &AppRelease{
 		Source:       ReleaseSourceGitlab,
 		Name:         request.Repository,
-		Version:      version,
+		Version:      normalizeVersionFormat(response.TagName),
 		NotesUrl:     response.Links.Self,
 		TimeReleased: parseRFC3339Time(response.ReleasedAt),
 	}, nil

+ 10 - 0
internal/feed/utils.go

@@ -105,3 +105,13 @@ func parseRFC3339Time(t string) time.Time {
 
 	return parsed
 }
+
+func normalizeVersionFormat(version string) string {
+	version = strings.ToLower(strings.TrimSpace(version))
+
+	if len(version) > 0 && version[0] != 'v' {
+		return "v" + version
+	}
+
+	return version
+}

+ 3 - 5
internal/feed/youtube.go

@@ -11,10 +11,8 @@ import (
 
 type youtubeFeedResponseXml struct {
 	Channel     string `xml:"author>name"`
-	ChannelLink struct {
-		Href string `xml:"href,attr"`
-	} `xml:"link"`
-	Videos []struct {
+	ChannelLink string `xml:"author>uri"`
+	Videos      []struct {
 		Title     string `xml:"title"`
 		Published string `xml:"published"`
 		Link      struct {
@@ -97,7 +95,7 @@ func FetchYoutubeChannelUploads(channelIds []string, videoUrlTemplate string, in
 				Title:        video.Title,
 				Url:          videoUrl,
 				Author:       response.Channel,
-				AuthorUrl:    response.ChannelLink.Href + "/videos",
+				AuthorUrl:    response.ChannelLink + "/videos",
 				TimePosted:   parseYoutubeFeedTime(video.Published),
 			})
 		}

+ 1 - 0
internal/glance/glance.go

@@ -73,6 +73,7 @@ type Page struct {
 	Width                 string   `yaml:"width"`
 	ShowMobileHeader      bool     `yaml:"show-mobile-header"`
 	HideDesktopNavigation bool     `yaml:"hide-desktop-navigation"`
+	CenterVertically      bool     `yaml:"center-vertically"`
 	Columns               []Column `yaml:"columns"`
 	mu                    sync.Mutex
 }

+ 0 - 1
internal/widget/bookmarks.go

@@ -21,7 +21,6 @@ type Bookmarks struct {
 			HideArrow    bool   `yaml:"hide-arrow"`
 		} `yaml:"links"`
 	} `yaml:"groups"`
-	Style string `yaml:"style"`
 }
 
 func (widget *Bookmarks) Initialize() error {

+ 0 - 1
internal/widget/stocks.go → internal/widget/markets.go

@@ -14,7 +14,6 @@ type Markets struct {
 	StocksRequests []feed.MarketRequest `yaml:"stocks"`
 	MarketRequests []feed.MarketRequest `yaml:"markets"`
 	Sort           string               `yaml:"sort-by"`
-	Style          string               `yaml:"style"`
 	Markets        feed.Markets         `yaml:"-"`
 }
 

+ 8 - 2
internal/widget/monitor.go

@@ -53,7 +53,8 @@ type Monitor struct {
 		StatusText              string           `yaml:"-"`
 		StatusStyle             string           `yaml:"-"`
 	} `yaml:"sites"`
-	Style string `yaml:"style"`
+	ShowFailingOnly bool `yaml:"show-failing-only"`
+	HasFailing      bool `yaml:"-"`
 }
 
 func (widget *Monitor) Initialize() error {
@@ -79,12 +80,17 @@ func (widget *Monitor) Update(ctx context.Context) {
 		return
 	}
 
+	widget.HasFailing = false
+
 	for i := range widget.Sites {
 		site := &widget.Sites[i]
 		status := &statuses[i]
-
 		site.Status = status
 
+		if status.Code >= 400 || status.TimedOut || status.Error != nil {
+			widget.HasFailing = true
+		}
+
 		if !status.TimedOut {
 			site.StatusText = statusCodeToText(status.Code)
 			site.StatusStyle = statusCodeToStyle(status.Code)