Add Target property to most widgets

pull/875/head
Ralph Ocdol 2025-11-20 17:14:51 +07:00
parent 784bf53425
commit 362b17c47c
38 changed files with 141 additions and 44 deletions

@ -270,7 +270,7 @@ function setupGroups() {
return;
}
openURLInNewTab(title.dataset.titleUrl, false);
openURLInNewTab(title.dataset.titleUrl, false, title.dataset.titleTarget);
event.preventDefault();
});
}
@ -278,7 +278,7 @@ function setupGroups() {
title.addEventListener("click", () => {
if (t == current) {
if (title.dataset.titleUrl !== undefined) {
openURLInNewTab(title.dataset.titleUrl);
openURLInNewTab(title.dataset.titleUrl, undefined, title.dataset.titleTarget);
}
return;

@ -31,8 +31,8 @@ export function clamp(value, min, max) {
// NOTE: inconsistent behavior between browsers when it comes to
// whether the newly opened tab gets focused or not, potentially
// depending on the event that this function is called from
export function openURLInNewTab(url, focus = true) {
const newWindow = window.open(url, '_blank', 'noopener,noreferrer');
export function openURLInNewTab(url, focus = true, target = '_blank') {
const newWindow = window.open(url, target, 'noopener,noreferrer');
if (focus && newWindow != null) newWindow.focus();
}

@ -4,10 +4,10 @@
<ul class="list list-gap-14 collapsible-container" data-collapse-after="{{ .CollapseAfter }}">
{{ range .ChangeDetections }}
<li>
<a class="size-h4 block text-truncate color-highlight" href="{{ .URL }}" target="_blank" rel="noreferrer">{{ .Title }}</a>
<a class="size-h4 block text-truncate color-highlight" href="{{ .URL }}" target="{{ $.Target }}" rel="noreferrer">{{ .Title }}</a>
<ul class="list-horizontal-text">
<li {{ dynamicRelativeTimeAttrs .LastChanged }}></li>
<li class="shrink min-width-0"><a class="visited-indicator" href="{{ .DiffURL }}" target="_blank" rel="noreferrer">diff:{{ .PreviousHash }}</a></li>
<li class="shrink min-width-0"><a class="visited-indicator" href="{{ .DiffURL }}" target="{{ $.Target }}" rel="noreferrer">diff:{{ .PreviousHash }}</a></li>
</ul>
</li>
{{ else }}

@ -24,7 +24,7 @@
<div class="min-width-0 grow">
{{- if .URL }}
<a href="{{ .URL | safeURL }}" class="color-highlight size-title-dynamic block text-truncate" {{ if not .SameTab }}target="_blank"{{ end }} rel="noreferrer">{{ .Name }}</a>
<a href="{{ .URL | safeURL }}" class="color-highlight size-title-dynamic block text-truncate" target="{{ .Target }}" rel="noreferrer">{{ .Name }}</a>
{{- else }}
<div class="color-highlight text-truncate size-title-dynamic">{{ .Name }}</div>
{{- end }}

@ -26,7 +26,7 @@
{{- end }}
{{- end }}
<div class="grow min-width-0">
<a href="{{ .DiscussionUrl | safeURL }}" class="size-title-dynamic color-primary-if-not-visited" target="_blank" rel="noreferrer">{{ .Title }}</a>
<a href="{{ .DiscussionUrl | safeURL }}" class="size-title-dynamic color-primary-if-not-visited" target="{{ $.Target }}" rel="noreferrer">{{ .Title }}</a>
{{- if .Tags }}
<div class="inline-block forum-post-tags-container">
<ul class="attachments">
@ -41,7 +41,7 @@
<li class="shrink-0">{{ .Score | formatApproxNumber }} points</li>
<li class="shrink-0{{ if .TargetUrl | safeURL }} forum-post-autohide{{ end }}">{{ .CommentCount | formatApproxNumber }} comments</li>
{{- if .TargetUrl }}
<li class="min-width-0"><a class="visited-indicator text-truncate block" href="{{ .TargetUrl }}" target="_blank" rel="noreferrer">{{ .TargetUrlDomain }}</a></li>
<li class="min-width-0"><a class="visited-indicator text-truncate block" href="{{ .TargetUrl }}" target="{{ $.Target }}" rel="noreferrer">{{ .TargetUrlDomain }}</a></li>
{{- end }}
</ul>
</div>

@ -6,7 +6,7 @@
<div class="widget-group-header">
<div class="widget-header gap-20" role="tablist">
{{- 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 }} 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>
<button class="widget-group-title{{ if eq $i 0 }} widget-group-title-current{{ end }}"{{ if ne "" .TitleURL }} data-title-url="{{ .TitleURL }}"{{ end }}{{ if ne "" .TitleTarget }} data-title-target="{{ .TitleTarget }}"{{ 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 }}
</div>
</div>

@ -5,11 +5,11 @@
{{ range .Markets }}
<div class="flex items-center gap-15">
<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>
<a{{ if ne "" .SymbolLink }} href="{{ .SymbolLink }}" target="{{ $.Target }}" 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 }}>
<a class="market-chart" {{ if ne "" .ChartLink }} href="{{ .ChartLink }}" target="{{ $.Target }}" rel="noreferrer"{{ end }}>
<svg class="market-chart shrink-0" viewBox="0 0 100 50">
<polyline fill="none" stroke="var(--color-text-subdue)" stroke-linejoin="round" stroke-width="1.5px" points="{{ .SvgChartPoints }}" vector-effect="non-scaling-stroke"></polyline>
</svg>

@ -24,7 +24,7 @@
{{ if .Icon.URL }}
{{ .Icon.ElemWithClass "square-20" }}
{{ end }}
<a class="size-title-dynamic color-highlight text-truncate block grow" href="{{ .URL | safeURL }}" {{ if not .SameTab }}target="_blank"{{ end }} rel="noreferrer">{{ .Title }}</a>
<a class="size-title-dynamic color-highlight text-truncate block grow" href="{{ .URL | safeURL }}" target="{{ .Target }}" rel="noreferrer">{{ .Title }}</a>
{{ if not .Status.TimedOut }}<div>{{ .Status.ResponseTime.Milliseconds | formatNumber }}ms</div>{{ end }}
{{ if eq .StatusStyle "ok" }}
<div class="square-18 shrink-0" title="{{ .Status.Code }}">

@ -25,7 +25,7 @@
{{ .Icon.ElemWithClass "square-32" }}
{{ end }}
<div class="grow min-width-0">
<a class="size-h3 color-highlight text-truncate block" href="{{ .URL | safeURL }}" {{ if not .SameTab }}target="_blank"{{ end }} rel="noreferrer">{{ .Title }}</a>
<a class="size-h3 color-highlight text-truncate block" href="{{ .URL | safeURL }}" target="{{ .Target }}" rel="noreferrer">{{ .Title }}</a>
<ul class="list-horizontal-text">
{{ if not .Status.Error }}
<li title="{{ .Status.Code }}">{{ .StatusText }}</li>

@ -14,11 +14,11 @@
{{ end }}
<div class="padding-widget flex flex-column grow relative">
{{ if ne "" .TargetUrl }}
<a class="color-highlight size-h5 text-truncate visited-indicator" href="{{ .TargetUrl }}" target="_blank" rel="noreferrer">{{ .TargetUrlDomain }}</a>
<a class="color-highlight size-h5 text-truncate visited-indicator" href="{{ .TargetUrl }}" target="{{ $.Target }}" rel="noreferrer">{{ .TargetUrlDomain }}</a>
{{ else }}
<div class="color-highlight size-h5 text-truncate">/r/{{ $.Subreddit }}</div>
{{ end }}
<a href="{{ .DiscussionUrl }}" class="text-truncate-3-lines color-primary-if-not-visited margin-top-7 margin-bottom-auto" target="_blank" rel="noreferrer">{{ .Title }}</a>
<a href="{{ .DiscussionUrl }}" class="text-truncate-3-lines color-primary-if-not-visited margin-top-7 margin-bottom-auto" target="{{ $.Target }}" rel="noreferrer">{{ .Title }}</a>
<ul class="list-horizontal-text margin-top-7">
<li {{ dynamicRelativeTimeAttrs .TimePosted }}></li>
<li>{{ .Score | formatApproxNumber }} points</li>

@ -13,11 +13,11 @@
{{ end }}
<div class="padding-widget relative">
{{ if ne "" .TargetUrl }}
<a class="color-highlight size-h5 text-truncate visited-indicator block" href="{{ .TargetUrl }}" target="_blank" rel="noreferrer">{{ .TargetUrlDomain }}</a>
<a class="color-highlight size-h5 text-truncate visited-indicator block" href="{{ .TargetUrl }}" target="{{ $.Target }}" rel="noreferrer">{{ .TargetUrlDomain }}</a>
{{ else }}
<div class="color-highlight size-h5 text-truncate">/r/{{ $.Subreddit }}</div>
{{ end }}
<a href="{{ .DiscussionUrl }}" class="text-truncate-3-lines color-primary-if-not-visited margin-top-7" target="_blank" rel="noreferrer">{{ .Title }}</a>
<a href="{{ .DiscussionUrl }}" class="text-truncate-3-lines color-primary-if-not-visited margin-top-7" target="{{ $.Target }}" rel="noreferrer">{{ .Title }}</a>
<ul class="list-horizontal-text margin-top-7">
<li {{ dynamicRelativeTimeAttrs .TimePosted }}></li>
<li>{{ .Score | formatApproxNumber }} points</li>

@ -5,7 +5,7 @@
{{ range .Releases }}
<li>
<div class="flex items-center gap-10">
<a class="size-h4 block text-truncate color-primary-if-not-visited" href="{{ .NotesUrl }}" target="_blank" rel="noreferrer">{{ .Name }}</a>
<a class="size-h4 block text-truncate color-primary-if-not-visited" href="{{ .NotesUrl }}" target="{{ $.Target }}" rel="noreferrer">{{ .Name }}</a>
{{ if $.ShowSourceIcon }}
<img class="flat-icon release-source-icon" src="{{ .SourceIconURL }}" alt="" loading="lazy">
{{ end }}

@ -1,7 +1,7 @@
{{ template "widget-base.html" . }}
{{ define "widget-content" }}
<a class="size-h4 color-highlight" href="https://github.com/{{ $.Repository.Name }}" target="_blank" rel="noreferrer">{{ .Repository.Name }}</a>
<a class="size-h4 color-highlight" href="https://github.com/{{ $.Repository.Name }}" target="{{ $.Target }}" rel="noreferrer">{{ .Repository.Name }}</a>
<ul class="list-horizontal-text">
<li>{{ .Repository.Stars | formatNumber }} stars</li>
<li>{{ .Repository.Forks | formatNumber }} forks</li>
@ -9,7 +9,7 @@
{{ if gt (len .Repository.Commits) 0 }}
<hr class="margin-block-8">
<a class="text-compact" href="https://github.com/{{ $.Repository.Name }}/commits" target="_blank" rel="noreferrer">Last {{ .CommitsLimit }} commits</a>
<a class="text-compact" href="https://github.com/{{ $.Repository.Name }}/commits" target="{{ $.Target }}" rel="noreferrer">Last {{ .CommitsLimit }} commits</a>
<div class="flex gap-7 size-h5 size-base-on-mobile margin-top-3">
<ul class="list list-gap-2">
{{ range .Repository.Commits }}
@ -18,7 +18,7 @@
</ul>
<ul class="list list-gap-2 min-width-0">
{{ range .Repository.Commits }}
<li><a class="color-primary-if-not-visited text-truncate block" title="{{ .Author }}" target="_blank" rel="noreferrer" href="https://github.com/{{ $.Repository.Name }}/commit/{{ .Sha }}">{{ .Message }}</a></li>
<li><a class="color-primary-if-not-visited text-truncate block" title="{{ .Author }}" target="{{ $.Target }}" rel="noreferrer" href="https://github.com/{{ $.Repository.Name }}/commit/{{ .Sha }}">{{ .Message }}</a></li>
{{ end }}
</ul>
</div>
@ -26,7 +26,7 @@
{{ if gt (len .Repository.PullRequests) 0 }}
<hr class="margin-block-8">
<a class="text-compact" href="https://github.com/{{ $.Repository.Name }}/pulls" target="_blank" rel="noreferrer">Open pull requests ({{ .Repository.OpenPullRequests | formatNumber }} total)</a>
<a class="text-compact" href="https://github.com/{{ $.Repository.Name }}/pulls" target="{{ $.Target }}" rel="noreferrer">Open pull requests ({{ .Repository.OpenPullRequests | formatNumber }} total)</a>
<div class="flex gap-7 size-h5 size-base-on-mobile margin-top-3">
<ul class="list list-gap-2">
{{ range .Repository.PullRequests }}
@ -35,7 +35,7 @@
</ul>
<ul class="list list-gap-2 min-width-0">
{{ range .Repository.PullRequests }}
<li><a class="color-primary-if-not-visited text-truncate block" target="_blank" rel="noreferrer" href="https://github.com/{{ $.Repository.Name }}/pull/{{ .Number }}">{{ .Title }}</a></li>
<li><a class="color-primary-if-not-visited text-truncate block" target="{{ $.Target }}" rel="noreferrer" href="https://github.com/{{ $.Repository.Name }}/pull/{{ .Number }}">{{ .Title }}</a></li>
{{ end }}
</ul>
</div>
@ -43,7 +43,7 @@
{{ if gt (len .Repository.Issues) 0 }}
<hr class="margin-block-10">
<a class="text-compact" href="https://github.com/{{ $.Repository.Name }}/issues" target="_blank" rel="noreferrer">Open issues ({{ .Repository.OpenIssues | formatNumber }} total)</a>
<a class="text-compact" href="https://github.com/{{ $.Repository.Name }}/issues" target="{{ $.Target }}" rel="noreferrer">Open issues ({{ .Repository.OpenIssues | formatNumber }} total)</a>
<div class="flex gap-7 size-h5 size-base-on-mobile margin-top-3">
<ul class="list list-gap-2">
{{ range .Repository.Issues }}
@ -52,7 +52,7 @@
</ul>
<ul class="list list-gap-2 min-width-0">
{{ range .Repository.Issues }}
<li><a class="color-primary-if-not-visited text-truncate block" target="_blank" rel="noreferrer" href="https://github.com/{{ $.Repository.Name }}/issues/{{ .Number }}">{{ .Title }}</a></li>
<li><a class="color-primary-if-not-visited text-truncate block" target="{{ $.Target }}" rel="noreferrer" href="https://github.com/{{ $.Repository.Name }}/issues/{{ .Number }}">{{ .Title }}</a></li>
{{ end }}
</ul>
</div>

@ -14,11 +14,11 @@
{{ end }}
</div>
<div class="grow min-width-0">
<a class="size-h3 color-primary-if-not-visited" href="{{ .Link }}" target="_blank" rel="noreferrer">{{ .Title }}</a>
<a class="size-h3 color-primary-if-not-visited" href="{{ .Link }}" target="{{ $.Target }}" rel="noreferrer">{{ .Title }}</a>
<ul class="list-horizontal-text flex-nowrap">
<li {{ dynamicRelativeTimeAttrs .PublishedAt }}></li>
<li class="min-width-0">
<a class="block text-truncate" href="{{ .ChannelURL }}" target="_blank" rel="noreferrer">{{ .ChannelName }}</a>
<a class="block text-truncate" href="{{ .ChannelURL }}" target="{{ $.Target }}" rel="noreferrer">{{ .ChannelName }}</a>
</li>
</ul>
{{ if ne "" .Description }}

@ -16,7 +16,7 @@
</svg>
{{ end }}
<div class="rss-card-2-content padding-inline-widget">
<a href="{{ .Link }}" class="block text-truncate color-primary-if-not-visited" target="_blank" rel="noreferrer">{{ .Title }}</a>
<a href="{{ .Link }}" class="block text-truncate color-primary-if-not-visited" target="{{ $.Target }}" rel="noreferrer">{{ .Title }}</a>
<ul class="list-horizontal-text flex-nowrap margin-top-5">
<li class="shrink-0" {{ dynamicRelativeTimeAttrs .PublishedAt }}></li>
<li class="min-width-0 text-truncate">{{ .ChannelName }}</li>

@ -16,7 +16,7 @@
</svg>
{{ end }}
<div class="margin-bottom-widget padding-inline-widget flex flex-column grow">
<a href="{{ .Link }}" class="text-truncate-3-lines color-primary-if-not-visited margin-top-10 margin-bottom-auto" target="_blank" rel="noreferrer">{{ .Title }}</a>
<a href="{{ .Link }}" class="text-truncate-3-lines color-primary-if-not-visited margin-top-10 margin-bottom-auto" target="{{ $.Target }}" rel="noreferrer">{{ .Title }}</a>
<ul class="list-horizontal-text flex-nowrap margin-top-7">
<li class="shrink-0" {{ dynamicRelativeTimeAttrs .PublishedAt }}></li>
<li class="min-width-0 text-truncate">{{ .ChannelName }}</li>

@ -4,11 +4,11 @@
<ul class="list list-gap-14 collapsible-container{{ if .SingleLineTitles }} single-line-titles{{ end }}" data-collapse-after="{{ .CollapseAfter }}">
{{ range .Items }}
<li>
<a class="title size-title-dynamic color-primary-if-not-visited" href="{{ .Link }}" target="_blank" rel="noreferrer">{{ .Title }}</a>
<a class="title size-title-dynamic color-primary-if-not-visited" href="{{ .Link }}" target="{{ $.Target }}" rel="noreferrer">{{ .Title }}</a>
<ul class="list-horizontal-text flex-nowrap">
<li {{ dynamicRelativeTimeAttrs .PublishedAt }}></li>
<li class="min-width-0">
<a class="block text-truncate" href="{{ .ChannelURL }}" target="_blank" rel="noreferrer">{{ .ChannelName }}</a>
<a class="block text-truncate" href="{{ .ChannelURL }}" target="{{ $.Target }}" rel="noreferrer">{{ .ChannelName }}</a>
</li>
</ul>
</li>

@ -13,7 +13,7 @@
</div>
{{ end }}
{{ if .Exists }}
<a href="https://twitch.tv/{{ .Login }}" target="_blank" rel="noreferrer">
<a href="https://twitch.tv/{{ .Login }}" target="{{ $.Target }}" rel="noreferrer">
<img class="twitch-channel-avatar thumbnail" src="{{ .AvatarUrl }}" alt="" loading="lazy">
</a>
{{ else }}
@ -23,11 +23,11 @@
{{ end }}
</div>
<div class="min-width-0">
<a href="https://twitch.tv/{{ .Login }}" class="size-h3{{ if .IsLive }} color-highlight{{ end }} block text-truncate" target="_blank" rel="noreferrer">{{ .Name }}</a>
<a href="https://twitch.tv/{{ .Login }}" class="size-h3{{ if .IsLive }} color-highlight{{ end }} block text-truncate" target="{{ $.Target }}" rel="noreferrer">{{ .Name }}</a>
{{ if .Exists }}
{{ if .IsLive }}
{{ if .Category }}
<a class="text-truncate block" href="https://www.twitch.tv/directory/category/{{ .CategorySlug }}" target="_blank" rel="noreferrer">{{ .Category }}</a>
<a class="text-truncate block" href="https://www.twitch.tv/directory/category/{{ .CategorySlug }}" target="{{ $.Target }}" rel="noreferrer">{{ .Category }}</a>
{{ end }}
<ul class="list-horizontal-text">
<li {{ dynamicRelativeTimeAttrs .LiveSince }}></li>

@ -7,7 +7,7 @@
<div class="flex gap-10 items-start">
<img class="twitch-category-thumbnail thumbnail" loading="lazy" src="{{ .AvatarUrl }}" alt="">
<div class="min-width-0">
<a class="size-h3 color-highlight text-truncate block" href="https://www.twitch.tv/directory/category/{{ .Slug }}" target="_blank" rel="noreferrer">{{ .Name }}</a>
<a class="size-h3 color-highlight text-truncate block" href="https://www.twitch.tv/directory/category/{{ .Slug }}" target="{{ $.Target }}" rel="noreferrer">{{ .Name }}</a>
<ul class="list-horizontal-text">
<li>{{ .ViewersCount | formatApproxNumber }} viewers</li>
{{ if .IsNew }}

@ -1,11 +1,11 @@
{{ define "video-card-contents" }}
<img class="video-thumbnail thumbnail" loading="lazy" src="{{ .ThumbnailUrl }}" alt="">
<div class="margin-top-10 margin-bottom-widget flex flex-column grow padding-inline-widget">
<a class="text-truncate-2-lines margin-bottom-auto color-primary-if-not-visited" href="{{ .Url | safeURL }}" target="_blank" rel="noreferrer">{{ .Title }}</a>
<a class="text-truncate-2-lines margin-bottom-auto color-primary-if-not-visited" href="{{ .Url | safeURL }}" target="{{ $.Target }}" rel="noreferrer">{{ .Title }}</a>
<ul class="list-horizontal-text flex-nowrap margin-top-7">
<li class="shrink-0" {{ dynamicRelativeTimeAttrs .TimePosted }}></li>
<li class="min-width-0">
<a class="block text-truncate" href="{{ .AuthorUrl }}" target="_blank" rel="noreferrer">{{ .Author }}</a>
<a class="block text-truncate" href="{{ .AuthorUrl }}" target="{{ $.Target }}" rel="noreferrer">{{ .Author }}</a>
</li>
</ul>
</div>

@ -9,11 +9,11 @@
<li class="flex thumbnail-parent gap-10 items-center">
<img class="video-horizontal-list-thumbnail thumbnail" loading="lazy" src="{{ .ThumbnailUrl }}" alt="">
<div class="min-width-0">
<a class="block text-truncate color-primary-if-not-visited" href="{{ .Url | safeURL }}" target="_blank" rel="noreferrer">{{ .Title }}</a>
<a class="block text-truncate color-primary-if-not-visited" href="{{ .Url | safeURL }}" target="{{ $.Target }}" rel="noreferrer">{{ .Title }}</a>
<ul class="list-horizontal-text flex-nowrap">
<li class="shrink-0" {{ dynamicRelativeTimeAttrs .TimePosted }}></li>
<li class="min-width-0">
<a class="block text-truncate" href="{{ .AuthorUrl }}" target="_blank" rel="noreferrer">{{ .Author }}</a>
<a class="block text-truncate" href="{{ .AuthorUrl }}" target="{{ $.Target }}" rel="noreferrer">{{ .Author }}</a>
</li>
</ul>
</div>

@ -2,7 +2,7 @@
{{- if not .HideHeader }}
<div class="widget-header">
{{- if ne "" .TitleURL }}
<h2><a href="{{ .TitleURL | safeURL }}" target="_blank" rel="noreferrer" class="uppercase">{{ .Title }}</a></h2>
<h2><a href="{{ .TitleURL | safeURL }}" target="{{ if ne .TitleTarget "" }}{{ .TitleTarget }}{{ else }}_blank{{ end }}" rel="noreferrer" class="uppercase">{{ .Title }}</a></h2>
{{- else }}
<h2 class="uppercase">{{ .Title }}</h2>
{{- end }}

@ -21,6 +21,7 @@ type changeDetectionWidget struct {
Token string `yaml:"token"`
Limit int `yaml:"limit"`
CollapseAfter int `yaml:"collapse-after"`
Target string `yaml:"target"`
}
func (widget *changeDetectionWidget) initialize() error {
@ -38,6 +39,10 @@ func (widget *changeDetectionWidget) initialize() error {
widget.InstanceURL = "https://www.changedetection.io"
}
if widget.Target == "" {
widget.Target = "_blank"
}
return nil
}

@ -70,6 +70,7 @@ func (widget *dnsStatsWidget) initialize() error {
widget.
withTitle("DNS Stats").
withTitleURL(titleURL).
withTitleTarget("_blank").
withCacheDuration(10 * time.Minute)
switch widget.Service {

@ -24,6 +24,7 @@ type dockerContainersWidget struct {
FormatContainerNames bool `yaml:"format-container-names"`
Containers dockerContainerList `yaml:"-"`
LabelOverrides map[string]map[string]string `yaml:"containers"`
Target string `yaml:"target"`
}
func (widget *dockerContainersWidget) initialize() error {
@ -33,6 +34,10 @@ func (widget *dockerContainersWidget) initialize() error {
widget.SockPath = "/var/run/docker.sock"
}
if widget.Target == "" {
widget.Target = "_blank"
}
return nil
}
@ -50,6 +55,13 @@ func (widget *dockerContainersWidget) update(ctx context.Context) {
}
containers.sortByStateIconThenName()
for i := range containers {
container := &containers[i]
if container.Target == "" {
container.Target = widget.Target
}
}
widget.Containers = containers
}
@ -63,6 +75,7 @@ const (
dockerContainerLabelURL = "glance.url"
dockerContainerLabelDescription = "glance.description"
dockerContainerLabelSameTab = "glance.same-tab"
dockerContainerLabelTarget = "glance.target"
dockerContainerLabelIcon = "glance.icon"
dockerContainerLabelID = "glance.id"
dockerContainerLabelParent = "glance.parent"
@ -113,7 +126,7 @@ func (l *dockerContainerLabels) getOrDefault(label, def string) string {
type dockerContainer struct {
Name string
URL string
SameTab bool
Target string
Image string
State string
StateText string
@ -173,11 +186,24 @@ func fetchDockerContainers(
for i := range containers {
container := &containers[i]
// Backward compatibility, literal string is used in sameTab to
// capture if it was set by the user explicitly
target := container.Labels.getOrDefault(dockerContainerLabelTarget, "")
sameTab := container.Labels.getOrDefault(dockerContainerLabelSameTab, "")
if target == "" {
switch sameTab {
case "true":
target = "_self"
case "false":
target = "_blank"
}
}
dc := dockerContainer{
Name: deriveDockerContainerName(container, formatNames),
URL: container.Labels.getOrDefault(dockerContainerLabelURL, ""),
Description: container.Labels.getOrDefault(dockerContainerLabelDescription, ""),
SameTab: stringToBool(container.Labels.getOrDefault(dockerContainerLabelSameTab, "false")),
Target: target,
Image: container.Image,
State: strings.ToLower(container.State),
StateText: strings.ToLower(container.Status),

@ -28,6 +28,7 @@ func (widget *hackerNewsWidget) initialize() error {
widget.
withTitle("Hacker News").
withTitleURL("https://news.ycombinator.com/").
withTitleTarget("_blank").
withCacheDuration(30 * time.Minute)
if widget.Limit <= 0 {

@ -18,6 +18,7 @@ type lobstersWidget struct {
SortBy string `yaml:"sort-by"`
Tags []string `yaml:"tags"`
ShowThumbnails bool `yaml:"-"`
Target string `yaml:"target"`
Filters filterableFields[forumPost] `yaml:"filters"`
}
@ -43,6 +44,10 @@ func (widget *lobstersWidget) initialize() error {
widget.CollapseAfter = 5
}
if widget.Target == "" {
widget.Target = "_blank"
}
return nil
}

@ -21,6 +21,7 @@ type marketsWidget struct {
ChartLinkTemplate string `yaml:"chart-link-template"`
SymbolLinkTemplate string `yaml:"symbol-link-template"`
Sort string `yaml:"sort-by"`
Target string `yaml:"target"`
Markets marketList `yaml:"-"`
}
@ -32,6 +33,10 @@ func (widget *marketsWidget) initialize() error {
widget.MarketRequests = widget.StocksRequests
}
if widget.Target == "" {
widget.Target = "_blank"
}
for i := range widget.MarketRequests {
m := &widget.MarketRequests[i]

@ -24,19 +24,24 @@ type monitorWidget struct {
ErrorURL string `yaml:"error-url"`
Title string `yaml:"title"`
Icon customIconField `yaml:"icon"`
SameTab bool `yaml:"same-tab"`
Target string `yaml:"target"`
StatusText string `yaml:"-"`
StatusStyle string `yaml:"-"`
AltStatusCodes []int `yaml:"alt-status-codes"`
} `yaml:"sites"`
Style string `yaml:"style"`
ShowFailingOnly bool `yaml:"show-failing-only"`
Target string `yaml:"target"`
HasFailing bool `yaml:"-"`
}
func (widget *monitorWidget) initialize() error {
widget.withTitle("Monitor").withCacheDuration(5 * time.Minute)
if widget.Target == "" {
widget.Target = "_blank"
}
return nil
}
@ -72,6 +77,7 @@ func (widget *monitorWidget) update(ctx context.Context) {
site.StatusText = statusCodeToText(status.Code, site.AltStatusCodes)
site.StatusStyle = statusCodeToStyle(status.Code, site.AltStatusCodes)
site.Target = ternary(site.Target != "", site.Target, widget.Target)
}
}

@ -34,6 +34,7 @@ type redditWidget struct {
Limit int `yaml:"limit"`
CollapseAfter int `yaml:"collapse-after"`
RequestURLTemplate string `yaml:"request-url-template"`
Target string `yaml:"target"`
Filters filterableFields[forumPost] `yaml:"filters"`
@ -85,9 +86,14 @@ func (widget *redditWidget) initialize() error {
a.enabled = true
}
if widget.Target == "" {
widget.Target = "_blank"
}
widget.
withTitle("r/" + widget.Subreddit).
withTitleURL("https://www.reddit.com/r/" + widget.Subreddit + "/").
withTitleTarget("_blank").
withCacheDuration(30 * time.Minute)
return nil

@ -26,6 +26,7 @@ type releasesWidget struct {
Limit int `yaml:"limit"`
CollapseAfter int `yaml:"collapse-after"`
ShowSourceIcon bool `yaml:"show-source-icon"`
Target string `yaml:"target"`
}
func (widget *releasesWidget) initialize() error {
@ -39,6 +40,10 @@ func (widget *releasesWidget) initialize() error {
widget.CollapseAfter = 5
}
if widget.Target == "" {
widget.Target = "_blank"
}
for i := range widget.Repositories {
r := widget.Repositories[i]

@ -21,6 +21,7 @@ type repositoryWidget struct {
CommitsLimit int `yaml:"commits-limit"`
ExcludeDraftPRs bool `yaml:"exclude-draft-pull-requests"`
Repository repository `yaml:"-"`
Target string `yaml:"target"`
}
func (widget *repositoryWidget) initialize() error {
@ -38,6 +39,10 @@ func (widget *repositoryWidget) initialize() error {
widget.CommitsLimit = -1
}
if widget.Target == "" {
widget.Target = "_blank"
}
return nil
}

@ -38,6 +38,7 @@ type rssWidget struct {
CollapseAfter int `yaml:"collapse-after"`
SingleLineTitles bool `yaml:"single-line-titles"`
PreserveOrder bool `yaml:"preserve-order"`
Target string `yaml:"target"`
Items rssFeedItemList `yaml:"-"`
NoItemsMessage string `yaml:"-"`
@ -73,6 +74,10 @@ func (widget *rssWidget) initialize() error {
}
}
if widget.Target == "" {
widget.Target = "_blank"
}
widget.cachedFeeds = make(map[string]*cachedRSSFeed)
return nil

@ -39,6 +39,7 @@ func (widget *torrentsWidget) initialize() error {
widget.
withTitle("Torrents").
withTitleURL(widget.URL).
withTitleTarget("_blank").
withCacheDuration(time.Second * 5)
if widget.URL == "" {

@ -20,12 +20,14 @@ type twitchChannelsWidget struct {
Channels []twitchChannel `yaml:"-"`
CollapseAfter int `yaml:"collapse-after"`
SortBy string `yaml:"sort-by"`
Target string `yaml:"target"`
}
func (widget *twitchChannelsWidget) initialize() error {
widget.
withTitle("Twitch Channels").
withTitleURL("https://www.twitch.tv/directory/following").
withTitleTarget("_blank").
withCacheDuration(time.Minute * 10)
if widget.CollapseAfter == 0 || widget.CollapseAfter < -1 {
@ -36,6 +38,10 @@ func (widget *twitchChannelsWidget) initialize() error {
widget.SortBy = "viewers"
}
if widget.Target == "" {
widget.Target = "_blank"
}
return nil
}

@ -19,12 +19,14 @@ type twitchGamesWidget struct {
Exclude []string `yaml:"exclude"`
Limit int `yaml:"limit"`
CollapseAfter int `yaml:"collapse-after"`
Target string `yaml:"target"`
}
func (widget *twitchGamesWidget) initialize() error {
widget.
withTitle("Top games on Twitch").
withTitleURL("https://www.twitch.tv/directory?sort=VIEWER_COUNT").
withTitleTarget("_blank").
withCacheDuration(time.Minute * 10)
if widget.Limit <= 0 {
@ -35,6 +37,10 @@ func (widget *twitchGamesWidget) initialize() error {
widget.CollapseAfter = 5
}
if widget.Target == "" {
widget.Target = "_blank"
}
return nil
}

@ -32,6 +32,7 @@ type videosWidget struct {
Limit int `yaml:"limit"`
IncludeShorts bool `yaml:"include-shorts"`
SortBy string `yaml:"sort-by"`
Target string `yaml:"target"`
Filters filterableFields[video] `yaml:"filters"`
}
@ -51,6 +52,10 @@ func (widget *videosWidget) initialize() error {
widget.CollapseAfter = 7
}
if widget.Target == "" {
widget.Target = "_blank"
}
// A bit cheeky, but from a user's perspective it makes more sense when channels and
// playlists are separate things rather than specifying a list of channels and some of
// them awkwardly have a "playlist:" prefix

@ -156,6 +156,7 @@ type widgetBase struct {
Type string `yaml:"type"`
Title string `yaml:"title"`
TitleURL string `yaml:"title-url"`
TitleTarget string `yaml:"title-target"`
HideHeader bool `yaml:"hide-header"`
CSSClass string `yaml:"css-class"`
CustomCacheDuration durationField `yaml:"cache"`
@ -260,6 +261,14 @@ func (w *widgetBase) withTitleURL(titleURL string) *widgetBase {
return w
}
func (w *widgetBase) withTitleTarget(titleTarget string) *widgetBase {
if w.TitleTarget == "" {
w.TitleTarget = titleTarget
}
return w
}
func (w *widgetBase) withCacheDuration(duration time.Duration) *widgetBase {
w.cacheType = cacheTypeDuration