Compare commits

..
22 Commits
Author SHA1 Message Date
Svilen Markov b37f8a8375 Merge pull request #194 from glanceapp/backport-fixes
Create release / release (push) Has been cancelled
Backport fixes from v0.6.0 to v0.5.0
2024-08-09 16:08:30 +01:00
Svilen Markov 139937f887 Fix missing RSS thumbnail images 2024-08-09 16:03:24 +01:00
Wyatt Gill b8b90451b6 Use GitHub's latest release API endpoint
The current releases widget uses the releases endpoint to pull the 10
most recent releases and filter them to find the latest release. This
causes a problem when a repository's latest release is outside of the 10
most recent (e.g. 10 prereleases):

ERROR No live release found repository=cross-seed/cross-seed url="https://api.github.com/repos/cross-seed/cross-seed/releases?per_page=10"

This is no longer a problem when using the latest release endpoint which
grabs the latest release, ignoring draft releases and prereleases.
2024-08-09 16:02:15 +01:00
Svilen Markov c9a2aff6a6 Fix search input color 2024-08-09 16:01:15 +01:00
Svilen Markov 82b6531a07 Add funding 2024-08-07 19:18:17 +01:00
Svilen Markov a981025679 Merge pull request #108 from wfg/github-actions
Create release workflow
2024-08-07 18:58:13 +01:00
Svilen Markov f9ea2d73e1 Tidy up previous build files 2024-08-07 18:50:06 +01:00
Svilen Markov a5728e9407 Update release workflow 2024-08-07 18:47:50 +01:00
Svilen Markov 1411268e35 Merge pull request #80 from c0smicdev/main
Add Kanagawa Dark theme
2024-07-03 19:51:42 +01:00
c0smic 645802981e Update theme preview 2024-06-14 16:56:33 +00:00
c0smic 0348a2475c Delete preview of old version 2024-06-14 16:55:44 +00:00
Wyatt Gill d5cf881d7a Produce example 2024-06-03 16:16:43 -05:00
Wyatt Gill 050d3e6cd2 Add release workflow using GoReleaser 2024-06-03 16:16:43 -05:00
c0smic d27993d2c9 fix: use negative color with higher contrast 2024-05-29 16:53:31 +00:00
c0smic bfc65d51e3 Merge branch 'glanceapp:main' into main 2024-05-29 16:50:18 +00:00
c0smicandSvilen Markov 3c29d38599 fix: Fix typo
Co-authored-by: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com>
2024-05-27 18:11:35 +00:00
c0smic 060609e2cb Add Kanagawa Dark to themes 2024-05-18 14:25:08 +00:00
c0smic a1c48bf012 Use new Kanagawa Dark Image 2024-05-18 14:24:15 +00:00
c0smic a67fb66d2e Delete old Kanagawa Dark image 2024-05-18 14:23:32 +00:00
c0smic 3001f4845a Add new Kanagawa Dark image 2024-05-18 14:20:17 +00:00
c0smic 192c1ad669 Rename kanagawa dark preview image 2024-05-18 14:15:35 +00:00
c0smic 89ae8a3607 Add Kanagawa Dark Theme preview 2024-05-18 14:13:56 +00:00
31 changed files with 221 additions and 542 deletions
+1
View File
@@ -0,0 +1 @@
github: [glanceapp]
+39
View File
@@ -0,0 +1,39 @@
name: Create release
permissions:
contents: write
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout the target Git reference
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Golang
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Set up Docker buildx
uses: docker/setup-buildx-action@v3
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: release
+72
View File
@@ -0,0 +1,72 @@
project_name: glanceapp/glance
checksum:
disable: true
builds:
- binary: glance
env:
- CGO_ENABLED=0
goos:
- linux
- openbsd
- freebsd
- windows
- darwin
goarch:
- amd64
- arm64
- arm
- 386
goarm:
- 7
ldflags:
- -s -w -X github.com/glanceapp/glance/internal/glance.buildVersion={{ .Tag }}
archives:
-
name_template: "glance-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}"
files:
- nothing*
format_overrides:
- goos: windows
format: zip
dockers:
- image_templates:
- &amd64_image "{{ .ProjectName }}:{{ .Tag }}-amd64"
build_flag_templates:
- --platform=linux/amd64
goarch: amd64
use: buildx
dockerfile: Dockerfile.goreleaser
- image_templates:
- &arm64v8_image "{{ .ProjectName }}:{{ .Tag }}-arm64"
build_flag_templates:
- --platform=linux/arm64
goarch: arm64
use: buildx
dockerfile: Dockerfile.goreleaser
- image_templates:
- &arm64v7_image "{{ .ProjectName }}:{{ .Tag }}-arm64v7"
build_flag_templates:
- --platform=linux/arm64/v7
goarch: arm
goarm: 7
use: buildx
dockerfile: Dockerfile.goreleaser
docker_manifests:
- name_template: "{{ .ProjectName }}:{{ .Tag }}"
image_templates:
- *amd64_image
- *arm64v8_image
- *arm64v7_image
- name_template: "{{ .ProjectName }}:latest"
skip_push: auto
image_templates:
- *amd64_image
- *arm64v8_image
- *arm64v7_image
+8 -6
View File
@@ -1,11 +1,13 @@
FROM alpine:3.20.1 FROM golang:1.22.5-alpine3.20 AS builder
ARG TARGETOS
ARG TARGETARCH
ARG TARGETVARIANT
WORKDIR /app WORKDIR /app
COPY build/glance-$TARGETOS-$TARGETARCH${TARGETVARIANT} /app/glance COPY . /app
RUN CGO_ENABLED=0 go build .
FROM alpine:3.20
WORKDIR /app
COPY --from=builder /app/glance .
EXPOSE 8080/tcp EXPOSE 8080/tcp
ENTRYPOINT ["/app/glance"] ENTRYPOINT ["/app/glance"]
+8
View File
@@ -0,0 +1,8 @@
FROM alpine:3.20
WORKDIR /app
COPY glance .
EXPOSE 8080/tcp
ENTRYPOINT ["/app/glance"]
-14
View File
@@ -1,14 +0,0 @@
FROM golang:1.22.3-alpine3.19 AS builder
WORKDIR /app
COPY . /app
RUN CGO_ENABLED=0 go build .
FROM alpine:3.19
WORKDIR /app
COPY --from=builder /app/glance .
EXPOSE 8080/tcp
ENTRYPOINT ["/app/glance"]
+1 -1
View File
@@ -101,7 +101,7 @@ Build the image:
**Make sure to replace "owner" with your name or organization.** **Make sure to replace "owner" with your name or organization.**
```bash ```bash
docker build -t owner/glance:latest -f Dockerfile.single-platform . docker build -t owner/glance:latest .
``` ```
Push the image to your registry: Push the image to your registry:
-34
View File
@@ -234,8 +234,6 @@ theme:
> .widget-type-rss a { > .widget-type-rss a {
> font-size: 1.5rem; > font-size: 1.5rem;
> } > }
>
> In addition, you can also use the `css-class` property which is available on every widget to set custom class names for individual widgets.
## Pages & Columns ## Pages & Columns
@@ -356,9 +354,7 @@ pages:
| ---- | ---- | -------- | | ---- | ---- | -------- |
| type | string | yes | | type | string | yes |
| title | string | no | | title | string | no |
| title-url | string | no |
| cache | string | no | | cache | string | no |
| css-class | string | no |
#### `type` #### `type`
Used to specify the widget. Used to specify the widget.
@@ -366,9 +362,6 @@ Used to specify the widget.
#### `title` #### `title`
The title of the widget. If left blank it will be defined by the widget. The title of the widget. If left blank it will be defined by the widget.
#### `title-url`
The URL to go to when clicking on the widget's title. If left blank it will be defined by the widget (if available).
#### `cache` #### `cache`
How long to keep the fetched data in memory. The value is a string and must be a number followed by one of s, m, h, d. Examples: How long to keep the fetched data in memory. The value is a string and must be a number followed by one of s, m, h, d. Examples:
@@ -383,9 +376,6 @@ cache: 1d # 1 day
> >
> Not all widgets can have their cache duration modified. The calendar and weather widgets update on the hour and this cannot be changed. > Not all widgets can have their cache duration modified. The calendar and weather widgets update on the hour and this cannot be changed.
#### `css-class`
Set custom CSS classes for the specific widget instance.
### RSS ### RSS
Display a list of articles from multiple RSS feeds. Display a list of articles from multiple RSS feeds.
@@ -582,23 +572,11 @@ Preview:
#### Properties #### Properties
| Name | Type | Required | Default | | Name | Type | Required | Default |
| ---- | ---- | -------- | ------- | | ---- | ---- | -------- | ------- |
| instance-url | string | no | https://lobste.rs/ |
| custom-url | string | no | |
| limit | integer | no | 15 | | limit | integer | no | 15 |
| collapse-after | integer | no | 5 | | collapse-after | integer | no | 5 |
| sort-by | string | no | hot | | sort-by | string | no | hot |
| tags | array | no | | | tags | array | no | |
##### `instance-url`
The base URL for a lobsters instance hosted somewhere other than on lobste.rs. Example:
```yaml
instance-url: https://www.journalduhacker.net/
```
##### `custom-url`
A custom URL to retrieve lobsters posts from. If this is specified, the `instance-url`, `sort-by` and `tags` properties are ignored.
##### `limit` ##### `limit`
The maximum number of posts to show. The maximum number of posts to show.
@@ -746,16 +724,10 @@ Preview:
| <kbd>Ctrl</kbd> + <kbd>Enter</kbd> | Perform search in a new tab | Search input is focused and not empty | | <kbd>Ctrl</kbd> + <kbd>Enter</kbd> | Perform search in a new tab | Search input is focused and not empty |
| <kbd>Escape</kbd> | Leave focus | Search input is focused | | <kbd>Escape</kbd> | Leave focus | Search input is focused |
> [!TIP]
>
> You can use the property `new-tab` with a value of `true` if you want to show search results in a new tab by default. <kbd>Ctrl</kbd> + <kbd>Enter</kbd> will then show results in the same tab.
#### Properties #### Properties
| Name | Type | Required | Default | | Name | Type | Required | Default |
| ---- | ---- | -------- | ------- | | ---- | ---- | -------- | ------- |
| search-engine | string | no | duckduckgo | | search-engine | string | no | duckduckgo |
| new-tab | boolean | no | false |
| autofocus | boolean | no | false |
| bangs | array | no | | | bangs | array | no | |
##### `search-engine` ##### `search-engine`
@@ -766,12 +738,6 @@ Either a value from the table below or a URL to a custom search engine. Use `{QU
| duckduckgo | `https://duckduckgo.com/?q={QUERY}` | | duckduckgo | `https://duckduckgo.com/?q={QUERY}` |
| google | `https://www.google.com/search?q={QUERY}` | | google | `https://www.google.com/search?q={QUERY}` |
##### `new-tab`
When set to `true`, swaps the shortcuts for showing results in the same or new tab, defaulting to showing results in a new tab.
##### `new-tab`
When set to `true`, automatically focuses the search input on page load.
##### `bangs` ##### `bangs`
What now? [Bangs](https://duckduckgo.com/bangs). They're shortcuts that allow you to use the same search box for many different sites. Assuming you have it configured, if for example you start your search input with `!yt` you'd be able to perform a search on YouTube: What now? [Bangs](https://duckduckgo.com/bangs). They're shortcuts that allow you to use the same search box for many different sites. Assuming you have it configured, if for example you start your search input with `!yt` you'd be able to perform a search on YouTube:
Binary file not shown.

After

Width:  |  Height:  |  Size: 549 KiB

+10
View File
@@ -53,6 +53,16 @@ theme:
primary-color: 97 13 80 primary-color: 97 13 80
``` ```
### Kanagawa Dark
![screenshot](images/themes/kanagawa-dark.png)
```yaml
theme:
background-color: 240 13 14
primary-color: 51 33 68
negative-color: 358 100 68
contrast-multiplier: 1.2
```
### Tucan ### Tucan
![screenshot](images/themes/tucan.png) ![screenshot](images/themes/tucan.png)
```yaml ```yaml
+3 -3
View File
@@ -4,16 +4,16 @@ go 1.22.3
require ( require (
github.com/mmcdole/gofeed v1.3.0 github.com/mmcdole/gofeed v1.3.0
golang.org/x/text v0.16.0 golang.org/x/text v0.14.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require ( require (
github.com/PuerkitoBio/goquery v1.9.2 // indirect github.com/PuerkitoBio/goquery v1.9.1 // indirect
github.com/andybalholm/cascadia v1.3.2 // indirect github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/mmcdole/goxpp v1.1.1 // indirect github.com/mmcdole/goxpp v1.1.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
golang.org/x/net v0.27.0 // indirect golang.org/x/net v0.24.0 // indirect
) )
+6 -6
View File
@@ -1,5 +1,5 @@
github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE= github.com/PuerkitoBio/goquery v1.9.1 h1:mTL6XjbJTZdpfL+Gwl5U2h1l9yEkJjhmlTeV9VPW7UI=
github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk= github.com/PuerkitoBio/goquery v1.9.1/go.mod h1:cW1n6TmIMDoORQU5IU/P1T3tGFunOeXEpGP2WHRwkbY=
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -33,8 +33,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -54,8 +54,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-41
View File
@@ -1,14 +1,8 @@
package assets package assets
import ( import (
"crypto/md5"
"embed" "embed"
"encoding/hex"
"io"
"io/fs" "io/fs"
"log/slog"
"strconv"
"time"
) )
//go:embed static //go:embed static
@@ -19,38 +13,3 @@ var _templateFS embed.FS
var PublicFS, _ = fs.Sub(_publicFS, "static") var PublicFS, _ = fs.Sub(_publicFS, "static")
var TemplateFS, _ = fs.Sub(_templateFS, "templates") var TemplateFS, _ = fs.Sub(_templateFS, "templates")
func getFSHash(files fs.FS) string {
hash := md5.New()
err := fs.WalkDir(files, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
file, err := files.Open(path)
if err != nil {
return err
}
if _, err := io.Copy(hash, file); err != nil {
return err
}
return nil
})
if err == nil {
return hex.EncodeToString(hash.Sum(nil))[:10]
}
slog.Warn("Could not compute assets cache", "err", err)
return strconv.FormatInt(time.Now().Unix(), 10)
}
var PublicFSHash = getFSHash(PublicFS)
-1
View File
@@ -263,7 +263,6 @@ html {
a { a {
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
overflow-wrap: break-word;
} }
ul { ul {
+12 -12
View File
@@ -107,7 +107,7 @@ function updateRelativeTimeForElements(elements)
} }
} }
function setupSearchBoxes() { function setupSearchboxes() {
const searchWidgets = document.getElementsByClassName("search"); const searchWidgets = document.getElementsByClassName("search");
if (searchWidgets.length == 0) { if (searchWidgets.length == 0) {
@@ -117,7 +117,6 @@ function setupSearchBoxes() {
for (let i = 0; i < searchWidgets.length; i++) { for (let i = 0; i < searchWidgets.length; i++) {
const widget = searchWidgets[i]; const widget = searchWidgets[i];
const defaultSearchUrl = widget.dataset.defaultSearchUrl; const defaultSearchUrl = widget.dataset.defaultSearchUrl;
const newTab = widget.dataset.newTab === "true";
const inputElement = widget.getElementsByClassName("search-input")[0]; const inputElement = widget.getElementsByClassName("search-input")[0];
const bangElement = widget.getElementsByClassName("search-bang")[0]; const bangElement = widget.getElementsByClassName("search-bang")[0];
const bangs = widget.querySelectorAll(".search-bangs > input"); const bangs = widget.querySelectorAll(".search-bangs > input");
@@ -148,13 +147,14 @@ function setupSearchBoxes() {
query = input; query = input;
searchUrlTemplate = defaultSearchUrl; searchUrlTemplate = defaultSearchUrl;
} }
if (query.length == 0 && currentBang == null) {
if (query.length == 0) {
return; return;
} }
const url = searchUrlTemplate.replace("!QUERY!", encodeURIComponent(query)); const url = searchUrlTemplate.replace("!QUERY!", encodeURIComponent(query));
if (newTab && !event.ctrlKey || !newTab && event.ctrlKey) { if (event.ctrlKey) {
window.open(url, '_blank').focus(); window.open(url, '_blank').focus();
} else { } else {
window.location.href = url; window.location.href = url;
@@ -170,13 +170,9 @@ function setupSearchBoxes() {
} }
const handleInput = (event) => { const handleInput = (event) => {
const value = event.target.value.trim(); const value = event.target.value.trimStart();
if (value in bangsMap) {
changeCurrentBang(bangsMap[value]);
return;
}
const words = value.split(" "); const words = value.split(" ");
if (words.length >= 2 && words[0] in bangsMap) { if (words.length >= 2 && words[0] in bangsMap) {
changeCurrentBang(bangsMap[words[0]]); changeCurrentBang(bangsMap[words[0]]);
return; return;
@@ -555,7 +551,7 @@ async function setupPage() {
try { try {
setupClocks() setupClocks()
setupCarousels(); setupCarousels();
setupSearchBoxes(); setupSearchboxes();
setupCollapsibleLists(); setupCollapsibleLists();
setupCollapsibleGrids(); setupCollapsibleGrids();
setupDynamicRelativeTime(); setupDynamicRelativeTime();
@@ -573,4 +569,8 @@ async function setupPage() {
} }
} }
setupPage(); if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", setupPage);
} else {
setupPage();
}
+1 -1
View File
@@ -6,7 +6,7 @@
"start_url": "/", "start_url": "/",
"icons": [ "icons": [
{ {
"src": "app-icon.png", "src": "/static/app-icon.png",
"type": "image/png", "type": "image/png",
"sizes": "512x512" "sizes": "512x512"
} }
+5 -5
View File
@@ -11,12 +11,12 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Glance"> <meta name="apple-mobile-web-app-title" content="Glance">
<meta name="theme-color" content="{{ if ne nil .App.Config.Theme.BackgroundColor }}{{ .App.Config.Theme.BackgroundColor }}{{ else }}hsl(240, 8%, 9%){{ end }}"> <meta name="theme-color" content="{{ if ne nil .App.Config.Theme.BackgroundColor }}{{ .App.Config.Theme.BackgroundColor }}{{ else }}hsl(240, 8%, 9%){{ end }}">
<link rel="apple-touch-icon" sizes="512x512" href="{{ .App.AssetPath "app-icon.png" }}"> <link rel="apple-touch-icon" sizes="512x512" href="/static/app-icon.png">
<link rel="icon" type="image/png" sizes="50x50" href="{{ .App.AssetPath "favicon.png" }}"> <link rel="icon" type="image/png" sizes="50x50" href="/static/favicon.png">
<link rel="manifest" href="/static/manifest.json"> <link rel="manifest" href="/static/manifest.json">
<link rel="icon" type="image/png" href="{{ .App.AssetPath "favicon.png" }}" /> <link rel="icon" type="image/png" href="/static/favicon.png" />
<link rel="stylesheet" href="{{ .App.AssetPath "main.css" }}"> <link rel="stylesheet" href="/static/main.css?v={{ .App.Config.Server.StartedAt.Unix }}">
<script type="module" src="{{ .App.AssetPath "main.js" }}"></script> <script async src="/static/main.js?v={{ .App.Config.Server.StartedAt.Unix }}"></script>
{{ block "document-head-after" . }}{{ end }} {{ block "document-head-after" . }}{{ end }}
</head> </head>
<body> <body>
+2 -2
View File
@@ -3,7 +3,7 @@
{{ define "widget-content-classes" }}widget-content-frameless{{ end }} {{ define "widget-content-classes" }}widget-content-frameless{{ end }}
{{ define "widget-content" }} {{ define "widget-content" }}
<div class="search widget-content-frame padding-inline-widget flex gap-15 items-center" data-default-search-url="{{ .SearchEngine }}" data-new-tab="{{ .NewTab }}"> <div class="search widget-content-frame padding-inline-widget flex gap-15 items-center" data-default-search-url="{{ .SearchEngine }}">
<div class="search-bangs"> <div class="search-bangs">
{{ range .Bangs }} {{ range .Bangs }}
<input type="hidden" data-shortcut="{{ .Shortcut }}" data-title="{{ .Title }}" data-url="{{ .URL }}"> <input type="hidden" data-shortcut="{{ .Shortcut }}" data-title="{{ .Title }}" data-url="{{ .URL }}">
@@ -16,7 +16,7 @@
</svg> </svg>
</div> </div>
<input class="search-input" type="text" placeholder="Type here to search…" autocomplete="off"{{ if .Autofocus }} autofocus{{ end }}> <input class="search-input" type="text" placeholder="Type here to search…" autocomplete="off">
<div class="search-bang"></div> <div class="search-bang"></div>
<kbd class="hide-on-mobile" title="Press [S] to focus the search input">S</kbd> <kbd class="hide-on-mobile" title="Press [S] to focus the search input">S</kbd>
+3 -3
View File
@@ -1,13 +1,13 @@
<div class="widget widget-type-{{ .GetType }}{{ if ne "" .CSSClass }} {{ .CSSClass }}{{ end }}"> <div class="widget widget-type-{{ .GetType }}">
<div class="widget-header"> <div class="widget-header">
{{ if ne "" .TitleURL}}<a href="{{ .TitleURL }}" target="_blank" rel="noreferrer" class="uppercase">{{ .Title }}</a>{{ else }}<div class="uppercase">{{ .Title }}</div>{{ end }} <div class="uppercase">{{ .Title }}</div>
{{ if and .Error .ContentAvailable }} {{ if and .Error .ContentAvailable }}
<div class="notice-icon notice-icon-major" title="{{ .Error }}"></div> <div class="notice-icon notice-icon-major" title="{{ .Error }}"></div>
{{ else if .Notice }} {{ else if .Notice }}
<div class="notice-icon notice-icon-minor" title="{{ .Notice }}"></div> <div class="notice-icon notice-icon-minor" title="{{ .Notice }}"></div>
{{ end }} {{ end }}
</div> </div>
<div class="widget-content{{ if .ContentAvailable }} {{ block "widget-content-classes" . }}{{ end }}{{ end }}"> <div class="widget-content {{ if .ContentAvailable }}{{ block "widget-content-classes" . }}{{ end }}{{ end }}">
{{ if .ContentAvailable }} {{ if .ContentAvailable }}
{{ block "widget-content" . }}{{ end }} {{ block "widget-content" . }}{{ end }}
{{ else }} {{ else }}
+11 -21
View File
@@ -55,30 +55,20 @@ func getLobstersPostsFromFeed(feedUrl string) (ForumPosts, error) {
return posts, nil return posts, nil
} }
func FetchLobstersPosts(customURL string, instanceURL string, sortBy string, tags []string) (ForumPosts, error) { func FetchLobstersPosts(sortBy string, tags []string) (ForumPosts, error) {
var feedUrl string var feedUrl string
if customURL != "" { if sortBy == "hot" {
feedUrl = customURL sortBy = "hottest"
} else if sortBy == "new" {
sortBy = "newest"
}
if len(tags) == 0 {
feedUrl = "https://lobste.rs/" + sortBy + ".json"
} else { } else {
if instanceURL != "" { tags := strings.Join(tags, ",")
instanceURL = strings.TrimRight(instanceURL, "/") + "/" feedUrl = "https://lobste.rs/t/" + tags + ".json"
} else {
instanceURL = "https://lobste.rs/"
}
if sortBy == "hot" {
sortBy = "hottest"
} else if sortBy == "new" {
sortBy = "newest"
}
if len(tags) == 0 {
feedUrl = instanceURL + sortBy + ".json"
} else {
tags := strings.Join(tags, ",")
feedUrl = instanceURL + "t/" + tags + ".json"
}
} }
posts, err := getLobstersPostsFromFeed(feedUrl) posts, err := getLobstersPostsFromFeed(feedUrl)
+23 -35
View File
@@ -44,25 +44,12 @@ func sanitizeFeedDescription(description string) string {
return description return description
} }
func shortenFeedDescriptionLen(description string, maxLen int) string {
description, _ = limitStringLength(description, 1000)
description = sanitizeFeedDescription(description)
description, limited := limitStringLength(description, maxLen)
if limited {
description += "…"
}
return description
}
type RSSFeedRequest struct { type RSSFeedRequest struct {
Url string `yaml:"url"` Url string `yaml:"url"`
Title string `yaml:"title"` Title string `yaml:"title"`
HideCategories bool `yaml:"hide-categories"` HideCategories bool `yaml:"hide-categories"`
HideDescription bool `yaml:"hide-description"` HideDescription bool `yaml:"hide-description"`
ItemLinkPrefix string `yaml:"item-link-prefix"` ItemLinkPrefix string `yaml:"item-link-prefix"`
IsDetailed bool `yaml:"-"`
} }
type RSSFeedItems []RSSFeedItem type RSSFeedItems []RSSFeedItem
@@ -94,6 +81,7 @@ func getItemsFromRSSFeedTask(request RSSFeedRequest) ([]RSSFeedItem, error) {
rssItem := RSSFeedItem{ rssItem := RSSFeedItem{
ChannelURL: feed.Link, ChannelURL: feed.Link,
Title: item.Title,
} }
if request.ItemLinkPrefix != "" { if request.ItemLinkPrefix != "" {
@@ -120,34 +108,34 @@ func getItemsFromRSSFeedTask(request RSSFeedRequest) ([]RSSFeedItem, error) {
} }
} }
if item.Title != "" { if !request.HideDescription && item.Description != "" {
rssItem.Title = item.Title description, _ := limitStringLength(item.Description, 1000)
} else { description = sanitizeFeedDescription(description)
rssItem.Title = shortenFeedDescriptionLen(item.Description, 100) description, limited := limitStringLength(description, 200)
if limited {
description += "…"
}
rssItem.Description = description
} }
if request.IsDetailed { if !request.HideCategories {
if !request.HideDescription && item.Description != "" && item.Title != "" { var categories = make([]string, 0, 6)
rssItem.Description = shortenFeedDescriptionLen(item.Description, 200)
}
if !request.HideCategories { for _, category := range item.Categories {
var categories = make([]string, 0, 6) if len(categories) == 6 {
break
for _, category := range item.Categories {
if len(categories) == 6 {
break
}
if len(category) == 0 || len(category) > 30 {
continue
}
categories = append(categories, category)
} }
rssItem.Categories = categories if len(category) == 0 || len(category) > 30 {
continue
}
categories = append(categories, category)
} }
rssItem.Categories = categories
} }
if request.Title != "" { if request.Title != "" {
+7 -14
View File
@@ -38,10 +38,10 @@ type Theme struct {
} }
type Server struct { type Server struct {
Host string `yaml:"host"` Host string `yaml:"host"`
Port uint16 `yaml:"port"` Port uint16 `yaml:"port"`
AssetsPath string `yaml:"assets-path"` AssetsPath string `yaml:"assets-path"`
AssetsHash string `yaml:"-"` StartedAt time.Time `yaml:"-"`
} }
type Column struct { type Column struct {
@@ -189,13 +189,7 @@ func FileServerWithCache(fs http.FileSystem, cacheDuration time.Duration) http.H
}) })
} }
func (a *Application) AssetPath(asset string) string {
return "/static/" + a.Config.Server.AssetsHash + "/" + asset
}
func (a *Application) Serve() error { func (a *Application) Serve() error {
a.Config.Server.AssetsHash = assets.PublicFSHash
// TODO: add gzip support, static files must have their gzipped contents cached // TODO: add gzip support, static files must have their gzipped contents cached
// TODO: add HTTPS support // TODO: add HTTPS support
mux := http.NewServeMux() mux := http.NewServeMux()
@@ -203,10 +197,7 @@ func (a *Application) Serve() error {
mux.HandleFunc("GET /{$}", a.HandlePageRequest) mux.HandleFunc("GET /{$}", a.HandlePageRequest)
mux.HandleFunc("GET /{page}", a.HandlePageRequest) mux.HandleFunc("GET /{page}", a.HandlePageRequest)
mux.HandleFunc("GET /api/pages/{page}/content/{$}", a.HandlePageContentRequest) mux.HandleFunc("GET /api/pages/{page}/content/{$}", a.HandlePageContentRequest)
mux.Handle( mux.Handle("GET /static/{path...}", http.StripPrefix("/static/", FileServerWithCache(http.FS(assets.PublicFS), 2*time.Hour)))
fmt.Sprintf("GET /static/%s/{path...}", a.Config.Server.AssetsHash),
http.StripPrefix("/static/"+a.Config.Server.AssetsHash, FileServerWithCache(http.FS(assets.PublicFS), 8*time.Hour)),
)
if a.Config.Server.AssetsPath != "" { if a.Config.Server.AssetsPath != "" {
absAssetsPath, err := filepath.Abs(a.Config.Server.AssetsPath) absAssetsPath, err := filepath.Abs(a.Config.Server.AssetsPath)
@@ -225,6 +216,8 @@ func (a *Application) Serve() error {
Handler: mux, Handler: mux,
} }
a.Config.Server.StartedAt = time.Now()
slog.Info("Starting server", "host", a.Config.Server.Host, "port", a.Config.Server.Port) slog.Info("Starting server", "host", a.Config.Server.Host, "port", a.Config.Server.Port)
return server.ListenAndServe() return server.ListenAndServe()
} }
+1 -4
View File
@@ -21,10 +21,7 @@ type HackerNews struct {
} }
func (widget *HackerNews) Initialize() error { func (widget *HackerNews) Initialize() error {
widget. widget.withTitle("Hacker News").withCacheDuration(30 * time.Minute)
withTitle("Hacker News").
withTitleURL("https://news.ycombinator.com/").
withCacheDuration(30 * time.Minute)
if widget.Limit <= 0 { if widget.Limit <= 0 {
widget.Limit = 15 widget.Limit = 15
+2 -10
View File
@@ -12,8 +12,6 @@ import (
type Lobsters struct { type Lobsters struct {
widgetBase `yaml:",inline"` widgetBase `yaml:",inline"`
Posts feed.ForumPosts `yaml:"-"` Posts feed.ForumPosts `yaml:"-"`
InstanceURL string `yaml:"instance-url"`
CustomURL string `yaml:"custom-url"`
Limit int `yaml:"limit"` Limit int `yaml:"limit"`
CollapseAfter int `yaml:"collapse-after"` CollapseAfter int `yaml:"collapse-after"`
SortBy string `yaml:"sort-by"` SortBy string `yaml:"sort-by"`
@@ -22,13 +20,7 @@ type Lobsters struct {
} }
func (widget *Lobsters) Initialize() error { func (widget *Lobsters) Initialize() error {
widget.withTitle("Lobsters").withCacheDuration(time.Hour) widget.withTitle("Lobsters").withCacheDuration(30 * time.Minute)
if widget.InstanceURL == "" {
widget.withTitleURL("https://lobste.rs")
} else {
widget.withTitleURL(widget.InstanceURL)
}
if widget.SortBy == "" || (widget.SortBy != "hot" && widget.SortBy != "new") { if widget.SortBy == "" || (widget.SortBy != "hot" && widget.SortBy != "new") {
widget.SortBy = "hot" widget.SortBy = "hot"
@@ -46,7 +38,7 @@ func (widget *Lobsters) Initialize() error {
} }
func (widget *Lobsters) Update(ctx context.Context) { func (widget *Lobsters) Update(ctx context.Context) {
posts, err := feed.FetchLobstersPosts(widget.CustomURL, widget.InstanceURL, widget.SortBy, widget.Tags) posts, err := feed.FetchLobstersPosts(widget.SortBy, widget.Tags)
if !widget.canContinueUpdateAfterHandlingErr(err) { if !widget.canContinueUpdateAfterHandlingErr(err) {
return return
+1 -4
View File
@@ -54,10 +54,7 @@ func (widget *Reddit) Initialize() error {
} }
} }
widget. widget.withTitle("/r/" + widget.Subreddit).withCacheDuration(30 * time.Minute)
withTitle("/r/" + widget.Subreddit).
withTitleURL("https://www.reddit.com/r/" + widget.Subreddit + "/").
withCacheDuration(30 * time.Minute)
return nil return nil
} }
+3 -2
View File
@@ -39,9 +39,10 @@ func (widget *RSS) Initialize() error {
widget.CardHeight = 0 widget.CardHeight = 0
} }
if widget.Style == "detailed-list" { if widget.Style != "detailed-list" {
for i := range widget.FeedRequests { for i := range widget.FeedRequests {
widget.FeedRequests[i].IsDetailed = true widget.FeedRequests[i].HideCategories = true
widget.FeedRequests[i].HideDescription = true
} }
} }
-2
View File
@@ -19,8 +19,6 @@ type Search struct {
cachedHTML template.HTML `yaml:"-"` cachedHTML template.HTML `yaml:"-"`
SearchEngine string `yaml:"search-engine"` SearchEngine string `yaml:"search-engine"`
Bangs []SearchBang `yaml:"bangs"` Bangs []SearchBang `yaml:"bangs"`
NewTab bool `yaml:"new-tab"`
Autofocus bool `yaml:"autofocus"`
} }
func convertSearchUrl(url string) string { func convertSearchUrl(url string) string {
+1 -4
View File
@@ -18,10 +18,7 @@ type TwitchChannels struct {
} }
func (widget *TwitchChannels) Initialize() error { func (widget *TwitchChannels) Initialize() error {
widget. widget.withTitle("Twitch Channels").withCacheDuration(time.Minute * 10)
withTitle("Twitch Channels").
withTitleURL("https://www.twitch.tv/directory/following").
withCacheDuration(time.Minute * 10)
if widget.CollapseAfter == 0 || widget.CollapseAfter < -1 { if widget.CollapseAfter == 0 || widget.CollapseAfter < -1 {
widget.CollapseAfter = 5 widget.CollapseAfter = 5
+1 -4
View File
@@ -18,10 +18,7 @@ type TwitchGames struct {
} }
func (widget *TwitchGames) Initialize() error { func (widget *TwitchGames) Initialize() error {
widget. widget.withTitle("Top games on Twitch").withCacheDuration(time.Minute * 10)
withTitle("Top games on Twitch").
withTitleURL("https://www.twitch.tv/directory?sort=VIEWER_COUNT").
withCacheDuration(time.Minute * 10)
if widget.Limit <= 0 { if widget.Limit <= 0 {
widget.Limit = 10 widget.Limit = 10
-10
View File
@@ -119,8 +119,6 @@ const (
type widgetBase struct { type widgetBase struct {
Type string `yaml:"type"` Type string `yaml:"type"`
Title string `yaml:"title"` Title string `yaml:"title"`
TitleURL string `yaml:"title-url"`
CSSClass string `yaml:"css-class"`
CustomCacheDuration DurationField `yaml:"cache"` CustomCacheDuration DurationField `yaml:"cache"`
ContentAvailable bool `yaml:"-"` ContentAvailable bool `yaml:"-"`
Error error `yaml:"-"` Error error `yaml:"-"`
@@ -187,14 +185,6 @@ func (w *widgetBase) withTitle(title string) *widgetBase {
return w return w
} }
func (w *widgetBase) withTitleURL(titleURL string) *widgetBase {
if w.TitleURL == "" {
w.TitleURL = titleURL
}
return w
}
func (w *widgetBase) withCacheDuration(duration time.Duration) *widgetBase { func (w *widgetBase) withCacheDuration(duration time.Duration) *widgetBase {
w.cacheType = cacheTypeDuration w.cacheType = cacheTypeDuration
-303
View File
@@ -1,303 +0,0 @@
package main
import (
"flag"
"fmt"
"os"
"os/exec"
"path"
"strings"
)
// bunch of spaget but it does the job for now
// TODO: tidy up and add a proper build system with CI
const buildPath = "./build"
const archivesPath = "./build/archives"
const executableName = "glance"
const ownerAndRepo = "glanceapp/glance"
const moduleName = "github.com/" + ownerAndRepo
type archiveType int
const (
archiveTypeTarGz archiveType = iota
archiveTypeZip
)
type buildInfo struct {
version string
}
type buildTarget struct {
os string
arch string
armV int
extension string
archive archiveType
}
var buildTargets = []buildTarget{
{
os: "windows",
arch: "amd64",
extension: ".exe",
archive: archiveTypeZip,
},
{
os: "windows",
arch: "arm64",
extension: ".exe",
archive: archiveTypeZip,
},
{
os: "linux",
arch: "amd64",
},
{
os: "linux",
arch: "arm64",
},
{
os: "linux",
arch: "arm",
armV: 6,
},
{
os: "linux",
arch: "arm",
armV: 7,
},
{
os: "openbsd",
arch: "amd64",
},
{
os: "openbsd",
arch: "386",
},
}
func hasUncommitedChanges() (bool, error) {
output, err := exec.Command("git", "status", "--porcelain").CombinedOutput()
if err != nil {
return false, err
}
return len(output) > 0, nil
}
func main() {
flags := flag.NewFlagSet("", flag.ExitOnError)
specificTag := flags.String("tag", "", "Which tagged version to build")
err := flags.Parse(os.Args[1:])
if err != nil {
fmt.Println(err)
os.Exit(1)
}
uncommitedChanges, err := hasUncommitedChanges()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if uncommitedChanges {
fmt.Println("There are uncommited changes - commit, stash or discard them first")
os.Exit(1)
}
cwd, err := os.Getwd()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
_, err = os.Stat(buildPath)
if err == nil {
fmt.Println("Cleaning up build path")
os.RemoveAll(buildPath)
}
os.Mkdir(buildPath, 0755)
os.Mkdir(archivesPath, 0755)
var version string
if *specificTag == "" {
version, err := getVersionFromGit()
if err != nil {
fmt.Println(version, err)
os.Exit(1)
}
} else {
version = *specificTag
}
output, err := exec.Command("git", "checkout", "tags/"+version).CombinedOutput()
if err != nil {
fmt.Println(string(output))
fmt.Println(err)
os.Exit(1)
}
info := buildInfo{
version: version,
}
for _, target := range buildTargets {
fmt.Printf("Building for %s/%s\n", target.os, target.arch)
if err := build(cwd, info, target); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
versionTag := fmt.Sprintf("%s:%s", ownerAndRepo, version)
latestTag := fmt.Sprintf("%s:latest", ownerAndRepo)
fmt.Println("Building docker image")
var dockerBuildOptions = []string{
"docker", "build",
"--platform=linux/amd64,linux/arm64,linux/arm/v7",
"-t", versionTag,
}
if !strings.Contains(version, "beta") {
dockerBuildOptions = append(dockerBuildOptions, "-t", latestTag)
}
dockerBuildOptions = append(dockerBuildOptions, ".")
output, err = exec.Command("sudo", dockerBuildOptions...).CombinedOutput()
if err != nil {
fmt.Println(string(output))
fmt.Println(err)
os.Exit(1)
}
var input string
fmt.Print("Push docker image? [y/n]: ")
fmt.Scanln(&input)
if input != "y" {
os.Exit(0)
}
output, err = exec.Command(
"sudo", "docker", "push", versionTag,
).CombinedOutput()
if err != nil {
fmt.Printf("Failed pushing %s:\n", versionTag)
fmt.Println(string(output))
fmt.Println(err)
os.Exit(1)
}
if strings.Contains(version, "beta") {
return
}
output, err = exec.Command(
"sudo", "docker", "push", latestTag,
).CombinedOutput()
if err != nil {
fmt.Printf("Failed pushing %s:\n", latestTag)
fmt.Println(string(output))
fmt.Println(err)
os.Exit(1)
}
}
func getVersionFromGit() (string, error) {
output, err := exec.Command("git", "describe", "--tags", "--abbrev=0").CombinedOutput()
if err == nil {
return strings.TrimSpace(string(output)), err
}
return string(output), err
}
func archiveFile(name string, target string, t archiveType) error {
var output []byte
var err error
if t == archiveTypeZip {
output, err = exec.Command("zip", "-j", path.Join(archivesPath, name+".zip"), target).CombinedOutput()
} else if t == archiveTypeTarGz {
output, err = exec.Command("tar", "-C", buildPath, "-czf", path.Join(archivesPath, name+".tar.gz"), name).CombinedOutput()
}
if err != nil {
fmt.Println(string(output))
return err
}
return nil
}
func build(workingDir string, info buildInfo, target buildTarget) error {
var name string
if target.arch != "arm" {
name = fmt.Sprintf("%s-%s-%s%s", executableName, target.os, target.arch, target.extension)
} else {
name = fmt.Sprintf("%s-%s-%sv%d", executableName, target.os, target.arch, target.armV)
}
binaryPath := path.Join(buildPath, name)
glancePackage := moduleName + "/internal/glance"
flags := "-s -w"
flags += fmt.Sprintf(" -X %s.buildVersion=%s", glancePackage, info.version)
cmd := exec.Command(
"go",
"build",
"--trimpath",
"--ldflags",
flags,
"-o",
binaryPath,
)
cmd.Dir = workingDir
env := append(os.Environ(), "GOOS="+target.os, "GOARCH="+target.arch, "CGO_ENABLED=0")
if target.arch == "arm" {
env = append(env, fmt.Sprintf("GOARM=%d", target.armV))
}
cmd.Env = env
output, err := cmd.CombinedOutput()
if err != nil {
fmt.Println(err)
fmt.Println(string(output))
return err
}
os.Chmod(binaryPath, 0755)
fmt.Println("Creating archive")
if err := archiveFile(name, binaryPath, target.archive); err != nil {
return err
}
return nil
}