@brian (and others!) Here's something to wet your appetite:

For starters, here is the base minimal Leo script to make html that 
represents the outline. (easily customizable)



























*output = ''indentation = '        'def AllRootChildren():    p = 
c.rootPosition()    while p:        yield p        p.moveToNext()# 
Recursive function to generate the HTML structuredef doChildren(children):  
  global output    for child in children:        indent = indentation * 
child.level()        output += f"\n{indent}    <li>"        output += 
f"\n{indent}        <b>{child.h}</b>"        output += f"\n{indent}       
 <pre>\n{child.b}\n{indent}        </pre>"        output += f"\n{indent}   
     <ul>"        doChildren(child.children())        output += 
f"\n{indent}        </ul>"        output += f"\n{indent}    </li>"# 
Startoutput += "<ul>"doChildren(AllRootChildren())output += 
"\n</ul>"g.es(output)*

This generates valid HTML in the Leo Log Pane . (paste it in a new file and 
save it as a .html file to open it in your browser, remove the line with 
'child.b' if you only want the headlines)

*I've also made a script that makes a real, complete HTML document with the 
same layout and functionality as you've provided (with bootstrap accordions 
and everything, etc..) See the "brian-html" @button node in the attached 
leo File *

To have a button (or @command available) that does not reside in your 
current outline (and pollute the HTML output), put it in the @buttons 
(plural) section of your myLeoSettings.leo like so, and do alt+x 
*reload-settings 
*(or restart leo): you should see those 'html' buttons above any outline 
you open to export them as valid html documents in the log pane.

[image: myLeoSettings.png]

Check out the attached leo file for those 3 html generating @buttons!

I'll make a repository with even much better examples (which do not repeat 
data for clones, a single body pane on the side, keyboard navigation, 
instantly responsive like a read-only Leo, etc...) in a day or so... 

In the meantime, please try out those examples and give me your thought! :)

Félix
On Monday, September 8, 2025 at 11:51:55 PM UTC-4 Félix wrote:

> @Edward 
>
> You're right - unknown html files, unlike safer PDF file, images, word 
> documents, etc., are a security risk and people should not 'open/run' by 
> double clicking on a html file willy-nilly. 
>
> For instance, when I went through Brian's example *in notepad first, *just 
> to make sure...
>
> But I'm a web developper, so I could recognized the imports he made as 
> standard bootstrap from known 'content-delivery-networks' or "CDN" which 
> are harmless and expected.  *So yeah, people should not open those if 
> they are not sure of having confidence in them like I did.*
>
> (But some html file is still less dangerous than a python script, or a 
> leoscript that is ran (CTRL+B). because those have network AND full file 
> read/write capabilities all over your drives, unlike a html file in a 
> browser who can only do malignant stuff in your bookmarks, cookies, open / 
> redirect to phishing URLs etc and cannot read arbitrary files.)
>
> But like Thomas pointed out, the more common and dangerous stuff are 
> contributions to popular open-source repositories that are then compiled in 
> sites/software all over the place! :O 
>
> @Brian
>
> Examples are coming soon!! 
>
> Félix
>
>
> On Monday, September 8, 2025 at 4:04:06 PM UTC-4 [email protected] wrote:
>
>> Here's the bigger threat -
>>
>>
>> https://it.slashdot.org/story/25/09/08/1843235/hackers-hijack-npm-packages-with-2-billion-weekly-downloads-in-supply-chain-attack?utm_source=rss1.0mainlinkanon&utm_medium=feed
>>
>> On Monday, September 8, 2025 at 5:30:44 AM UTC-4 Edward K. Ream wrote:
>>
>>> On Mon, Sep 8, 2025 at 12:09 AM Félix <[email protected]> wrote:
>>>
>>> I've written the converter, and the javascript to allow navigation in 
>>>> the outline (expand collapse, etc...)
>>>
>>>
>>> What happens if someone replaces your javascript with malicious code and 
>>> distributes the resulting .html file?
>>>
>>> Edward
>>>
>>

