Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
function IndentationToList(options) {
'use strict';
return Main(options);
function Interpolate(template, variables) {
var names = Object.keys(variables);
var result = template;
for (var i = 0, iTop = names.length; i < iTop; i++) {
var name = names[i];
var value = variables[name];
var re = RegExp('\\b' + name + '\\b', 'g');
result = result.replace(re, value);
}
return result;
}
function Parse(lineText, lineNumber, indent) {
var aux = lineText.split(indent);
var result = {
'level': aux.length - 1,
'label': aux.pop(),
'line': lineNumber
};
return result;
}
function InitOptions(options) {
var defaultOptions = {
indent: ' ',
templates: {
folder: '<li><label class="toggle" for="ID">FOLDER</label> <input type="checkbox" id="ID" /><ol>CONTENT</ol></li>',
file: '<li class="file" href="">FILE</li>',
folderWithoutContent: '<li><label for="ID">FOLDER</label> <input type="checkbox" id="ID" /></li>',
contentWithoutFolder: '<ol>CONTENT</ol>'
},
text: ''
};
if (options.constructor.name !== 'Object') {
options = {
text: String(options)
};
}
var result = {
indent: options.indent || defaultOptions.indent,
templates: options.templates || defaultOptions.templates,
text: options.text
};
return result;
}
function InitLines(text) {
var result = text.
replace('\r\n', '\n'). // remove double line endings
replace('\r', '\n'). // remove odd line endings
split('\n'). // get lines
filter(function(value) { // remove empty lines
return value !== '';
});
result.push(''); // append an empty line
return result;
}
function InitStack() {
var result = {
data: [],
lastParent: null,
lastChild: null
};
result.lastChild = result.data;
return result;
}
function AddChild(stack, info, asSibling) {
var last = asSibling ? stack.lastParent : stack.lastChild;
info.parent = last;
var node = [info];
last.push(node);
stack.lastParent = info.parent;
stack.lastChild = node;
}
function ChildrenToHTML(stack, templates, contentWithoutFolder) {
var last = stack.lastParent;
var parent = last[0];
var children = last.splice(1); // last.length === 1
var items = [];
for (var i = 0, iTop = children.length; i < iTop; i++) {
var child = children[i];
var isStringified = typeof child[0] === 'string';
var isFolder = ! isStringified && child[0].label.search(/\/$/) >= 0;
var isFile = ! isStringified && ! isFolder;
var html;
switch (true) {
case isStringified:
html = child[0];
break;
case isFolder:
html = Interpolate(templates.folderWithoutContent, {ID: 'folder_' + child[0].line, FOLDER: child[0].label});
break;
case isFile:
html = Interpolate(templates.file, {FILE: child[0].label});
break;
}
items.push(html);
}
var template = contentWithoutFolder ? templates.contentWithoutFolder : templates.folder;
last[0] = Interpolate(template, {ID: 'folder_' + parent.line, FOLDER: parent.label, CONTENT: items.join('')});
stack.lastParent = parent.parent;
stack.lastChild = last;
}
function ParentsToHTML(stack, templates) {
stack.data.pop(); // remove the last child (due to the last empty line)
stack.data.unshift({ // prepend a fictitious parent to easily wrap up all real parents
label: '',
level: -1,
line: '',
parent: null
});
ChildrenToHTML(stack, templates, true);
var result = stack.lastChild[0];
return result;
}
function Main(options) {
options = InitOptions(options);
if (! options.text) {
return '';
}
var lines = InitLines(options.text);
var stack = InitStack();
var level = -1;
for (var i = 0, iTop = lines.length; i < iTop; i++) {
var line = lines[i];
var Parsed = Parse(line, i, options.indent);
switch (true) {
case Parsed.level > level:
AddChild(stack, Parsed);
level += 1; // go one level in
break;
case Parsed.level === level:
AddChild(stack, Parsed, true);
break;
case Parsed.level < level:
ChildrenToHTML(stack, options.templates);
level -= 1; // go only one level out, so that ChildrenToHTML can be called again (if any)
i -= 1; // still need to process the current line
break;
}
}
// thanks to the last empty line, all children have been stringified by now
var result = ParentsToHTML(stack, options.templates);
return result;
}
}
// SAMPLE TEXT
//---
// components/
// login/
// login.html
// login.js
// register/
// register.html
// register.js
//===
// SAMPLE USAGE
//---
// var text = document.getElementById('text').innerHTML;
// var converted = IndentationToList(text);
// document.getElementById('tree').innerHTML = converted;
//===
// CSS
//---
// See http://www.thecssninja.com/css/css-tree-menu for a suitable CSS
// Otherwise, try this, which I derived from that, by removing icons.
//---
// ol.tree
// {
// padding: 0;
// width: 300px;
// }
// li
// {
// position: relative;
// margin-left: -15px;
// list-style: none;
// }
// li.file
// {
// }
// li.file a
// {
// color: #4C4C4C;
// padding-left: 21px;
// text-decoration: none;
// display: block;
// }
// li input
// {
// position: absolute;
// left: 0;
// margin-left: 0;
// opacity: 0;
// z-index: 2;
// cursor: pointer;
// height: 1em;
// width: 1em;
// top: 0;
// }
// li input + ol
// {
// margin: -0.938em 0 0 -44px; /* 15px */
// height: 1em;
// }
// li input + ol > li { display: none; margin-left: -14px !important; padding-left: 1px; }
// li label
// {
// display: block;
// color:navy;
// }
// li label.toggle
// {
// cursor: pointer;
// font-weight: 600;
// }
// li input:checked + ol
// {
// margin: -1.25em 0 0 -44px; /* 20px */
// padding: 1.563em 0 0 80px;
// height: auto;
// }
// li input:checked + ol > li { display: block; margin: 0 0 0.125em; /* 2px */}
// li input:checked + ol > li:last-child { margin: 0 0 0.063em; /* 1px */ }
//===
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment