Merge branch 'main' into dev

This commit is contained in:
Svilen Markov 2025-03-12 18:04:16 +00:00
commit 2f50f5ef34
12 changed files with 61 additions and 31 deletions

View File

@ -356,7 +356,7 @@ pages:
### Properties ### Properties
| Name | Type | Required | Default | | Name | Type | Required | Default |
| ---- | ---- | -------- | ------- | | ---- | ---- | -------- | ------- |
| title | string | yes | | | name | string | yes | |
| slug | string | no | | | slug | string | no | |
| width | string | no | | | width | string | no | |
| center-vertically | boolean | no | false | | center-vertically | boolean | no | false |
@ -374,7 +374,7 @@ The URL friendly version of the title which is used to access the page. For exam
#### `width` #### `width`
The maximum width of the page on desktop. Possible values are `slim` and `wide`. The maximum width of the page on desktop. Possible values are `slim` and `wide`.
* default: `1600px` * default: `1600px` (when no value is specified)
* slim: `1100px` * slim: `1100px`
* wide: `1920px` * wide: `1920px`

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -53,6 +53,16 @@ theme:
primary-color: 97 13 80 primary-color: 97 13 80
``` ```
### Gruvbox Dark
![screenshot](images/themes/gruvbox.png)
```yaml
theme:
background-color: 0 0 16
primary-color: 43 59 81
positive-color: 61 66 44
negative-color: 6 96 59
```
### Kanagawa Dark ### Kanagawa Dark
![screenshot](images/themes/kanagawa-dark.png) ![screenshot](images/themes/kanagawa-dark.png)
```yaml ```yaml

View File

@ -68,7 +68,7 @@ func newApplication(config *config) (*application, error) {
for w := range column.Widgets { for w := range column.Widgets {
widget := column.Widgets[w] widget := column.Widgets[w]
app.widgetByID[widget.id()] = widget app.widgetByID[widget.GetID()] = widget
widget.setProviders(providers) widget.setProviders(providers)
} }

View File

@ -284,7 +284,9 @@ function setupGroups() {
for (let i = 0; i < titles.length; i++) { for (let i = 0; i < titles.length; i++) {
titles[i].classList.remove("widget-group-title-current"); titles[i].classList.remove("widget-group-title-current");
titles[i].setAttribute("aria-selected", "false");
tabs[i].classList.remove("widget-group-content-current"); tabs[i].classList.remove("widget-group-content-current");
tabs[i].setAttribute("aria-hidden", "true");
} }
if (current < t) { if (current < t) {
@ -296,7 +298,9 @@ function setupGroups() {
current = t; current = t;
title.classList.add("widget-group-title-current"); title.classList.add("widget-group-title-current");
title.setAttribute("aria-selected", "true");
tabs[t].classList.add("widget-group-content-current"); tabs[t].classList.add("widget-group-content-current");
tabs[t].setAttribute("aria-hidden", "false");
}); });
} }
} }
@ -670,6 +674,7 @@ async function setupPage() {
setupLazyImages(); setupLazyImages();
} finally { } finally {
pageElement.classList.add("content-ready"); pageElement.classList.add("content-ready");
pageElement.setAttribute("aria-busy", "false");
for (let i = 0; i < contentReadyCallbacks.length; i++) { for (let i = 0; i < contentReadyCallbacks.length; i++) {
contentReadyCallbacks[i](); contentReadyCallbacks[i]();

View File

@ -110,7 +110,7 @@
.visited-indicator:not(.text-truncate)::after, .visited-indicator:not(.text-truncate)::after,
.visited-indicator.text-truncate::before, .visited-indicator.text-truncate::before,
.bookmarks-link:not(.bookmarks-link-no-arrow)::after { .bookmarks-link:not(.bookmarks-link-no-arrow)::after {
content: '↗'; content: '↗' / "";
margin-left: 0.5em; margin-left: 0.5em;
display: inline-block; display: inline-block;
position: relative; position: relative;
@ -189,7 +189,7 @@
} }
.expand-toggle-button-icon::before { .expand-toggle-button-icon::before {
content: ''; content: '' / "";
font-size: 0.8rem; font-size: 0.8rem;
transform: rotate(90deg); transform: rotate(90deg);
line-height: 1; line-height: 1;
@ -341,6 +341,19 @@ html, body, .body-content {
height: 100%; height: 100%;
} }
h1, h2, h3, h4, h5 {
font: inherit;
}
.visually-hidden {
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
a { a {
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
@ -563,7 +576,7 @@ kbd:active {
} }
.summary::after { .summary::after {
content: "◀"; content: "◀" / "";
font-size: 1.2em; font-size: 1.2em;
position: absolute; position: absolute;
top: 0; top: 0;
@ -822,7 +835,7 @@ details[open] .summary::after {
} }
.list-horizontal-text > *:not(:last-child)::after { .list-horizontal-text > *:not(:last-child)::after {
content: '•'; content: '•' / "";
color: var(--color-text-subdue); color: var(--color-text-subdue);
margin: 0 0.4rem; margin: 0 0.4rem;
position: relative; position: relative;

View File

@ -76,7 +76,7 @@
<summary class="summary">Top blocked domains</summary> <summary class="summary">Top blocked domains</summary>
<ul class="list list-gap-4 list-with-transition size-h5"> <ul class="list list-gap-4 list-with-transition size-h5">
{{ range .Stats.TopBlockedDomains }} {{ range .Stats.TopBlockedDomains }}
<li class="flex justify-between"> <li class="flex justify-between gap-10">
<div class="text-truncate rtl">{{ .Domain }}</div> <div class="text-truncate rtl">{{ .Domain }}</div>
<div class="text-right" style="width: 4rem;"><span class="color-highlight">{{ .PercentBlocked }}</span>%</div> <div class="text-right" style="width: 4rem;"><span class="color-highlight">{{ .PercentBlocked }}</span>%</div>
</li> </li>

View File

@ -4,17 +4,18 @@
{{ define "widget-content" }} {{ define "widget-content" }}
<div class="widget-group-header"> <div class="widget-group-header">
<div class="widget-header gap-20"> <div class="widget-header gap-20" role="tablist">
{{ range $i, $widget := .Widgets }} {{- range $i, $widget := .Widgets }}
<button class="widget-group-title{{ if eq $i 0 }} widget-group-title-current{{ end }}"{{ if ne "" .TitleURL }} data-title-url="{{ .TitleURL }}"{{ end }}>{{ $widget.Title }}</button> <button class="widget-group-title{{ if eq $i 0 }} widget-group-title-current{{ end }}"{{ if ne "" .TitleURL }} data-title-url="{{ .TitleURL }}"{{ end }} aria-selected="{{ if eq $i 0 }}true{{ else }}false{{ end }}" arial-level="2" role="tab" aria-controls="widget-{{ .GetID }}-tabpanel-{{ $i }}" id="widget-{{ .GetID }}-tab-{{ $i }}">{{ $widget.Title }}</button>
{{ end }} {{- end }}
</div> </div>
</div> </div>
<div class="widget-group-contents"> <div class="widget-group-contents">
{{ range $i, $widget := .Widgets }} {{- range $i, $widget := .Widgets }}
<div class="widget-group-content{{ if eq $i 0 }} widget-group-content-current{{ end }}">{{ .Render }}</div> <div class="widget-group-content{{ if eq $i 0 }} widget-group-content-current{{ end }}" id="widget-{{ .GetID }}-tabpanel-{{ $i }}" role="tabpanel" aria-labelledby="widget-{{ .GetID }}-tab-{{ $i }}" aria-hidden="{{ if eq $i 0 }}false{{ else }}true{{ end }}">
{{ end }} {{- .Render -}}
</div>
{{- end }}
</div> </div>
{{ end }} {{ end }}

View File

@ -25,7 +25,7 @@
{{ define "navigation-links" }} {{ define "navigation-links" }}
{{ range .App.Config.Pages }} {{ range .App.Config.Pages }}
<a href="{{ $.App.Config.Server.BaseURL }}/{{ .Slug }}" class="nav-item{{ if eq .Slug $.Page.Slug }} nav-item-current{{ end }}">{{ .Title }}</a> <a href="{{ $.App.Config.Server.BaseURL }}/{{ .Slug }}" class="nav-item{{ if eq .Slug $.Page.Slug }} nav-item-current{{ end }}"{{ if eq .Slug $.Page.Slug }} aria-current="page"{{ end }}>{{ .Title }}</a>
{{ end }} {{ end }}
{{ end }} {{ end }}
@ -35,10 +35,10 @@
<div class="header-container content-bounds"> <div class="header-container content-bounds">
<div class="header flex padding-inline-widget widget-content-frame"> <div class="header flex padding-inline-widget widget-content-frame">
<!-- TODO: Replace G with actual logo, first need an actual logo --> <!-- TODO: Replace G with actual logo, first need an actual logo -->
<div class="logo">{{ if ne "" .App.Config.Branding.LogoURL }}<img src="{{ .App.Config.Branding.LogoURL }}" alt="">{{ else if ne "" .App.Config.Branding.LogoText }}{{ .App.Config.Branding.LogoText }}{{ else }}G{{ end }}</div> <div class="logo" aria-hidden="true">{{ if ne "" .App.Config.Branding.LogoURL }}<img src="{{ .App.Config.Branding.LogoURL }}" alt="">{{ else if ne "" .App.Config.Branding.LogoText }}{{ .App.Config.Branding.LogoText }}{{ else }}G{{ end }}</div>
<div class="nav flex grow"> <nav class="nav flex grow">
{{ template "navigation-links" . }} {{ template "navigation-links" . }}
</div> </nav>
</div> </div>
</div> </div>
{{ end }} {{ end }}
@ -57,17 +57,19 @@
</div> </div>
<div class="content-bounds grow"> <div class="content-bounds grow">
<div class="page" id="page"> <main class="page" id="page" aria-live="polite" aria-busy="true">
<h1 class="visually-hidden">{{ .Page.Title }}</h1>
<div class="page-content" id="page-content"></div> <div class="page-content" id="page-content"></div>
<div class="page-loading-container"> <div class="page-loading-container">
<!-- TODO: add a bigger/better loading indicator --> <!-- TODO: add a bigger/better loading indicator -->
<div class="loading-icon"></div> <div class="visually-hidden">Loading</div>
</div> <div class="loading-icon" aria-hidden="true"></div>
</div> </div>
</main>
</div> </div>
{{ if not .App.Config.Branding.HideFooter }} {{ if not .App.Config.Branding.HideFooter }}
<div class="footer flex items-center flex-column"> <footer class="footer flex items-center flex-column">
{{ if eq "" .App.Config.Branding.CustomFooter }} {{ if eq "" .App.Config.Branding.CustomFooter }}
<div> <div>
<a class="size-h3" href="https://github.com/glanceapp/glance" target="_blank" rel="noreferrer">Glance</a> {{ if ne "dev" .App.Version }}<a class="visited-indicator" title="Release notes" href="https://github.com/glanceapp/glance/releases/tag/{{ .App.Version }}" target="_blank" rel="noreferrer">{{ .App.Version }}</a>{{ else }}({{ .App.Version }}){{ end }} <a class="size-h3" href="https://github.com/glanceapp/glance" target="_blank" rel="noreferrer">Glance</a> {{ if ne "dev" .App.Version }}<a class="visited-indicator" title="Release notes" href="https://github.com/glanceapp/glance/releases/tag/{{ .App.Version }}" target="_blank" rel="noreferrer">{{ .App.Version }}</a>{{ else }}({{ .App.Version }}){{ end }}
@ -75,7 +77,7 @@
{{ else }} {{ else }}
{{ .App.Config.Branding.CustomFooter }} {{ .App.Config.Branding.CustomFooter }}
{{ end }} {{ end }}
</div> </footer>
{{ end }} {{ end }}
<div class="mobile-navigation-offset"></div> <div class="mobile-navigation-offset"></div>

View File

@ -2,9 +2,9 @@
{{- if not .HideHeader}} {{- if not .HideHeader}}
<div class="widget-header"> <div class="widget-header">
{{- if ne "" .TitleURL }} {{- if ne "" .TitleURL }}
<a href="{{ .TitleURL | safeURL }}" target="_blank" rel="noreferrer" class="uppercase">{{ .Title }}</a> <h2><a href="{{ .TitleURL | safeURL }}" target="_blank" rel="noreferrer" class="uppercase">{{ .Title }}</a></h2>
{{- else }} {{- else }}
<div class="uppercase">{{ .Title }}</div> <h2 class="uppercase">{{ .Title }}</h2>
{{- end }} {{- end }}
{{- if .IsWIP }} {{- if .IsWIP }}
<div data-popover-type="html" data-popover-position="above"> <div data-popover-type="html" data-popover-position="above">

View File

@ -166,8 +166,7 @@ func fetchMarketsDataFromYahoo(marketRequests []marketRequest) (marketList, erro
points := svgPolylineCoordsFromYValues(100, 50, maybeCopySliceWithoutZeroValues(prices)) points := svgPolylineCoordsFromYValues(100, 50, maybeCopySliceWithoutZeroValues(prices))
currency, exists := currencyToSymbol[response.Chart.Result[0].Meta.Currency] currency, exists := currencyToSymbol[strings.ToUpper(response.Chart.Result[0].Meta.Currency)]
if !exists { if !exists {
currency = response.Chart.Result[0].Meta.Currency currency = response.Chart.Result[0].Meta.Currency
} }

View File

@ -125,13 +125,13 @@ type widget interface {
// These need to be exported because they get called in templates // These need to be exported because they get called in templates
Render() template.HTML Render() template.HTML
GetType() string GetType() string
GetID() uint64
initialize() error initialize() error
requiresUpdate(*time.Time) bool requiresUpdate(*time.Time) bool
setProviders(*widgetProviders) setProviders(*widgetProviders)
update(context.Context) update(context.Context)
setID(uint64) setID(uint64)
id() uint64
handleRequest(w http.ResponseWriter, r *http.Request) handleRequest(w http.ResponseWriter, r *http.Request)
setHideHeader(bool) setHideHeader(bool)
} }
@ -188,7 +188,7 @@ func (w *widgetBase) update(ctx context.Context) {
} }
func (w *widgetBase) id() uint64 { func (w *widgetBase) GetID() uint64 {
return w.ID return w.ID
} }