The Tree Navigation Panel automatically scans article section markers and builds a live, clickable navigation list inside the editor.
Allows writers to instantly jump between large article sections without endless scrolling.
Improves workflow speed, organization, readability, and editing efficiency, especially for knowledge bases, tutorials, documentation, and long-form technical articles.
Reason It Is needed
While working on Articles for our Carrz-Fox-Fire Promotions site, I needed a way to get to a section faster than either scrolling the textarea or doing a Search to find the section.
This way, I can add in the ˜˜ name of section ˜˜ and then when I need to go back, I click on the [Tree] button, and select that section and the textarea scrolls directly to it.
Best Use case
In large Articles, you can go by the sections that either have a Chapter Heading or start talking about something different.
Test out the demo to see how it works. Remove the ˜˜ from the textarea with the [Tree] tab open, to see it disappear, and then appear again when you add it back.
The [Button Tab] buttons do not work. They are here for show only. The sole focus is on the Tree Navigation and how it works.
Live Template Example - Family Tree Navigation Demo
[HTML - Load.html]
CFFCS | CarrzSynEdit: | HTML (Hyper Text Markup Language)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>BBCode Hint Tree Demo</title>
</head>
<body>

<div class="wrapper">

    <div class="sidebar">
        <div class="tabs">
            <button class="tab-btn active" data-tab="buttons">Button Tab</button>
            <button class="tab-btn" data-tab="tree">Tree Tab</button>
        </div>

        <div class="tab-content active" id="buttons">
            <div class="button-list">
                <button>Insert Table</button>
                <button>Insert Tabs</button>
                <button>Insert Code</button>
                <button>Insert Hint</button>
            </div>
        </div>

        <div class="tab-content" id="tree">
            <div id="treeList"></div>
        </div>
    </div>
    <div class="editor-wrap">

<textarea id="editor">
˜˜ Database Setup ˜˜

Some content here.

˜˜ Authentication ˜˜

Some more content here.

˜˜ API Connections ˜˜

Even more content.

˜˜ Family Tree ˜˜

This is another section.

˜˜ SQL Setup ˜˜

Database notes.
</textarea>
    </div>
</div>
</body>
</html>

[CSS - Load.css]
CFFCS | CarrzSynEdit: | CSS (Cascading Style Sheets)
body{
    margin: 0;
    font-family: Arial, Helvetica, sans-serif;
    background: #f4f7fb;
    color: #263442;
}

.wrapper{
    display: flex;
    height: 100vh;
}

.sidebar{
    width: 280px;
    background: #fff;
    border-right: 1px solid #d6dce8;
    display: flex;
    flex-direction: column;
}

.tabs{
    display: flex;
    border-bottom: 1px solid #d6dce8;
}

.tab-btn{
    flex: 1;
    padding: 14px;
    border: none;
    background: #eef4fb;
    cursor: pointer;
    font-weight: bold;
}

.tab-btn.active{
    background: #4c90d9;
    color: #fff;
}

.tab-content{
    display: none;
    padding: 15px;
    overflow: auto;
    flex: 1;
}

.tab-content.active{
    display: block;
}

.tree-item{
    padding: 10px 12px;
    margin-bottom: 6px;
    border-radius: 8px;
    background: #f5f8fc;
    cursor: pointer;
}

.tree-item:hover{
    background: #dcecff;
}

.editor-wrap{
    flex: 1;
    padding: 20px;
}

textarea{
    width: 100%;
    height: calc(100vh - 40px);
    resize: none;
    border: 1px solid #cfd9e5;
    border-radius: 10px;
    padding: 18px;
    font-size: 15px;
    line-height: 1.7;
    font-family: Consolas, monospace;
    box-sizing: border-box;
}

.button-list{
    display: flex;
    flex-direction: column;
    gap:8px;
}

.button-list button{
    padding: 12px;
    border: none;
    border-radius: 8px;
    background: #4c90d9;
    color: #fff;
    cursor: pointer;
    font-weight: bold;
}

Dete: July 8, 2026
Tree Navigation Scroll Bug Fix

Updated Function:
- cffScrollEditorToTreePosition()

What Changed:
- Replaced the fixed line-height scroll calculation.
- Added a mirror-element-based position calculation that measures the actual rendered text.
- Scroll position now accounts for wrapped lines, different font sizes, padding, and textarea width.
- Tree navigation now jumps to the correct section even in very long articles.

Only this function was changed. No other Tree logic, parsing, or Favorites functionality was modified.

