From 32716f8ff1fd9b45edd0c408786bc52ee10752a2 Mon Sep 17 00:00:00 2001 From: vickydotbat Date: Fri, 3 Jul 2026 08:59:24 +0200 Subject: [PATCH] feat: wiki badge and /wiki links in global search results Override Harmony's search partials so results tagged by the wiki plugin (isWikiArticle + wikiPath) show a gold Wiki badge and link to /wiki/... instead of /post/..., in the quick-search dropdown and both /search views. Forum results render unchanged; posts_list_item is shared with account and group pages, where the flags are never set and the conditional no-ops. Co-Authored-By: Claude Fable 5 --- ...6-07-02-wiki-search-result-badge-design.md | 7 +++ languages/en-GB/westgate.json | 3 + scss/westgate/_search.scss | 17 ++++++ templates/partials/posts_list_item.tpl | 28 ++++++++++ templates/partials/quick-search-results.tpl | 50 +++++++++++++++++ templates/partials/search-results.tpl | 55 +++++++++++++++++++ tests/wiki-search-badge-contract.test.js | 42 ++++++++++++++ theme.scss | 1 + 8 files changed, 203 insertions(+) create mode 100644 languages/en-GB/westgate.json create mode 100644 scss/westgate/_search.scss create mode 100644 templates/partials/posts_list_item.tpl create mode 100644 templates/partials/quick-search-results.tpl create mode 100644 templates/partials/search-results.tpl create mode 100644 tests/wiki-search-badge-contract.test.js diff --git a/docs/superpowers/specs/2026-07-02-wiki-search-result-badge-design.md b/docs/superpowers/specs/2026-07-02-wiki-search-result-badge-design.md index 44489ea..fb6c75c 100644 --- a/docs/superpowers/specs/2026-07-02-wiki-search-result-badge-design.md +++ b/docs/superpowers/specs/2026-07-02-wiki-search-result-badge-design.md @@ -59,6 +59,13 @@ NodeBB version: Planning resolves which, based on the actual stock partials in the pinned NodeBB/theme base. +**Resolved (2026-07-03): template override.** Harmony's row markup is small and +self-contained, so the theme overrides `partials/posts_list_item.tpl` (`/search` +posts view — shared with account/groups pages, where the wiki flags are never +set and the conditional no-ops), `partials/search-results.tpl` (`/search` topics +view), and `partials/quick-search-results.tpl` (dropdown). No client-side +decoration needed. + ## Styling Add badge styles in the theme SCSS (`scss/`, wired via `theme.scss`), matching diff --git a/languages/en-GB/westgate.json b/languages/en-GB/westgate.json new file mode 100644 index 0000000..6b3e767 --- /dev/null +++ b/languages/en-GB/westgate.json @@ -0,0 +1,3 @@ +{ + "wiki-badge": "Wiki" +} diff --git a/scss/westgate/_search.scss b/scss/westgate/_search.scss new file mode 100644 index 0000000..ba19512 --- /dev/null +++ b/scss/westgate/_search.scss @@ -0,0 +1,17 @@ +/* Wiki badge on global search results (quick-search dropdown + /search page). + Marks results tagged by nodebb-plugin-westgate-wiki (isWikiArticle/wikiPath). */ +.westgate-wiki-badge { + font-family: var(--wg-font-ui); + font-size: 0.62em; + font-weight: 600; + letter-spacing: 0.09em; + text-transform: uppercase; + color: var(--wg-gold); + background: rgba(194, 163, 90, 0.1); + border: 1px solid var(--wg-ledger-border); + vertical-align: middle; +} + +.quick-search-results .westgate-wiki-badge { + font-size: 0.7rem; +} diff --git a/templates/partials/posts_list_item.tpl b/templates/partials/posts_list_item.tpl new file mode 100644 index 0000000..9637d78 --- /dev/null +++ b/templates/partials/posts_list_item.tpl @@ -0,0 +1,28 @@ +
  • +
    + + {{{ if ./isWikiArticle }}} [[westgate:wiki-badge]] {{{ else }}}{{{ if ./isMainPost }}} {{{ end }}}{{{ end }}}{./topic.title} + + +
    + + +
    + {./content} +
    +
    +
    + {buildCategoryLabel(./category, "a", "border")} + + {{{ each ./topic.tags }}} + {./valueEscaped} + {{{ end }}} + +
    +
  • \ No newline at end of file diff --git a/templates/partials/quick-search-results.tpl b/templates/partials/quick-search-results.tpl new file mode 100644 index 0000000..3e60396 --- /dev/null +++ b/templates/partials/quick-search-results.tpl @@ -0,0 +1,50 @@ +{{{ if (multiplePages && !config.theme.topMobilebar) }}} + +{{{ end }}} + + + +{{{ if multiplePages }}} + +{{{ end }}} +{{{ if (multiplePages && config.theme.topMobilebar) }}} + +{{{ end }}} + +{{{if !posts.length}}} +
    [[search:no-matches]] + +{{{end}}} \ No newline at end of file diff --git a/templates/partials/search-results.tpl b/templates/partials/search-results.tpl new file mode 100644 index 0000000..fd5327b --- /dev/null +++ b/templates/partials/search-results.tpl @@ -0,0 +1,55 @@ +{{{ if matchCount }}} +
    [[search:results-matching, {matchCount}, {search_query}, {time}]]
    +{{{ else }}} +{{{ if search_query }}} +
    [[search:no-matches]]
    +{{{ end }}} +{{{ end }}} + +
    + {{{ if showAsPosts }}} + {{{ if posts.length }}} + + {{{ end }}} + {{{ end }}} + + {{{ if showAsTopics }}} + {{{ each posts }}} +
    + + {{{ end }}} + {{{ end }}} + + {{{ if users.length }}} + + {{{ end }}} + + {{{ if tags.length }}} +
    + +
    + {{{ end }}} + + {{{ if categories.length }}} +
      + {{{each categories}}} + + {{{end}}} +
    + {{{ end }}} + + +
    \ No newline at end of file diff --git a/tests/wiki-search-badge-contract.test.js b/tests/wiki-search-badge-contract.test.js new file mode 100644 index 0000000..c998292 --- /dev/null +++ b/tests/wiki-search-badge-contract.test.js @@ -0,0 +1,42 @@ +'use strict'; + +// Contract: search results tagged by nodebb-plugin-westgate-wiki +// (isWikiArticle + wikiPath) render with a wiki badge and link to /wiki/..., +// while untagged forum results keep the stock /post/... link. + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +function read(relativePath) { + const filePath = path.join(__dirname, '..', relativePath); + assert(fs.existsSync(filePath), `${relativePath} should exist`); + return fs.readFileSync(filePath, 'utf8'); +} + +function assertIncludes(haystack, needle, message) { + assert( + haystack.includes(needle), + message || `Expected file to include ${needle}` + ); +} + +[ + ['templates/partials/posts_list_item.tpl', './isWikiArticle', '{./wikiPath}', '/post/{encodeURIComponent(./pid)}'], + ['templates/partials/search-results.tpl', './isWikiArticle', '{./wikiPath}', '/post/{encodeURIComponent(./pid)}'], + ['templates/partials/quick-search-results.tpl', 'posts.isWikiArticle', '{posts.wikiPath}', '/post/{encodeURIComponent(posts.pid)}'], +].forEach(([templatePath, flag, wikiHref, forumHref]) => { + const template = read(templatePath); + assertIncludes(template, flag, `${templatePath} should branch on the wiki result flag`); + assertIncludes(template, wikiHref, `${templatePath} should link wiki results to their wikiPath`); + assertIncludes(template, forumHref, `${templatePath} should keep the stock post link for forum results`); + assertIncludes(template, 'westgate-wiki-badge', `${templatePath} should render the wiki badge`); +}); + +assertIncludes(read('theme.scss'), 'westgate/search', 'theme.scss should import the search styles'); +assertIncludes(read('scss/westgate/_search.scss'), '.westgate-wiki-badge', 'search styles should define the wiki badge'); + +const languageKeys = JSON.parse(read('languages/en-GB/westgate.json')); +assert(languageKeys['wiki-badge'], 'westgate language file should define the wiki-badge label'); + +console.log('wiki search badge contract tests passed'); diff --git a/theme.scss b/theme.scss index ed6fbfc..39ae4b7 100644 --- a/theme.scss +++ b/theme.scss @@ -7,6 +7,7 @@ @import "./scss/westgate/categories"; @import "./scss/westgate/topics"; @import "./scss/westgate/posts"; +@import "./scss/westgate/search"; @import "./scss/westgate/controls"; @import "./scss/westgate/widgets"; @import "./scss/westgate/responsive";