jdsteinbach / eleventy-plugin-toc

11ty plugin to generate a TOC from page content
61 stars 19 forks source link

Allow wrapper to take a function #35

Open bennypowers opened 2 years ago

bennypowers commented 2 years ago

Hi! 👋

Firstly, thanks for your work on this project! 🙂

Today I used patch-package to patch eleventy-plugin-toc@1.1.5 for the project I'm working on.

Users may need finer control over the wrapping element. In this patch, I allow wrapper to be a binary function taking content and label, which returns HTML. the wrapping element must apply aria-label="${label}"

Here is the diff that solved my problem:

diff --git a/node_modules/eleventy-plugin-toc/.eleventy.js b/node_modules/eleventy-plugin-toc/.eleventy.js
index 6a926b8..f87651d 100644
--- a/node_modules/eleventy-plugin-toc/.eleventy.js
+++ b/node_modules/eleventy-plugin-toc/.eleventy.js
@@ -4,7 +4,7 @@ const parseOptions = require('./src/ParseOptions')
 module.exports = (eleventyConfig, globalOpts) => {
   globalOpts = globalOpts || {}
   eleventyConfig.namespace(globalOpts, () => {
-    eleventyConfig.addFilter('toc', (content, localOpts) => {
+    eleventyConfig.addFilter('toc', function (content, localOpts) {
       return buildTOC(content, parseOptions(localOpts, globalOpts))
     })
   })
diff --git a/node_modules/eleventy-plugin-toc/src/BuildTOC.js b/node_modules/eleventy-plugin-toc/src/BuildTOC.js
index 50ced4a..00e3e60 100644
--- a/node_modules/eleventy-plugin-toc/src/BuildTOC.js
+++ b/node_modules/eleventy-plugin-toc/src/BuildTOC.js
@@ -29,11 +29,12 @@ const BuildTOC = (text, opts) => {

   const label = wrapperLabel ? `aria-label="${wrapperLabel}"` : ''

-  return wrapper
-    ? `<${wrapper} class="${wrapperClass}" ${label}>
-        ${BuildList(headings, ul, flat)}
-      </${wrapper}>`
-    : BuildList(headings, ul, flat)
+  const content = BuildList(headings, ul, flat);
+  return (
+      typeof wrapper === 'function' ? wrapper(content, label)
+    : wrapper ? `<${wrapper} class="${wrapperClass}" ${label}>${content}</${wrapper}>`
+    : content
+  );
 }

 module.exports = BuildTOC

This issue body was partially generated by patch-package.

behoppe commented 1 year ago

@bennypowers I echo your appreciation for this plugin. I wonder if you could say a bit more about how one might use the changes that you made. I am a JavaScript newbie, but I am certainly looking for finer control of the wrapper classes, and I think you're solving my problem (?).

I am hoping to use this plugin to generate HTML that looks like the following, with specific classes for not only the nav wrapper but also the ul, li, and a selectors:

<nav id="bd-toc-nav" class="page-toc">
    <ul class="visible nav section-nav flex-column">
        <li class="toc-h2 nav-item toc-entry">
            <a class="reference internal nav-link" href="#lorem-ipsum">
                 Lorem ipsum
            </a>
       </li>
       <li class="toc-h2 nav-item toc-entry">
           <a class="reference internal nav-link" href="#morbi-tincidunt">
                Morbi tincidunt
           </a>
       </li>
    </ul>
</nav>

@bennypowers can I use your comment plus some new JS function (from selector to the appropriate class-string) to do this? Thank you for any tips.

bennypowers commented 1 year ago

What you can do is copy the contents of that snippet to a file called patches/eleventy-toc-plugin+1.1.5, then add patch-package to your postinstall script in package.json to apply the patch every time you run npm i or npm ci

"scripts": "npx patch-package"
behoppe commented 1 year ago

Thank you @bennypowers that is very helpful. I am still a bit confused about how to pass values from my site to the updated TOC plugin (probably because I am new to JavaScript). Would you mind sharing a small example? With the current TOC plugin, my site adds it like so, which I think needs to change after wrapper becomes a function, right? Thank you very much for your help.

  eleventyConfig.addPlugin(pluginTOC, {
    tags: ['h1', 'h2'],
    wrapper: 'nav',
    wrapperClass: 'visible nav section-nav flex-column',
    ul: true
  });
bennypowers commented 1 year ago

I think that with my patch you could do this:

eleventyConfig.addPlugin(pluginTOC, {
  tags: ['h1', 'h2'],
  wrapper(content, label) {
    return `<nav id="bd-toc-nav" class="page-toc" aria-label="Site navigation">
  ${content.replaceAll('<ul>', '<ul class="visible nav section-nav flex-column">').replaceAll('<li>', '<li class="toc-h2 nav-item toc-entry">')}
  </nav>`
  },
  wrapperClass: 'visible nav section-nav flex-column',
  ul: true
});

If you wanted to get more complicated, you consider parsing the content html with something like cheerio or parse5 and alter the syntax tree. That would be a challenge, but rewarding, considering how you've described your level of comfort with js and web technologies.

behoppe commented 1 year ago

Thank you @bennypowers I got my TOC to work. Yeah! I had to name the file patches/eleventy-plugin-toc+1.1.5 (moving toc to end) and I added a replaceAll for the a selector like so:

  eleventyConfig.addPlugin(pluginTOC, {
    tags: ['h1', 'h2'],
    wrapper(content, label) {
      return `<nav id="bd-toc-nav" class="page-toc" aria-label="Site navigation">
    ${content.replaceAll('<ul>', '<ul class="visible nav section-nav flex-column">').replaceAll('<li>', '<li class="toc-h2 nav-item toc-entry">').replaceAll('<a href', '<a class="reference internal nav-link" href')}
    </nav>`
    },
    wrapperClass: 'visible nav section-nav flex-column',
    ul: true
  });

I confess I am still hazy on when exactly my scripts get executed. It seems like I need to run npx patch-package manually, probably because my scripts is wrong. I wonder if you see something amiss below? Thanks again for your help!

 "scripts": {
    "build": "npx @11ty/eleventy",
    "bench": "DEBUG=Eleventy:Benchmark* npx @11ty/eleventy",
    "watch": "npx @11ty/eleventy --watch",
    "serve": "npx @11ty/eleventy --serve",
    "start": "npx @11ty/eleventy --serve",
    "debug": "DEBUG=* npx @11ty/eleventy",
    "postinstall": "patch-package"
  },
behoppe commented 1 year ago

Just wanted to confirm that it works! (Despite my confusion about script execution.) Thanks again for this patch.