Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e9ff2df
maint(webpack): Set watcher dir to src.
thet Oct 25, 2024
bf8d576
feat(pat-structure): Support for pushStage, baseUrl.
thet Oct 25, 2024
e6c678a
breaking(pat-toolbar): Change to a class based Pattern. Should do no …
thet Jan 3, 2024
bac2b67
feat(pat-toolbar): Optionally import CSS.
thet Jan 3, 2024
f2be6aa
feat(pat-toolbar): Allow to customize via options.
thet Jan 3, 2024
97763d6
maint(pat-toolbar): Modernize code.
thet Jan 3, 2024
8828368
maint(pat-toolbar): Restructure for better reusability.
thet Jan 3, 2024
c40fbd0
feat(pat-toolbar): After toolbar, also initialize patterns on persona…
thet Jan 3, 2024
793dde1
feat(pat-toolbar): Dispatch event on toolbar after reloading.
thet Jan 4, 2024
fc5fd9b
feat(pat-toolbar): Reload itself on location change.
thet Oct 25, 2024
d8f3315
fix(pat-toolbar): Improve toolbar reload method to not reload unneces…
thet Oct 25, 2024
843653c
maint(pat-toolbar): Cleanup.
thet Dec 10, 2024
fdf9414
feat(pat-navigationmarker): Modernize: class based Pattern, add optio…
thet Sep 25, 2024
16bc8f2
feat(pat-navigationmarker): re-scan nav after navigate event / url ch…
thet Sep 25, 2024
0c32812
feat(pat-navigationmarker): fire "pat-navigationmarker.scanned" event…
thet Sep 25, 2024
135238a
feat(pat-navigationmarker): multiple things: details support, BBB bre…
thet Sep 27, 2024
555469b
feat(pat-navigationmarker): Also mark the anchor itself as current.
thet Sep 27, 2024
93be910
fix(pat-navigationmarker): link-canonical isn't always updated. just …
thet Aug 26, 2025
c160dc0
yarn install.
thet Sep 2, 2025
0154bac
fixup structure.
thet Oct 10, 2025
6291589
feat(pat-base-url): Add new pattern to update data-base-url on the bo…
thet Oct 14, 2025
8e68d0f
fixup pat-toolbar: Remove the render URL cleanup method. This is now …
thet Oct 14, 2025
7817f4a
fixup pat-toolbar: Rename render-url to render-view, as URL was seman…
thet Oct 14, 2025
0cd91d3
fixup pat-structure: No need to set the base URL. Using history.pushS…
thet Oct 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@11ty/eleventy-upgrade-help": "3.0.1",
"@patternslib/pat-code-editor": "4.0.1",
"@patternslib/patternslib": "9.10.3",
"@plone/plonetheme-barceloneta-base": "^3.3.0",
"@plone/registry": "^2.5.4",
"backbone": "1.6.1",
"backbone.paginator": "2.0.8",
Expand Down
4 changes: 4 additions & 0 deletions src/pat/base-url/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# pat-base-url

Update the `data-base-url` attribute on the body tag when a `navigate` event is
thrown and thus the browser's URL has changed.
49 changes: 49 additions & 0 deletions src/pat/base-url/base-url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Set the base URL based on the current location, listening on navigation changes.
import { BasePattern } from "@patternslib/patternslib/src/core/basepattern";
import registry from "@patternslib/patternslib/src/core/registry";
import events from "@patternslib/patternslib/src/core/events";

class Pattern extends BasePattern {
static name = "pat-base-url";
static trigger = "body";

init() {
events.add_event_listener(
window.navigation,
"navigate",
"thet-base-url--set",
this.set_base_url.bind(this)
);
}

set_base_url() {
let url = window.location.href;

// Split the following words from the URL as we want to get the
// contents absolute URL.
const split_words = [
// NOTE: order matters.
"/@@", // also catches @@folder_contents and @@edit
"/++", // traversal urls.
"/folder_contents",
"/edit",
"/view",
"#",
"?",
];

// Split all split words out of url
url = split_words.reduce((url_, split_) => url_.split(split_)[0], url);
// Remove the trailing slash
if (url[url.length -1] === "/") {
url = url.substring(0, url.length - 1)
}

// Set the contents absolute URL on `data-base-url`.
document.body.dataset.baseUrl = url;
}
}

registry.register(Pattern);
export default Pattern;
export { Pattern };
49 changes: 49 additions & 0 deletions src/pat/base-url/base-url.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import "@patternslib/patternslib/src/core/polyfills";
import utils from "@patternslib/patternslib/src/core/utils";
import Pattern from "./base-url";

describe("pat-base-url tests", () => {
afterEach(() => {
document.body.innerHTML = "";
});

it("is initialized correctly", async () => {
document.body.dataset.baseUrl = "http://localhost/not/okay"

new Pattern(document.body);
await utils.timeout(1); // wait a tick for async to settle.

expect(document.body.dataset.baseUrl).toBe("http://localhost/not/okay");
history.pushState(null, "", "/okay/okay");
expect(document.body.dataset.baseUrl).toBe("http://localhost/okay/okay");
});

it("Cleans the URL from views and traversal URLs.", async () => {
new Pattern(document.body);
await utils.timeout(1); // wait a tick for async to settle.

history.pushState(null, "", "/okay/@@okay");
expect(document.body.dataset.baseUrl).toBe("http://localhost/okay");

history.pushState(null, "", "/okay/++okay");
expect(document.body.dataset.baseUrl).toBe("http://localhost/okay");

history.pushState(null, "", "/okay/folder_contents");
expect(document.body.dataset.baseUrl).toBe("http://localhost/okay");

history.pushState(null, "", "/okay/edit");
expect(document.body.dataset.baseUrl).toBe("http://localhost/okay");

history.pushState(null, "", "/okay/view");
expect(document.body.dataset.baseUrl).toBe("http://localhost/okay");

history.pushState(null, "", "/okay/#okay");
expect(document.body.dataset.baseUrl).toBe("http://localhost/okay");

history.pushState(null, "", "/okay/?okay");
expect(document.body.dataset.baseUrl).toBe("http://localhost/okay");

history.pushState(null, "", "/okay/?okay#okay");
expect(document.body.dataset.baseUrl).toBe("http://localhost/okay");
});
});
152 changes: 107 additions & 45 deletions src/pat/navigationmarker/navigationmarker.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,114 @@
import $ from "jquery";
import Base from "@patternslib/patternslib/src/core/base";

