[{"title":"$:/plugins/crosseye/simple-outline","description":"Simple collapsible outline widget using details/summary elements","author":"Scott Sauyet","version":"0.5.0","core-version":">=5.3.0","plugin-type":"plugin","list":"readme installation usage license","dependents":"","type":"application/json","text":"{\"tiddlers\":{\"$:/plugins/crosseye/simple-outline/installation\":{\"title\":\"$:/plugins/crosseye/simple-outline/installation\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"!! Installing by drag-and-drop\\n\\nDrag  [[$:/plugins/crosseye/simple-outline]] to your wiki. Save and refresh.\\n\\nIf you want to be sure to have the latest version, make sure you're viewing this from from https://crosseye.github.io/TW5-SimpleOutlinePlugin/.  For older versions, see [[About|https://crosseye.github.io/TW5-SimpleOutlinePlugin/#About]].\\n\\n!! Installing manually on a Node wiki\\n\\nDownload the latest release from [[GitHub|https://github.com/CrossEye/TW5-SimpleOutlinePlugin/releases]], copy the folder `/wiki/plugins/simple-outline` into your wiki's `plugins` folder, and restart your wiki.\\n\\n!! Requirements\\n\\nTiddlyWiki 5.3.0 or later.  No other plugins are required.\"},\"$:/plugins/crosseye/simple-outline/license\":{\"title\":\"$:/plugins/crosseye/simple-outline/license\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"MIT License\\n\\nCopyright (c) 2026 Scott Sauyet\\n\\nPermission is hereby granted, free of charge, to any person obtaining a copy\\nof this software and associated documentation files (the \\\"Software\\\"), to deal\\nin the Software without restriction, including without limitation the rights\\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\\ncopies of the Software, and to permit persons to whom the Software is\\nfurnished to do so, subject to the following conditions:\\n\\nThe above copyright notice and this permission notice shall be included in all\\ncopies or substantial portions of the Software.\\n\\nTHE SOFTWARE IS PROVIDED \\\"AS IS\\\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\\nSOFTWARE.\\n\"},\"$:/plugins/crosseye/simple-outline/readme\":{\"title\":\"$:/plugins/crosseye/simple-outline/readme\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"The ''Simple Outline'' plugin adds a `<$simple-outline>` widget that renders a collapsible, nested outline from a plain-text description.  Each node can reference a tiddler — when the tiddler has body text, the node becomes expandable and shows that content inline.  Open/closed state is preserved for the duration of the session.\\n\\nSee the [[demo wiki|https://crosseye.github.io/TW5-SimpleOutlinePlugin/]] for full documentation and live examples.\\n\"},\"$:/plugins/crosseye/simple-outline/styles\":{\"title\":\"$:/plugins/crosseye/simple-outline/styles\",\"type\":\"text/css\",\"tags\":\"$:/tags/Stylesheet\",\"text\":\"/*-- simple-outline: default styles -----------------------------------------*/\\n\\n.outline {\\n\\tfont-family: 'Encode Sans', sans-serif;\\n\\tfont-size: 13pt;\\n}\\n\\n.outline .level-0  { margin-left: 0; }\\n.outline .level-1  { margin-left: 1em; }\\n.outline .level-2  { margin-left: 2em; }\\n.outline .level-3  { margin-left: 3em; }\\n.outline .level-4  { margin-left: 4em; }\\n.outline .level-5  { margin-left: 5em; }\\n.outline .level-6  { margin-left: 6em; }\\n.outline .level-7  { margin-left: 7em; }\\n.outline .level-8  { margin-left: 8em; }\\n.outline .level-9  { margin-left: 9em; }\\n.outline .level-10 { margin-left: 10em; }\\n\\n.outline details > summary {\\n\\tdisplay: flex;\\n\\talign-items: center;\\n\\tlist-style: none;        /* suppress native marker */\\n\\tcursor: pointer;\\n\\tpadding-top: 1px;\\n\\tpadding-bottom: 1px;\\n}\\n\\n.outline details > summary::-webkit-details-marker {\\n\\tdisplay: none;           /* suppress native marker (WebKit) */\\n}\\n\\n.outline details {\\n\\tmargin-top: 3px;\\n}\\n\\n.outline div {\\n\\tmargin-top: 1em;\\n}\\n\\n.outline div.item {\\n\\tmargin-top: 2px;\\n}\\n\\n/* Single .so-arrow span — toc-closed-icon SVG rendered inside; CSS rotates it\\n   90° when open.  One node instead of two avoids show/hide index complexity. */\\n.outline .so-arrow {\\n\\tdisplay: inline-flex;\\n\\talign-items: center;\\n\\tflex-shrink: 0;\\n\\twidth: 0.85em;\\n\\theight: 0.85em;\\n\\tmargin-right: 4px;\\n\\ttransition: transform 0.15s ease;\\n}\\n\\n.outline .so-arrow svg {\\n\\twidth: 0.85em;\\n\\theight: 0.85em;\\n}\\n\\n.outline details[open] > summary > .so-arrow {\\n\\ttransform: rotate(90deg);\\n}\\n\\n.outline h2 {\\n\\tfont-weight: bold;\\n\\tfont-size: 1.3em;\\n\\tpadding: 2px 5px;\\n\\tmargin-top: 0.8em;\\n}\\n\\n.outline .ltgraybox {\\n\\tdisplay: block;\\n\\tbackground-color: #f1f1f5;\\n\\tpadding: 10px 10px 5px 20px;\\n\\tmargin-top: 5px;\\n\\tmargin-bottom: 5px;\\n\\tcolor: #000;\\n}\\n\\n.outline table {\\n\\tborder: 1px solid #dddddd;\\n\\twidth: auto;\\n\\tmax-width: 100%;\\n\\tcaption-side: bottom;\\n\\tmargin-top: 1em;\\n\\tmargin-bottom: 1em;\\n\\tborder-collapse: collapse;\\n\\tborder-spacing: 0;\\n}\\n\\n.outline .altsand td {\\n\\tpadding: 10px;\\n\\tborder: 1px solid #aaa;\\n\\tvertical-align: top;\\n}\\n\\n.outline .altsand tr:nth-child(odd)  td { background-color: #F1F1D6; }\\n.outline .altsand tr:nth-child(even) td { background-color: #F7F7E7; }\\n\\n.outline ul {\\n\\tlist-style: none;\\n}\\n\\n.outline ul li::before {\\n\\tcontent: \\\"•\\\";\\n\\tcolor: #33f;\\n\\tfont-size: 140%;\\n\\tdisplay: inline-block;\\n\\tvertical-align: top;\\n\\tmargin-left: -0.8em;\\n\\twidth: 0.8em;\\n}\\n\\n.outline .thingraydk { color: #888; }\\n\\n/*-- so-version-links: inline download link list on Version History page -----*/\\n.so-version-links a { margin-right: 0.6em; }\\n\\n/*-- so-detail-title: linked tiddler title inside detail-template content ----*/\\n.outline .so-detail-title {\\n\\tdisplay: block;\\n\\tfont-weight: bold;\\n\\tcolor: #33c;\\n\\ttext-decoration: none;\\n\\tborder-bottom: 1px solid #aac;\\n\\tmargin-bottom: 0.4em;\\n}\\n\\n.outline .so-detail-title:hover {\\n\\ttext-decoration: underline;\\n}\\n\"},\"$:/plugins/crosseye/simple-outline/usage\":{\"title\":\"$:/plugins/crosseye/simple-outline/usage\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"!! Create Outline\\n\\nOnce you've installed `simple-outline` to your TiddlyWiki 5.3.0+ wiki and saved, you can create an outline tiddler and use the widget:\\n\\n```\\n<$simple-outline text={{MyOutlineTiddler}} class=\\\"outline\\\"/>\\n```\\n\\nOr supply the outline text inline:\\n\\n```\\n<$simple-outline text=\\\"\\\"\\\"\\n!! Section heading\\n\\n  Group label\\n    + tiddler-name\\n    + display text :: tiddler-title\\n\\\"\\\"\\\" class=\\\"outline\\\"/>\\n```\\n\\n!! Input Format\\n\\nEach line of the `text` attribute describes one node:\\n\\n|!Syntax |!Meaning |\\n|`!! Heading` |Section header — renders as `<h2>` |\\n|`+ tiddler-name` |Tiddler reference — expands to show its body if it exists |\\n|`display text :: tiddler-name` |Same, with an explicit label instead of the tiddler title |\\n|Plain text |Structural group — collapsible if it has children |\\n|Indentation |Each extra indent level (leading spaces) is one nesting level deeper |\\n\\nFor tiddler references, the displayed label comes from the first of these fields that exists on the tiddler: `summary` → `caption` → the name as written.\\n\\n!! Attributes\\n\\n|!Attribute |!Default |!Purpose |\\n|`text` |— |The outline description (required) |\\n|`class` |`outline` |CSS class(es) added to the container `<div>` |\\n|`summary-template` |— |Tiddler rendered inside each `<summary>` element; receives `currentTiddler` and `so-label` |\\n|`detail-template` |— |Tiddler rendered as the expanded body for each tiddler item; receives `currentTiddler` and `so-label` |\\n\\n!! CSS Hooks\\n\\nThe plugin generates a predictable DOM structure that you can target with CSS.\\n\\n|!Selector |!Element |\\n|`.outline` (or your `class`) |The container `<div>` |\\n|`.level-0` … `.level-10` |Applied to every element; controls indentation |\\n|`.outline details` |Any collapsible node (group or tiddler item with content) |\\n|`.outline details[open]` |A currently-expanded node |\\n|`.outline details > summary` |The clickable row header |\\n|`.so-arrow` |`<span>` inside `<summary>` holding the rotating arrow icon |\\n|`.so-label` |`<span>` wrapping the display text inside `<summary>` |\\n|`.so-toggle` |`<span>` after `.so-label` when `summary-template` is active — empty; useful for custom expand/collapse indicators |\\n|`.ltgraybox` |`<div>` wrapping expanded tiddler content |\\n|`div.item` |Non-expandable tiddler reference (tiddler has no body) |\\n|`.outline h2` |Section header |\\n\\nTo replace the default arrow with your own indicator:\\n\\n```css\\n/* hide the default arrow */\\n.my-outline .so-arrow { display: none !important; }\\n\\n/* use .so-toggle to show + / – instead */\\n.my-outline details > summary .so-toggle::before { content: \\\"+\\\"; }\\n.my-outline details[open] > summary .so-toggle::before { content: \\\"−\\\"; }\\n```\\n\\n!! summary-template\\n\\nWhen set, the named tiddler is rendered inside each `<summary>` element instead of plain text.  Two variables are available: `<<currentTiddler>>` (the item's tiddler title) and `<<so-label>>` (the pre-computed display label).\\n\\nExample template tiddler:\\n\\n```\\n<span class=\\\"so-label\\\"><<so-label>></span><span class=\\\"so-toggle\\\"></span>\\n```\\n\\nUsage:\\n\\n```\\n<$simple-outline text={{MyOutline}} class=\\\"my-outline\\\"\\n  summary-template=\\\"My Summary Template\\\"/>\\n```\\n\\n!! detail-template\\n\\nWhen set, the named tiddler is rendered as the expanded content for each item instead of transcluding the tiddler's body directly.  The same two variables are available.\\n\\nExample template tiddler — adds a linked title above the body:\\n\\n```\\n<$link to=<<currentTiddler>> class=\\\"so-detail-title\\\"><<currentTiddler>></$link>\\n<$transclude $tiddler=<<currentTiddler>> $mode=\\\"block\\\"/>\\n```\\n\\nCSS for the linked title:\\n\\n```css\\n.outline .so-detail-title {\\n    display: block;\\n    font-weight: bold;\\n    border-bottom: 1px solid #aac;\\n    margin-bottom: 0.4em;\\n}\\n```\\n\\nUsage:\\n\\n```\\n<$simple-outline text={{MyOutline}} class=\\\"outline\\\"\\n  detail-template=\\\"My Detail Template\\\"/>\\n```\\n\"},\"$:/plugins/crosseye/simple-outline/widgets/simple-outline.js\":{\"title\":\"$:/plugins/crosseye/simple-outline/widgets/simple-outline.js\",\"text\":\"/*\\\\\\ntitle: $:/plugins/crosseye/simple-outline/widgets/simple-outline.js\\ntype: application/javascript\\nmodule-type: widget\\n\\nSimple collapsible outline widget.\\n\\nUsage:\\n  <$simple-outline text=\\\"\\\"\\\"\\n  !! Section header\\n    + tiddler-name\\n    Group label\\n      + another-tiddler\\n  \\\"\\\"\\\" class=\\\"outline\\\"/>\\n\\n  or via transclusion/variable:\\n  <$simple-outline text={{MyOutlineTiddler}} class=\\\"my-outline\\\"/>\\n\\n  with a summary template:\\n  <$simple-outline text=\\\"\\\"\\\"...\\\"\\\"\\\" summary-template=\\\"My Summary Template\\\"/>\\n\\nInput format:\\n  !! prefix    → section header (h2)\\n  + prefix     → tiddler reference (reads summary/caption + text fields)\\n  plain text   → structural group node (collapsible if it has children)\\n  indentation  → nesting (children are indented under their parent)\\n  :: separator → display text :: tiddler title (overrides display label)\\n\\nsummary-template:\\n  Title of a tiddler to render inside each <summary> element for tiddler items.\\n  Two variables are set when the template is rendered:\\n    currentTiddler — the item tiddler title\\n    so-label       — the pre-computed label (summary → caption → display fallback)\\n  When absent, so-label is used as plain text.\\n\\ndetail-template:\\n  Title of a tiddler to render as the expanded body for tiddler items.\\n  The same two variables are set: currentTiddler and so-label.\\n  When absent, the tiddler's own body is transcluded directly.\\n\\nDisclosure arrows:\\n  Each collapsible <summary> receives two child spans:\\n    .so-arrow-closed  — rendered via <<toc-closed-icon>> ($:/core/images/right-arrow)\\n    .so-arrow-open    — rendered via <<toc-open-icon>>   ($:/core/images/down-arrow)\\n  CSS controls visibility based on details[open].  Custom styles that supply\\n  their own arrow mechanism should hide both spans with display:none !important.\\n\\nSession state:\\n  Open/closed state is stored in tiddlers under $:/state/simple-outline/<qualification>/<path>\\n  using the same <<qualification>> variable the core TOC macros use.  Path segments\\n  are the node label or tiddler title, so state survives reordering.  Navigating\\n  away and back restores the tree to the same open/closed positions.\\n\\\\*/\\n(function() {\\n\\\"use strict\\\";\\n\\nvar Widget = require(\\\"$:/core/modules/widgets/widget.js\\\").widget;\\n\\n//-- Parser (adapted from raw-js/index.js) ------------------------------------\\n\\nfunction sanitize(str) {\\n\\treturn str.trim().replace(/\\\\n\\\\s*\\\\n/g, \\\"\\\\n\\\");\\n}\\n\\nfunction cut(str, ch) {\\n\\tvar pos = str.indexOf(ch);\\n\\treturn pos === -1 ? [str, \\\"\\\"] : [str.slice(0, pos), str.slice(pos + 1)];\\n}\\n\\nfunction outdent(str) {\\n\\tvar spaces = Math.max(0, str.search(/\\\\S/));\\n\\tvar re = new RegExp(\\\"(^|\\\\\\\\n)[ \\\\\\\\t]{\\\" + spaces + \\\"}\\\", \\\"g\\\");\\n\\treturn str.replace(re, \\\"$1\\\");\\n}\\n\\nfunction makeChildren(str) {\\n\\treturn str === \\\"\\\" ? [] : str.split(/\\\\n(?!\\\\s)/).map(makeNode);\\n}\\n\\nfunction makeNode(str) {\\n\\tvar parts = cut(str, \\\"\\\\n\\\");\\n\\treturn {value: parts[0].trim(), children: makeChildren(outdent(parts[1]))};\\n}\\n\\nfunction extract(node) {\\n\\tvar v       = node.value;\\n\\tvar header  = v.startsWith(\\\"!!\\\");\\n\\tvar tidLink = v.startsWith(\\\"+\\\");\\n\\tvar content = v.slice(tidLink ? 1 : header ? 2 : 0).trim();\\n\\tvar parts   = content.split(\\\"::\\\").map(function(s) { return s.trim(); });\\n\\treturn {\\n\\t\\tvalue:    content,\\n\\t\\theader:   header,\\n\\t\\ttidLink:  tidLink,\\n\\t\\tdisplay:  parts[0],\\n\\t\\ttiddler:  parts.length > 1 ? parts[1] : parts[0],\\n\\t\\tchildren: node.children.map(extract)\\n\\t};\\n}\\n\\n//-- Parse tree node helpers --------------------------------------------------\\n\\nfunction macroAttr(name) {\\n\\treturn {type: \\\"macro\\\", value: {name: name, params: []}};\\n}\\n\\nfunction transcludeNode(tiddlerAttr, isBlock) {\\n\\treturn {\\n\\t\\ttype: \\\"transclude\\\",\\n\\t\\tattributes: {tiddler: tiddlerAttr},\\n\\t\\tisBlock: !!isBlock,\\n\\t\\tchildren: []\\n\\t};\\n}\\n\\n//-- Widget -------------------------------------------------------------------\\n\\nvar SimpleOutlineWidget = function(parseTreeNode, options) {\\n\\tthis.initialise(parseTreeNode, options);\\n};\\n\\nSimpleOutlineWidget.prototype = new Widget();\\n\\nSimpleOutlineWidget.prototype.render = function(parent, nextSibling) {\\n\\tthis.parentDomNode = parent;\\n\\tthis.computeAttributes();\\n\\tthis.execute();\\n\\tthis.referencedTiddlers = [];\\n\\n\\t// summaryTargets: [{label, tiddlerTitle, domNode}]\\n\\t// Filled during the tree walk when summary-template is in use.\\n\\tthis.summaryTargets = [];\\n\\t// iconTargets: [{closedDomNode, openDomNode}]\\n\\t// One entry per collapsible node; child widgets render the TW arrow icons.\\n\\tthis.iconTargets = [];\\n\\t// contentTargets: [{tiddlerTitle, label, domNode}]\\n\\t// Filled for every tiddler item that has expandable content.\\n\\tthis.contentTargets = [];\\n\\n\\tthis.summaryTemplate = this.getAttribute(\\\"summary-template\\\", \\\"\\\");\\n\\tif(this.summaryTemplate) {\\n\\t\\tthis.referencedTiddlers.push(this.summaryTemplate);\\n\\t}\\n\\tthis.detailTemplate = this.getAttribute(\\\"detail-template\\\", \\\"\\\");\\n\\tif(this.detailTemplate) {\\n\\t\\tthis.referencedTiddlers.push(this.detailTemplate);\\n\\t}\\n\\n\\t// Base path for state tiddlers.  <<qualification>> is a per-rendering-context\\n\\t// hash that the core TOC macros also use, ensuring instances in different\\n\\t// tiddlers don't share state.\\n\\tvar qualification = this.getVariable(\\\"qualification\\\", {defaultValue: \\\"\\\"});\\n\\tthis.stateBase = \\\"$:/state/simple-outline\\\" + qualification;\\n\\n\\tvar container = this.document.createElement(\\\"div\\\");\\n\\tcontainer.className = this.getAttribute(\\\"class\\\", \\\"outline\\\");\\n\\n\\tvar text = this.getAttribute(\\\"text\\\", \\\"\\\");\\n\\tif(text.trim()) {\\n\\t\\tvar tree = makeChildren(sanitize(text)).map(extract);\\n\\t\\tthis.renderTree(tree, container, 0, \\\"\\\");\\n\\t}\\n\\n\\tparent.insertBefore(container, nextSibling);\\n\\tthis.domNodes.push(container);\\n\\n\\t// Build allNodes and allDomNodes in parallel so we can render each child\\n\\t// widget into the right DOM node regardless of how many nodes each target\\n\\t// contributes (summary targets = 1 node, icon targets = 2, content = 1).\\n\\tvar self = this;\\n\\tvar allNodes    = [];\\n\\tvar allDomNodes = [];\\n\\n\\tthis.summaryTargets.forEach(function(t) {\\n\\t\\t// Wrap in two $set nodes so the template sees currentTiddler and so-label.\\n\\t\\tallNodes.push({\\n\\t\\t\\ttype: \\\"set\\\",\\n\\t\\t\\tattributes: {\\n\\t\\t\\t\\tname:  {type: \\\"string\\\", value: \\\"currentTiddler\\\"},\\n\\t\\t\\t\\tvalue: {type: \\\"string\\\", value: t.tiddlerTitle}\\n\\t\\t\\t},\\n\\t\\t\\tchildren: [{\\n\\t\\t\\t\\ttype: \\\"set\\\",\\n\\t\\t\\t\\tattributes: {\\n\\t\\t\\t\\t\\tname:  {type: \\\"string\\\", value: \\\"so-label\\\"},\\n\\t\\t\\t\\t\\tvalue: {type: \\\"string\\\", value: t.label}\\n\\t\\t\\t\\t},\\n\\t\\t\\t\\tchildren: [transcludeNode({type: \\\"string\\\", value: self.summaryTemplate}, false)]\\n\\t\\t\\t}]\\n\\t\\t});\\n\\t\\tallDomNodes.push(t.domNode);\\n\\t});\\n\\n\\tthis.iconTargets.forEach(function(t) {\\n\\t\\tallNodes.push(transcludeNode(macroAttr(\\\"toc-closed-icon\\\"), false));\\n\\t\\tallDomNodes.push(t.arrowDomNode);\\n\\t});\\n\\n\\tthis.contentTargets.forEach(function(t) {\\n\\t\\tif(self.detailTemplate) {\\n\\t\\t\\tallNodes.push({\\n\\t\\t\\t\\ttype: \\\"set\\\",\\n\\t\\t\\t\\tattributes: {\\n\\t\\t\\t\\t\\tname:  {type: \\\"string\\\", value: \\\"currentTiddler\\\"},\\n\\t\\t\\t\\t\\tvalue: {type: \\\"string\\\", value: t.tiddlerTitle}\\n\\t\\t\\t\\t},\\n\\t\\t\\t\\tchildren: [{\\n\\t\\t\\t\\t\\ttype: \\\"set\\\",\\n\\t\\t\\t\\t\\tattributes: {\\n\\t\\t\\t\\t\\t\\tname:  {type: \\\"string\\\", value: \\\"so-label\\\"},\\n\\t\\t\\t\\t\\t\\tvalue: {type: \\\"string\\\", value: t.label}\\n\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\tchildren: [transcludeNode({type: \\\"string\\\", value: self.detailTemplate}, true)]\\n\\t\\t\\t\\t}]\\n\\t\\t\\t});\\n\\t\\t} else {\\n\\t\\t\\tallNodes.push(transcludeNode({type: \\\"string\\\", value: t.tiddlerTitle}, true));\\n\\t\\t}\\n\\t\\tallDomNodes.push(t.domNode);\\n\\t});\\n\\n\\tif(allNodes.length) {\\n\\t\\tthis.makeChildWidgets(allNodes);\\n\\t\\tthis.children.forEach(function(child, i) {\\n\\t\\t\\tchild.render(allDomNodes[i], null);\\n\\t\\t});\\n\\t}\\n};\\n\\nSimpleOutlineWidget.prototype.refresh = function(changedTiddlers) {\\n\\tvar changedAttributes = this.computeAttributes();\\n\\tif(Object.keys(changedAttributes).length > 0) {\\n\\t\\tthis.refreshSelf();\\n\\t\\treturn true;\\n\\t}\\n\\t// Re-render the whole outline if a referenced tiddler changed structurally\\n\\t// (e.g. text went from empty to non-empty, or tiddler appeared/disappeared,\\n\\t// or the summary-template tiddler itself changed).\\n\\t// State tiddlers ($:/state/simple-outline/...) are intentionally NOT in\\n\\t// referencedTiddlers — toggling a node must not trigger a full re-render.\\n\\tvar needsRefresh = this.referencedTiddlers.some(function(title) {\\n\\t\\treturn !!changedTiddlers[title];\\n\\t});\\n\\tif(needsRefresh) {\\n\\t\\tthis.refreshSelf();\\n\\t\\treturn true;\\n\\t}\\n\\t// Otherwise let the transclude children refresh themselves in place.\\n\\treturn this.refreshChildren(changedTiddlers);\\n};\\n\\n//-- Tree rendering -----------------------------------------------------------\\n\\nSimpleOutlineWidget.prototype.renderTree = function(nodes, parent, level, path) {\\n\\tvar self = this;\\n\\tnodes.forEach(function(node) {\\n\\t\\tself.renderNode(node, parent, level, path);\\n\\t});\\n};\\n\\n// Add a single .so-arrow span to a <summary> element and register it as an\\n// icon target.  CSS rotates the icon 90° when details[open] — no show/hide.\\nSimpleOutlineWidget.prototype.addArrows = function(summary) {\\n\\tvar arrow = this.document.createElement(\\\"span\\\");\\n\\tarrow.className = \\\"so-arrow\\\";\\n\\tsummary.appendChild(arrow);\\n\\tthis.iconTargets.push({arrowDomNode: arrow});\\n};\\n\\n// Wire up session-state persistence for a <details> element.\\n// stateTitle is a qualified tiddler title unique to this node's position.\\n// On render: restore open attribute from the state tiddler if present.\\n// On toggle: write/delete the state tiddler (does NOT trigger full re-render\\n// because state tiddlers are not in referencedTiddlers).\\nSimpleOutlineWidget.prototype.wireState = function(el, stateTitle) {\\n\\tvar wiki = this.wiki;\\n\\tif(wiki.getTiddlerText(stateTitle, \\\"\\\") === \\\"open\\\") {\\n\\t\\tel.setAttribute(\\\"open\\\", \\\"\\\");\\n\\t}\\n\\tel.addEventListener(\\\"toggle\\\", function() {\\n\\t\\tif(el.open) {\\n\\t\\t\\twiki.setText(stateTitle, \\\"text\\\", null, \\\"open\\\");\\n\\t\\t} else {\\n\\t\\t\\twiki.deleteTiddler(stateTitle);\\n\\t\\t}\\n\\t});\\n};\\n\\nSimpleOutlineWidget.prototype.renderNode = function(node, parent, level, path) {\\n\\tvar self       = this;\\n\\tvar cls        = \\\"level-\\\" + level;\\n\\tvar doc        = this.document;\\n\\t// Use node label or tiddler title as path segment — stable across reordering.\\n\\tvar key        = node.tidLink ? node.tiddler : node.value;\\n\\tvar nodePath   = path + \\\"/\\\" + key;\\n\\tvar stateTitle = this.stateBase + nodePath;\\n\\tvar el, summary, contentDiv, p, h2, labelSpan, toggleSpan;\\n\\n\\tif(node.children.length) {\\n\\t\\tif(node.header) {\\n\\t\\t\\t// !! Section header with children\\n\\t\\t\\tel = doc.createElement(\\\"div\\\");\\n\\t\\t\\tel.className = cls;\\n\\t\\t\\th2 = doc.createElement(\\\"h2\\\");\\n\\t\\t\\th2.textContent = node.value;\\n\\t\\t\\tel.appendChild(h2);\\n\\t\\t\\tself.renderTree(node.children, el, level + 1, nodePath);\\n\\t\\t} else {\\n\\t\\t\\t// Plain group node — collapsible\\n\\t\\t\\tel = doc.createElement(\\\"details\\\");\\n\\t\\t\\tel.className = cls;\\n\\t\\t\\tself.wireState(el, stateTitle);\\n\\t\\t\\tsummary = doc.createElement(\\\"summary\\\");\\n\\t\\t\\tself.addArrows(summary);\\n\\t\\t\\tif(self.summaryTemplate) {\\n\\t\\t\\t\\t// Mirror the so-label/so-toggle structure the template produces,\\n\\t\\t\\t\\t// so the same CSS rules apply to group nodes.\\n\\t\\t\\t\\tlabelSpan = doc.createElement(\\\"span\\\");\\n\\t\\t\\t\\tlabelSpan.className = \\\"so-label\\\";\\n\\t\\t\\t\\tlabelSpan.textContent = node.value;\\n\\t\\t\\t\\ttoggleSpan = doc.createElement(\\\"span\\\");\\n\\t\\t\\t\\ttoggleSpan.className = \\\"so-toggle\\\";\\n\\t\\t\\t\\tsummary.appendChild(labelSpan);\\n\\t\\t\\t\\tsummary.appendChild(toggleSpan);\\n\\t\\t\\t} else {\\n\\t\\t\\t\\tlabelSpan = doc.createElement(\\\"span\\\");\\n\\t\\t\\t\\tlabelSpan.className = \\\"so-label\\\";\\n\\t\\t\\t\\tlabelSpan.textContent = node.value;\\n\\t\\t\\t\\tsummary.appendChild(labelSpan);\\n\\t\\t\\t}\\n\\t\\t\\tel.appendChild(summary);\\n\\t\\t\\tself.renderTree(node.children, el, level + 1, nodePath);\\n\\t\\t}\\n\\t\\tparent.appendChild(el);\\n\\n\\t} else if(node.tidLink) {\\n\\t\\t// + tiddler reference\\n\\t\\tvar tid = self.wiki.getTiddler(node.tiddler);\\n\\t\\tself.referencedTiddlers.push(node.tiddler);\\n\\n\\t\\tvar label = tid\\n\\t\\t\\t? (tid.fields.summary || tid.fields.caption || node.display)\\n\\t\\t\\t: node.display;\\n\\t\\tvar hasContent = tid && tid.fields.text && tid.fields.text.trim();\\n\\n\\t\\tif(hasContent) {\\n\\t\\t\\tel = doc.createElement(\\\"details\\\");\\n\\t\\t\\tel.className = cls;\\n\\t\\t\\tself.wireState(el, stateTitle);\\n\\t\\t\\tsummary = doc.createElement(\\\"summary\\\");\\n\\t\\t\\tself.addArrows(summary);\\n\\t\\t\\tif(self.summaryTemplate) {\\n\\t\\t\\t\\t// Template renders its own label structure; register for child widget.\\n\\t\\t\\t\\tself.summaryTargets.push({label: label, tiddlerTitle: node.tiddler, domNode: summary});\\n\\t\\t\\t} else {\\n\\t\\t\\t\\tlabelSpan = doc.createElement(\\\"span\\\");\\n\\t\\t\\t\\tlabelSpan.className = \\\"so-label\\\";\\n\\t\\t\\t\\tlabelSpan.textContent = label;\\n\\t\\t\\t\\tsummary.appendChild(labelSpan);\\n\\t\\t\\t}\\n\\t\\t\\tel.appendChild(summary);\\n\\t\\t\\tcontentDiv = doc.createElement(\\\"div\\\");\\n\\t\\t\\tcontentDiv.className = \\\"ltgraybox\\\";\\n\\t\\t\\tel.appendChild(contentDiv);\\n\\t\\t\\tself.contentTargets.push({tiddlerTitle: node.tiddler, label: label, domNode: contentDiv});\\n\\t\\t} else {\\n\\t\\t\\tel = doc.createElement(\\\"div\\\");\\n\\t\\t\\tel.className = cls + \\\" item\\\";\\n\\t\\t\\tp = doc.createElement(\\\"p\\\");\\n\\t\\t\\tp.textContent = label;\\n\\t\\t\\tel.appendChild(p);\\n\\t\\t}\\n\\t\\tparent.appendChild(el);\\n\\n\\t} else {\\n\\t\\t// Plain label (leaf, no + prefix)\\n\\t\\tel = doc.createElement(\\\"div\\\");\\n\\t\\tel.className = cls;\\n\\t\\tel.textContent = node.value;\\n\\t\\tparent.appendChild(el);\\n\\t}\\n};\\n\\nexports[\\\"simple-outline\\\"] = SimpleOutlineWidget;\\n\\n})();\\n\",\"type\":\"application/javascript\",\"module-type\":\"widget\"}}}","revision":"0","bag":"default"}]