Fix #7581 - selection of blocks
[roojs1] / Roo / bootstrap / form / HtmlEditorToolbar / Context.js
diff --git a/Roo/bootstrap/form/HtmlEditorToolbar/Context.js b/Roo/bootstrap/form/HtmlEditorToolbar/Context.js
new file mode 100644 (file)
index 0000000..e559805
--- /dev/null
@@ -0,0 +1,492 @@
+  
+/**
+ * @class Roo.bootstrap.form.HtmlEditorToolbar.Context
+ * @parent Roo.bootstrap.form.HtmlEditor
+ * @extends Roo.bootstrap.nav.Simplebar
+ * Basic Toolbar
+ * 
+ * @example
+ * Usage:
+ *
+ new Roo.bootstrap.form.HtmlEditor({
+    ....
+    toolbars : [
+        {
+            xtyle: 'Standard',
+            disable : { fonts: 1 , format: 1, ..., ... , ...],
+            btns : [ .... ]
+        },
+        {
+            xtyle : 'Context',
+            ....
+        }
+    }
+     
+ * 
+ * 
+ */
+Roo.bootstrap.form.HtmlEditorToolbar.Context = function(config)
+{
+    
+    Roo.apply(this, config);
+    
+    
+    Roo.bootstrap.form.HtmlEditorToolbar.Context.superclass.constructor.call(this, config);
+    
+    this.editor = config.editor;
+    this.editorcore = config.editor.editorcore;
+    
+    this.buttons   = new Roo.util.MixedCollection(false, function(o) { return o.cmd; });
+    
+}
+
+Roo.bootstrap.form.HtmlEditorToolbar.Context.types = {
+    'IMG' : [
+        {
+            name : 'width',
+            title: "Width",
+            width: 40
+        },
+        {
+            name : 'height',
+            title: "Height",
+            width: 40
+        },
+        {
+            name : 'align',
+            title: "Align",
+            opts : [ [""],[ "left"],[ "right"],[ "center"],[ "top"]],
+            width : 80
+            
+        },
+        {
+            name : 'border',
+            title: "Border",
+            width: 40
+        },
+        {
+            name : 'alt',
+            title: "Alt",
+            width: 120
+        },
+        {
+            name : 'src',
+            title: "Src",
+            width: 220
+        }
+        
+    ],
+    
+    /*
+    'A' : [
+        {
+            name : 'name',
+            title: "Name",
+            width: 50
+        },
+        {
+            name : 'target',
+            title: "Target",
+            width: 120
+        },
+        {
+            name : 'href',
+            title: "Href",
+            width: 220
+        } // border?
+        
+    ],
+    */
+    /*
+    'INPUT' : [
+        {
+            name : 'name',
+            title: "name",
+            width: 120
+        },
+        {
+            name : 'value',
+            title: "Value",
+            width: 120
+        },
+        {
+            name : 'width',
+            title: "Width",
+            width: 40
+        }
+    ],
+    'LABEL' : [
+         {
+            name : 'for',
+            title: "For",
+            width: 120
+        }
+    ],
+    'TEXTAREA' : [
+        {
+            name : 'name',
+            title: "name",
+            width: 120
+        },
+        {
+            name : 'rows',
+            title: "Rows",
+            width: 20
+        },
+        {
+            name : 'cols',
+            title: "Cols",
+            width: 20
+        }
+    ],
+    'SELECT' : [
+        {
+            name : 'name',
+            title: "name",
+            width: 120
+        },
+        {
+            name : 'selectoptions',
+            title: "Options",
+            width: 200
+        }
+    ],
+    
+    // should we really allow this??
+    // should this just be 
+    'BODY' : [
+        
+        {
+            name : 'title',
+            title: "Title",
+            width: 200,
+            disabled : true
+        }
+    ],
+    */
+    '*' : [
+        // empty.
+    ]
+
+};
+
+Roo.extend(Roo.bootstrap.form.HtmlEditorToolbar.Context, Roo.bootstrap.nav.Simplebar,  {
+    
+    editor : false,
+    editorcore : false,
+    buttons : false,
+    
+    button_groups : false, // subtoolbars...  - buttson?
+    active_group : false,
+    
+    selectedNode : false,
+    
+    onRender : function(ct, position)
+    {
+       // Roo.log("Call onRender: " + this.xtype);
+        
+        this.constructor.superclass.onRender.call(this, ct, position);
+       
+        
+         
+        
+          
+        // disable everything...
+        var ty = this.constructor.types;
+        this.button_groups = {};
+        // block toolbars are built in updateToolbar when needed.
+        for (var i in  ty) {
+            this.button_groups[i] = this.buildToolbarGroup(ty[i],i);
+        }
+        this.buildToolbarDelete();
+          this.hide();
+        // the all the btns;
+        this.editor.on('editorevent', this.updateToolbar, this);
+        
+    },
+    onFirstFocus: function() {
+       
+    },
+    
+    
+    buildToolbarGroup: function(tlist, key )
+    {
+        var editor = this.editor;
+        var editorcore = this.editorcore;
+        var tb = this;
+       
+        var ret = [];
+         
+        for (var i = 0; i < tlist.length; i++) {
+            
+            // newer versions will use xtype cfg to create menus.
+            if (typeof(tlist[i].xtype) != 'undefined') {
+                tb[typeof(tlist[i].name)== 'undefined' ? 'add' : 'addField'](Roo.factory(tlist[i]));
+                continue;
+            }
+            
+            var item = tlist[i];
+            ret.push(
+                this.addxtypeChild({
+                    xtype : 'Element',
+                    xns : Roo.bootstrap,
+                    cls : 'roo-htmleditor-context-label-' + key + '-' + item.name,
+                    html : item.title
+                })  
+            );
+            
+            // add a text entry!?
+            ret.push(
+                this.addxtypeChild({
+                    xtype : 'Input',
+                    xns : Roo.bootstrap.form,
+                    cls : 'roo-htmleditor-context-entry-' + key + '-' + item.name,
+                    name: '-roo-edit-' + item.name,
+                    attrname : item.name,
+                    width: item.width,
+                    //allowBlank:true,
+                    value: '',
+                    listeners: {
+                        'change' : function(f, nv, ov) {
+                            tb.selectedNode.setAttribute(f.attrname, nv);
+                            editorcore.syncValue();
+                        }
+                    }
+                })
+            );
+                
+        }
+        // hide them all..
+        ret.forEach(function(e) {
+            e.hide();
+        });
+        ret.name = key;
+        
+        return ret;
+    },
+    buildToolbarDelete : function()
+    {
+        
+        this.addxtypeChild({
+            xtype : 'Element',
+            xns : Roo.bootstrap,
+            cls : 'roo-htmleditor-fill'
+        });
+        
+        this.deleteBtn = this.addxtypeChild({
+            size : 'sm',
+            xtype: 'Button',
+            xns: Roo.bootstrap,
+            fa: 'trash',
+            listeners : {
+                click : this.onDelete.createDelegate(this)
+            }
+        });
+        this.deleteBtn.hide();     
+        
+    },
+    
+    
+    onDelete : function()
+    {
+        var range = this.editorcore.createRange();
+        var selection = this.editorcore.getSelection();
+        var sn = this.selectedNode;
+        range.setStart(sn,0);
+        range.setEnd(sn,0); 
+        
+        
+        if (sn.hasAttribute('data-block')) {
+            var block = Roo.htmleditor.Block.factory(tb.selectedNode);
+            if (block) {
+                block.removeNode();
+                selection.removeAllRanges();
+                selection.addRange(range);
+                this.updateToolbar(null, null, null);
+            }   
+             
+        }
+        if (!sn) {
+            return; // should not really happen..
+        }
+        if (sn && sn.tagName == 'BODY') {
+            return;
+        }
+        var stn =  sn.childNodes[0] || sn.nextSibling || sn.previousSibling || sn.parentNode;
+        
+        // remove and keep parents.
+        a = new Roo.htmleditor.FilterKeepChildren({tag : false});
+        a.replaceTag(sn);
+        
+        selection.removeAllRanges();
+        selection.addRange(range);
+        this.editorcore.fireEditorEvent(false);
+        
+        
+    },
+    /**
+     * Protected method that will not generally be called directly. It triggers
+     * a toolbar update by reading the markup state of the current selection in the editor.
+     *
+     * Note you can force an update by calling on('editorevent', scope, false)
+     */
+    updateToolbar: function(editor ,ev, sel)
+    {
+        var ty = this.constructor.types;
+        
+        
+        if (ev) {
+            ev.stopEvent(); // se if we can stop this looping with mutiple events.
+        }
+        
+         
+        // capture mouse up - this is handy for selecting images..
+        // perhaps should go somewhere else...
+        if(!this.editorcore.activated){
+            this.editor.onFirstFocus();
+            return;
+        }
+        //Roo.log(ev ? ev.target : 'NOTARGET');
+        
+        
+        // http://developer.yahoo.com/yui/docs/simple-editor.js.html
+        // selectNode - might want to handle IE?
+         
+        if (ev &&
+            (ev.type == 'mouseup' || ev.type == 'click' ) &&
+            ev.target && ev.target.tagName != 'BODY' ) { // && ev.target.tagName == 'IMG') {
+            // they have click on an image...
+            // let's see if we can change the selection...
+            sel = ev.target;
+           
+             
+        }
+        
+        // this forces an id..
+        Array.from(this.editorcore.doc.body.querySelectorAll('.roo-ed-selection')).forEach(function(e) {
+            e.classList.remove('roo-ed-selection');
+        });
+          
+        var ans = this.editorcore.getAllAncestors();
+        
+               
+        if (!sel) { 
+            sel = ans.length ? (ans[0] ?  ans[0]  : ans[1]) : this.editorcore.doc.body;
+            sel = sel ? sel : this.editorcore.doc.body;
+            sel = sel.tagName.length ? sel : this.editorcore.doc.body;
+            
+        }
+        
+        var tn = sel.tagName.toUpperCase();
+        var lastSel = this.selectedNode;
+        this.selectedNode = sel;
+        var left_label = tn;
+        
+        // ok see if we are editing a block?
+        
+        var db = false;
+        // you are not actually selecting the block.
+        if (sel && sel.hasAttribute('data-block')) {
+            db = sel;
+        } else if (sel && sel.closest('[data-block]')) {
+            
+            db = sel.closest('[data-block]');
+             
+        }
+        
+        
+        var block = false;
+        if (db && this.editorcore.enableBlocks) {
+            block = Roo.htmleditor.Block.factory(db);
+            
+            
+            if (block) {
+                db.className = (
+                        db.classList.length > 0  ? db.className + ' ' : ''
+                    )  + 'roo-ed-selection';
+                 
+                 // since we removed it earlier... its not there..
+                tn = 'BLOCK.' + db.getAttribute('data-block');
+                
+                //this.editorcore.selectNode(db);
+                if (typeof(this.button_groups[tn]) == 'undefined') {
+                   this.button_groups[tn] = this.buildBlockToolbar( block );
+                }
+                this.selectedNode = db;
+                left_label = block.friendly_name;
+                 
+            }
+              
+            
+        }
+        
+        
+        if ( this.active_group !== false && this.active_group.name == tn && lastSel == this.selectedNode && ev !== false) {
+            return; // no change?
+        }
+        
+        if (tn == 'BODY') {
+            this.deleteBtn.hide();
+            this.hide();
+            this.hideActiveGroup();
+            return;
+            
+        }
+        
+        
+        if (this.active_group) {
+            this.hideActiveGroup();
+        }
+        this.showActiveGroup(tn);
+        this.show();
+        this.deleteBtn.show();
+        
+    },
+    hideActiveGroup : function()
+    {
+        this.hide();
+        if (this.active_group === false) {
+            return;
+        }
+        this.active_group.forEach(function(e) {
+            e.hide();
+        });
+        this.active_group = false;
+    },
+    showActiveGroup : function(tn)
+    {
+        
+        if (typeof(this.button_groups[tn]) == 'undefined') {
+            
+            return;
+        }
+        
+        this.active_group = this.button_groups[tn];
+        
+        this.active_group.forEach(function(e) {
+            e.show();
+        });
+        
+        // update attributes
+        if (this.selectedNode.hasAttribute('data-block') ) {
+            var block = Roo.htmleditor.Block.factory(this.selectedNode);
+            this.active_group.forEach(function(e) {
+                e.setValue(this.selectedNode.getAttribute(block[e.name]));
+            }, this);
+                
+            return;
+            
+        }
+        // based on attributes...
+        this.active_group.forEach(function(e) {
+            if (typeof(e.attrname) == 'undefined') {
+                return;
+            }
+             e.setValue(this.selectedNode.getAttribute(e.attrname));
+        }, this);
+        
+            
+    }
+    
+});
\ No newline at end of file