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",
+ },
+ ],
+ })
+ )
+ )
+ );
+ });
+ }
+);