-- 
You received this message because you are subscribed to the Google Groups 
"leo-editor" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/leo-editor/740e4f97-7cfc-4a64-8f6d-03c56f21a074n%40googlegroups.com.
<?xml version="1.0" encoding="utf-8"?>
<!-- Created by Leo: https://leo-editor.github.io/leo-editor/leo_toc.html -->
<leo_file xmlns:leo="https://leo-editor.github.io/leo-editor/namespaces/leo-python-editor/1.1"; >
<leo_header file_format="2"/>
<globals/>
<preferences/>
<find_panel_settings/>
<vnodes>
<v t="felix.20250909014304.1"><vh>@buttons</vh>
<v t="felix.20250909014304.3"><vh>@button minimal-html</vh></v>
<v t="felix.20250909014304.4"><vh>@button brian-html</vh></v>
<v t="felix.20250909014304.5"><vh>@button felix-html</vh></v>
</v>
</vnodes>
<tnodes>
<t tx="felix.20250909014304.1"></t>
<t tx="felix.20250909014304.3">@language python

output = ''
indentation = '        '

def AllRootChildren():
    p = c.rootPosition()
    while p:
        yield p
        p.moveToNext()

# Recursive function to generate the HTML structure
def doChildren(children):
    global output
    for child in children:
        indent = indentation * child.level()
        output += f"\n{indent}    &lt;li&gt;"
        output += f"\n{indent}        &lt;b&gt;{child.h}&lt;/b&gt;"
        output += f"\n{indent}        &lt;pre&gt;\n{child.b}\n{indent}        &lt;/pre&gt;"
        output += f"\n{indent}        &lt;ul&gt;"
        doChildren(child.children())
        output += f"\n{indent}        &lt;/ul&gt;"
        output += f"\n{indent}    &lt;/li&gt;"

# Start
output += "&lt;ul&gt;"
doChildren(AllRootChildren())
output += "\n&lt;/ul&gt;"
g.es(output)
</t>
<t tx="felix.20250909014304.4">@language python

header = """&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;

&lt;head&gt;
    &lt;meta charset="utf-8" /&gt;
    &lt;title&gt;Nested Collapsible Sections — v7 (CSS tooltips, production)&lt;/title&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1" /&gt;

    &lt;!-- Bootstrap CSS --&gt;
    &lt;link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"; rel="stylesheet"&gt;

    &lt;style&gt;
        :root {
            --base-padding: 1.25rem;
            --indent-step: 1rem;
        }

        body {
            font-family: Inter, system-ui, sans-serif;
            background-color: #f3f4f6;
        }

        .container {
            max-width: 1000px;
            background-color: white;
            border-radius: 0.75rem;
            box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
            padding: 2rem;
            --depth: 0;
        }

        .accordion-item .accordion-item {
            --depth: calc(var(--depth, 0) + 1);
        }

        .accordion-item {
            border-radius: 0.75rem;
            border: none;
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
            margin-bottom: 1rem;
            overflow: visible;
            /* prevent tooltip clipping */
        }

        .btn-accordion {
            display: flex;
            align-items: center;
            width: 100%;
            text-align: left;
            padding: 1rem var(--base-padding);
            border-radius: 0.75rem;
            border: none;
            transition: background-color 0.3s ease;
            font-weight: 600;
            gap: 0.25rem;
            padding-left: calc(var(--base-padding) + var(--depth, 0) * var(--indent-step));
            background-color: transparent;
            color: inherit;
            position: relative;
            /* required for tooltip positioning */
        }

        .btn-accordion:not(.bg-light) {
            background-color: #f8fafc;
        }

        .btn-accordion:hover {
            background-color: #e2e8f0;
        }

        .btn-accordion:focus {
            box-shadow: none;
            outline: 2px solid #3b82f6;
            outline-offset: 2px;
        }

        .heading-text {
            display: inline-block;
            user-select: none;
        }

        .toggle-arrow {
            font-size: 0.75rem;
            line-height: 1;
            transition: transform 0.25s ease;
            color: #000;
            flex-shrink: 0;
        }

        .btn-accordion[aria-expanded="false"] .toggle-arrow {
            transform: rotate(-90deg);
        }

        .btn-accordion[aria-expanded="true"] .toggle-arrow {
            transform: rotate(0deg);
        }

        .collapse-content {
            padding: 1rem var(--base-padding);
            background-color: #fff;
            padding-left: calc(var(--base-padding) + var(--depth, 0) * var(--indent-step));
        }

        /* Tooltip */
        .btn-accordion .tooltip-html {
            position: absolute;
            bottom: calc(100% + 8px);
            left: 50%;
            /* centered above button */
            transform: translateX(-50%) scale(0.95);
            opacity: 0;
            transition: opacity 0.12s ease, transform 0.12s ease;
            background: #1f2937;
            color: #fff;
            padding: 0.5rem 0.75rem;
            border-radius: 0.375rem;
            font-size: 0.875rem;
            max-width: 250px;
            white-space: normal;
            /* allow wrapping */
            text-align: center;
            z-index: 1200;
            pointer-events: none;
            box-shadow: 0 6px 18px rgba(31, 41, 55, 0.12);
        }

        .btn-accordion .tooltip-html::after {
            content: "";
            position: absolute;
            top: 100%;
            left: 50%;
            transform: translateX(-50%);
            border-left: 6px solid transparent;
            border-right: 6px solid transparent;
            border-top: 6px solid #1f2937;
        }

        .btn-accordion:hover .tooltip-html {
            transform: translateX(-50%) scale(1);
            opacity: 1;
        }
    &lt;/style&gt;
&lt;/head&gt;

&lt;body class="py-4"&gt;
    &lt;div class="container"&gt;
        &lt;h1 class="text-center mb-4 fw-bold text-dark"&gt;Documentation with Collapsible Sections&lt;/h1&gt;
        &lt;p class="text-center text-muted mb-5"&gt;Click on a heading to expand or collapse its content.&lt;/p&gt;"""

