Allow to config the external client

This commit is contained in:
dtonon 2026-09-24 16:24:29 +02:00
parent 58e81d1dba
commit 5f8bf2fe7e
6 changed files with 28 additions and 8 deletions

View file

@ -14,6 +14,7 @@ PUBLIC_SEARCH=no # yes | no — enable the search box (req
PUBLIC_LABELS= # comma-separated discussion labels (e.g., bug,feature,question) PUBLIC_LABELS= # comma-separated discussion labels (e.g., bug,feature,question)
PUBLIC_NOSTRCONNECT_RELAYS= # comma-separated relays for nostrconnect:// (bunker) login; defaults to bucket.coracle.social,relay.primal.net PUBLIC_NOSTRCONNECT_RELAYS= # comma-separated relays for nostrconnect:// (bunker) login; defaults to bucket.coracle.social,relay.primal.net
PUBLIC_BLOSSOM_URL= # Blossom server URL for uploads (e.g., https://blossom.primal.net) PUBLIC_BLOSSOM_URL= # Blossom server URL for uploads (e.g., https://blossom.primal.net)
PUBLIC_EXTERNAL_CLIENT= # external client for profiles/events, {e} is the bech32 entity; default https://njump.me/{e}
# Theme color overrides — quote the value, a leading # is read as a comment otherwise # Theme color overrides — quote the value, a leading # is read as a comment otherwise
PUBLIC_ACCENT_COLOR= # override the accent color (default #e32a6d), e.g. "#00ff00"; hover shade derived PUBLIC_ACCENT_COLOR= # override the accent color (default #e32a6d), e.g. "#00ff00"; hover shade derived
PUBLIC_SECONDARY_COLOR= # override the secondary color (default #ffaf25), e.g. "#0000ff"; hover shade derived PUBLIC_SECONDARY_COLOR= # override the secondary color (default #ffaf25), e.g. "#0000ff"; hover shade derived

View file

