Modifying wikitext for headers to be collapsible?

So! I’ve done a bit of brainstorming and playing around with VSCode (and repeatedly asked ChatGPT to fix my errors :sweat_smile:) but I’ve created a non-tiddlywiki html demo of what I think would work if we replaced the vanilla JavaScript with built in TiddlyWiki functionality.

! WARNING !
This demo code does have JavaScript in it, and you should NOT just haphazardly trust code on the internet! but I think most of us are pretty used to that…

But, here’s my experiment, you can just throw this into a .txt file, rename it to .htm or .html (whatever is your cup of tea) and open it in browser to see it in action, or you can copy it into vscode.dev to look at the code itself. Nvm… I forgot TTW just… shows code. My brains a little fried haha…

It uses the html tags to apply the functionality to the paragraphs following the headers to emulate the similar functionality to the details disclosure element.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Collapsible Headers</title>
    <style>
        h2 {
            border-bottom: solid 1px rgba(128, 128, 128, 0.5); /* <<color muted-foreground>> is what I would use instead imo. This is just for stylistic testing, to see if i could get the border to disappear when collapsed. */
        }
        h2.closed {
            border: none;
        }
        .collapsible-content {
            display: block; /* Default state is open */
            padding-left: 20px; /* Indent the content for clarity */
            margin: 0;
        }
        .collapsible-header {
            cursor: pointer;
            padding: 10px;
            margin: 0;
        }
        .collapsible-header.closed:after {
            content:" ...";
            color: rgba(128, 128, 128, 0.5); /* <<color muted-foreground>> is what I would use instead imo. */
        }
        .collapsible-content.collapsed {
            display: none;
        }
    </style>
</head>
<body>
    <h1 class="collapsible-header h1-main-header">Main Header</h1>
    <div class="collapsible-content p-main-header">
        <h2 class="collapsible-header h2-sub-header">Sub Header</h2>
        <div class="collapsible-content p-sub-header">
            <p>Normal Text Here.</p>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', () => {
            const headers = document.querySelectorAll('.collapsible-header');

            headers.forEach(header => {
                header.addEventListener('click', () => {
                    const isOpen = !header.classList.contains('closed');
                    toggleContent(header, !isOpen);
                    header.classList.toggle('closed', isOpen);
                });
            });

            function toggleContent(header, shouldOpen) {
                const content = header.nextElementSibling;
                if (content) {
                    content.classList.toggle('collapsed', !shouldOpen);
                    // Handle nested collapsible content
                    let nestedContent = content.querySelector('.collapsible-content');
                    while (nestedContent) {
                        nestedContent.classList.toggle('collapsed', !shouldOpen);
                        nestedContent = nestedContent.querySelector('.collapsible-content');
                    }
                }
            }
        });
    </script>
</body>
</html>

I think instead of using the tag type as the tag prefix to accompany collapsible-header/content, just having a sluggified header text with a wikified suffix (sub-header-019357135 … kinda… stuff.)

How about a screenshot…

I’ve been reading, but been too busy to contribute.

I was picturing an output much like what you used here:

    <h1 class="collapsible-header h1-main-header">Main Header</h1>
    <div class="collapsible-content p-main-header">
        <h2 class="collapsible-header h2-sub-header">Sub Header</h2>
        <div class="collapsible-content p-sub-header">
            <p>Normal Text Here.</p>
        </div>
    </div>

But the big concern is this is awkward to achieve. Transforming the headers is simple, and determining the nesting isn’t too complex. But finding the content between the headers might be much more awkward. Expanding a bit, this still works as expected:

 <h1 class="collapsible-header h1-main-header">Main Header</h1>  
  <h1 class="collapsible-header h1-main-header">Main Header</h1>
  <div class="collapsible-content p-main-header">
    <h2 class="collapsible-header h2-sub-header">Sub Header</h2>
    <div class="collapsible-content p-sub-header">
      <p>Normal Text Here.</p>
    </div>
    <h2 class="collapsible-header h2-sub-header">Another Sub Header</h2>
    <div class="collapsible-content p-sub-header">
      <p>More Normal Text Here.</p>
    </div>
  </div>
  <h1 class="collapsible-header h1-main-header">Another Main Header</h1>
  <div class="collapsible-content p-main-header">
    <h2 class="collapsible-header h2-sub-header">A third Sub Header</h2>
    <div class="collapsible-content p-sub-header">
      <p>Non-normal Text Here.</p>
    </div>
  </div>

But finding which content applies to which header, and then nesting that in a DIV you can expand and collapse is trickier. It’s certainly doable, but that will make the job much more challenging. I suppose with Jeremy’s suggestion of a totally new parser, this wouldn’t be as hard – just maintaining a stack of open headers would probably do it. But I’m picturing this running as an optional step after the syntax tree has been generated, which I think would be more flexible, and easy to enable/disable as desired.

Either way, I think this is far from trivial.

BTW, I posted my extension of your code to http://scott.sauyet.com/Tiddlywiki/Demo/Misc/ttw10654/demo1.html.

thank you very much!:blush:

And, yes I agree. the h1-main-header area atleast in my mind, was supposed to be a placeholder for whatever is used as the text of the header. However I’m still unsure if it communicates well enough to avoid them being too ambiguous. And yea… a bit awkward for applying css.

So for instance if I had a header of “Tomatos make good soup” it would take that, slugify it, and use it for the tags used in linking the header and div elements, and then in the event you have two headers with the same name or nested and with the same name, wikifying it would atleast keep them unique.