export default Base.extend({
name: "navigationmarker",
trigger: ".pat-navigationmarker",
parser: "mockup",
init: function () {
const portal_url = document.body.dataset.portalUrl;
var href =
document.querySelector('head link[rel="canonical"]').href ||
import { BasePattern } from "@patternslib/patternslib/src/core/basepattern";
import events from "@patternslib/patternslib/src/core/events";
import Parser from "@patternslib/patternslib/src/core/parser";
import registry from "@patternslib/patternslib/src/core/registry";

export const parser = new Parser("navigationmarker");
parser.addArgument("portal-url", undefined);
parser.addArgument("parent-selector", undefined);

class Pattern extends BasePattern {
static name = "navigationmarker";
static trigger = ".pat-navigationmarker";
static parser = parser;

async init() {
this.portal_url = this.options.portalUrl || document.body.dataset.portalUrl;

events.add_event_listener(
window.navigation,
"navigate",
"pat-navigationmarker--history-changed",
() => {
this.scan_navigation();
}
);

this.scan_navigation();
}

scan_navigation() {
const href =
//document.querySelector('head link[rel="canonical"]')?.href ||
window.location.href;

$("a", this.$el).each(function () {
var navlink = this.href.replace("/view", "");
if (href.indexOf(navlink) !== -1) {
var parent = $(this).parent();
const anchors = this.el.querySelectorAll("a");

// check the input-openers within the path
var check = parent.find("> input");
if (check.length) {
check[0].checked = true;
}
for (const anchor of anchors) {

// set "inPath" to all nav items which are within the current path
// check if parts of navlink are in canonical url parts
var hrefParts = href.split("/");
var navParts = navlink.split("/");
var inPath = false;
for (var i = 0, size = navParts.length; i < size; i++) {
// The last path-part must match.
inPath = false;
if (navParts[i] === hrefParts[i]) {
inPath = true;
}
}
if (navlink === portal_url && href !== portal_url) {
// Avoid marking "Home" with "inPath", when not actually there
inPath = false;
}
if (inPath) {
parent.addClass("inPath");
}
//const parent = anchor.parentElement;
const parent = this.options.parentSelector ? anchor.closest(this.options.parentSelector) : anchor.parentElement;
const navlink = anchor.href.replace("/view", "");

// We can exit early, if the navlink is not part of the current URL.
if (href.indexOf(navlink) === -1) {
this.clear(parent);
this.clear(anchor);
continue;
}

// set "current" to the current selected nav item, if it is in the navigation structure.
if (href === navlink) {
parent.addClass("current");
//// BBB
//// check the input-openers within the path
//const check = parent.querySelector(":scope > input");
//if (check) {
// check.checked = true;
//}

// set "inPath" to all nav items which are within the current path
// check if parts of navlink are in canonical url parts
//
const href_parts = href.split("/");
const nav_parts = navlink.split("/");
let inPath = false;

// The last part of the URL must match.
const nav_compare = nav_parts[nav_parts.length - 1];
const href_compare = href_parts[nav_parts.length - 1];
if (nav_compare === href_compare) {
inPath = true;
}

// Avoid marking "Home" with "inPath", when not actually there
if (navlink === this.portal_url && href !== this.portal_url) {
inPath = false;
}

// Set the class
if (inPath) {
// inPath is set along with current | TODO: OR NOT, verify.
parent.classList.add("inPath");
if (parent.tagName === "DETAILS") {
parent.open = true;
}

}
});
},
});

// set "current" to the current selected nav item, if it is in the navigation structure.
if (href === navlink) {
parent.classList.add("current");
anchor.classList.add("current");
}
}

this.el.dispatchEvent(events.generic_event(`${this.name}.scanned`));
}

clear(element) {
// Clear all classes
if (element.classList.contains("inPath")) {
element.classList.remove("inPath");
}
if (element.classList.contains("current")) {
element.classList.remove("current");
}
if (element.tagName === "DETAILS") {
element.open = false;
}
}
}

// Register Pattern class in the global pattern registry
registry.register(Pattern);

// Make it available
export default Pattern;
8 changes: 5 additions & 3 deletions src/pat/structure/js/views/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ export default BaseView.extend({
// ignore this, fake event trigger to element that is not visible
return;
}
if ($el.is("a") || $el.parent().is("a") || $el.hasClass("popover-structure-query-active")) {
if (
$el.is("a") ||
$el.parent().is("a") ||
$el.hasClass("popover-structure-query-active")
) {
// elements that should not close
// NOTE: "popover-structure-query-active" is set on body when
// select2 elements are clicked inside the structure filter
Expand Down Expand Up @@ -183,8 +187,6 @@ export default BaseView.extend({
// of some kind - use the base object instead for that by not
// specifying a path.
path = "";
// TODO figure out whether the following event after this is
// needed at all.
}
$("body").trigger("structure-url-changed", [path]);

Expand Down
Loading