diff --git a/common/common.scss b/common/common.scss index 161d187..dabab3a 100644 --- a/common/common.scss +++ b/common/common.scss @@ -1,3 +1,5 @@ +@use "lib/viewport"; + @if $Hide_dropdowns == "true" { ol.category-breadcrumb { display: none; @@ -5,8 +7,12 @@ } @if $Hide_default_links == "true" { - #navigation-bar { - li:not(.navigation-toggle, [class*="custom_"]) { + .list-control-toggle-link-content li:not([class*="custom_"]) { + display: none; + } + + @include viewport.from(sm) { + #navigation-bar li:not([class*="custom_"]) { display: none; } } diff --git a/javascripts/discourse/initializers/init-nav-bar-additions.js b/javascripts/discourse/initializers/init-nav-bar-additions.js index 5fd1619..e7169e9 100644 --- a/javascripts/discourse/initializers/init-nav-bar-additions.js +++ b/javascripts/discourse/initializers/init-nav-bar-additions.js @@ -5,26 +5,18 @@ export default { initialize() { withPluginApi((api) => { - const itemsSetting = settings.Nav_links; - const items = itemsSetting - .split("|") - .map((item) => item.trim()) - .filter(Boolean); - - for (const item of items) { - const splitSec = item.split(";").map((section) => section.trim()); - const filter = splitSec[0]; - const filterDasherized = splitSec[0].replace(/\s+/g, "-").toLowerCase(); - const title = splitSec[1]; - const location = splitSec[2]; - + for (const { + display_name: displayName, + title, + url, + } of settings.nav_links) { api.addNavigationBarItem({ - name: `custom_${filterDasherized}`, - displayName: filter, + name: `custom_${displayName.replace(/\s+/g, "-").toLowerCase()}`, + displayName, title, - href: location, + href: url, forceActive: (category, args, router) => - router.currentURL.includes(location), + router.currentURL?.split("?")[0] === url, }); } }); diff --git a/locales/en.yml b/locales/en.yml new file mode 100644 index 0000000..8d65002 --- /dev/null +++ b/locales/en.yml @@ -0,0 +1,16 @@ +en: + theme_metadata: + settings: + nav_links: + description: Custom links to display in the topic list navigation + schema: + properties: + display_name: + label: Display name + description: The text that shows on the menu + title: + label: Title + description: The text that shows on hover + url: + label: URL + description: The link (e.g., /latest or https://discourse.org) diff --git a/migrations/settings/0001-migrate-nav-links-to-objects.js b/migrations/settings/0001-migrate-nav-links-to-objects.js new file mode 100644 index 0000000..c705410 --- /dev/null +++ b/migrations/settings/0001-migrate-nav-links-to-objects.js @@ -0,0 +1,37 @@ +export default function migrate(settings) { + if (!settings.has("Nav_links")) { + return settings; + } + + const oldValue = settings.get("Nav_links"); + settings.delete("Nav_links"); + + if (Array.isArray(oldValue)) { + settings.set("nav_links", oldValue); + return settings; + } + + if (typeof oldValue === "string") { + const newLinks = []; + + oldValue.split("|").forEach((link) => { + const [displayName, title, url] = link + .split(";") + .map((section) => section.trim()); + + if (displayName && url) { + const newLink = { display_name: displayName, url }; + + if (title) { + newLink.title = title; + } + + newLinks.push(newLink); + } + }); + + settings.set("nav_links", newLinks); + } + + return settings; +} diff --git a/settings.yml b/settings.yml index baaba43..b0ee740 100644 --- a/settings.yml +++ b/settings.yml @@ -1,12 +1,37 @@ -Nav_links: - type: list - default: "Latest;topics with recent posts;/latest|Categories;all topics grouped by category;/categories|Top;the most active topics in the last year, month, week or day;/top" - description: - en: | - Semicolon delimited in this order: display name;description;URL -
Display name: text that shows on the menu -
Description: text that shows on hover -
URL: the link (e.g., /latest or https://discourse.org) +nav_links: + type: objects + default: + - display_name: Latest + title: topics with recent posts + url: /latest + - display_name: Categories + title: all topics grouped by category + url: /categories + - display_name: Top + title: the most active topics in the last year, month, week or day + url: /top + schema: + name: link + identifier: display_name + properties: + display_name: + type: string + required: true + validations: + min_length: 1 + max_length: 100 + title: + type: string + validations: + min_length: 1 + max_length: 1000 + url: + type: string + required: true + validations: + min_length: 1 + max_length: 2048 + url: true Hide_dropdowns: default: false description: diff --git a/test/unit/migrations/settings/0001-migrate-nav-links-to-objects-test.js b/test/unit/migrations/settings/0001-migrate-nav-links-to-objects-test.js new file mode 100644 index 0000000..9093cf9 --- /dev/null +++ b/test/unit/migrations/settings/0001-migrate-nav-links-to-objects-test.js @@ -0,0 +1,127 @@ +import { module, test } from "qunit"; +import migrate from "../../../../migrations/settings/0001-migrate-nav-links-to-objects"; + +module( + "Custom Top Navigation Links | Migrations | Settings | 0001-migrate-nav-links-to-objects", + function () { + test("does nothing when the old setting is not present", function (assert) { + const settings = new Map(Object.entries({ Hide_dropdowns: true })); + + const result = migrate(settings); + + assert.deepEqual( + Object.fromEntries(result.entries()), + Object.fromEntries(new Map(Object.entries({ Hide_dropdowns: true }))) + ); + }); + + test("migrate when value of setting is already an array", function (assert) { + const links = [ + { + display_name: "Latest", + title: "topics with recent posts", + url: "/latest", + }, + ]; + const settings = new Map(Object.entries({ Nav_links: links })); + + const result = migrate(settings); + + assert.deepEqual( + Object.fromEntries(result.entries()), + Object.fromEntries(new Map(Object.entries({ nav_links: links }))) + ); + }); + + test("migrate when old setting value is an empty string", function (assert) { + const settings = new Map(Object.entries({ Nav_links: "" })); + + const result = migrate(settings); + + assert.deepEqual( + Object.fromEntries(result.entries()), + Object.fromEntries(new Map(Object.entries({ nav_links: [] }))) + ); + }); + + test("migrate when old setting value is invalid", function (assert) { + const settings = new Map( + Object.entries({ + Nav_links: "Missing URL|Missing URL 2;some title|;;/no-name", + }) + ); + + const result = migrate(settings); + + assert.deepEqual( + Object.fromEntries(result.entries()), + Object.fromEntries(new Map(Object.entries({ nav_links: [] }))) + ); + }); + + test("migrate when title is not provided", function (assert) { + const settings = new Map( + Object.entries({ + Nav_links: "External link;;https://meta.discourse.org", + }) + ); + + const result = migrate(settings); + + assert.deepEqual( + Object.fromEntries(result.entries()), + Object.fromEntries( + new Map( + Object.entries({ + nav_links: [ + { + display_name: "External link", + url: "https://meta.discourse.org", + }, + ], + }) + ) + ) + ); + }); + + test("migrate", function (assert) { + const settings = new Map( + Object.entries({ + Nav_links: + "Latest;topics with recent posts;/latest|Categories;all topics grouped by category;/categories|Top;the most active topics in the last year, month, week or day;/top", + }) + ); + + const result = migrate(settings); + + assert.deepEqual( + Object.fromEntries(result.entries()), + Object.fromEntries( + new Map( + Object.entries({ + nav_links: [ + { + display_name: "Latest", + title: "topics with recent posts", + url: "/latest", + }, + { + display_name: "Categories", + title: "all topics grouped by category", + url: "/categories", + }, + { + display_name: "Top", + title: + "the most active topics in the last year, month, week or day", + url: "/top", + }, + ], + }) + ) + ) + ); + }); + } +);