@ -34,6 +34,7 @@ Squalk is configured entirely through environment variables (all prefixed `PUBLI
| `PUBLIC_SEARCH` | no | `no` | `yes` to show a search box at the top of the homepage. Requires a relay with NIP-50 search support. | | `PUBLIC_SEARCH` | no | `no` | `yes` to show a search box at the top of the homepage. Requires a relay with NIP-50 search support. |
| `PUBLIC_LABELS` | no | — | Comma-separated discussion labels offered when composing, e.g. `bug,feature,question`. | | `PUBLIC_LABELS` | no | — | Comma-separated discussion labels offered when composing, e.g. `bug,feature,question`. |
| `PUBLIC_BLOSSOM_URL` | no | — | Blossom server URL used for media uploads, e.g. `https://blossom.primal.net`. Uploads are disabled when unset. | | `PUBLIC_BLOSSOM_URL` | no | — | Blossom server URL used for media uploads, e.g. `https://blossom.primal.net`. Uploads are disabled when unset. |
| `PUBLIC_EXTERNAL_CLIENT` | no | `https://njump.me/{e}` | URL template of the external Nostr client used to open profiles and events outside the forum. `{e}` is replaced with the bech32 entity (npub, nevent, naddr), e.g. `https://nostr.at/{e}`. |
| `PUBLIC_ACCENT_COLOR` | no | `#e32a6d` | Override the accent (primary) color. Quote the value (`"#00ff00"`) — an unquoted leading `#` is read as a comment. The hover shade is derived automatically. | | `PUBLIC_ACCENT_COLOR` | no | `#e32a6d` | Override the accent (primary) color. Quote the value (`"#00ff00"`) — an unquoted leading `#` is read as a comment. The hover shade is derived automatically. |
| `PUBLIC_SECONDARY_COLOR` | no | `#ffaf25` | Override the secondary color. Same quoting rule and derived hover shade as above. | | `PUBLIC_SECONDARY_COLOR` | no | `#ffaf25` | Override the secondary color. Same quoting rule and derived hover shade as above. |

View file

@ -2,6 +2,7 @@
import { loadNostrUser, type NostrUser } from "$lib/gadgets"; import { loadNostrUser, type NostrUser } from "$lib/gadgets";
import * as nip19 from "@nostr/tools/nip19"; import * as nip19 from "@nostr/tools/nip19";
import { profilePath } from "$lib/forum/profiles"; import { profilePath } from "$lib/forum/profiles";
import { externalLink } from "$lib/config";
import { import {
resolveThreadRef, resolveThreadRef,
threadRefHref, threadRefHref,
@ -311,7 +312,7 @@
const entity = dest.slice(6).toLowerCase(); const entity = dest.slice(6).toLowerCase();
try { try {
nip19.decode(entity); nip19.decode(entity);
return { type: "link", href: `https://njump.me/${entity}`, label }; return { type: "link", href: externalLink(entity), label };
} catch { } catch {
return { type: "text", value: label }; return { type: "text", value: label };
} }
@ -642,7 +643,7 @@
> >
{:else} {:else}
<a <a
href="https://njump.me/{inline.entity}" href={externalLink(inline.entity)}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="text-accent break-all hover:underline">{inline.label}</a class="text-accent break-all hover:underline">{inline.label}</a

View file

@ -73,3 +73,20 @@ export const FAVICON =
encodeURIComponent( encodeURIComponent(
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill-rule="evenodd" fill="${ACCENT_COLOR || DEFAULT_ACCENT}" d="M16 0a16 16 0 1 0 0 32a16 16 0 1 0 0-32zM16 8a8 8 0 1 1 0 16a8 8 0 1 1 0-16z"/></svg>`, `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill-rule="evenodd" fill="${ACCENT_COLOR || DEFAULT_ACCENT}" d="M16 0a16 16 0 1 0 0 32a16 16 0 1 0 0-32zM16 8a8 8 0 1 1 0 16a8 8 0 1 1 0-16z"/></svg>`,
); );
// External Nostr client used to open events and profiles that live outside
// the forum. {e} is replaced with the bech32 entity (npub, nevent, naddr...).
const DEFAULT_EXTERNAL_CLIENT = "https://njump.me/{e}";
export const EXTERNAL_CLIENT = env.PUBLIC_EXTERNAL_CLIENT?.includes("{e}")
? env.PUBLIC_EXTERNAL_CLIENT
: DEFAULT_EXTERNAL_CLIENT;
export const EXTERNAL_CLIENT_NAME = (() => {
try {
return new URL(EXTERNAL_CLIENT.replace("{e}", "x")).hostname;
} catch {
return "external client";
}
})();
export function externalLink(entity: string): string {
return EXTERNAL_CLIENT.replace("{e}", entity);
}

View file

@ -1,5 +1,5 @@
import * as nip19 from "@nostr/tools/nip19"; import * as nip19 from "@nostr/tools/nip19";
import { RELAY_URL } from "./config"; import { RELAY_URL, externalLink } from "./config";
// Curated TLD list: gTLDs, popular new gTLDs, common ccTLDs // Curated TLD list: gTLDs, popular new gTLDs, common ccTLDs
const TLDS = [ const TLDS = [
@ -157,7 +157,7 @@ function decodeEntity(entity: string): ChatToken | null {
return { return {
type: "entity", type: "entity",
entity, entity,
href: `https://njump.me/${entity}`, href: externalLink(entity),
label: shortNostrEntity(entity), label: shortNostrEntity(entity),
id: decoded.type === "note" ? decoded.data : decoded.data.id, id: decoded.type === "note" ? decoded.data : decoded.data.id,
}; };
@ -165,7 +165,7 @@ function decodeEntity(entity: string): ChatToken | null {
return { return {
type: "entity", type: "entity",
entity, entity,
href: `https://njump.me/${entity}`, href: externalLink(entity),
label: shortNostrEntity(entity), label: shortNostrEntity(entity),
}; };
} catch { } catch {

View file

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { page } from "$app/state"; import { page } from "$app/state";
import { MODE } from "$lib/config"; import { MODE, EXTERNAL_CLIENT_NAME, externalLink } from "$lib/config";
import { getGroupName } from "$lib/group.svelte"; import { getGroupName } from "$lib/group.svelte";
import { groupsStore } from "$lib/groups.svelte"; import { groupsStore } from "$lib/groups.svelte";
import { import {
@ -235,12 +235,12 @@
</span> </span>
{/if} {/if}
<a <a
href="https://njump.me/{npub}" href={externalLink(npub)}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="text-accent text-sm hover:underline" class="text-accent text-sm hover:underline"
> >
View on njump ↗ View on {EXTERNAL_CLIENT_NAME} ↗
</a> </a>
</div> </div>