kinda like,

\define header(name) <h1 class="collapsible-header h1-$name$-((wikify))">$name$</h1>

which would change your code to: (also what is in double parenthesis is palceholder for stuff I currently don’t remember how to do.)

<h1 class="collapsible-header h1-main-header-01">Main Header</h1>  
  <h1 class="collapsible-header h1-main-header-02">Main Header</h1>
  <div class="collapsible-content div-main-header-01">
    <h2 class="collapsible-header h2-sub-header-01">Sub Header</h2>
    <div class="collapsible-content div-sub-header-01">
      <p>Normal Text Here.</p>
    </div>
    <h2 class="collapsible-header h2-another-sub-header-01">Another Sub Header</h2>
    <div class="collapsible-content div-another-sub-header-01">
      <p>More Normal Text Here.</p>
    </div>
  </div>
  <h1 class="collapsible-header h1-another-main-header-01">Another Main Header</h1>
  <div class="collapsible-content div-another-main-header-01">
    <h2 class="collapsible-header h2-a-third-sub-header-01">A third Sub Header</h2>
    <div class="collapsible-content div-a-third-sub-header-01">
      <p>Non-normal Text Here.</p>
    </div>
  </div>

I wanted to skip the divs entirely and use the content (collapsible-content) tags on the p elements instead, but being that they are inheretly inline elements, they did not function as that :sweat_smile:

Sidenote, something I am just now noticing but, it should be div-sub-header instead of p, thats deprecated. whoopsie!

Do you have any suggestions that would be less awkward in implementation? this was the only approach I could come up with unfortunately.

No, I think it’s an intrinsically difficult problem. You have a series of nodes in the document, and you want to wrap contiguous collections of them into new containers.

It’s not hard to structure a document directly with those collections. The <section> tag is built on this idea, and <section>s nest well. But TiddlyWiki is not designed to simply give you that nesting.

Outlining has a sad history in HTML, I’m afraid.

I do have the ability, I’m quite sure, to write a JS algorithm that does this conversion for HTML. But I don’t have time for a few days to even try, and I still have no idea how to port that into TW.

1 Like

If I am not wrong in your example you do delimit the collapsible part of the content using a div.

If you are prepared to do this back in standard html or tiddlywiki this should be trivial. It can be implemented in a details tag, custom html tag, using a widget and a number of other methods. So this makes me wonder if you are going down an unnecessary path?

  • The methods I am thinking of require the content author to decide the extent of the collapsible content, which is not automatic but is more powerful.

Speculation.

Perhaps with an editor toolbar button to assist, we make use of a HTML <hn> with an attribute that names the heading, then allow another HTML element that indicates the end of the collapsible area to also have the matching name?

  • This pattern if made to work could be used to support other features.

Post script;

If we created a widget we could add a range of sources to use within headings

This was offered as my original idea in the beginning actually! but it was deemed to be bad practice, and also from an accessibility point of view, it would be problematic for screen readers.

this is one of the reasons I don’t want to change the syntax used for normal headers as well, so it can work with other plugins and features, or without any of these modifications or work with backwards compatibility possible.

As far as making it a manual process, that would mean typing each tiddler using the normal header tags, correct? in which case, yes, that will always remain backwards compatible, but it takes away ease of use from users :pensive:

Would it be reasonable that it if the support for an extension is not available it resulted in un-collapsible headings?

hm… like, an external source, like a web browser extension, or viewing TW’s code on a different platform like VSCode?

I was thinking any html or in this case tiddltywiki without the matching css or plugin support etc…
Although I am still searching for a solution, there exists similar features already in HTML/CSS for example addressing the nth element. eg collapse the nth h1 element.

The idea would be to make the heading or an other element clickable and this would toggle the nominated “section” from display or not, ideally with a little animation.

  • In the absence of the extra css just display the section

Would you be able to provide some dummy code as an example of what your thinking of?

because right now I’m thinking that maybe your proposing the use of a button thats generated along with each header element and then a reveal widget generated along with it, or something to that effect.

Yes and No, I still have a very open mind, If I were a CSS Genius I would first see if I can rule that out.

  • eg !.classname for the header and a section also with the classname as an attribute.

However I am of the firm opinion rather than fiddle with the h tags we would insert or wrap headers and or content as you would with a button or reveal, but a much more elegant way, and as requested that defaults to Un collapsed.

1 Like

I am working on a widget to do this and it works however the content is overlayed on the content below;

\function open.section.state() [all[current]addprefix[/]addprefix<id>addprefix[$:/state/open.section/]]
\widget $.section(pre id:0)
<!-- empty lines required for operation -->
<$button class="tc-btn-invisible" popup=<<open.section.state>> >

<$transclude $variable=pre/>

</$button>

<$reveal type=popup stateTitle=<<open.section.state>> position=popup animate=yes>

<$slot $name="ts-raw"/>
<br>
</$reveal>
\end $.section

Before

<$.section pre="!! Test heading">

Body

</$.section>

After

Note: Since the pre value is wikify able you can use headings but this is a more generic solution. The content of the pre is available and clickable, we could have alternate text for post, ie when closed.

Its still buggy

It may be possible to use the parameters widget, and Genisis widget to power the reveal widget which would make all the reveal parameters available when using the $.section widget, ie pass through the parameters,

The id could be drawn from pre as it is most likely unique