From 8da3c9317a40d137c8e5db99d6b2d902b659d408 Mon Sep 17 00:00:00 2001 From: Ole Eskild Steensen Date: Wed, 7 Dec 2022 15:30:29 +0100 Subject: [PATCH] Implemented support for table of content --- .eleventy.js | 49 +----- package-lock.json | 17 ++ package.json | 1 + src/helpers/constants.js | 3 +- src/helpers/utils.js | 47 ++++++ src/site/_includes/components/graphScript.njk | 2 +- src/site/_includes/components/sidebar.njk | 61 ++++--- src/site/_includes/layouts/note.njk | 2 +- src/site/index.11tydata.js | 12 ++ src/site/index.njk | 2 +- src/site/notes/notes.11tydata.js | 2 +- src/site/styles/digital-garden-base.scss | 159 +++++++++++++----- 12 files changed, 250 insertions(+), 107 deletions(-) create mode 100644 src/helpers/utils.js diff --git a/.eleventy.js b/.eleventy.js index 4c1ee82..1e981d1 100644 --- a/.eleventy.js +++ b/.eleventy.js @@ -3,6 +3,10 @@ const markdownIt = require("markdown-it"); const fs = require('fs'); const matter = require('gray-matter'); const faviconPlugin = require('eleventy-favicon'); +const tocPlugin = require('eleventy-plugin-toc'); + +const {headerToId, namedHeadingsFilter} = require("./src/helpers/utils") + module.exports = function(eleventyConfig) { let markdownLib = markdownIt({ @@ -184,6 +188,7 @@ module.exports = function(eleventyConfig) { eleventyConfig.addPassthroughCopy("src/site/img"); eleventyConfig.addPlugin(faviconPlugin, { destination: 'dist' }); + eleventyConfig.addPlugin(tocPlugin, {ul:true, tags: ['h1','h2', 'h3', 'h4', 'h5', 'h6']}); eleventyConfig.addFilter('jsonify', function (variable) { return JSON.stringify(variable); }); @@ -201,47 +206,3 @@ module.exports = function(eleventyConfig) { }; }; - -function headerToId(heading) { - return slugify(heading); -} - -//https://github.com/rstacruz/markdown-it-named-headings/blob/master/index.js -function namedHeadingsFilter(md, options) { - md.core.ruler.push('named_headings', namedHeadings.bind(null, md)); -} - -function namedHeadings(md, state) { - - var ids = {} - - state.tokens.forEach(function(token, i) { - if (token.type === 'heading_open') { - var text = md.renderer.render(state.tokens[i + 1].children, md.options) - var id = headerToId(text); - var uniqId = uncollide(ids, id) - ids[uniqId] = true - setAttr(token, 'id', uniqId) - } - }) -} - -function uncollide(ids, id) { - if (!ids[id]) return id - var i = 1 - while (ids[id + '-' + i]) { i++ } - return id + '-' + i -} - -function setAttr(token, attr, value, options) { - var idx = token.attrIndex(attr) - - if (idx === -1) { - token.attrPush([attr, value]) - } else if (options && options.append) { - token.attrs[idx][1] = - token.attrs[idx][1] + ' ' + value - } else { - token.attrs[idx][1] = value - } -} diff --git a/package-lock.json b/package-lock.json index 70d2bca..2a4b6cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "axios": "^0.26.1", "dotenv": "^10.0.0", "eleventy-favicon": "^1.1.2", + "eleventy-plugin-toc": "^1.1.5", "fs-file-tree": "^1.1.1", "gray-matter": "^4.0.3", "lunr": "^2.3.9", @@ -1729,6 +1730,14 @@ "to-ico": "^1.1.5" } }, + "node_modules/eleventy-plugin-toc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/eleventy-plugin-toc/-/eleventy-plugin-toc-1.1.5.tgz", + "integrity": "sha512-Fo5AZZSBH8CKvz0axJQA9nmnTFOflAMFrngaKER4rOz3C6oDwqxK8N+kNFepmIsieTPkrH+iREWLJ+/9j5JjUg==", + "dependencies": { + "cheerio": "^1.0.0-rc.10" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -7649,6 +7658,14 @@ "to-ico": "^1.1.5" } }, + "eleventy-plugin-toc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/eleventy-plugin-toc/-/eleventy-plugin-toc-1.1.5.tgz", + "integrity": "sha512-Fo5AZZSBH8CKvz0axJQA9nmnTFOflAMFrngaKER4rOz3C6oDwqxK8N+kNFepmIsieTPkrH+iREWLJ+/9j5JjUg==", + "requires": { + "cheerio": "^1.0.0-rc.10" + } + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", diff --git a/package.json b/package.json index ed31bfa..af9b980 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "axios": "^0.26.1", "dotenv": "^10.0.0", "eleventy-favicon": "^1.1.2", + "eleventy-plugin-toc": "^1.1.5", "fs-file-tree": "^1.1.1", "gray-matter": "^4.0.3", "lunr": "^2.3.9", diff --git a/src/helpers/constants.js b/src/helpers/constants.js index b6ed90a..110be22 100644 --- a/src/helpers/constants.js +++ b/src/helpers/constants.js @@ -5,5 +5,6 @@ exports.ALL_NOTE_SETTINGS= [ "dgShowLocalGraph", "dgShowInlineTitle", "dgShowFileTree", - "dgEnableSearch" + "dgEnableSearch", + "dgShowToc" ]; \ No newline at end of file diff --git a/src/helpers/utils.js b/src/helpers/utils.js new file mode 100644 index 0000000..03ddd38 --- /dev/null +++ b/src/helpers/utils.js @@ -0,0 +1,47 @@ +const slugify = require("@sindresorhus/slugify"); + +function headerToId(heading) { + return slugify(heading); +} + +function namedHeadings(md, state) { + + var ids = {} + + state.tokens.forEach(function(token, i) { + if (token.type === 'heading_open') { + var text = md.renderer.render(state.tokens[i + 1].children, md.options) + var id = headerToId(text); + var uniqId = uncollide(ids, id) + ids[uniqId] = true + setAttr(token, 'id', uniqId) + } + }) +} + +function uncollide(ids, id) { + if (!ids[id]) return id + var i = 1 + while (ids[id + '-' + i]) { i++ } + return id + '-' + i +} + +function setAttr(token, attr, value, options) { + var idx = token.attrIndex(attr) + + if (idx === -1) { + token.attrPush([attr, value]) + } else if (options && options.append) { + token.attrs[idx][1] = + token.attrs[idx][1] + ' ' + value + } else { + token.attrs[idx][1] = value + } +} + +//https://github.com/rstacruz/markdown-it-named-headings/blob/master/index.js +exports.namedHeadingsFilter = function (md, options) { + md.core.ruler.push('named_headings', namedHeadings.bind(null, md)); +} + +exports.headerToId = headerToId; \ No newline at end of file diff --git a/src/site/_includes/components/graphScript.njk b/src/site/_includes/components/graphScript.njk index 0fd67bf..8ed2d14 100644 --- a/src/site/_includes/components/graphScript.njk +++ b/src/site/_includes/components/graphScript.njk @@ -117,7 +117,7 @@ ctx.textBaseline = 'top'; ctx.fillText(label, node.x, node.y + nodeR + 2); }) - .linkColor(() => getCssVar("--text-normal")) + .linkColor(() => getCssVar("--text-muted") || getCssVar("--text-normal")) .graphData(gData) .onNodeClick(node => { window.location = node.url; diff --git a/src/site/_includes/components/sidebar.njk b/src/site/_includes/components/sidebar.njk index 3ba90e9..bcd70e9 100644 --- a/src/site/_includes/components/sidebar.njk +++ b/src/site/_includes/components/sidebar.njk @@ -5,32 +5,53 @@ {%if settings.dgShowLocalGraph === true%}
-
Interactive graph
+
Connected Pages
- {%endif%} + {%endif%} - {%if settings.dgShowBacklinks === true %} - - - + -{%if settings.dgShowLocalGraph === true %} - {%include "components/graphScript.njk"%} -{% endif %} + {%if settings.dgShowLocalGraph === true %} + {%include "components/graphScript.njk"%} + {% endif %} \ No newline at end of file diff --git a/src/site/_includes/layouts/note.njk b/src/site/_includes/layouts/note.njk index 8201b25..4c09231 100644 --- a/src/site/_includes/layouts/note.njk +++ b/src/site/_includes/layouts/note.njk @@ -27,7 +27,7 @@ permalink: "notes/{{ page.fileSlug | slugify }}/" {% endif %} {{ content | link | highlight | safe}} - {% if settings.dgShowBacklinks === true or settings.dgShowLocalGraph === true%} + {% if settings.dgShowBacklinks === true or settings.dgShowLocalGraph === true or settings.dgShowToc === true%} {%include "components/sidebar.njk"%} {% endif %} diff --git a/src/site/index.11tydata.js b/src/site/index.11tydata.js index af1e457..21c20e0 100644 --- a/src/site/index.11tydata.js +++ b/src/site/index.11tydata.js @@ -4,6 +4,11 @@ const settings = require("../helpers/constants"); const wikilink = /\[\[(.*?\|.*?)\]\]/g +const markdownIt = require("markdown-it"); +const md = markdownIt({ + html: true, +}).use(require("../helpers/utils").namedHeadingsFilter); + function caselessCompare(a, b) { return a.toLowerCase() === b.toLowerCase(); } @@ -111,6 +116,13 @@ module.exports = { return currentnote.data.page.fileSlug; } return ""; + }, + content: (data) => { + const currentnote = data.collections.gardenEntry && data.collections.gardenEntry[0]; + if (currentnote && currentnote.template && currentnote.template.frontMatter && currentnote.template.frontMatter.content) { + return md.render(currentnote.template.frontMatter.content); + } + return ""; } } } \ No newline at end of file diff --git a/src/site/index.njk b/src/site/index.njk index 9dddb70..d2b1019 100644 --- a/src/site/index.njk +++ b/src/site/index.njk @@ -24,7 +24,7 @@ {%- endfor -%} - {%if settings.dgShowBacklinks === true or settings.dgShowLocalGraph === true%} + {% if settings.dgShowBacklinks === true or settings.dgShowLocalGraph === true or settings.dgShowToc === true%} {%include "components/sidebar.njk" %} {%endif%} diff --git a/src/site/notes/notes.11tydata.js b/src/site/notes/notes.11tydata.js index fad1677..ff26d7d 100644 --- a/src/site/notes/notes.11tydata.js +++ b/src/site/notes/notes.11tydata.js @@ -99,6 +99,6 @@ module.exports = { noteSettings[setting] = settingValue; }); return noteSettings; - } + } } } \ No newline at end of file diff --git a/src/site/styles/digital-garden-base.scss b/src/site/styles/digital-garden-base.scss index 44e43b4..c21d45a 100644 --- a/src/site/styles/digital-garden-base.scss +++ b/src/site/styles/digital-garden-base.scss @@ -6,6 +6,7 @@ body { overflow-x: hidden; } + .content { max-width: 800px; margin: auto; @@ -72,14 +73,13 @@ ul.task-list { .sidebar { position: fixed; top: 50%; - transform: translateY(-50%); + transform: translateY(calc(-50% + 75px)); right: 0; height: 100%; min-width: 25px; display: flex; z-index: 3; max-width: 350px; - margin-top: 75px; } .expand-line { @@ -92,11 +92,67 @@ ul.task-list { padding-right: 20px; width: 100%; overflow-y: auto; + display: flex; + flex-direction: column; + justify-content: flex-start; + height: 87%; +} + +.toc { + padding-right: 5px; + background-color: var(--background-primary); + padding: 10px; + border-radius: 10px; +} + +.toc-container { + font-size: 1rem; + max-height: 220px; + overflow-y: auto; + margin-bottom:10px; + border-left: 1px solid var(--text-accent); + ul { + list-style-type: none; + padding-inline-start: 15px !important; + margin-top: 0; + margin-bottom: 0; + } + ul:not(:first-child) { + margin-bottom:3px; + } + + li{ + padding-top: 4px; + &::before{ + content: '# ' !important; + color: var(--text-accent); + font-size: 0.8rem; + } + a{ + text-decoration: none; + &:hover{ + text-decoration: underline; + } + } + } +} + +.toc-title-container { + display: flex; + justify-content: flex-start; + .toc-title { + font-size: 1.2rem !important; + color: var(--h6-color); + width: fit-content; + padding: 3px 7px 3px 0; + border-radius: 10px 10px 0 0; + } } .backlinks { - margin-top: 50px; - background-color: var(--background-secondary); + flex: 1; + margin-top: 10px; + background-color: var(--background-primary); border-radius: 10px; padding: 10px; @@ -107,20 +163,37 @@ ul.task-list { } } +.backlink-list { + border-left: 1px solid var(--text-accent); + max-height: 200px; + overflow-y: auto; + overflow-x:hidden; +} + .backlink-card { - padding-bottom: 4px; + padding-bottom: 8px; border-radius: 4px; width: 100%; font-size: 1rem; + margin-left: 10px; + color: var(--text-accent); + i { + font-size: 0.8rem; + } } -.graph{ - .graph-title{ +.no-backlinks-message{ + font-size: 0.8rem; + color: var(--text-normal); +} + +.graph { + .graph-title { width: fit-content; background-color: var(--background-secondary); margin: 10px 0 0 0; - padding: 12px; - font-size: 18px !important; + padding: 3px 7px; + font-size: 1.2rem !important; border-radius: 10px 10px 0 0; color: var(--h6-color); } @@ -141,9 +214,10 @@ ul.task-list { margin-top: 50px; } - .graph{ + .graph { display: none; } + .backlinks { margin-top: 0; } @@ -152,24 +226,24 @@ ul.task-list { .filetree-sidebar { margin: 0; z-index: 10; - padding: 10px; - top:0px; - left:0; - position: fixed; - height: 100%; - background-color: var(--background-secondary); - color: var(--text-muted); - overflow-y:auto; + padding: 10px; + top: 0px; + left: 0; + position: fixed; + height: 100%; + background-color: var(--background-secondary); + color: var(--text-muted); + overflow-y: auto; width: 250px; } .empty-navbar { - display: flex; - justify-content: flex-end; + display: flex; + justify-content: flex-end; margin-bottom: -60px; @media(max-width: 800px) { - justify-content: center; + justify-content: center; } .search-button { @@ -191,12 +265,12 @@ ul.task-list { align-items: center; .navbar-inner { - display: flex; - align-items: center; + display: flex; + align-items: center; cursor: pointer; } - .search-button{ + .search-button { @media(max-width: 800px) { min-width: 120px; @@ -207,6 +281,11 @@ ul.task-list { .notelink { padding: 5px 0 5px 25px; + a{ + &:hover { + text-decoration: underline !important; + } + } } .foldername-wrapper { @@ -219,31 +298,31 @@ ul.task-list { } .filelist { - border-left: 1px solid var(--text-accent); } -.notelink.active-note{ - a{ +.notelink.active-note { + a { color: var(--text-accent); } + color: var(--text-accent); background-color: var(--background-primary); transform: translateX(10px); } -.fullpage-overlay{ - background-color: rgba(0,0,0,0.5); - position: absolute; - top:0; - right:0; - left:0; - bottom:0; +.fullpage-overlay { + background-color: rgba(0, 0, 0, 0.5); + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; z-index: 5 } .search-container { - background-color: rgba(0,0,0,0.5); + background-color: rgba(0, 0, 0, 0.5); position: fixed; top: 0; right: 0; @@ -279,6 +358,7 @@ ul.task-list { color: var(--text-primary); } + .search-box input:focus { outline: none; } @@ -317,6 +397,7 @@ ul.task-list { color: var(--text-secondary); margin-right: 20px; } + .search-link { display: block; margin-bottom: 4px; @@ -333,14 +414,16 @@ ul.task-list { margin: 10px 65px; border: 1px solid var(--text-normal); cursor: pointer; - >span{ + + >span { padding: 3px 3px 3px 10px; } - >i{ + + >i { margin-left: 10px; } - &:hover{ + &:hover { border: 1px solid var(--text-accent); }