footer = """
    &lt;/div&gt;

    &lt;!-- Bootstrap JS Bundle (collapse only) --&gt;
    &lt;script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"&gt;&lt;/script&gt;
&lt;/body&gt;

&lt;/html&gt;"""

output = ''
indentation = '        '
positionId = 0

def AllRootChildren():
    p = c.rootPosition()
    while p:
        yield p
        p.moveToNext()

# Recursive function to generate the HTML structure
def doChildren(children):
    global output, positionId
    for child in children:
        positionId += 1
        indent = indentation * (child.level()+1)
        output += f'\n{indent}&lt;div class="accordion-item"&gt;'
        output += f'\n{indent}    &lt;button class="btn btn-accordion text-dark" data-bs-toggle="collapse"'
        output += f'\n{indent}        data-bs-target="#nested{positionId}" aria-expanded="false" aria-controls="nested{positionId}"'
        output += f'\n{indent}        aria-describedby="tooltip-section{positionId}"&gt;'
        output += f'\n{indent}        &lt;span class="heading-text"&gt;{child.h}&lt;/span&gt;'
        output += f'\n{indent}        &lt;span class="toggle-arrow"&gt;&amp;#9660;&lt;/span&gt;'
        output += f'\n{indent}        &lt;span class="tooltip-html" id="tooltip-section{positionId}" role="tooltip"&gt;'
        output += f'\n{indent}            Some tooltip: Level: {child.level()} Order: {child._childIndex}'
        output += f'\n{indent}        &lt;/span&gt;'
        output += f'\n{indent}    &lt;/button&gt;'
        output += f'\n{indent}    &lt;div id="nested{positionId}" class="collapse"&gt;'
        output += f'\n{indent}        &lt;div class="collapse-content"&gt;'
        fixed_body = child.b.replace('\n', f'&lt;br&gt;\n{indent}            ')  # use import html and 
        output += f'\n{indent}            &lt;p&gt;{fixed_body}&lt;/p&gt;'
        doChildren(child.children())
        output += f'\n{indent}        &lt;/div&gt;'
        output += f'\n{indent}    &lt;/div&gt;'
        output += f'\n{indent}&lt;/div&gt;'

# Start
output += header
doChildren(AllRootChildren())
output += footer
g.es(output)
</t>
<t tx="felix.20250909014304.5">@language python
g.es('Coming Soon!')</t>
</tnodes>
</leo_file>

Reply via email to