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