<!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>
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;
}
// === 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();
}
})();