run.php

<?php

echo str_repeat("\n",9);

if (!empty($_SERVER['REDIRECT_URL'])&&$_SERVER['REDIRECT_URL']!='/'){
    include __DIR__.$_SERVER['REDIRECT_URL'];
    exit;
}
?>
<html>
<head>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.10.1/beautify-html.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.10.1/beautify.js"></script>
<style>

    html,body,table,tr {
        min-width:100%;
        min-height:100%;
        padding:0;
        margin:0;
        height:100%;
    }
    td {
        width: 33%;
        min-height:100%;
    }

    td > div {
        width:100%;
        height:100%;
    }

    #blocks > div {
        cursor:pointer;
    }

    #template_creator {
        display:none;
    }

</style>

<script>

    class ContentContainer {

        constructor(node){
            this.newestNodeId = 0;
            this.node = node;
            const thisArg = this;
            node.addEventListener('dragover',function(event){event.preventDefault();});
            node.ondrop = function(event){thisArg.dropElement.call(thisArg,event);} ;
            console.log(node);
            console.log('content container constructed');
        }
        dropElement(event){
            console.log('dropElement__start'); 
            event.preventDefault();
            const cursorY = event.clientY;
            const elementHtml = event.dataTransfer.getData('html');
            const insertable = this.htmlAsNode(elementHtml);
            console.log(insertable);
            if (event.target.getAttribute("id")=="content"){
                this.placeNodeByEvent(insertable,event);
            } else {
                this.placeNearNode(event,insertable);
            }
            insertable.removeAttribute('draggable');
            console.log('dropElement__endish'); 
            this.updateHtmlOutput();
            console.log('dropElement__end'); 

        }
        updateHtmlOutput(){
            const html = document.getElementById("html");
            html.value = html_beautify(document.getElementById("content").innerHTML);
        }
        getFirstChild(parent){
            var insertNode = parent.firstChild;
            while (insertNode !== null
                    &&typeof insertNode !== undefined 
                    &&insertNode.nodeName=="#text"
                    &&insertNode.nextSibling !== null){
                insertNode = insertNode.nextSibling;
            }
            return insertNode;
        }
        placeNearNode(event,newNode){
            var lastParent = event.target;
            const cursorY = event.clientY;
            const content = document.getElementById("content");
            
            const existingNode = this.isCursorOnTopHalf(cursorY,lastParent) 
                                    ? lastParent : lastParent.nextSibling;
            this.placeBefore(newNode,existingNode);
        }
        placeBefore(newNode,existingNode){
            const content = document.getElementById("content");
            existingNode.parentNode.insertBefore(newNode,existingNode);
            newNode.removeAttribute('draggable');
            const id = this.getNewNodeId();
            wyg.nodeMap[id] = {'settings':settings==null ? '' : settings, 'node':newNode};
        }
        getNewNodeId(){
            if (this.newestNodeId>wyg.nodeMap.length){
                this.newestNodeId += 1;
            } else {
                this.newestNodeId = wyg.nodeMap.length;
            }
            return this.newestNodeId;
        }
        htmlAsNode(html){
            var div = document.createElement('div');
            div.innerHTML = html.trim();

            return div.firstChild;
        }
        placeNodeByEvent(insertable,event){
            return this.placeNodeNearXY(
                    insertable, 
                    event.cursorX,
                    event.cursorY,
                    event.target
                    );
        }
        placeNodeNearXY(insertable,x,y,targetNode = null){
            console.log('placeNodeNearXY__start');
            if (targetNode==null)targetNode = document.getElementById("content");
            let relatedChild = this.getNearestChild(targetNode,x,y,1);
            let goesAbove = this.isCursorOnTopHalf(y,relatedChild);
            //let insertable = this.blockAsContentNode(this.htmlAsNode(insertHtml));
            console.log(relatedChild);
            const existingNode = (relatedChild === undefined ||relatedChild === null || typeof relatedChild ===undefined) 
                                    ? targetNode.firstChild : 
                                        (goesAbove 
                                        ? relatedChild 
                                        : relatedChild.nextSibling
                                        );

            this.placeBefore(insertable,existingNode);
            console.log('placeNodeNearXY__end');

        }
        isCursorOnTopHalf(y,node){
            if (typeof node !== 'object' 
                ||node.nodeName=='#text')return true;
            var range = document.createRange();
            range.selectNode(node);
            //range.selectNodeContents(node);
            var rects = range.getClientRects();
            if (typeof rects === undefined
                ||typeof rects[0] === undefined)return true;
            const nodeY = rects[0]['y'];
            const nodeEnd = nodeY + node.offsetHeight;
            const avg = (nodeY+nodeEnd)/2;

            if (y<avg)return true;
            else return false;
        }
        getNearestChild(parentNode,x,y,maxDepth=1){
            return this.getNearestVerticalChild(parentNode,y,1);
        }
        nodeLocation(node){
            var range = document.createRange();
            range.selectNode(node);
            var rects = range.getClientRects();
            if (rects.length > 0) {
                return rects[0]['y'];
            }
        }
        getNearestVerticalChild(parentNode,y){
            console.log('    getNearestVerticalChild__start');
            var closestChild;
            var diff = 0;
            var nodeY;
            var childNode;
            console.log('\t\tparentNode');
            console.log(parentNode);
            for(childNode of parentNode.childNodes){
                // if (typeof childNode !== 'object'
                //     ||childNode.nodeName=='#text')continue;
                nodeY = this.nodeLocation(childNode);
                console.log(childNode+':::nodeY_:'+nodeY);
                if (diff===0
                    ||diff>Math.abs(y-nodeY)){
                        diff = Math.abs(y-nodeY);
                        closestChild = childNode;
                    }
            }
            if (closestChild==null){
                closestChild = document.createTextNode('');
                parentNode.appendChild(closestChild);
            }
            console.log('    getNearestVerticalChild__end');

            return closestChild;
        }
    }
    class DragContainer {
        constructor(node){
            this.node = node;
            //const thisArg = this;
            // node.addEventListener('drop', function(event){thisArg.dropElement.call(thisArg,event);} );
             console.log('drag container constructed');
        }
        
    }
    class TemplateEditor {

        constructor(template){
            this.template = template;
            this.templateNode = template.node;
        }
        display(){
            
            wyg.enableTemplateEditing();
            const blockHtml = document.getElementById("html");
            blockHtml.value = html_beautify(this.templateNode.outerHTML.trim());
            const blockCreator = document.getElementById("template_creator");
            const nodeId = wyg.nodeMap.length;
            wyg.nodeMap[nodeId] = this.templateNode;
            blockCreator.setAttribute('data-nodeid',nodeId);
            var thisObj = this;
            document.getElementById("template_editor_cancel").onclick = 
                function(event){
                    thisObj.cancel.call(thisObj,event);
                };
            document.getElementById("template_editor_save").onclick = 
                function(event){
                    thisObj.save.call(thisObj,event);
                };
        }
        save(){
            const blockCreator = document.getElementById("template_creator");
            const blockHtml = document.getElementById("html");
            const nodeId = blockCreator.getAttribute('data-nodeid');
            const settingsHtml = document.getElementById("settings_html");
            const node = wyg.nodeMap[nodeId];
            node.innerHTML = blockHtml.value;
                var url = "/save_element_template.php";
                var params = "element_html="+blockHtml.value
                            +"&settings_html="+settingsHtml.value;
                var xhr = new XMLHttpRequest();
                xhr.open("POST", url, true);
                xhr.onreadystatechange = function(event){
                    if (this.readyState===4){
                        console.log(this.response);
                    }
                }

                xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                xhr.send(params);

            this.cancel();
            //wyg.setupDrag();
            wyg.refreshElementTemplates();
        }
        cancel(){
            wyg.disableTemplateEditing();
        }
    }
    class WYSIWYG {

        constructor(){
            this.nodeMap = [];
        }
        showTemplateEditor(){
            this.enableTemplateEditing();
            const blocks = document.getElementById("content");
            const newBlock = document.createElement('div');
            newBlock.className = 'block';
            blocks.appendChild(newBlock);
            const template = new Draggable(newBlock);
            const templateEditor = new TemplateEditor(template);
            templateEditor.display();
        }
        enableTemplateEditing(){
            const templateCreator = document.getElementById("template_creator");
            const contentArea = document.getElementById("content");
            const toggleButton = document.getElementById("toggle_edit_button");
            toggleButton.innerText = 'Disable Template Editing';
            contentArea.style.display = "none";
            templateCreator.style.display = "block";
        }
        initiate(){
            this.content = new ContentContainer(document.getElementById('content'));
            this.drag_container = new DragContainer(document.getElementById("drag_container"));
            this.draggables = [];

            const children = this.drag_container.node.childNodes;
            let draggable;

            for (let node of children){
                if (typeof node === undefined || node.nodeName=='#text')continue;
                draggable = new Draggable(node);
                this.draggables.push(draggable);
                console.log('draggable pushed');
            }
        }
    }
    class Draggable {
        constructor(node){
            this.node = node;
            this.initiate();
        }
        initiate(){
            let nodeClicked = this.nodeClicked;
            let dragStarted = this.dragStarted;
            let thisArg = this;
            this.node.addEventListener('dragstart',function(event){dragStarted.call(thisArg,event)});
            this.node.addEventListener('click',function(){nodeClicked.call(thisArg);});
            this.node.setAttribute("draggable","true");
            
        }
        dragStarted(event){
            event.dataTransfer.setData("html", event.target.outerHTML);

        }
        nodeClicked(){
            console.log("a click"+this.node.tagName);
        }
    }
    class Setting {

    }
    class Inserted {

    }
    var wyg = new WYSIWYG();
    window.onload = function(){wyg.initiate.call(wyg)};
    console.error('Template Editor save() is broken. Line 234. Log message on line 331 after window.onload');
</script>
</head>
<body>
<table>
<tr>
    <td>
        <div id="settings">
        </div>
    </td>
    <td style="border:1px solid black;">
        <div id="content"></div>
        <div id="template_creator">
            <legend for="element_html">Element HTML</legend><br>
            <textarea id="block_html" name="element_html" rows="3" cols="70"></textarea>
            <br>
            <legend for="element_settings_html">Element SettingsHTML</legend><br>
            <textarea id="settings_html" name="element_settings_html" rows="3" cols="70"></textarea>
            <br>
            <button id="template_editor_cancel">Cancel</button>&nbsp;&nbsp;
            <button id="template_editor_save">Save Template</button>
        </div>
    </td>
    <td>
        <div>
            <div>
                <div id="drag_container">
                    <?php
                        $pdo = new PDO('mysql:host=localhost;dbname=test','reed','pass_word');
                        $statement = $pdo->prepare("SELECT * FROM wysiwyg_elements ORDER BY element_html ASC");
                        $statement->execute();
                        $elements = $statement->fetchAll();

                        foreach ($elements as $tagName=>$data){
                            echo $data['element_html'];
                        }
                    ?>
                </div>
            </div>
            <br><br>
            <button onclick="wyg.showTemplateEditor('');">Create New Template</button>&nbsp;&nbsp;
            <button id="toggle_edit_button" onclick="wyg.toggleTemplateEditing();">Enable Template Editing</button>
        </div>
    </td>

</tr>
<tr>
<td></td><td><textarea id="html"></textarea></td><td></td>
</tr>
</table>
</body>
</html>