From e66d56dd9337e0ee074bef76d78fbf379f46786a Mon Sep 17 00:00:00 2001 From: Markus Stark Date: Tue, 16 Jun 2026 17:29:23 +0200 Subject: [PATCH 1/9] Add OpenSearch description --- public/opensearch.xml | 8 ++++++++ src/index.html | 1 + 2 files changed, 9 insertions(+) create mode 100644 public/opensearch.xml diff --git a/public/opensearch.xml b/public/opensearch.xml new file mode 100644 index 00000000..53388b47 --- /dev/null +++ b/public/opensearch.xml @@ -0,0 +1,8 @@ + + + NüschtOS Search + Simple and fast static-page NixOS option and packages search + UTF-8 + favicon.ico + + diff --git a/src/index.html b/src/index.html index a5a0e480..4182d2a4 100644 --- a/src/index.html +++ b/src/index.html @@ -7,6 +7,7 @@ + From f29ba6686bb2707e545904600970f89f9e55f70b Mon Sep 17 00:00:00 2001 From: Markus Stark Date: Tue, 16 Jun 2026 22:33:34 +0200 Subject: [PATCH 2/9] Split OpenSearch descriptions Signed-off-by: Markus Stark --- public/{opensearch.xml => opensearch-options.xml} | 6 +++--- public/opensearch-packages.xml | 8 ++++++++ src/index.html | 3 ++- 3 files changed, 13 insertions(+), 4 deletions(-) rename public/{opensearch.xml => opensearch-options.xml} (55%) create mode 100644 public/opensearch-packages.xml diff --git a/public/opensearch.xml b/public/opensearch-options.xml similarity index 55% rename from public/opensearch.xml rename to public/opensearch-options.xml index 53388b47..300afa39 100644 --- a/public/opensearch.xml +++ b/public/opensearch-options.xml @@ -1,8 +1,8 @@ - NüschtOS Search - Simple and fast static-page NixOS option and packages search + NüschtOS Options Search + Simple and fast static-page NixOS options search UTF-8 favicon.ico - + diff --git a/public/opensearch-packages.xml b/public/opensearch-packages.xml new file mode 100644 index 00000000..8ba48668 --- /dev/null +++ b/public/opensearch-packages.xml @@ -0,0 +1,8 @@ + + + NüschtOS Packages Search + Simple and fast static-page NixOS packages search + UTF-8 + favicon.ico + + diff --git a/src/index.html b/src/index.html index 4182d2a4..86ad3be7 100644 --- a/src/index.html +++ b/src/index.html @@ -7,7 +7,8 @@ - + + From 9915c6821e3ac83b246a687542377985e5a9ca3d Mon Sep 17 00:00:00 2001 From: Markus Stark Date: Wed, 17 Jun 2026 22:22:38 +0200 Subject: [PATCH 3/9] Follow up on OpenSearch feedback --- nix/frontend.nix | 2 ++ public/opensearch-options.xml | 4 ++-- public/opensearch-packages.xml | 4 ++-- src/app/app.component.ts | 39 ++++++++++++++++++++++++++++++++-- src/index.html | 2 -- 5 files changed, 43 insertions(+), 8 deletions(-) diff --git a/nix/frontend.nix b/nix/frontend.nix index c7bf7a87..8aa6885f 100644 --- a/nix/frontend.nix +++ b/nix/frontend.nix @@ -26,6 +26,8 @@ stdenv.mkDerivation (finalAttrs: { postPatch = '' substituteInPlace src/index.html \ --replace-fail '##TITLE##' ${lib.escapeShellArg config.title} + substituteInPlace public/opensearch-options.xml public/opensearch-packages.xml \ + --replace-fail '##TITLE##' ${lib.escapeShellArg config.title} # remove development files rm -rf public/data diff --git a/public/opensearch-options.xml b/public/opensearch-options.xml index 300afa39..6e1b3335 100644 --- a/public/opensearch-options.xml +++ b/public/opensearch-options.xml @@ -1,7 +1,7 @@ - NüschtOS Options Search - Simple and fast static-page NixOS options search + ##TITLE## - Options + ##TITLE## options search UTF-8 favicon.ico diff --git a/public/opensearch-packages.xml b/public/opensearch-packages.xml index 8ba48668..c6b8dbea 100644 --- a/public/opensearch-packages.xml +++ b/public/opensearch-packages.xml @@ -1,7 +1,7 @@ - NüschtOS Packages Search - Simple and fast static-page NixOS packages search + ##TITLE## - Packages + ##TITLE## packages search UTF-8 favicon.ico diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 8bc25d82..0c614aec 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,6 +1,9 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { RouterLink, RouterOutlet } from '@angular/router'; +import { ChangeDetectionStrategy, Component, DestroyRef, inject } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; +import { NavigationEnd, Router, RouterLink, RouterOutlet } from '@angular/router'; import { CONFIG } from './core/config.domain'; +import { filter } from 'rxjs'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Component({ selector: 'app-root', @@ -16,4 +19,36 @@ export class AppComponent { protected readonly OPTIONS_ENABLED = CONFIG.scopes.findIndex(scope => scope.optionsEnabled) != -1; protected readonly PACKAGES_ENABLED = CONFIG.scopes.findIndex(scope => scope.packagesEnabled) != -1; + private readonly router = inject(Router); + private readonly document = inject(DOCUMENT); + private readonly destroyRef = inject(DestroyRef); + + constructor() { + this.syncSearchLink(this.router.url); + + this.router.events + .pipe( + filter((event): event is NavigationEnd => event instanceof NavigationEnd), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe(() => this.syncSearchLink(this.router.url)); + } + + private syncSearchLink(url: string): void { + const type = url.startsWith('/packages') ? 'packages' : url.startsWith('/options') ? 'options' : null; + const existing = this.document.head.querySelector('link[data-nuschtos-opensearch]'); + + if (!type) { + existing?.remove(); + return; + } + + const link = existing ?? this.document.head.appendChild(this.document.createElement('link')); + link.rel = 'search'; + link.type = 'application/opensearchdescription+xml'; + link.dataset.nuschtosOpensearch = 'true'; + link.title = `${CONFIG.title} ${type === 'options' ? 'Options' : 'Packages'} Search`; + link.href = `${CONFIG.baseHref}opensearch-${type}.xml`; + } + } diff --git a/src/index.html b/src/index.html index 86ad3be7..a5a0e480 100644 --- a/src/index.html +++ b/src/index.html @@ -7,8 +7,6 @@ - - From 55614db9686ca84961f0bf703a27636b373c7645 Mon Sep 17 00:00:00 2001 From: Markus Stark Date: Thu, 18 Jun 2026 12:53:06 +0200 Subject: [PATCH 4/9] Move OpenSearch link handling into pages --- src/app/app.component.ts | 39 +------------------ src/app/core/opensearch-link.ts | 18 +++++++++ .../pages/options/options-page.component.ts | 8 +++- .../pages/packages/packages-page.component.ts | 5 +++ 4 files changed, 31 insertions(+), 39 deletions(-) create mode 100644 src/app/core/opensearch-link.ts diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 0c614aec..8bc25d82 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,9 +1,6 @@ -import { ChangeDetectionStrategy, Component, DestroyRef, inject } from '@angular/core'; -import { DOCUMENT } from '@angular/common'; -import { NavigationEnd, Router, RouterLink, RouterOutlet } from '@angular/router'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { RouterLink, RouterOutlet } from '@angular/router'; import { CONFIG } from './core/config.domain'; -import { filter } from 'rxjs'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Component({ selector: 'app-root', @@ -19,36 +16,4 @@ export class AppComponent { protected readonly OPTIONS_ENABLED = CONFIG.scopes.findIndex(scope => scope.optionsEnabled) != -1; protected readonly PACKAGES_ENABLED = CONFIG.scopes.findIndex(scope => scope.packagesEnabled) != -1; - private readonly router = inject(Router); - private readonly document = inject(DOCUMENT); - private readonly destroyRef = inject(DestroyRef); - - constructor() { - this.syncSearchLink(this.router.url); - - this.router.events - .pipe( - filter((event): event is NavigationEnd => event instanceof NavigationEnd), - takeUntilDestroyed(this.destroyRef), - ) - .subscribe(() => this.syncSearchLink(this.router.url)); - } - - private syncSearchLink(url: string): void { - const type = url.startsWith('/packages') ? 'packages' : url.startsWith('/options') ? 'options' : null; - const existing = this.document.head.querySelector('link[data-nuschtos-opensearch]'); - - if (!type) { - existing?.remove(); - return; - } - - const link = existing ?? this.document.head.appendChild(this.document.createElement('link')); - link.rel = 'search'; - link.type = 'application/opensearchdescription+xml'; - link.dataset.nuschtosOpensearch = 'true'; - link.title = `${CONFIG.title} ${type === 'options' ? 'Options' : 'Packages'} Search`; - link.href = `${CONFIG.baseHref}opensearch-${type}.xml`; - } - } diff --git a/src/app/core/opensearch-link.ts b/src/app/core/opensearch-link.ts new file mode 100644 index 00000000..93f99901 --- /dev/null +++ b/src/app/core/opensearch-link.ts @@ -0,0 +1,18 @@ +import { CONFIG } from './config.domain'; + +export type OpenSearchType = 'options' | 'packages'; + +export function setOpenSearchLink(document: Document, type: OpenSearchType): void { + const existing = document.head.querySelector('link[data-nuschtos-opensearch]'); + const link = existing ?? document.head.appendChild(document.createElement('link')); + + link.rel = 'search'; + link.type = 'application/opensearchdescription+xml'; + link.dataset.nuschtosOpensearch = 'true'; + link.title = `${CONFIG.title} ${type === 'options' ? 'Options' : 'Packages'} Search`; + link.href = `${CONFIG.baseHref}opensearch-${type}.xml`; +} + +export function clearOpenSearchLink(document: Document): void { + document.head.querySelector('link[data-nuschtos-opensearch]')?.remove(); +} diff --git a/src/app/pages/options/options-page.component.ts b/src/app/pages/options/options-page.component.ts index d7931074..0c821ed8 100644 --- a/src/app/pages/options/options-page.component.ts +++ b/src/app/pages/options/options-page.component.ts @@ -1,11 +1,12 @@ import { ChangeDetectionStrategy, Component, Inject, LOCALE_ID, OnDestroy } from '@angular/core'; -import { CONFIG } from '../../core/config.domain'; +import { DOCUMENT } from '@angular/common'; import { OptionComponent } from '../../core/components/option/option.component'; import { OptionsService } from '../../core/data/options.service'; import { OptionsSearchComponent } from '../../core/components/search/search.component'; import { RouterLink } from '@angular/router'; -import { AsyncPipe, DecimalPipe, formatNumber } from '@angular/common'; +import { AsyncPipe, formatNumber } from '@angular/common'; import { BehaviorSubject, Subject, takeUntil } from 'rxjs'; +import { clearOpenSearchLink, setOpenSearchLink } from '../../core/opensearch-link'; @Component({ selector: 'app-options', @@ -27,8 +28,10 @@ export class OptionsPageComponent implements OnDestroy { constructor( protected readonly searchService: OptionsService, + @Inject(DOCUMENT) private readonly document: Document, @Inject(LOCALE_ID) private readonly locale: string ) { + setOpenSearchLink(this.document, 'options'); this.searchService.getIndexSize() .pipe(takeUntil(this.destroy$)) .subscribe(size => { @@ -36,6 +39,7 @@ export class OptionsPageComponent implements OnDestroy { }); } ngOnDestroy(): void { + clearOpenSearchLink(this.document); this.destroy$.next(); this.destroy$.complete(); } diff --git a/src/app/pages/packages/packages-page.component.ts b/src/app/pages/packages/packages-page.component.ts index 92a7008e..b00cba3c 100644 --- a/src/app/pages/packages/packages-page.component.ts +++ b/src/app/pages/packages/packages-page.component.ts @@ -1,9 +1,11 @@ import { ChangeDetectionStrategy, Component, Inject, LOCALE_ID, OnDestroy } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; import { PackagesSearchComponent } from '../../core/components/search/search.component'; import { PackagesService } from '../../core/data/packages.service'; import { PackageComponent } from "../../core/components/package/package.component"; import { AsyncPipe, formatNumber } from '@angular/common'; import { BehaviorSubject, Subject, takeUntil } from 'rxjs'; +import { clearOpenSearchLink, setOpenSearchLink } from '../../core/opensearch-link'; @Component({ selector: 'app-packages-page.component', @@ -23,8 +25,10 @@ export class PackagesPageComponent implements OnDestroy { constructor( protected readonly searchService: PackagesService, + @Inject(DOCUMENT) private readonly document: Document, @Inject(LOCALE_ID) private readonly locale: string ) { + setOpenSearchLink(this.document, 'packages'); this.searchService.getIndexSize() .pipe(takeUntil(this.destroy$)) .subscribe(size => { @@ -32,6 +36,7 @@ export class PackagesPageComponent implements OnDestroy { }); } ngOnDestroy(): void { + clearOpenSearchLink(this.document); this.destroy$.next(); this.destroy$.complete(); } From 0d143501d0253d3b2d336cf528193280f49937b8 Mon Sep 17 00:00:00 2001 From: Markus Stark Date: Thu, 18 Jun 2026 13:31:25 +0200 Subject: [PATCH 5/9] Fix OpenSearch dataset access --- src/app/core/opensearch-link.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/opensearch-link.ts b/src/app/core/opensearch-link.ts index 93f99901..815571b5 100644 --- a/src/app/core/opensearch-link.ts +++ b/src/app/core/opensearch-link.ts @@ -8,7 +8,7 @@ export function setOpenSearchLink(document: Document, type: OpenSearchType): voi link.rel = 'search'; link.type = 'application/opensearchdescription+xml'; - link.dataset.nuschtosOpensearch = 'true'; + link.dataset['nuschtosOpensearch'] = 'true'; link.title = `${CONFIG.title} ${type === 'options' ? 'Options' : 'Packages'} Search`; link.href = `${CONFIG.baseHref}opensearch-${type}.xml`; } From b5552eaba39d59552aacc805126e94085a40db05 Mon Sep 17 00:00:00 2001 From: Markus Stark Date: Thu, 18 Jun 2026 15:30:27 +0200 Subject: [PATCH 6/9] Skip Pages deploys on fork PRs --- .github/workflows/pages.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml index 7c9191d6..4a545fee 100644 --- a/.github/workflows/pages.yaml +++ b/.github/workflows/pages.yaml @@ -54,7 +54,9 @@ jobs: - run: rm package.json pnpm-lock.yaml - name: Publish to Cloudflare Pages - if: "github.repository_owner == 'NuschtOS'" + # Fork PRs do not receive the Pages secret, so skip deployment there. + # Trusted PRs and pushes can still publish when the secret is present. + if: github.repository_owner == 'NuschtOS' && (github.event_name != 'pull_request' || secrets.CLOUDFLARE_API_TOKEN != '') uses: cloudflare/wrangler-action@v4 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} From e1b63dceaf4b5b1d54df48a53bae79f9423f591a Mon Sep 17 00:00:00 2001 From: Sandro Date: Fri, 19 Jun 2026 00:09:37 +0200 Subject: [PATCH 7/9] Dedupe substituteInPlace --- nix/frontend.nix | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nix/frontend.nix b/nix/frontend.nix index 8aa6885f..f4c04a21 100644 --- a/nix/frontend.nix +++ b/nix/frontend.nix @@ -24,9 +24,7 @@ stdenv.mkDerivation (finalAttrs: { strictDeps = true; postPatch = '' - substituteInPlace src/index.html \ - --replace-fail '##TITLE##' ${lib.escapeShellArg config.title} - substituteInPlace public/opensearch-options.xml public/opensearch-packages.xml \ + substituteInPlace src/index.html public/opensearch-options.xml public/opensearch-packages.xml \ --replace-fail '##TITLE##' ${lib.escapeShellArg config.title} # remove development files From 735222c645de89c26d5992f7b9b4dcc3de418409 Mon Sep 17 00:00:00 2001 From: Markus Stark Date: Fri, 19 Jun 2026 13:37:20 +0200 Subject: [PATCH 8/9] Add missing Angular CDK dependency --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index dd3c5a1d..fce4cf5f 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@angular/common": "^22.0.0", "@angular/compiler": "^22.0.0", "@angular/core": "^22.0.0", + "@angular/cdk": "^22.0.0", "@angular/forms": "^22.0.0", "@angular/platform-browser": "^22.0.0", "@angular/router": "^22.0.0", From eb4e3e635d2b9e447b291fb9f0dd0d9cbb72070d Mon Sep 17 00:00:00 2001 From: Markus Stark Date: Fri, 19 Jun 2026 20:31:06 +0200 Subject: [PATCH 9/9] fix: make pages workflow PR-safe --- .github/workflows/pages.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml index 4a545fee..1f6e038b 100644 --- a/.github/workflows/pages.yaml +++ b/.github/workflows/pages.yaml @@ -54,9 +54,7 @@ jobs: - run: rm package.json pnpm-lock.yaml - name: Publish to Cloudflare Pages - # Fork PRs do not receive the Pages secret, so skip deployment there. - # Trusted PRs and pushes can still publish when the secret is present. - if: github.repository_owner == 'NuschtOS' && (github.event_name != 'pull_request' || secrets.CLOUDFLARE_API_TOKEN != '') + if: github.event_name == 'push' uses: cloudflare/wrangler-action@v4 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}