From d0ba213e84f043e990f561159ad5ea7d1d7931ad Mon Sep 17 00:00:00 2001 From: Kolasa Thomas Date: Thu, 2 May 2024 04:08:40 +0200 Subject: [PATCH 1/9] feat: categories plugin & active navigation --- categories.js | 38 ++++++++++++++++++ publish.js | 26 +++++++++--- static/scripts/nav.js | 88 ++++++++++++++++++++++++++++++++++++----- static/styles/jsdoc.css | 23 ++++++++--- 4 files changed, 155 insertions(+), 20 deletions(-) create mode 100644 categories.js diff --git a/categories.js b/categories.js new file mode 100644 index 00000000..cfbd749f --- /dev/null +++ b/categories.js @@ -0,0 +1,38 @@ +var logger = require('jsdoc/util/logger'); + +exports.defineTags = function(dictionary) { + dictionary.defineTag('category', { + mustHaveValue: true, + onTagged: function(doclet, tag) { + const category = tag.value; + if (!category) return; + if (env.conf.categories[category]) { + doclet.category = category; + } else { + logger.error(`Undefined category "${category}"`); + } + } + }); +}; + +exports.handlers = { + beforeParse: function() { + loadConfiguration(); + }, +}; + +function loadConfiguration () { + try { + const fs = require('jsdoc/fs'); + const confFileContents = fs.readFileSync(env.conf.categoriesFile, 'utf8'); + env.conf.categories = JSON.parse( (confFileContents || "{}" ) ); + env.conf.categoryList = Object.keys(env.conf.categories); + } catch (e) { + throw 'Could not load category file'; + } +} + +exports.getMembers = (data, category) => { + const doclets = data({category: category}).get(); + return doclets; +}; \ No newline at end of file diff --git a/publish.js b/publish.js index 5894d832..74da11f1 100644 --- a/publish.js +++ b/publish.js @@ -9,11 +9,11 @@ var path = require('jsdoc/path'); var taffy = require('@jsdoc/salty').taffy; var template = require('jsdoc/template'); var util = require('util'); +var categories = require('docdash/categories.js'); var htmlsafe = helper.htmlsafe; var linkto = helper.linkto; var resolveAuthorLinks = helper.resolveAuthorLinks; -var scopeToPunc = helper.scopeToPunc; var hasOwnProp = Object.prototype.hasOwnProperty; var data; @@ -322,9 +322,18 @@ function attachModuleSymbols(doclets, modules) { }); } -function buildMemberNav(items, itemHeading, itemsSeen, linktoFn) { +function buildCategoriesNav(items, itemsSeen, linktoFn) { var nav = ''; + for (var [category, members] of Object.entries(items)) { + var name = env.conf.categories[category].displayName; + nav += buildMemberNav(members, name, itemsSeen, linktoFn); + } + return nav; +} +function buildMemberNav(items, itemHeading, itemsSeen, linktoFn) { + var nav = ''; + if (items && items.length) { var itemsNav = ''; var docdash = env && env.conf && env.conf.docdash || {}; @@ -403,7 +412,7 @@ function buildMemberNav(items, itemHeading, itemsSeen, linktoFn) { itemsNav += ''; }); - if (itemsNav !== '') { + if (itemsNav !== '
  • ') { if(docdash.collapse === "top") { nav += '

    ' + itemHeading + '

    '; } @@ -439,7 +448,7 @@ function linktoExternal(longName, name) { * @return {string} The HTML for the navigation sidebar. */ -function buildNav(members) { +function buildNav(members, categoriesMembers) { var nav = '

    Home

    '; var seen = {}; var seenTutorials = {}; @@ -483,10 +492,11 @@ function buildNav(members) { return ret; } var defaultOrder = [ - 'Classes', 'Modules', 'Externals', 'Events', 'Namespaces', 'Mixins', 'Tutorials', 'Interfaces', 'Global' + 'Categories', 'Classes', 'Modules', 'Externals', 'Events', 'Namespaces', 'Mixins', 'Tutorials', 'Interfaces', 'Global' ]; var order = docdash.sectionOrder || defaultOrder; var sections = { + Categories: buildCategoriesNav(categoriesMembers, seen, linkto), Classes: buildMemberNav(members.classes, 'Classes', seen, linkto), Modules: buildMemberNav(members.modules, 'Modules', {}, linkto), Externals: buildMemberNav(members.externals, 'Externals', seen, linktoExternal), @@ -719,6 +729,10 @@ exports.publish = function(taffyData, opts, tutorials) { }); var members = helper.getMembers(data); + var categoriesMembers = {}; + for (var category of env.conf.categoryList) { + categoriesMembers[category] = categories.getMembers(data, category); + } members.tutorials = tutorials.children; // output pretty-printed source files by default @@ -735,7 +749,7 @@ exports.publish = function(taffyData, opts, tutorials) { view.outputSourceFiles = outputSourceFiles; // once for all - view.nav = buildNav(members); + view.nav = buildNav(members, categoriesMembers); attachModuleSymbols( find({ longname: {left: 'module:'} }), members.modules ); // generate the pretty-printed source files first so other pages can link to them diff --git a/static/scripts/nav.js b/static/scripts/nav.js index 6dd83134..4cb54074 100644 --- a/static/scripts/nav.js +++ b/static/scripts/nav.js @@ -1,12 +1,82 @@ -function scrollToNavItem() { - var path = window.location.href.split('/').pop().replace(/\.html/, ''); - document.querySelectorAll('nav a').forEach(function(link) { - var href = link.attributes.href.value.replace(/\.html/, ''); - if (path === href) { - link.scrollIntoView({block: 'center'}); - return; +function initNavigation() { + + // get current url data + const pathname = window.location.pathname.split('/'); + let file = pathname.pop(); + if (!file) file = "index.html"; + const hash = window.location.hash; + const id = hash.substring(1); + + setActiveItem(id); + setActiveParentItem(id); + scrollToCurrentItem(); + + // bind to scroll to set id live + window.addEventListener('scroll', function () { + debounceActiveHash(); + }); + + var hashTimeout; + function debounceActiveHash() { + clearTimeout(hashTimeout); + hashTimeout = setTimeout(findActiveHeader, 25); + } + + function findActiveHeader() { + const scrollTop = window.pageYOffset || document.documentElement.scrollTop; + const windowHeight = window.innerHeight; + const headers = Array.from(document.querySelectorAll("h4.name")); + const currentHeader = headers.find(header => { + const offsetTop = header.offsetTop; + const height = header.offsetHeight; + if (scrollTop <= offsetTop && (height + offsetTop) < (scrollTop + windowHeight)) { + return true; } - }) + }); + if (currentHeader) setActiveHash(currentHeader.id); + } + + function setActiveHash(id) { + removeActiveClass(); + setActiveItem(id); + setActiveParentItem(id); + setWindowHash(id); + } + + function removeActiveClass() { + const items = Array.from(document.querySelectorAll(`nav li`)); + items.forEach(item => item.classList.remove('active')); } - scrollToNavItem(); + function setActiveItem(id) { + const currentLink = document.querySelector(`a[href='${file}#${id}']`); + if (!currentLink) return; + const item = currentLink.closest('li'); + item.classList.add('active'); + } + + function setActiveParentItem(id) { + const currentLink = document.querySelector(`a[href='${file}']`); + if (!currentLink) return; + const item = currentLink.closest('li'); + if (item) item.classList.add('active'); + } + + function setWindowHash(id) { + const link = document.querySelector(`a[href='${file}#${id}']`); + const hash = link ? `#${id}` : ' '; + window.history.replaceState(null, null, hash); + } + + function scrollToCurrentItem() { + let item = document.querySelector(`li.active li.active`); + if (!item) item = document.querySelector(`li.active`); + if (!item) return; // index + document.addEventListener("DOMContentLoaded", function () { + item.scrollIntoView({ block: 'center' }); + }); + } + +} + +initNavigation(); diff --git a/static/styles/jsdoc.css b/static/styles/jsdoc.css index 0fe6d3cd..0b73f222 100644 --- a/static/styles/jsdoc.css +++ b/static/styles/jsdoc.css @@ -171,10 +171,6 @@ tt, code, kbd, samp{ padding: 1px 5px; } -pre { - padding-bottom: 1em; -} - .class-description { font-size: 130%; line-height: 140%; @@ -220,6 +216,7 @@ nav { overflow: auto; position: fixed; height: 100%; + overflow: auto; } nav #nav-search{ @@ -306,6 +303,23 @@ nav > h2 > a { color: #606 !important; } +nav .active { + position: relative; +} + +nav .active:before { + content: "▶"; + position: absolute; + font-size: 22px; + line-height: 15px; + left: -18px; +} + +nav .methods .active:before { + left: 8px; + font-size: 13px; +} + footer { color: hsl(0, 0%, 28%); margin-left: 250px; @@ -772,5 +786,4 @@ html[data-search-mode] .level-hide { url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg#source_sans_prolight') format('svg'); font-weight: 300; font-style: normal; - } From 2068e41ad2dc12b0bbdfc0c8b9707ff37bd46543 Mon Sep 17 00:00:00 2001 From: Kolasa Thomas Date: Fri, 3 May 2024 00:06:38 +0200 Subject: [PATCH 2/9] nit: remove unnesesery argument passing --- static/scripts/nav.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/scripts/nav.js b/static/scripts/nav.js index 4cb54074..33d64f28 100644 --- a/static/scripts/nav.js +++ b/static/scripts/nav.js @@ -8,7 +8,7 @@ function initNavigation() { const id = hash.substring(1); setActiveItem(id); - setActiveParentItem(id); + setActiveParentItem(); scrollToCurrentItem(); // bind to scroll to set id live @@ -55,7 +55,7 @@ function initNavigation() { item.classList.add('active'); } - function setActiveParentItem(id) { + function setActiveParentItem() { const currentLink = document.querySelector(`a[href='${file}']`); if (!currentLink) return; const item = currentLink.closest('li'); From 47687d28d8ad1c7fea69953d4ba96d800d9e636b Mon Sep 17 00:00:00 2001 From: Kolasa Thomas Date: Fri, 3 May 2024 02:19:18 +0200 Subject: [PATCH 3/9] fix: navigation publishing 'seen' respects only visible sections --- publish.js | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/publish.js b/publish.js index 74da11f1..8c784ec1 100644 --- a/publish.js +++ b/publish.js @@ -496,18 +496,18 @@ function buildNav(members, categoriesMembers) { ]; var order = docdash.sectionOrder || defaultOrder; var sections = { - Categories: buildCategoriesNav(categoriesMembers, seen, linkto), - Classes: buildMemberNav(members.classes, 'Classes', seen, linkto), - Modules: buildMemberNav(members.modules, 'Modules', {}, linkto), - Externals: buildMemberNav(members.externals, 'Externals', seen, linktoExternal), - Events: buildMemberNav(members.events, 'Events', seen, linkto), - Namespaces: buildMemberNav(members.namespaces, 'Namespaces', seen, linkto), - Mixins: buildMemberNav(members.mixins, 'Mixins', seen, linkto), - Tutorials: buildMemberNav(members.tutorials, 'Tutorials', seenTutorials, linktoTutorial), - Interfaces: buildMemberNav(members.interfaces, 'Interfaces', seen, linkto), - Global: buildMemberNavGlobal() + Categories: () => buildCategoriesNav(categoriesMembers, seen, linkto), + Classes: () => buildMemberNav(members.classes, 'Classes', seen, linkto), + Modules: () => buildMemberNav(members.modules, 'Modules', {}, linkto), + Externals: () => buildMemberNav(members.externals, 'Externals', seen, linktoExternal), + Events: () => buildMemberNav(members.events, 'Events', seen, linkto), + Namespaces: () => buildMemberNav(members.namespaces, 'Namespaces', seen, linkto), + Mixins: () => buildMemberNav(members.mixins, 'Mixins', seen, linkto), + Tutorials: () => buildMemberNav(members.tutorials, 'Tutorials', seenTutorials, linktoTutorial), + Interfaces: () => buildMemberNav(members.interfaces, 'Interfaces', seen, linkto), + Global: () => buildMemberNavGlobal() }; - order.forEach(member => nav += sections[member]); + order.forEach(section => nav += sections[section]()); return nav; } @@ -730,8 +730,10 @@ exports.publish = function(taffyData, opts, tutorials) { var members = helper.getMembers(data); var categoriesMembers = {}; - for (var category of env.conf.categoryList) { - categoriesMembers[category] = categories.getMembers(data, category); + if (env.conf.categoryList) { + for (var category of env.conf.categoryList) { + categoriesMembers[category] = categories.getMembers(data, category); + } } members.tutorials = tutorials.children; From 852cf527a39768e462f294ee70eda4b9dbff7b1a Mon Sep 17 00:00:00 2001 From: Kolasa Thomas Date: Fri, 3 May 2024 02:20:10 +0200 Subject: [PATCH 4/9] chore: version bump and changelog update --- CHANGELOG.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1327071..179bfeb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## Version 2.1.0 +* [feature] Added custom category plugin and categories navigation section +* [feature] Added active navigation (url hash updates on scroll) +* [fix] Fixed missing navigation items if one of sections omitted and item should be visible in other section + ## Version 2.0.2 * [feature] added noURLEncode option to not url encode links in the menu for unicode texts ## Version 2.0.1 diff --git a/package.json b/package.json index 91bd27f7..2bd4a40a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "docdash", - "version": "2.0.2", + "version": "2.1.0", "description": "A clean, responsive documentation template theme for JSDoc 3 inspired by lodash and minami", "main": "publish.js", "scripts": { From cddeccd6a52419903ce92d40644a105388cf601f Mon Sep 17 00:00:00 2001 From: Kolasa Thomas Date: Fri, 3 May 2024 02:21:03 +0200 Subject: [PATCH 5/9] docs: readme update with how to use "category" --- README.md | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 74383b76..9af0b81c 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,8 @@ See the config file for the [fixtures](fixtures/fixtures.conf.json) or the sampl "excludePattern": "(node_modules/|docs)" }, "plugins": [ - "plugins/markdown" + "plugins/markdown", + "node_modules/docdash/categories", ], "opts": { "template": "assets/template/docdash/", @@ -66,7 +67,8 @@ See the config file for the [fixtures](fixtures/fixtures.conf.json) or the sampl "templates": { "cleverLinks": false, "monospaceLinks": false - } + }, + "categoriesFile": "./categories.json" } ``` @@ -79,6 +81,7 @@ Docdash supports the following options: "static": [false|true], // Display the static members inside the navbar "sort": [false|true], // Sort the methods in the navbar "sectionOrder": [ // Order the main section in the navbar (default order shown here) + "Categories", "Classes", "Modules", "Externals", @@ -134,6 +137,38 @@ Docdash supports the following options: Place them anywhere inside your `jsdoc.json` file. +## Categories +Docdash supports custom categories through jsdoc plugin - plugin is available from docdash package. To use categories you need to load the plugin in your jsdoc.json and point location of your categories file: + +```json +"plugins": [ + "node_modules/docdash/categories", +], +"categoriesFile": "./categories.json" +``` + +Next you need to create your json with your category definitions where key is your category keyword - example categories.json +```json +{ + "model" : { + "displayName" : "Models and Collections" + }, + "component" : { + "displayName" : "Components" + } +} +``` + +Now you can use @category tag in your docs. +```js +/** + * @category model + */ +``` + +**If you have custom sectionOrder you need to add "Categories".** It is best to add Categories before all other sections because of how Docdash generates navigation elements. + + ## Contributors Thanks to [lodash](https://lodash.com) and [minami](https://github.com/nijikokun/minami). From 07463a539cfa736ef7dba192b61c3b7125321ec8 Mon Sep 17 00:00:00 2001 From: Kolasa Thomas Date: Fri, 3 May 2024 02:27:40 +0200 Subject: [PATCH 6/9] fix: add new categories plugin file to pacakge --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 2bd4a40a..091ec779 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ ], "files": [ "publish.js", + "categories.js", "static", "tmpl" ], From 514ead06ad7ad76f1ef60a70316c412ba01565f1 Mon Sep 17 00:00:00 2001 From: Kolasa Thomas Date: Sat, 4 May 2024 00:24:03 +0200 Subject: [PATCH 7/9] feat: make this repo main as separate npm package --- README.md | 7 +++++-- package.json | 12 +++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9af0b81c..d9bbb385 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ +# Docdash Extended +Clement Moron excellent Docdash template extended with support for @category plugin and some navigation improvements. + # Docdash -[![Build Status](https://api.travis-ci.org/clenemt/docdash.png?branch=master)](https://travis-ci.org/clenemt/docdash) [![npm version](https://badge.fury.io/js/docdash.svg)](https://badge.fury.io/js/docdash) [![license](https://img.shields.io/npm/l/docdash.svg)](LICENSE.md) +[![license](https://img.shields.io/npm/l/docdash.svg)](LICENSE.md) A clean, responsive documentation template theme for JSDoc 4. @@ -171,7 +174,7 @@ Now you can use @category tag in your docs. ## Contributors -Thanks to [lodash](https://lodash.com) and [minami](https://github.com/nijikokun/minami). +Thanks to [docdash](https://github.com/clenemt/docdash), [lodash](https://lodash.com) and [minami](https://github.com/nijikokun/minami). ## License Licensed under the Apache License, version 2.0. (see [Apache-2.0](LICENSE.md)). diff --git a/package.json b/package.json index 091ec779..5d7b1ce1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "docdash", - "version": "2.1.0", - "description": "A clean, responsive documentation template theme for JSDoc 3 inspired by lodash and minami", + "name": "docdash-extended", + "version": "1.0.0", + "description": "JSDoc 4 Docdash template extended with category plugin support", "main": "publish.js", "scripts": { "test": "jsdoc -c fixtures/fixtures.conf.json", @@ -17,11 +17,13 @@ "jsdoc": "latest", "watch-run": "latest" }, - "author": "Clement Moron ", + "author": "Tomasz Kolasa Date: Sat, 4 May 2024 00:31:08 +0200 Subject: [PATCH 8/9] fix: urls update --- package.json | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 5d7b1ce1..e8c55579 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "docdash-extended", - "version": "1.0.0", + "version": "1.0.1", "description": "JSDoc 4 Docdash template extended with category plugin support", "main": "publish.js", "scripts": { @@ -10,14 +10,14 @@ }, "repository": { "type": "git", - "url": "https://github.com/clenemt/docdash.git" + "url": "git+https://github.com/ThomasK0lasa/docdash-extended.git" }, "devDependencies": { "browser-sync": "latest", "jsdoc": "latest", "watch-run": "latest" }, - "author": "Tomasz Kolasa Date: Sat, 4 May 2024 00:42:03 +0200 Subject: [PATCH 9/9] fix: update paths to resolve new package --- README.md | 10 +++++----- package.json | 2 +- publish.js | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d9bbb385..519dafee 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,14 @@ See http://clenemt.github.io/docdash/ for a sample demo. :rocket: ## Install ```bash -$ npm install docdash +$ npm install docdash-extended ``` ## Usage Clone repository to your designated `jsdoc` template directory, then: ```bash -$ jsdoc entry-file.js -t path/to/docdash +$ jsdoc entry-file.js -t path/to/docdash-extended ``` ## Usage (npm) @@ -39,7 +39,7 @@ In your `jsdoc.json` file, add a template option. ```json "opts": { - "template": "node_modules/docdash" + "template": "node_modules/docdash-extended" } ``` @@ -61,7 +61,7 @@ See the config file for the [fixtures](fixtures/fixtures.conf.json) or the sampl "node_modules/docdash/categories", ], "opts": { - "template": "assets/template/docdash/", + "template": "assets/template/docdash-extended/", "encoding": "utf8", "destination": "docs/", "recurse": true, @@ -145,7 +145,7 @@ Docdash supports custom categories through jsdoc plugin - plugin is available fr ```json "plugins": [ - "node_modules/docdash/categories", + "node_modules/docdash-extended/categories", ], "categoriesFile": "./categories.json" ``` diff --git a/package.json b/package.json index e8c55579..de78421a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "docdash-extended", - "version": "1.0.1", + "version": "1.0.2", "description": "JSDoc 4 Docdash template extended with category plugin support", "main": "publish.js", "scripts": { diff --git a/publish.js b/publish.js index 8c784ec1..fa168842 100644 --- a/publish.js +++ b/publish.js @@ -9,7 +9,7 @@ var path = require('jsdoc/path'); var taffy = require('@jsdoc/salty').taffy; var template = require('jsdoc/template'); var util = require('util'); -var categories = require('docdash/categories.js'); +var categories = require('docdash-extended/categories.js'); var htmlsafe = helper.htmlsafe; var linkto = helper.linkto;