diff --git a/www/static/styles/toc.css b/www/static/styles/toc.css
index b6f1fb7..129619e 100644
--- a/www/static/styles/toc.css
+++ b/www/static/styles/toc.css
@@ -1,10 +1,25 @@
+
.table_of_contents {
position: sticky;
float: left;
- top: 3em;
+ top: 0px;
- width: 20em;
+ width: calc(var(--content-margin) - 2em);
+ margin-left: 2em;
+
+ padding-top: 0.8em;
+ padding-right: 0.5em;
+
+ padding-bottom: 0.5em;
+ border-radius: 0 0em 0em 0.5em;
+
+ box-shadow: -0.2em 0.2em 0.2em black;
+
+ --toc-fg: #cecece;
+ --toc-bg: #EE901544;
+
+ background-color: var(--bg_2);
& a {
display: inline-block;
@@ -19,23 +34,29 @@
padding-right: 0.2em;
padding-bottom: 0.2em;
- border-radius: 0.2em;
+
+
+ color: var(--toc-fg) !important;
}
& .active {
- background-color: var(--highlight_1);
- color: var(--bg_1);
+ background-color: var(--toc-bg);
+ border-bottom: 1px solid var(--highlight_1);
+
+ transition: all 0.2s;
}
& ol {
padding-left: 0.5em;
list-style: none;
-
- border-left: 1px solid #FFFFFF44;
}
& li {
+ border-radius: 0.2em;
+ border-bottom: 1px solid transparent;
+
margin-bottom: 0.2em;
+ transition: all 1s;
}
}
\ No newline at end of file
diff --git a/www/static/toc.js b/www/static/toc.js
new file mode 100644
index 0000000..13175e2
--- /dev/null
+++ b/www/static/toc.js
@@ -0,0 +1,265 @@
+// Code By Webdevtrick ( https://webdevtrick.com )
+// https://webdevtrick.com/dynamic-table-of-contents/
+//
+// Modified by dergens
+
+let tocId = "toc";
+
+const TOC_IDENTIFIER = "toc";
+const TOC_MAIN_CONTENT_ID = "#content_article"
+const TOC_TOP_PIXEL_MARGIN = 300;
+
+class TocTracker {
+ known_heading_elements = [];
+
+ constructor() {
+ this.intersection_observer = null;
+
+ this.tracking_update_callbacks = [];
+ this.tracking_rebuild_callbacks = [];
+ }
+
+ _disconnectIntersectionObserver() {
+ if(this.intersection_observer) {
+ this.intersection_observer.disconnect();
+ this.intersection_observer = null;
+ }
+ }
+
+ _setupIntersectionObserver() {
+ let options = {
+ root: null,
+ rootMargin: `-${TOC_TOP_PIXEL_MARGIN}px 0px 0px 0px`,
+ threshold: [1]
+ };
+
+ this.intersection_observer = new IntersectionObserver(
+ (entries) => this._findActiveHeading(),
+ options
+ );
+
+ this.known_heading_elements.forEach((element) => {
+ this.intersection_observer.observe(element.dom);
+ });
+ }
+
+ _searchForHeadings() {
+ const main = document.querySelector(TOC_MAIN_CONTENT_ID);
+ if (!main) {
+ throw Error("A `main` tag section is required to query headings from.");
+ }
+
+ let headings = main.querySelectorAll("h1, h2, h3, h4, h5, h6");
+ headings.forEach((heading, index) => {
+ const heading_level = parseInt(heading.tagName.slice(-1));
+
+ this.known_heading_elements.push({
+ level: heading_level,
+ name: heading.innerHTML,
+ dom: heading,
+ });
+ });
+ }
+
+ _sortHeadings() {
+ this.known_heading_elements.sort((a, b) => {
+ return a.dom.getBoundingClientRect().y
+ - b.dom.getBoundingClientRect().y;
+ });
+ }
+
+ _generateHeadingPaths() {
+ let current_path = [];
+
+ this.known_heading_elements.forEach(heading => {
+ while((current_path.length != 0) && (current_path[current_path.length-1].level >= heading.level))
+ current_path.pop();
+
+ current_path.push(heading);
+
+ // The zero does nothing. But according to stackoverflow, it makes it faster.
+ // Why? No idea.
+ // Just leave it in I suppose.
+ heading.path = current_path.slice(0);
+
+ });
+ }
+
+ _fillHeadingIDs() {
+ this.known_heading_elements.forEach(heading => {
+ heading.id = heading.dom.id;
+ if(heading.id)
+ return;
+
+ heading.dom.id = heading.path.map(i =>
+ i.name.replace(/\W+/g, '-').trim()
+ ).join('--').toLowerCase();
+
+ heading.id = heading.dom.id;
+ });
+ }
+
+ _findActiveHeading() {
+ const arr = this.known_heading_elements;
+
+ if(arr.length == 0)
+ return;
+
+ // Start should always be higher
+ // End always lower
+ let start = 0, end = arr.length - 1;
+ let mid = Math.floor((start + end) / 2);
+
+ // Special case handling, if all items are above the scroll margin
+ if(arr[end].dom.getBoundingClientRect().y < TOC_TOP_PIXEL_MARGIN) {
+ start = end;
+ mid = end;
+ }
+
+ // Iterate until we find the boundary
+ while (mid > start) {
+
+ // Check if the mid is above our boundary ((lower Y))
+ if (arr[mid].dom.getBoundingClientRect().y < TOC_TOP_PIXEL_MARGIN)
+ start = mid;
+ else
+ end = mid;
+
+ // Find the mid index
+ mid = Math.floor((start + end) / 2);
+ }
+
+ this.tracking_update_callbacks.forEach(callback => callback(arr[start]));
+
+ return arr[start];
+ }
+
+ reloadHeadings() {
+ this.known_heading_elements = [];
+
+ this._disconnectIntersectionObserver();
+ this._searchForHeadings();
+ this._sortHeadings();
+ this._generateHeadingPaths();
+ this._fillHeadingIDs();
+
+ this.tracking_rebuild_callbacks.forEach(item => item());
+
+ this._setupIntersectionObserver();
+ }
+
+ onTrackingUpdate(callback) {
+ this.tracking_update_callbacks.push(callback);
+
+ return callback;
+ }
+
+ onTrackingRebuild(callback) {
+ this.tracking_rebuild_callbacks.push(callback);
+
+ return callback;
+ }
+}
+
+class TocNavBarUpdater {
+ constructor(toc_tracker) {
+ this.toc_tracker = toc_tracker;
+ this.navbar_dom = null;
+
+ this.added_navbar_elements = [];
+
+ this.toc_tracker.onTrackingRebuild(() =>
+ this.trackingRebuildCallback() );
+
+ this.toc_tracker.onTrackingUpdate((element) => this.trackingUpdateCallback(element) );
+ }
+
+ _removeNavbarElements() {
+ this.added_navbar_elements.forEach(dom => dom.remove());
+ this.added_navbar_elements = [];
+ }
+
+ trackingRebuildCallback() {
+ this._removeNavbarElements();
+
+ this.navbar_dom = document.querySelector('.navbar ._path');
+ }
+
+ trackingUpdateCallback(element) {
+ this._removeNavbarElements();
+
+ const navbar_prev_node = this.navbar_dom.children[this.navbar_dom.children.length -1];
+
+ element.path.forEach(pathItem => {
+ let newNode = document.createElement('li');
+
+ const pathURL = location.pathname + '#' + pathItem.id + location.search;
+ newNode.innerHTML = " #" + pathItem.level + '' + pathItem.name + ''
+
+ this.navbar_dom.insertBefore(newNode, navbar_prev_node);
+ this.added_navbar_elements.push(newNode);
+ });
+
+ }
+}
+
+class TocSidemenu {
+ constructor(toc_tracker) {
+ this.toc_tracker = toc_tracker;
+
+ this.sidebar_dom = null;
+
+ this.sidebar_elements = {};
+ this.currently_active_entry = null;
+
+ toc_tracker.onTrackingRebuild(() => this.trackingRebuildCallback());
+ toc_tracker.onTrackingUpdate((element) => this.trackingUpdateCallback(element));
+ }
+
+ _clearSidebar() {
+ if(this.sidebar_dom) {
+ this.sidebar_dom.remove();
+ }
+
+ this.sidebar_elements = {};
+ this.currently_active_entry = null;
+ }
+
+ _generateSidebar() {
+ this.toc_tracker.known_heading_elements.forEach(element => {
+ let new_element = document.createElement('li');
+
+ const pathURL = location.pathname + '#' + element.id + location.search;
+
+ new_element.style = "padding-left: " + element.level * 0.8 + "em";
+ new_element.innerHTML = "" + element.name + "";
+
+ this.sidebar_elements[element.id] = new_element;
+
+ this.sidebar_dom.appendChild(new_element);
+ });
+ }
+
+ trackingRebuildCallback() {
+ this._clearSidebar();
+
+ this.sidebar_dom = document.createElement('ol');
+ document.querySelector('#toc').appendChild(this.sidebar_dom);
+
+ this._generateSidebar();
+ }
+
+ trackingUpdateCallback(entry) {
+ if(this.currently_active_entry) {
+ this.currently_active_entry.classList.remove('active');
+ }
+
+ let active_entry = this.sidebar_elements[entry.dom.id];
+ active_entry.classList.add('active');
+ this.currently_active_entry = active_entry;
+ }
+}
+
+let tracker = new TocTracker();
+let navbar_updater = new TocNavBarUpdater(tracker);
+let sidebar_updater = new TocSidemenu(tracker);
\ No newline at end of file
diff --git a/www/templates/root.html b/www/templates/root.html
index f0ab713..33dd9df 100644
--- a/www/templates/root.html
+++ b/www/templates/root.html
@@ -108,6 +108,6 @@
test?
{% endblock %}
-
+