[JavaScript - Load.js]
CFFCS | CarrzSynEdit: | JS (JavaScript)
// === BBCode Hint Tree Demo - Updated Tree Code ===
(function () {

    function byId(id) {
        return document.getElementById(id);
    }

    function cffSetFavoritesTab(tabName) {
        var buttons = document.querySelectorAll('.tab-btn');
        var buttonPanel = byId('buttons');
        var treePanel = byId('tree');

        buttons.forEach(function (btn) {
            btn.classList.toggle('active', btn.getAttribute('data-tab') === tabName);
        });

        if (buttonPanel) {
            buttonPanel.classList.toggle('active', tabName === 'buttons');
        }

        if (treePanel) {
            treePanel.classList.toggle('active', tabName === 'tree');
        }

        if (tabName === 'tree') {
            cffBuildEditorTree();
        }
    }

    function cffBuildEditorTree() {
        var editor = byId('editor');
        var treeList = byId('treeList');

        if (!editor || !treeList) {
            return;
        }

        var text = editor.value || ';
        var regex = /˜˜\s*(.*?)\s*˜˜/g;
        var match;
        var found = false;

        treeList.innerHTML = ';

        while ((match = regex.exec(text)) !== null) {
            var title = (match[1] || ').trim();
            var position = match.index;

            if (title === ' || /^\d+$/.test(title)) {
                continue;
            }

            found = true;

            var item = document.createElement('button');
            item.type = 'button';
            item.className = 'tree-item';
            item.textContent = title;
            item.setAttribute('data-tree-position', String(position));

            item.addEventListener('click', function () {
                var pos = parseInt(this.getAttribute('data-tree-position'), 10);

                if (isNaN(pos)) {
                    return;
                }

                cffScrollEditorToTreePosition(editor, pos);
            });

            treeList.appendChild(item);
        }

        if (!found) {
            var empty = document.createElement('div');
            empty.className = 'tree-empty';
            empty.textContent = 'No tree headings found. Use headings like ˜˜ Database Setup ˜˜ in the editor.';
            treeList.appendChild(empty);
        }
    }

    function cffScrollEditorToTreePosition(editor, pos) {
        if (!editor || isNaN(pos)) {
            return;
        }

        editor.focus();
        editor.setSelectionRange(pos, pos);

        var computed = window.getComputedStyle(editor);
        var mirror = document.createElement('div');
        var marker = document.createElement('span');

        mirror.style.position = 'absolute';
        mirror.style.left = '-99999px';
        mirror.style.top = '0';
        mirror.style.visibility = 'hidden';
        mirror.style.whiteSpace = 'pre-wrap';
        mirror.style.wordWrap = 'break-word';
        mirror.style.overflowWrap = 'break-word';
        mirror.style.boxSizing = computed.boxSizing;
        mirror.style.width = editor.clientWidth + 'px';
        mirror.style.paddingTop = computed.paddingTop;
        mirror.style.paddingRight = computed.paddingRight;
        mirror.style.paddingBottom = computed.paddingBottom;
        mirror.style.paddingLeft = computed.paddingLeft;
        mirror.style.borderTopWidth = computed.borderTopWidth;
        mirror.style.borderRightWidth = computed.borderRightWidth;
        mirror.style.borderBottomWidth = computed.borderBottomWidth;
        mirror.style.borderLeftWidth = computed.borderLeftWidth;
        mirror.style.fontFamily = computed.fontFamily;
        mirror.style.fontSize = computed.fontSize;
        mirror.style.fontWeight = computed.fontWeight;
        mirror.style.fontStyle = computed.fontStyle;
        mirror.style.letterSpacing = computed.letterSpacing;
        mirror.style.textTransform = computed.textTransform;
        mirror.style.lineHeight = computed.lineHeight;

        mirror.textContent = editor.value.substring(0, pos);
        marker.textContent = editor.value.substring(pos, pos + 1) || ' ';
        mirror.appendChild(marker);

        document.body.appendChild(mirror);

        var markerTop = marker.offsetTop;
        var targetTop = markerTop - Math.floor(editor.clientHeight * 0.25);

        editor.scrollTop = Math.max(0, targetTop);

        document.body.removeChild(mirror);
    }

    function wireDemoTreePanel() {
        var editor = byId('editor');

        document.querySelectorAll('.tab-btn').forEach(function (btn) {
            if (btn.getAttribute('data-cff-tree-wired') === '1') { return; }
            btn.setAttribute('data-cff-tree-wired', '1');

            btn.addEventListener('click', function () {
                cffSetFavoritesTab(this.getAttribute('data-tab') || 'buttons');
            });
        });

        if (editor && editor.getAttribute('data-cff-tree-input-wired') !== '1') {
            editor.setAttribute('data-cff-tree-input-wired', '1');
            editor.addEventListener('input', cffBuildEditorTree);
        }

        cffBuildEditorTree();
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', wireDemoTreePanel);
    } else {
        wireDemoTreePanel();
    }

})();