v0.7.8
glanceapp/glancev0.7.8Mar 26, 2025by svilenmarkov
AI Summary
A comprehensive update to the Custom API widget based on community feedback, adding support for insecure requests, parameters, and complex template functions.
Key Highlights
- Added `allow-insecure` property for APIs behind self-signed certificates.
- Added `parameters` property for specifying query parameters.
- Added `method`, `body-type`, and `body` properties for request configuration.
- Added `subrequests` property to make multiple API calls in one widget.
- Added numerous template functions including `trimPrefix`, `parseTime`, `toRelativeTime`, and sort functions.
New Features
- allow-insecure property
- parameters property
- method and body properties
- subrequests
- trimPrefix function
- trimSuffix function
- trimSpace function
- replaceAll function
- findMatch function
- findSubmatch function
- parseTime function
- toRelativeTime function
- parseRelativeTime function
- sortByString function
- sortByInt function
- sortByFloat function
- sortByTime function
- headers property for extension widget
Full Release Notes
The `custom-api` widget has received a lot of positive feedback since its introduction in v0.7.0. Many people have made and shared their own widgets over at the new [`community-widgets`](https://github.com/glanceapp/community-widgets) repository as well as the Discord server. This release includes many improvements based on feedback from the community that will make it even more capable and easier to use.
## New
### Insecure requests
You can now allow insecure requests (those to APIs behind a self-signed certificate) via a `allow-insecure` property:
```yaml
- type: custom-api
url: https://api.example.com
allow-insecure: true
```
(thanks @ralphocdol)
### Parameters
You can now specify query parameters via a `parameters` property:
```yaml
- type: custom-api
url: https://api.example.com
parameters:
foo: bar
baz: qux
```
> [!NOTE]
>
> Using the parameters property will override any query parameters specified in the URL.
(thanks @ralphocdol)
### Request method and body
You can now specify the request method and body via the `method`, `body-type` and `body` properties:
```yaml
- type: custom-api
url: https://api.example.com
method: POST
body-type: json
body:
foo: bar
baz: qux
```
If you set a `body`, the `method` will automatically be set to `POST` and the `body-type` will be set to `json`, so you don't have to specify them explicitly.
```yaml
- type: custom-api
url: https://api.example.com
body-type: string
body: |
foo=bar&baz=qux
```
(thanks @not-first)
### Subrequests
You can now make multiple requests in a single `custom-api` widget via a `subrequests` property:
```yaml
- type: custom-api
url: https://api.example.com
subrequests:
another-one:
url: https://api.example.com/another-one
and-another-one:
url: https://api.example.com/and-another-one
```
Subrequests can take all of the same properties as the main request, such as: `parameters`, `headers`, `method`, `body-type`, `body` and `allow-insecure`.
To access the JSON of a subrequest, you can use `.Subrequest "key"`:
```html
<p>{{ (.Subrequest "another-one").JSON.String "foo" }}</p>
```
This can get cumbersome to write if you need to reference the subrequest in multiple places, so you can instead assign it to a variable:
```html
{{ $anotherOne := .Subrequest "another-one" }}
<p>{{ $anotherOne.JSON.String "foo" }}</p>
<p>{{ $anotherOne.JSON.Int "bar" }}</p>
```
You can also access the response as you would on the main request:
```html
{{ $andAnotherOne := .Subrequest "and-another-one" }}
<p>{{ $andAnotherOne.Response.StatusCode }}</p>
```
(thanks @ralphocdol)
### New functions
#### `trimPrefix`
```json
{"foo": "bazbar"}
```
```html
<p>{{ .JSON.String "foo" | trimPrefix "baz" }}</p>
```
```html
<p>bar</p>
```
#### `trimSuffix`
```json
{"foo": "barbaz"}
```
```html
<p>{{ .JSON.String "foo" | trimSuffix "baz" }}</p>
```
```html
<p>bar</p>
```
#### `trimSpace`
```json
{"foo": " bar "}
```
```html
<p>{{ .JSON.String "foo" | trimSpace }}</p>
```
```html
<p>bar</p>
```
#### `replaceAll`
```json
{"foo": "barbazbar"}
```
```html
<p>{{ .JSON.String "foo" | replaceAll "baz" "bar" }}</p>
```
```html
<p>barbarbar</p>
```
#### `findMatch`
```json
{"foo": "bar-123456-baz"}
```
```html
<p>{{ .JSON.String "foo" | findMatch "\\d+" }}</p>
```
The pattern is a regular expression, although note that backslashes need to be escaped, so `\d` in a normal regular expression would be `\\d` here.
```html
<p>123456</p>
```
#### `findSubmatch`
```json
{"foo": "bar-unknown-value"}
```
```html
<p>{{ .JSON.String "foo" | findSubmatch "bar-(.*)" }}</p>
```
The pattern is a regular expression, and only the first submatch is returned.
```html
<p>unknown-value</p>
```
#### `parseTime`
```json
{"foo": "2021-01-02T15:04:05Z"}
```
```html
{{ $parsedTime := .JSON.String "foo" | parseTime "rfc3339" }}
<p>Year: {{ $parsedTime.Year }}</p>
<p>Month: {{ $parsedTime.Month }}</p>
<p>Day: {{ $parsedTime.Day }}</p>
<p>Hour: {{ $parsedTime.Hour }}</p>
<p>Minute: {{ $parsedTime.Minute }}</p>
<p>Second: {{ $parsedTime.Second }}</p>
```
Other accepted time formats are `unix`, `rfc3339nano`, `datetime`, `dateonly` or a custom format using Go's [date formatting](https://pkg.go.dev/time#pkg-constants). The returned object is Go's [`time.Time`](https://pkg.go.dev/time#Time).
```html
<p>Year: 2021</p>
<p>Month: January</p>
<p>Day: 2</p>
<p>Hour: 15</p>
<p>Minute: 4</p>
<p>Second: 5</p>
```
#### `toRelativeTime`
```json
{"foo": "2021-01-02T15:04:05Z"}
```
```html
<p {{ .JSON.String "foo" | parseTime "rfc3339" | toRelativeTime }}></p>
```
> [!NOTE]
>
> The return value of this function must be placed within a tag's attributes, not within its content.
```html
<p data-dynamic-relative-time="1609602245"></p>
```
This will automatically be converted to `1d`, `2h`, etc. on the client side.
#### `parseRelativeTime`
This is just a shorthand for parsing time and converting it to relative time. Instead of:
```html
<p {{ .JSON.String "foo" | parseTime "rfc3339" | toRelativeTime }}></p>
```
You can simply do:
```html
<p {{ .JSON.String "foo" | parseRelativeTime "rfc3339" }}></p>
```
#### `sortByString`, `sortByInt`, `sortByFloat`, `sortByTime`
```json
{"students": [
{"name": "Bob", "age": 20},
{"name": "Alice", "age": 30},
{"name": "Charlie", "age": 10}
]}
```
```html
{{ range .JSON.Array "students" | sortByString "name" "asc" }}
<p>{{ .String "name" }}</p>
{{ end }}
```
```html
<p>Alice</p>
<p>Bob</p>
<p>Charlie</p>
```
<hr>
```html
{{ range .JSON.Array "students" | sortByInt "age" "desc" }}
<p>{{ .Int "age" }}</p>
{{ end }}
```
```html
<p>30</p>
<p>20</p>
<p>10</p>
```
<hr>
```json
{"students": [
{"name": "Bob", "gpa": 3.5},
{"name": "Alice", "gpa": 4.0},
{"name": "Charlie", "gpa": 2.0}
]}
```
```html
{{ range .JSON.Array "students" | sortByFloat "gpa" "asc" }}
<p>{{ .Float "gpa" }}</p>
{{ end }}
```
```html
<p>2</p>
<p>3.5</p>
<p>4</p>
```
<hr>
```json
{"students": [
{"name": "Bob", "dob": "2000-01-01"},
{"name": "Alice", "dob": "1990-01-01"},
{"name": "Charlie", "dob": "2010-01-01"}
]}
```
```html
{{ range .JSON.Array "students" | sortByTime "dob" "dateonly" "asc" }}
<p>{{ .String "name" }}</p>
{{ end }}
```
Here, `dateonly` is the same format that you would use with `parseTime`.
```html
<p>Alice</p>
<p>Bob</p>
<p>Charlie</p>
```
## Other additions
### Extension widget `headers` property
You can now specify headers in the `extension` widget:
```yaml
- type: extension
headers:
Authorization: Bearer token
```
## Fixes
* Fixed being unable to parse an empty response body in the `custom-api` widget
* Fixed always overriding query parameters in the `extension` widget, they will now only be overridden if the `parameters` property is set