JSFiddle - React, Tailwind, and code Playground

by Anov Siradj

HTML

<h3>Create dir tree: </h3>
<br />

<ul id='tree'>
    <li>
        <div class="line">
            <input value='filename' />
            <span class='controls'> 
                &raquo; 
                <a href='#' data-func='add-sibling' >+sibling</a> |
                <a href='#' data-func='add-child'>+child</a>  |
                <a href='#' data-func='delete'>delete</a> 
            </span>
        </div>
    </li>
</ul>

<hr />

<h3>Output:</h3>
<br />
<pre id='out' cols='50' rows='20'></pre>

CSS

body {
    padding: 10px;
}

input, .line:before, .line:after {
    font-family: Courier New,monospace,serif;
    letter-spacing: -0.02em;
}
input {
    background: transparent;
    border: none;
}

ul {
    overflow: hidden;
}
.line:after {
    content: '│';
    position: absolute;
    bottom: 0;
    left: -1.42em;
    transform: rotate(90deg);
    -moz-transform: rotate(90deg);
    -webkit-transform: rotate(90deg);
}
li:last-child .line:before {
    content: '│';
    position: absolute;
    bottom: -0.69em;
    left: -2em;
    transform: scaleY(100);
    transform-origin: bottom;
    -moz-transform: scaleY(100);
    -moz-transform-origin: bottom;
    -webkit-transform: scaleY(100);
    -webkit-transform-origin: bottom;
}

li {
    list-style-type: none;
    padding-left: 2em;
}

.line {
    position: relative;
    display: inline-block;
}

.controls {
    visibility: hidden;    
}

.line:hover > .controls {
    visibility: visible;
}


a:hover {
    background-color: #ddd;
}

pre {
    line-height: 1.15em;
    font-family: Courier New,monospace,serif;
    letter-spacing: -0.02em;
}

JavaScript

var prefix = "├── ",
    prefix_last = "└── ",
    spacer = "│   ",
    spacer_e = "    ",
    ul_template = $('#tree'),
    li_template = $('li', ul_template).first().clone();


var action = {
    "add-sibling" : function(obj) {
        li_template.clone().insertAfter(obj);
    },
    "add-child" : function(obj) {
        var ul = obj.children('ul');
        if (ul.length === 0) {
            ul = $('<ul/>').appendTo(obj);
        }
        li_template.clone().appendTo(ul);
    },
    "delete" : function(obj) { obj.remove(); }
};

$(document).on('click', 'li .controls > a', function(e) {
    e.preventDefault();
    action[this.getAttribute('data-func')]($(this).closest('li'));
    rebuild_tree();
});

function get_subdir_text(obj, pad) {
   var padding = pad || "",
       out = "",
       items = obj.children("li"),
       last = items.length - 1;
   
   items.each(function(index) {
       var $this = $(this);
       out += padding 
           + ((index == last) ? prefix_last : prefix)
           + $this.find("> .line input").val() + "\n";
       var subdirs = $this.children("ul");
       if (subdirs.length) {
           
           out += get_subdir_text(subdirs, 
                    padding + ((index == last) ? spacer_e : spacer ));
       }
   });    
   return out;
}

function rebuild_tree() {    
    $('#out').text(get_subdir_text($('#tree')));
}

$("[data-func='add-sibling'],[data-func='add-child']", '#tree').click();

$(document).on('keyup', '#tree input', function(e){
    if (e.keyCode == '13') {
        if (e.shiftKey) {
            action['add-child']($(this).closest('li'));
        } else {
            action['add-sibling']($(this).closest('li'));
        }
    }

    rebuild_tree();
});