Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions projects/start-os/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ file tracks notable changes since the move to the monorepo.
plaintext address, including the passwords typed into it. See
[Gateways](https://docs.start9.com/start-os/gateways.html).

- **An action result that hands you a link can be opened in a new tab.** Where a
service returns a URL — an authorization link, an admin panel — the result
shows an open-in-new-tab button beside it.

### Changed

- **Your server's name is now its `.local` address, without the `.local` on the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ import { QRComponent } from 'src/app/routes/portal/components/qr.component'
{{ 'Copy' | i18n }}
</button>
}
@if (member.launchable) {
<a
tuiIconButton
appearance="icon"
size="s"
target="_blank"
rel="noreferrer"
tabindex="-1"
iconStart="@tui.external-link"
[href]="member.value"
[style.pointer-events]="'auto'"
>
{{ 'Open' | i18n }}
</a>
}
@if (member.qr) {
<button
tuiIconButton
Expand Down Expand Up @@ -108,7 +123,7 @@ export class ActionSuccessMemberComponent {
show(template: TemplateRef<any>) {
const masked = this.masked

this.masked = this.member.masked
this.masked = !!this.member.masked
this.dialog
.openComponent(template, { label: 'Scan this QR', size: 's' })
.subscribe({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,21 @@ import { SingleResult } from './types'
{{ 'Copy' | i18n }}
</button>
}
@if (single.launchable) {
<a
tuiIconButton
appearance="icon"
size="s"
target="_blank"
rel="noreferrer"
tabindex="-1"
iconStart="@tui.external-link"
[href]="single.value"
[style.pointer-events]="'auto'"
>
{{ 'Open' | i18n }}
</a>
}
</tui-textfield>
<ng-template #qr>
<app-qr
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1641,6 +1641,7 @@ Full changelog: https://github.com/Kixunil/btc-rpc-proxy/blob/master/CHANGELOG.m
copyable: false,
qr: true,
masked: false,
launchable: true,
value: 'https://guessagain.com',
},
],
Expand Down
6 changes: 6 additions & 0 deletions projects/start-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@
accept `default: null`**, which renders the field unselected and holds the form
unsubmittable until the user picks one

- **`launchable` on a `single` action result opens the value in a new tab**, for
a result that hands the user a link — an authorization URL, an admin panel.
The value must be an `http(s)` URL. `copyable`, `qr` and `masked` are optional
alongside it and default to `false`. See
[Single Value](https://docs.start9.com/packaging/actions.html#single-value)

### Fixed

- **Scaffolded package CI builds a draft PR when it becomes ready and rebuilds
Expand Down
20 changes: 17 additions & 3 deletions projects/start-sdk/docs/src/actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ Actions return structured results that the StartOS UI renders for the user.

`message` is prose shown under the title. It is rendered as **Markdown** — headings, lists, tables, code blocks, emphasis and links all work — and a single newline is kept as a line break, so text written as plain lines arrives as plain lines. Anything with its own line structure goes here.

`result` holds discrete values the user acts on: each one is a single-line field with optional copy, QR and masking, so a newline in a `value` is not rendered. A multi-line report goes in `message`, not in a `value`.
`result` holds discrete values the user acts on: each one is a single-line field with optional copy, QR, masking and link-opening, so a newline in a `value` is not rendered. A multi-line report goes in `message`, not in a `value`.

```typescript
return {
Expand All @@ -117,15 +117,29 @@ Restart the service once the rebuild finishes.`,
```typescript
result: {
type: 'single',
name: 'API Key',
description: null,
value: 'abc123',
masked: true,
copyable: true,
qr: false,
}
```

`copyable`, `qr`, `masked` and `launchable` are all optional and default to
`false`. `launchable` puts an open-in-new-tab button beside the value, so a
result that hands the user a link — an authorization URL, an admin panel — can
be clicked straight through:

```typescript
result: {
type: 'single',
value: 'https://btcpay.example.com/api-keys/authorize?permissions=btcpay.store.cancreateinvoice',
copyable: true,
launchable: true,
}
```

The value must be an `http(s)` URL for the button to go anywhere.

### Group of Values

```typescript
Expand Down
27 changes: 17 additions & 10 deletions shared-libs/crates/start-core/src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,10 @@ impl ActionResult {
message: Some(message),
result: value.map(|value| ActionResultValue::Single {
value,
copyable,
qr,
masked: false,
copyable: Some(copyable),
qr: Some(qr),
masked: None,
launchable: None,
}),
}),
Self::V1(a) => Self::V1(a),
Expand Down Expand Up @@ -192,12 +193,18 @@ pub enum ActionResultValue {
/// The actual string value to display. The UI renders it as a single-line field —
/// multi-line text belongs in the result's `message`.
value: String,
/// Whether or not to include a copy to clipboard icon to copy the value
copyable: bool,
/// Whether or not to also display the value as a QR code
qr: bool,
/// Whether or not to mask the value using ●●●●●●●, which is useful for password or other sensitive information
masked: bool,
/// (optional) Whether or not to include a copy to clipboard icon to copy the value
#[ts(optional)]
copyable: Option<bool>,
/// (optional) Whether or not to also display the value as a QR code
#[ts(optional)]
qr: Option<bool>,
/// (optional) Whether or not to mask the value using ●●●●●●●, which is useful for password or other sensitive information
#[ts(optional)]
masked: Option<bool>,
/// (optional) Whether or not to include an open in new tab icon to launch the value, which must be an http(s) URL
#[ts(optional)]
launchable: Option<bool>,
},
Group {
/// An new group of nested values, experienced by the user as an accordion dropdown
Expand All @@ -212,7 +219,7 @@ impl ActionResultValue {
write!(f, " ")?;
}
write!(f, "{value}")?;
if *qr {
if qr.unwrap_or_default() {
use qrcode::render::unicode;
writeln!(f)?;
for _ in 0..indent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,21 @@ export type ActionResultMember = {
*/
value: string
/**
* Whether or not to include a copy to clipboard icon to copy the value
* (optional) Whether or not to include a copy to clipboard icon to copy the value
*/
copyable: boolean
copyable?: boolean
/**
* Whether or not to also display the value as a QR code
* (optional) Whether or not to also display the value as a QR code
*/
qr: boolean
qr?: boolean
/**
* Whether or not to mask the value using ●●●●●●●, which is useful for password or other sensitive information
* (optional) Whether or not to mask the value using ●●●●●●●, which is useful for password or other sensitive information
*/
masked: boolean
masked?: boolean
/**
* (optional) Whether or not to include an open in new tab icon to launch the value, which must be an http(s) URL
*/
launchable?: boolean
}
| {
type: 'group'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,21 @@ export type ActionResultValue =
*/
value: string
/**
* Whether or not to include a copy to clipboard icon to copy the value
* (optional) Whether or not to include a copy to clipboard icon to copy the value
*/
copyable: boolean
copyable?: boolean
/**
* Whether or not to also display the value as a QR code
* (optional) Whether or not to also display the value as a QR code
*/
qr: boolean
qr?: boolean
/**
* Whether or not to mask the value using ●●●●●●●, which is useful for password or other sensitive information
* (optional) Whether or not to mask the value using ●●●●●●●, which is useful for password or other sensitive information
*/
masked: boolean
masked?: boolean
/**
* (optional) Whether or not to include an open in new tab icon to launch the value, which must be an http(s) URL
*/
launchable?: boolean
}
| {
type: 'group'
Expand Down