@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} <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>"
# Start
output += "<ul>"
doChildren(AllRootChildren())
output += "\n</ul>"
g.es(output)
</t>
<t tx="felix.20250909014304.4">@language python
header = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Nested Collapsible Sections — v7 (CSS tooltips, production)</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
: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;
}
</style>
</head>
<body class="py-4">
<div class="container">
<h1 class="text-center mb-4 fw-bold text-dark">Documentation with Collapsible Sections</h1>
<p class="text-center text-muted mb-5">Click on a heading to expand or collapse its content.</p>"""
footer = """
</div>
<!-- Bootstrap JS Bundle (collapse only) -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>"""
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}<div class="accordion-item">'
output += f'\n{indent} <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}">'
output += f'\n{indent} <span class="heading-text">{child.h}</span>'
output += f'\n{indent} <span class="toggle-arrow">&#9660;</span>'
output += f'\n{indent} <span class="tooltip-html" id="tooltip-section{positionId}" role="tooltip">'
output += f'\n{indent} Some tooltip: Level: {child.level()} Order: {child._childIndex}'
output += f'\n{indent} </span>'
output += f'\n{indent} </button>'
output += f'\n{indent} <div id="nested{positionId}" class="collapse">'
output += f'\n{indent} <div class="collapse-content">'
fixed_body = child.b.replace('\n', f'<br>\n{indent} ') # use import html and
output += f'\n{indent} <p>{fixed_body}</p>'
doChildren(child.children())
output += f'\n{indent} </div>'
output += f'\n{indent} </div>'
output += f'\n{indent}</div>'
# 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>