Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
150 changes: 96 additions & 54 deletions src/pat/autotoc/autotoc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import $ from "jquery";
import Base from "@patternslib/patternslib/src/core/base";
import utils from "@patternslib/patternslib/src/core/utils";
import Tab from "bootstrap/js/dist/tab";

export default Base.extend({
name: "autotoc",
Expand All @@ -10,8 +11,9 @@ export default Base.extend({
section: "section",
levels: "h1,h2,h3",
IDPrefix: "autotoc-item-",
classTOCName: "autotoc-nav",
classSectionName: "autotoc-section",
classTOCName: "autotoc-nav nav",
classContentAreaName: "autotoc-content tab-content",
classSectionName: "autotoc-section tab-pane fade",
classLevelPrefixName: "autotoc-level-",
classActiveName: "active",
scrollDuration: "slow",
Expand All @@ -26,67 +28,108 @@ export default Base.extend({

var self = this;

self.$toc = $("<nav/>").addClass(self.options.classTOCName);
const $nav = $("<nav/>")
.attr("aria-label", "Tab Navigation");

const $toc = $("<ul/>")
.addClass(self.options.classTOCName)
.appendTo($nav);

if (self.options.prependTo) {
self.$toc.prependTo(self.options.prependTo);
$nav.prependTo(self.options.prependTo);
} else if (self.options.appendTo) {
self.$toc.appendTo(self.options.appendTo);
$nav.appendTo(self.options.appendTo);
} else {
self.$toc.prependTo(self.$el);
$nav.prependTo(self.$el);
}

if (self.options.className) {
self.$el.addClass(self.options.className);
}

$(self.options.section, self.$el).addClass(self.options.classSectionName);
$(self.options.section, self.$el)
.addClass(self.options.classSectionName)
.attr("role", "tabpanel")
.attr("tabindex", "0");

var asTabs = self.$el.hasClass("autotabs");

if (asTabs) {
$toc.addClass("nav-tabs");
self.$contentArea = $("<div/>").addClass(self.options.classContentAreaName);
self.$contentArea.insertAfter($nav);
$(self.options.section, self.$el).appendTo(self.$contentArea);
$(self.options.section, self.$el).find("legend").hide();
} else {
$toc.addClass([
"flex-column",
"float-end",
"border",
"mt-0",
"me-0",
"mb-3",
"ms-3",
"py-2",
"px-0"
]);
}

var activeId = null;

$(self.options.levels, self.$el).each(function (i) {
const section = this.closest(self.options.section);
const $level = $(this);
let id = $level.prop("id") ? $level.prop("id") : $(section).prop("id");

if (!id || $("#" + id).length > 0) {
id = self.options.IDPrefix + self.name + "-" + i;
}
if (window.location.hash === "#" + id) {
activeId = id;
const $section = $level.closest(self.options.section);
let sectionId = $section.prop("id");
let sectionHash = `#${sectionId}`;
const tabId = `${self.options.IDPrefix}${self.name}-${i}`;
const tabHash = `#${tabId}`;
const levelId = `${tabId}-pane`;
const levelHash = `#${levelId}`;

if (!asTabs) {
$level.attr("id", levelId).attr("aria-labelledby", tabId);
} else {
$section.attr("aria-labelledby", tabId);
if (!sectionId) {
// sections without ID get auto generated "<tabId>-pane"
sectionId = levelId;
sectionHash = `#${levelId}`;
$section.attr("id", levelId);
}
}
if (activeId === null && $level.hasClass(self.options.classActiveName)) {
activeId = id;

// NOTE: if you have nested autotocs then you have to add the
// parent autotoc tabId to `options.IDPrefix` of the sub autotoc
// in order to mark parent and sub tab as active.
if (activeId === null && (window.location.hash.indexOf(tabHash) == 0 || $level.hasClass(self.options.classActiveName))) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cihanandac this solves the nested tab activation problem. however, we need to fix the IDPrefix of theming controlpanel before we merge this, otherwise you cannot access the subtabs anymore.

activeId = tabId;
}
$level.data("navref", id);

const $navItem = $("<li/>");
$navItem
.addClass("nav-item")
.attr("role", "presentation")
.appendTo($toc);

const $nav = $("<a/>");
$nav.appendTo(self.$toc)
.text($level.text())
.attr("id", id)
.attr("href", "#" + id)
.addClass(self.options.classLevelPrefixName + self.getLevel($level))
$nav.appendTo($navItem)
.html($level.html())
.attr("id", tabId)
.attr("href", asTabs ? sectionHash : levelHash)
.attr("aria-controls", asTabs ? sectionId : levelId)
.attr("data-bs-toggle", "tab")
.attr("data-bs-target", asTabs ? sectionHash : levelHash)
.addClass([
"nav-link",
self.options.classLevelPrefixName + self.getLevel($level),
])
.on("click", function (e, options) {
e.stopPropagation();
e.preventDefault();
if (!options) {
options = {
doScroll: true,
skipHash: false,
};
}
var $el = $(this);
self.$toc
.children("." + self.options.classActiveName)
.removeClass(self.options.classActiveName);
self.$el
.children("." + self.options.classActiveName)
.removeClass(self.options.classActiveName);
$(e.target).addClass(self.options.classActiveName);
$level
.parents(self.options.section)
.addClass(self.options.classActiveName);
if (
options.doScroll !== false &&
self.options.scrollDuration &&
Expand All @@ -107,37 +150,36 @@ export default Base.extend({
$(this).trigger("clicked");
if (!options.skipHash) {
if (window.history && window.history.pushState) {
window.history.pushState({}, "", "#" + $el.attr("id"));
window.history.pushState({}, "", tabHash);
}
}
});
$level.data("autotoc-trigger-id", id);

if (!asTabs) {
$nav.addClass([
"text-decoration-underline",
"p-0",
"mx-3",
]);
}

self.tabs.push({
section: section,
id: id,
section: $section[0],
id: levelId,
nav: $nav[0],
});
});

if (activeId) {
$("a#" + activeId).trigger("click", {
doScroll: true,
skipHash: true,
});
} else {
self.$toc.find("a").first().trigger("click", {
doScroll: false,
skipHash: true,
});
}
const activeTabButton = activeId ? $toc.find("a#" + activeId)[0] : $toc.find("a").first()[0];
const tab = Tab.getOrCreateInstance(activeTabButton);
tab.show();

// After DOM tree is built, initialize eventual validation
this.initialize_validation(self.$el);
this.initialize_validation();
},

initialize_validation: function ($el) {
const el = $el[0];
initialize_validation: function () {
const el = this.el

// Initialize only on forms
const form = el.closest("form");
Expand Down
63 changes: 2 additions & 61 deletions src/pat/autotoc/autotoc.scss
Original file line number Diff line number Diff line change
@@ -1,69 +1,10 @@
.pat-autotoc {
.autotoc-nav {
float: right;
border: 1px solid #ddd;
padding: 0.5em 0;
margin: 0 0 1em 1em;
a {
display: block;
}
a:focus {
outline-style: none;
}
.autotoc-level-1 {
margin: 0 1em 0 1em;
}
.autotoc-level-2 {
margin: 0 1em 0 2em;
margin-left: 2em;
}
.autotoc-level-3 {
margin: 0 1em 0 3em;
}
}
&.autotabs {
.autotoc-nav {
float: none;
padding: 0;
margin: 0 0 0.3em 0;
border: 0;
border-bottom: 1px solid #ddd;
&:after {
content: "";
display: table;
line-height: 0;
}
a {
display: inline-block;
margin: 0 0.5em -1px 0.5em;
line-height: 1.5em;
padding: 0.4em 0.8em;
text-decoration: none;
border-radius: 4px 4px 0 0;
&.active {
border: 1px solid #ddd;
border-bottom-color: var(--bs-body-bg);
color: var(--bs-secondary-text);
cursor: default;
}
&.active:hover {
background-color: transparent;
}
&:hover {
// background-color: #eee;
background-color: var(--bs-secondary-bg);
border-color: #eee;
border-bottom-color: var(--bs-body-bg);
}
}
}
.autotoc-section {
display: none;
&.active {
display: block;
legend {
display: none;
}
}
margin-left: 3em;
}
}
}
Loading