]> git.mjollnir.org Git - s9y.git/commitdiff
Drag and drop based plugin layout manager. Hooray for DHTML. ;)
authorgarvinhicking <garvinhicking>
Thu, 1 Jun 2006 11:18:53 +0000 (11:18 +0000)
committergarvinhicking <garvinhicking>
Thu, 1 Jun 2006 11:18:53 +0000 (11:18 +0000)
bundled-libs/dragdrop.js [new file with mode: 0644]
docs/NEWS
include/admin/plugins.inc.php
include/functions_plugins_admin.inc.php
include/plugin_api.inc.php
serendipity_admin.php
templates/default/admin/img/grablet.gif [new file with mode: 0644]
templates/default/admin/img/grablet_over.gif [new file with mode: 0644]
templates/default/admin/pluginmanager.css [new file with mode: 0644]

diff --git a/bundled-libs/dragdrop.js b/bundled-libs/dragdrop.js
new file mode 100644 (file)
index 0000000..ffc4e73
--- /dev/null
@@ -0,0 +1,676 @@
+/**********************************************************
+ Very minorly modified from the example by Tim Taylor
+ http://tool-man.org/examples/sorting.html
+ Added Coordinate.prototype.inside( northwest, southeast );
+    Copyright (c) 2005 Tim Taylor Consulting <http://tool-man.org/>
+    
+    Permission is hereby granted, free of charge, to any person obtaining a
+    copy of this software and associated documentation files (the "Software"),
+    to deal in the Software without restriction, including without limitation
+    the rights to use, copy, modify, merge, publish, distribute, sublicense,
+    and/or sell copies of the Software, and to permit persons to whom the
+    Software is furnished to do so, subject to the following conditions:
+    
+    The above copyright notice and this permission notice shall be included
+    in all copies or substantial portions of the Software.
+    
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+    IN THE SOFTWARE.
+ **********************************************************/
+
+var Coordinates = {
+    ORIGIN : new Coordinate(0, 0),
+
+    northwestPosition : function(element) {
+        var x = parseInt(element.style.left);
+        var y = parseInt(element.style.top);
+
+        return new Coordinate(isNaN(x) ? 0 : x, isNaN(y) ? 0 : y);
+    },
+
+    southeastPosition : function(element) {
+        return Coordinates.northwestPosition(element).plus(
+                new Coordinate(element.offsetWidth, element.offsetHeight));
+    },
+
+    northwestOffset : function(element, isRecursive) {
+        var offset = new Coordinate(element.offsetLeft, element.offsetTop);
+
+        if (!isRecursive) return offset;
+
+        var parent = element.offsetParent;
+        while (parent) {
+            offset = offset.plus(
+                    new Coordinate(parent.offsetLeft, parent.offsetTop));
+            parent = parent.offsetParent;
+        }
+        return offset;
+    },
+
+    southeastOffset : function(element, isRecursive) {
+        return Coordinates.northwestOffset(element, isRecursive).plus(
+                new Coordinate(element.offsetWidth, element.offsetHeight));
+    },
+
+    fixEvent : function(event) {
+        event.windowCoordinate = new Coordinate(event.clientX, event.clientY);
+    }
+};
+
+function Coordinate(x, y) {
+    this.x = x;
+    this.y = y;
+}
+
+Coordinate.prototype.toString = function() {
+    return "(" + this.x + "," + this.y + ")";
+}
+
+Coordinate.prototype.plus = function(that) {
+    return new Coordinate(this.x + that.x, this.y + that.y);
+}
+
+Coordinate.prototype.minus = function(that) {
+    return new Coordinate(this.x - that.x, this.y - that.y);
+}
+
+Coordinate.prototype.distance = function(that) {
+    var deltaX = this.x - that.x;
+    var deltaY = this.y - that.y;
+
+    return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
+}
+
+Coordinate.prototype.max = function(that) {
+    var x = Math.max(this.x, that.x);
+    var y = Math.max(this.y, that.y);
+    return new Coordinate(x, y);
+}
+
+Coordinate.prototype.constrain = function(min, max) {
+    if (min.x > max.x || min.y > max.y) return this;
+
+    var x = this.x;
+    var y = this.y;
+
+    if (min.x != null) x = Math.max(x, min.x);
+    if (max.x != null) x = Math.min(x, max.x);
+    if (min.y != null) y = Math.max(y, min.y);
+    if (max.y != null) y = Math.min(y, max.y);
+
+    return new Coordinate(x, y);
+}
+
+Coordinate.prototype.reposition = function(element) {
+    element.style["top"] = this.y + "px";
+    element.style["left"] = this.x + "px";
+}
+
+Coordinate.prototype.equals = function(that) {
+    if (this == that) return true;
+    if (!that || that == null) return false;
+
+    return this.x == that.x && this.y == that.y;
+}
+
+// returns true of this point is inside specified box
+Coordinate.prototype.inside = function(northwest, southeast) {
+    if ((this.x >= northwest.x) && (this.x <= southeast.x) &&
+        (this.y >= northwest.y) && (this.y <= southeast.y)) {
+        
+        return true;
+    }
+    return false;
+}
+
+/*
+ * drag.js - click & drag DOM elements
+ *
+ * originally based on Youngpup's dom-drag.js, www.youngpup.net
+ */
+
+/**********************************************************
+ Further modified from the example by Tim Taylor
+ http://tool-man.org/examples/sorting.html
+ Changed onMouseMove where it calls group.onDrag and then
+ adjusts the offset for changes to the DOM.  If the item
+ being moved changed parents it would be off so changed to
+ get the absolute offset (recursive northwestOffset).
+ **********************************************************/
+
+var Drag = {
+    BIG_Z_INDEX : 10000,
+    group : null,
+    isDragging : false,
+
+    makeDraggable : function(group) {
+        group.handle = group;
+        group.handle.group = group;
+
+        group.minX = null;
+        group.minY = null;
+        group.maxX = null;
+        group.maxY = null;
+        group.threshold = 0;
+        group.thresholdY = 0;
+        group.thresholdX = 0;
+
+        group.onDragStart = new Function();
+        group.onDragEnd = new Function();
+        group.onDrag = new Function();
+        
+        // TODO: use element.prototype.myFunc
+        group.setDragHandle = Drag.setDragHandle;
+        group.setDragThreshold = Drag.setDragThreshold;
+        group.setDragThresholdX = Drag.setDragThresholdX;
+        group.setDragThresholdY = Drag.setDragThresholdY;
+        group.constrain = Drag.constrain;
+        group.constrainVertical = Drag.constrainVertical;
+        group.constrainHorizontal = Drag.constrainHorizontal;
+
+        group.onmousedown = Drag.onMouseDown;
+    },
+
+    constrainVertical : function() {
+        var nwOffset = Coordinates.northwestOffset(this, true);
+        this.minX = nwOffset.x;
+        this.maxX = nwOffset.x;
+    },
+
+    constrainHorizontal : function() {
+        var nwOffset = Coordinates.northwestOffset(this, true);
+        this.minY = nwOffset.y;
+        this.maxY = nwOffset.y;
+    },
+
+    constrain : function(nwPosition, sePosition) {
+        this.minX = nwPosition.x;
+        this.minY = nwPosition.y;
+        this.maxX = sePosition.x;
+        this.maxY = sePosition.y;
+    },
+
+    setDragHandle : function(handle) {
+        if (handle && handle != null) 
+            this.handle = handle;
+        else
+            this.handle = this;
+
+        this.handle.group = this;
+        this.onmousedown = null;
+        this.handle.onmousedown = Drag.onMouseDown;
+    },
+
+    setDragThreshold : function(threshold) {
+        if (isNaN(parseInt(threshold))) return;
+
+        this.threshold = threshold;
+    },
+
+    setDragThresholdX : function(threshold) {
+        if (isNaN(parseInt(threshold))) return;
+
+        this.thresholdX = threshold;
+    },
+
+    setDragThresholdY : function(threshold) {
+        if (isNaN(parseInt(threshold))) return;
+
+        this.thresholdY = threshold;
+    },
+
+    onMouseDown : function(event) {
+        event = Drag.fixEvent(event);
+        Drag.group = this.group;
+
+        var group = this.group;
+        var mouse = event.windowCoordinate;
+        var nwOffset = Coordinates.northwestOffset(group, true);
+        var nwPosition = Coordinates.northwestPosition(group);
+        var sePosition = Coordinates.southeastPosition(group);
+        var seOffset = Coordinates.southeastOffset(group, true);
+
+        group.originalOpacity = group.style.opacity;
+        group.originalZIndex = group.style.zIndex;
+        group.initialWindowCoordinate = mouse;
+        // TODO: need a better name, but don't yet understand how it
+        // participates in the magic while dragging 
+        group.dragCoordinate = mouse;
+
+        Drag.showStatus(mouse, nwPosition, sePosition, nwOffset, seOffset);
+
+        group.onDragStart(nwPosition, sePosition, nwOffset, seOffset);
+
+        // TODO: need better constraint API
+        if (group.minX != null)
+            group.minMouseX = mouse.x - nwPosition.x + 
+                    group.minX - nwOffset.x;
+        if (group.maxX != null) 
+            group.maxMouseX = group.minMouseX + group.maxX - group.minX;
+
+        if (group.minY != null)
+            group.minMouseY = mouse.y - nwPosition.y + 
+                    group.minY - nwOffset.y;
+        if (group.maxY != null) 
+            group.maxMouseY = group.minMouseY + group.maxY - group.minY;
+
+        group.mouseMin = new Coordinate(group.minMouseX, group.minMouseY);
+        group.mouseMax = new Coordinate(group.maxMouseX, group.maxMouseY);
+
+        document.onmousemove = Drag.onMouseMove;
+        document.onmouseup = Drag.onMouseUp;
+
+        return false;
+    },
+
+    showStatus : function(mouse, nwPosition, sePosition, nwOffset, seOffset) {
+        /*window.status = 
+                "mouse: " + mouse.toString() + "    " + 
+                "NW pos: " + nwPosition.toString() + "    " + 
+                "SE pos: " + sePosition.toString() + "    " + 
+                "NW offset: " + nwOffset.toString() + "    " +
+                "SE offset: " + seOffset.toString();*/
+    },
+
+    onMouseMove : function(event) {
+        event = Drag.fixEvent(event);
+        var group = Drag.group;
+        var mouse = event.windowCoordinate;
+        var nwOffset = Coordinates.northwestOffset(group, true);
+        var nwPosition = Coordinates.northwestPosition(group);
+        var sePosition = Coordinates.southeastPosition(group);
+        var seOffset = Coordinates.southeastOffset(group, true);
+
+        Drag.showStatus(mouse, nwPosition, sePosition, nwOffset, seOffset);
+
+        if (!Drag.isDragging) {
+            if (group.threshold > 0) {
+                var distance = group.initialWindowCoordinate.distance(
+                        mouse);
+                if (distance < group.threshold) return true;
+            } else if (group.thresholdY > 0) {
+                var deltaY = Math.abs(group.initialWindowCoordinate.y - mouse.y);
+                if (deltaY < group.thresholdY) return true;
+            } else if (group.thresholdX > 0) {
+                var deltaX = Math.abs(group.initialWindowCoordinate.x - mouse.x);
+                if (deltaX < group.thresholdX) return true;
+            }
+
+            Drag.isDragging = true;
+            group.style["zIndex"] = Drag.BIG_Z_INDEX;
+            group.style["opacity"] = 0.75;
+        }
+
+        // TODO: need better constraint API
+        var adjusted = mouse.constrain(group.mouseMin, group.mouseMax);
+        nwPosition = nwPosition.plus(adjusted.minus(group.dragCoordinate));
+        nwPosition.reposition(group);
+        group.dragCoordinate = adjusted;
+
+        // once dragging has started, the position of the group
+        // relative to the mouse should stay fixed.  They can get out
+        // of sync if the DOM is manipulated while dragging, so we
+        // correct the error here
+        //
+        // TODO: what we really want to do is find the offset from
+        // our corner to the mouse coordinate and adjust to keep it
+        // the same
+        
+        // changed to be recursive/use absolute offset for corrections
+        var offsetBefore = Coordinates.northwestOffset(group, true);
+        group.onDrag(nwPosition, sePosition, nwOffset, seOffset);
+        var offsetAfter = Coordinates.northwestOffset(group, true);
+
+        if (!offsetBefore.equals(offsetAfter)) {
+            var errorDelta = offsetBefore.minus(offsetAfter);
+            nwPosition = Coordinates.northwestPosition(group).plus(errorDelta);
+            nwPosition.reposition(group);
+        }
+
+        return false;
+    },
+
+    onMouseUp : function(event) {
+        event = Drag.fixEvent(event);
+        var group = Drag.group;
+
+        var mouse = event.windowCoordinate;
+        var nwOffset = Coordinates.northwestOffset(group, true);
+        var nwPosition = Coordinates.northwestPosition(group);
+        var sePosition = Coordinates.southeastPosition(group);
+        var seOffset = Coordinates.southeastOffset(group, true);
+
+        document.onmousemove = null;
+        document.onmouseup   = null;
+        group.onDragEnd(nwPosition, sePosition, nwOffset, seOffset);
+
+        if (Drag.isDragging) {
+            // restoring zIndex before opacity avoids visual flicker in Firefox
+            group.style["zIndex"] = group.originalZIndex;
+            group.style["opacity"] = group.originalOpacity;
+        }
+
+        Drag.group = null;
+        Drag.isDragging = false;
+
+        return false;
+    },
+
+    fixEvent : function(event) {
+        if (typeof event == 'undefined') event = window.event;
+        Coordinates.fixEvent(event);
+
+        return event;
+    }
+};
+
+/**********************************************************
+ Adapted from the sortable lists example by Tim Taylor
+ http://tool-man.org/examples/sorting.html
+ Modified by Tom Westcott : http://www.cyberdummy.co.uk 
+ **********************************************************/
+
+var DragDrop = {
+    firstContainer : null,
+    lastContainer : null,
+    parent_id : null,
+    parent_group : null,
+    makeListContainer : function(list, group) {
+        // each container becomes a linked list node
+        if (this.firstContainer == null) {
+            this.firstContainer = this.lastContainer = list;
+            list.previousContainer = null;
+            list.nextContainer = null;
+        } else {
+            list.previousContainer = this.lastContainer;
+            list.nextContainer = null;
+            this.lastContainer.nextContainer = list;
+            this.lastContainer = list;
+        }
+        
+        // these functions are called when an item is draged over
+        // a container or out of a container bounds.  onDragOut
+        // is also called when the drag ends with an item having
+        // been added to the container
+        list.onDragOver = new Function();
+        list.onDragOut = new Function();
+                list.onDragDrop = new Function();
+                list.group = group;
+        
+        var items = list.getElementsByTagName( "li" );
+        
+        for (var i = 0; i < items.length; i++) {
+            DragDrop.makeItemDragable(items[i]);
+        }
+    },
+        
+    serData : function ( group, theid ) {
+        var container = DragDrop.firstContainer;
+        var j = 0;
+        var string = "";
+        
+        while (container != null) {
+            if(theid != null && container.id != theid) {
+              container = container.nextContainer;
+              continue;
+            }
+
+            if(group != null && container.group != group) {
+              container = container.nextContainer;
+              continue;
+            }
+        
+            j ++;
+            if (j > 1) {
+              string += ":";
+            }
+            string += container.id;
+            
+            var items = container.getElementsByTagName( "li" );
+            string += "(";
+            for (var i = 0; i < items.length; i++) {
+                if(i > 0) {
+                    string += ",";
+                }
+
+                string += items[i].id;
+            }
+            string += ")";
+                    
+            container = container.nextContainer;
+        }
+
+        return string;   
+    },
+
+    makeItemDragable : function(item) {
+        Drag.makeDraggable(item);
+        item.setDragThreshold(5);
+        
+        // tracks if the item is currently outside all containers
+        item.isOutside = false;
+        
+        item.onDragStart = DragDrop.onDragStart;
+        item.onDrag = DragDrop.onDrag;
+        item.onDragEnd = DragDrop.onDragEnd;
+    },
+
+    onDragStart : function(nwPosition, sePosition, nwOffset, seOffset) {
+        // update all container bounds, since they may have changed
+        // on a previous drag
+        //
+        // could be more smart about when to do this
+        var container = DragDrop.firstContainer;
+        while (container != null) {
+            container.northwest = Coordinates.northwestOffset( container, true );
+            container.southeast = Coordinates.southeastOffset( container, true );
+            container = container.nextContainer;
+        }
+        
+        // item starts out over current parent
+        this.parentNode.onDragOver();
+        parent_id = this.parentNode.id;
+        parent_group = this.parentNode.group;
+    },
+
+    onDrag : function(nwPosition, sePosition, nwOffset, seOffset) {
+        // check if we were nowhere
+        if (this.isOutside) {
+            // check each container to see if in its bounds
+            var container = DragDrop.firstContainer;
+            while (container != null) {
+
+                if ((nwOffset.inside( container.northwest, container.southeast ) ||
+                    seOffset.inside( container.northwest, container.southeast )) && container.group == parent_group) {
+                    // we're inside this one
+                    container.onDragOver();
+                    this.isOutside = false;
+                    
+                    // since isOutside was true, the current parent is a
+                    // temporary clone of some previous container node and
+                    // it needs to be removed from the document
+                    var tempParent = this.parentNode;
+                    tempParent.removeChild( this );
+                    container.appendChild( this );
+                    tempParent.parentNode.removeChild( tempParent );
+                    break;
+                }
+                container = container.nextContainer;
+            }
+            // we're still not inside the bounds of any container
+            if (this.isOutside)
+                return;
+        
+        // check if we're outside our parent's bounds
+        } else if (!(nwOffset.inside( this.parentNode.northwest, this.parentNode.southeast ) ||
+            seOffset.inside( this.parentNode.northwest, this.parentNode.southeast ))) {
+            
+            this.parentNode.onDragOut();
+            this.isOutside = true;
+            
+            // check if we're inside a new container's bounds
+            var container = DragDrop.firstContainer;
+            while (container != null) {
+                if ((nwOffset.inside( container.northwest, container.southeast ) ||
+                    seOffset.inside( container.northwest, container.southeast )) && container.group == parent_group) {
+                    // we're inside this one
+                    container.onDragOver();
+                    this.isOutside = false;
+                    this.parentNode.removeChild( this );
+                    container.appendChild( this );
+                    break;
+                }
+                container = container.nextContainer;
+            }
+            // if we're not in any container now, make a temporary clone of
+            // the previous container node and add it to the document
+            if (this.isOutside) {
+                var tempParent = this.parentNode.cloneNode( false );
+                this.parentNode.removeChild( this );
+                tempParent.appendChild( this );
+                // body puts a border or item at bottom of page if do not have this
+                                tempParent.style.border = 0;
+                document.getElementsByTagName( "body" ).item(0).appendChild( tempParent );
+                return;
+            }
+        }
+        
+        // if we get here, we're inside some container bounds, so we do
+        // everything the original dragsort script did to swap us into the
+        // correct position
+        
+        var parent = this.parentNode;
+                
+        var item = this;
+        var next = DragUtils.nextItem(item);
+        while (next != null && this.offsetTop >= next.offsetTop - 2) {
+            var item = next;
+            var next = DragUtils.nextItem(item);
+        }
+        if (this != item) {
+            DragUtils.swap(this, next);
+            return;
+        }
+
+        var item = this;
+        var previous = DragUtils.previousItem(item);
+        while (previous != null && this.offsetTop <= previous.offsetTop + 2) {
+            var item = previous;
+            var previous = DragUtils.previousItem(item);
+        }
+        if (this != item) {
+            DragUtils.swap(this, item);
+            return;
+        }
+    },
+
+    onDragEnd : function(nwPosition, sePosition, nwOffset, seOffset) {
+        // if the drag ends and we're still outside all containers
+        // it's time to remove ourselves from the document or add 
+                // to the trash bin
+        if (this.isOutside) {
+            var container = DragDrop.firstContainer;
+            while (container != null) {
+               if (container.id == parent_id) {
+                 break;
+               }
+               container = container.nextContainer;
+            }
+            this.isOutside = false;
+            this.parentNode.removeChild( this );
+            container.appendChild( this );
+            this.style["top"] = "0px";
+            this.style["left"] = "0px";
+            //var container = DragDrop.firstContainer;
+            //container.appendChild( this );
+            return;
+        }
+        this.parentNode.onDragOut();
+        this.parentNode.onDragDrop();
+        this.style["top"] = "0px";
+        this.style["left"] = "0px";
+    }
+};
+
+var DragUtils = {
+    swap : function(item1, item2) {
+        var parent = item1.parentNode;
+        parent.removeChild(item1);
+        parent.insertBefore(item1, item2);
+
+        item1.style["top"] = "0px";
+        item1.style["left"] = "0px";
+    },
+
+    nextItem : function(item) {
+        var sibling = item.nextSibling;
+        while (sibling != null) {
+            if (sibling.nodeName == item.nodeName) return sibling;
+            sibling = sibling.nextSibling;
+        }
+        return null;
+    },
+
+    previousItem : function(item) {
+        var sibling = item.previousSibling;
+        while (sibling != null) {
+            if (sibling.nodeName == item.nodeName) return sibling;
+            sibling = sibling.previousSibling;
+        }
+        return null;
+    }        
+};
+
+/*************************
+ * Custom Serendipity code
+ *************************/
+function pluginMoverInit() {
+    var list = document.getElementById("left_col");
+    DragDrop.makeListContainer(list, 'g1');
+    list.onDragOver = function() { this.style["border"] = "1px solid #4d759b"; };
+    list.onDragOut = function() { this.style["border"] = "none"; };
+    
+    list = document.getElementById("hide_col");
+    DragDrop.makeListContainer(list, 'g1');
+    list.onDragOver = function() { this.style["border"] = "1px solid #4d759b"; };
+    list.onDragOut = function() {this.style["border"] = "none"; };
+    
+    list = document.getElementById("right_col");
+    DragDrop.makeListContainer(list, 'g1');
+    list.onDragOver = function() { this.style["border"] = "1px solid #4d759b"; };
+    list.onDragOut = function() {this.style["border"] = "none"; };
+}
+
+function pluginMoverInitEvent() {
+    var list = document.getElementById("event_col");
+    DragDrop.makeListContainer(list, 'g1');
+    list.onDragOver = function() { this.style["border"] = "1px solid #4d759b"; };
+    list.onDragOut = function() { this.style["border"] = "none"; };
+    
+    list = document.getElementById("eventh_col");
+    DragDrop.makeListContainer(list, 'g1');
+    list.onDragOver = function() { this.style["border"] = "1px solid #4d759b"; };
+    list.onDragOut = function() {this.style["border"] = "none"; };
+}
+
+function pluginMovergetSort() {
+  order = document.getElementById("order");
+  order.value = DragDrop.serData('g1', null);
+  return order.value;
+}
+
+function pluginMovergetSortEvent() {
+  order = document.getElementById("eventorder");
+  order.value = DragDrop.serData('g1', null);
+  return order.value;
+}
\ No newline at end of file
index 3358446d25d15d61a9ca698d5b96adca4928bc9b..bf52a6e2af25442cd8f2cfe1dbf3df8e31555471 100644 (file)
--- a/docs/NEWS
+++ b/docs/NEWS
@@ -3,6 +3,10 @@
 Version 1.1-alpha5()
 ------------------------------------------------------------------------
 
+   * Implemented Drag and Drop based plugin configuration panel for
+     re-ordering plugin layout. Uses JavaScript - works like old
+     manager when having JS disabled! (garvinhicking)
+
    * Changed "Auto-Login" via Cookie behaviour to only issue single-
      time valid cookies to users and no longer put username/pw into
      serialized cookie data. Many thanks to Yasuo Ohgaki for giving
index e0f1630be063ead84be3172a73eb4f160e2ae67d..0325575511b7ffda211a62eaf264b590c0fee7d4 100644 (file)
@@ -111,7 +111,7 @@ if (isset($_GET['serendipity']['plugin_to_conf'])) {
     echo "</ul>\n";
     ?>
     </div>
-<?php } elseif ( isset($_POST['SAVECONF']) ) { ?>
+<?php } elseif ( isset($_POST['SAVECONF'])) { ?>
     <div class="serendipityAdminMsgSuccess"><?php echo DONE .': '. sprintf(SETTINGS_SAVED_AT, serendipity_strftime('%H:%M:%S')); ?></div>
 <?php } ?>
 
@@ -344,6 +344,41 @@ if (isset($_GET['serendipity']['plugin_to_conf'])) {
 } else {
     /* show general plugin list */
 
+    /* preparse Javascript-generated input */
+    if (isset($_POST['SAVE']) && !empty($_POST['serendipity']['pluginorder'])) {
+        $parts = explode(':', $_POST['serendipity']['pluginorder']);
+        $col_assoc = array(
+            'left_col'   => 'left',
+            'right_col'  => 'right',
+            'hide_col'   => 'hide',
+            'event_col'  => 'event',
+            'eventh_col' => 'eventh'
+        );
+        foreach($parts AS $sidepart) {
+            preg_match('@^(.+)\((.*)\)$@imsU', $sidepart, $matches);
+            if (!isset($col_assoc[$matches[1]])) {
+                continue;
+            }
+            $pluginsidelist = explode(',', $matches[2]);
+            foreach($pluginsidelist AS $pluginname) {
+                $pluginname = trim(urldecode($pluginname));
+
+                if (empty($pluginname)) {
+                    continue;
+                }
+                $serendipity['POST']['placement'][$pluginname] = $col_assoc[$matches[1]];
+                $new_order[] = $pluginname;
+                
+            }
+        }
+
+        if (is_array($new_order)) {
+            foreach($new_order AS $new_order_pos => $order_plugin) {
+                serendipity_db_query("UPDATE {$serendipity['dbPrefix']}plugins SET sort_order = ". (int)$new_order_pos . " WHERE name='" . serendipity_db_escape_string($order_plugin) . "'");
+            }
+        }
+    }
+
     if (isset($_POST['SAVE']) && isset($_POST['serendipity']['placement']) && serendipity_checkFormToken()) {
         foreach ($_POST['serendipity']['placement'] as $plugin_name => $placement) {
             serendipity_plugin_api::update_plugin_placement(
@@ -405,7 +440,14 @@ if (isset($_GET['serendipity']['plugin_to_conf'])) {
         }
     }
 ?>
+
+<?php if (isset($_POST['SAVE'])) { ?>
+    <div class="serendipityAdminMsgSuccess"><?php echo DONE .': '. sprintf(SETTINGS_SAVED_AT, serendipity_strftime('%H:%M:%S')); ?></div>
+<?php } ?>
+
     <?php echo BELOW_IS_A_LIST_OF_INSTALLED_PLUGINS ?>
+    <?php echo '<script src="bundled-libs/dragdrop.js" type="text/javascript"></script>'; ?>
+
     <br />
     <br />
 
index 225f3b9efea7d0a278c5d8d37375fed30c222e20..dd4b5489939750ffe4d02df6f0182222a5daea21 100644 (file)
@@ -2,6 +2,7 @@
 # Copyright (c) 2003-2005, Jannis Hermanns (on behalf the Serendipity Developer Team)
 # All rights reserved.  See LICENSE file for licensing details
 
+
 if (defined('S9Y_FRAMEWORK_PLUGINS_ADMIN')) {
     return;
 }
@@ -50,17 +51,30 @@ function serendipity_pluginListSort($x, $y) {
  */
 function show_plugins($event_only = false)
 {
+    static $opts = array(
+                    'left'   => LEFT,
+                    'right'  => RIGHT,
+                    'hide'   => HIDDEN,
+                    'event'  => PLUGIN_ACTIVE,
+                    'eventh' => PLUGIN_INACTIVE
+    );
+
     global $serendipity;
+
+    if (!$event_only) {
+        echo '<script type="text/javascript">addLoadEvent(pluginMoverInit);</script>';
+        echo '    <form action="?serendipity[adminModule]=plugins" method="post" onsubmit="pluginMovergetSort(); return true">';
+        echo '        <input type="hidden" name="serendipity[pluginorder]" id="order" value="" />';
+
+    } else {
+        echo '<script type="text/javascript">addLoadEvent(pluginMoverInitEvent);</script>';
+        echo '    <form action="?serendipity[adminModule]=plugins" method="post" onsubmit="pluginMovergetSortEvent(); return true">';
+        echo '        <input type="hidden" name="serendipity[pluginorder]" id="eventorder" value="" />';
+    }
+
+    echo serendipity_setFormToken();
 ?>
-    <form action="?serendipity[adminModule]=plugins" method="post">
-        <?php echo serendipity_setFormToken(); ?>
-        <table border="0" cellpadding="5" cellspacing="0" width="100%">
-            <tr>
-                <td colspan="2">&nbsp;</td>
-                <td><strong><?php echo TITLE; ?></strong></td>
-                <td><strong><?php echo PERMISSIONS; ?></strong></td>
-                <td colspan="3" align="center"><strong><?php echo PLACEMENT; ?></strong></td>
-            </tr>
+    <table class="pluginmanager" border="0" cellpadding="5" cellspacing="3" width="100%">
 <?php
     $errors     = array();
 
@@ -68,11 +82,14 @@ function show_plugins($event_only = false)
     if ($event_only) {
         $plugin_placements = array('event', 'eventh');
     } else {
-        $plugin_placements = array('left', 'right', 'hide');
+        $plugin_placements = array('left', 'hide', 'right');
     }
 
     $total = 0;
     foreach ($plugin_placements as $plugin_placement) {
+        echo '<td class="pluginmanager_side">';
+        echo '<div class="heading">' . $opts[$plugin_placement] . '</div>';
+        echo '<ol id="' . $plugin_placement . '_col" class="pluginmanager_container">';
         $plugins = serendipity_plugin_api::enum_plugins($plugin_placement);
 
         if (!is_array($plugins)) {
@@ -131,48 +148,44 @@ function show_plugins($event_only = false)
                 $movedown = ($moveup != '' ? '&nbsp;' : '') . '<a href="?' . serendipity_setFormToken('url') . '&amp;serendipity[adminModule]=plugins&amp;submit=move+down&serendipity[plugin_to_move]=' . $key . $event_only_uri . '" style="border: 0"><img src="' . serendipity_getTemplateFile('admin/img/downarrow.png') . '" height="16" width="16" alt="'. DOWN .'" border="0" /></a>';
             }
 ?>
-            <tr>
-                <td style="border-bottom: 1px solid #000000">
-                    <div>
-                    <?php if ($is_plugin_editable) { ?>
-                        <input type="checkbox" name="serendipity[plugin_to_remove][]" value="<?php echo $plugin_data['name']; ?>" />
-                    <?php } else { ?>
-                        &nbsp;
-                    <?php } ?>
-                    </div>
-                </td>
-                <td style="border-bottom: 1px solid #000000" width="16">
+            <li class="pluginmanager_item_<?php echo ($sort_idx % 2 ? 'even' : 'uneven'); ?>" id="<?php echo $key; ?>">
+                <a href="#grab<?php echo $key; ?>" id="grab<?php echo $key; ?>" class="pluginmanager_grablet"></a>
+                <?php if ($is_plugin_editable) { ?>
+                    <input type="checkbox" name="serendipity[plugin_to_remove][]" value="<?php echo $plugin_data['name']; ?>" />
+                <?php } ?>
+
                 <?php if ( $can_configure ) { ?>
-                    <a href="?serendipity[adminModule]=plugins&amp;serendipity[plugin_to_conf]=<?php echo $key ?>"><img src="<?php echo serendipity_getTemplateFile('admin/img/configure.png') ?>" style="border: 0; vertical-align: bottom;"></a>
-                <?php } else { ?>
-                    &nbsp;
+                    <a class="pluginmanager_configure" href="?serendipity[adminModule]=plugins&amp;serendipity[plugin_to_conf]=<?php echo $key ?>"><img src="<?php echo serendipity_getTemplateFile('admin/img/configure.png') ?>" style="border: 0; vertical-align: bottom;"></a>
                 <?php } ?>
-                </td>
-                <td style="border-bottom: 1px solid #000000"><strong>
+
+                    <span class="pluginmanager_title">
                 <?php if ( $can_configure ) { ?>
                     <a href="?serendipity[adminModule]=plugins&amp;serendipity[plugin_to_conf]=<?php echo $key ?>"><?php echo $title; ?></a>
                 <?php } else { ?>
                     <?php echo $title; ?>
-                <?php } ?></strong><br />
-                    <div style="font-size: 8pt"><?php echo $desc; ?></div>
-                </td>
-                <td style="border-bottom: 1px solid #000000" nowrap="nowrap"><?php ownership($plugin_data['authorid'], $plugin_data['name'], $is_plugin_owner); ?></td>
-                <td style="border-bottom: 1px solid #000000" nowrap="nowrap"><?php echo $place ?></td>
-                <td style="border-bottom: 1px solid #000000"><?php echo $moveup ?></td>
-                <td style="border-bottom: 1px solid #000000"><?php echo $movedown ?></td>
-            </tr>
+                <?php } ?></span><br />
+                    <div class="pluginmanager_description" style="font-size: 8pt"><?php echo $desc; ?></div>
+                    <div class="pluginmanager_ownership"><?php ownership($plugin_data['authorid'], $plugin_data['name'], $is_plugin_owner); ?></div>
+                    <noscript>
+                    <div class="pluginmanager_place"><?php echo $place; ?></div>
+                    <div class="pluginmanager_move"><?php echo $moveup ?> <?php echo $movedown ?></div>
+                    </noscript>
+            </li>
 <?php
             $sort_idx++;
         }
+
+        echo '</ol></td>';
     }
+    
 ?>
         <tr>
-            <td colspan="6" align="right"><?php printf(PLUGIN_AVAILABLE_COUNT, $total); ?></td>
+            <td colspan="3" align="right"><?php printf(PLUGIN_AVAILABLE_COUNT, $total); ?></td>
         </tr>
         </table>
         <br />
         <div>
-            <input type="submit" name="REMOVE" title="<?php echo REMOVE_TICKED_PLUGINS; ?>"  value="<?php echo DELETE; ?>" class="serendipityPrettyButton" />
+            <input type="submit" name="REMOVE" title="<?php echo DELETE; ?>"  value="<?php echo REMOVE_TICKED_PLUGINS; ?>" class="serendipityPrettyButton" />
             <input type="submit" name="SAVE"   title="<?php echo SAVE_CHANGES_TO_LAYOUT; ?>" value="<?php echo SAVE; ?>" class="serendipityPrettyButton" />
         </div>
 </form>
index 393560b4247f1e0272d1946dc9a1bdce21bfba27..0dc1a5bb7653bedd983cc623cddd17861394362f 100644 (file)
@@ -95,7 +95,7 @@ class serendipity_plugin_api {
  * @access  public
  * @param   string  classname of the plugin to insert (see description above for details)
  * @param   boolean (reserved) variable to indicate a copy of an existing instance
- * @param   string  The type of the plugin to insert (event/left/right/hidden)
+ * @param   string  The type of the plugin to insert (event/left/right/hide/eventh)
  * @param   int     The authorid of the plugin owner
  * @param   string  The source path of the plugin file
  * @return  string  ID of the new plugin
@@ -322,7 +322,7 @@ class serendipity_plugin_api {
  * Returns a list of currently installed plugins
  *
  * @access public
- * @param   string  The filter for plugins (left|right|hidden|event)
+ * @param   string  The filter for plugins (left|right|hide|event|eventh)
  * @return  array   The list of plugins
  */
     function get_installed_plugins($filter = '*') {
@@ -342,7 +342,7 @@ class serendipity_plugin_api {
  * Searches for installed plugins based on specific conditions
  *
  * @access public
- * @param   string  The filter for plugins (left|right|hidden|event)
+ * @param   string  The filter for plugins (left|right|hide|event|eventh)
  * @param   boolean If true, the filtering logic will be reversed an all plugins that are NOT part of the filter will be returned
  * @param   string  Filter by a specific classname (like 'serendipity_plugin_archives'). Can take SQL wildcards.
  * @param   string  Filter by a specific plugin instance id
@@ -384,7 +384,7 @@ class serendipity_plugin_api {
  * Count the number of plugins to which the filter criteria matches
  *
  * @access public
- * @param   string  The filter for plugins (left|right|hidden|event)
+ * @param   string  The filter for plugins (left|right|hide|event|eventh)
  * @param   boolean If true, the filtering logic will be reversed an all plugins that are NOT part of the filter will be evaluated
  * @return  int     Number of plugins that were found.
  */
@@ -726,7 +726,7 @@ class serendipity_plugin_api {
  *
  * @access public
  * @param   string  The instance ID of a plugin
- * @param   string  The new placement of a plugin (left|right|hidden)
+ * @param   string  The new placement of a plugin (left|right|hide|event|eventh)
  * @param   string  A new sort order for the plugin
  * @return
  */
@@ -735,8 +735,8 @@ class serendipity_plugin_api {
         global $serendipity;
 
         $admin = '';
-        if (!serendipity_checkPermission('adminPlugins') && $placement == 'hidden') {
-            // Only administrators can set plugins to 'hidden' if they are not the owners.
+        if (!serendipity_checkPermission('adminPlugins') && $placement == 'hide') {
+            // Only administrators can set plugins to 'hide' if they are not the owners.
             $admin = " AND (authorid = 0 OR authorid = {$serendipity['authorid']})";
         }
 
@@ -747,7 +747,7 @@ class serendipity_plugin_api {
         }
 
         $sql .= "WHERE name='$name' $admin";
-
+        
         return serendipity_db_query($sql);
     }
 
@@ -781,7 +781,7 @@ class serendipity_plugin_api {
  * Get a list of Sidebar plugins and pass them to Smarty
  *
  * @access public
- * @param   string      The side of plugins to show (left/right/hidden)
+ * @param   string      The side of plugins to show (left/right/hide/event/eventh)
  * @param   string      deprecated: Indicated which wrapping HTML element to use for plugins
  * @param   boolean     Indicates whether only all plugins should be shown that are not in the $side list
  * @param   string      Only show plugins of this plugin class
index 6c4ce540202955c84e8f04f253cbd1cef9d0e3c7..9389fff58211545c258e7edcade934b20fa0db11 100644 (file)
@@ -42,6 +42,7 @@ if (serendipity_is_iframe()) {
         <title><?php echo SERENDIPITY_ADMIN_SUITE; ?></title>
         <meta http-equiv="Content-Type" content="text/html; charset=<?php echo LANG_CHARSET; ?>" />
         <link rel="stylesheet" type="text/css" href="<?php echo $css_file; ?>" />
+        <link rel="stylesheet" type="text/css" href="<?php echo serendipity_getTemplateFile('admin/pluginmanager.css'); ?>" />
         <script type="text/javascript">
         function spawn() {
             if (self.Spawnextended) {
diff --git a/templates/default/admin/img/grablet.gif b/templates/default/admin/img/grablet.gif
new file mode 100644 (file)
index 0000000..0c42cd5
Binary files /dev/null and b/templates/default/admin/img/grablet.gif differ
diff --git a/templates/default/admin/img/grablet_over.gif b/templates/default/admin/img/grablet_over.gif
new file mode 100644 (file)
index 0000000..5b47a98
Binary files /dev/null and b/templates/default/admin/img/grablet_over.gif differ
diff --git a/templates/default/admin/pluginmanager.css b/templates/default/admin/pluginmanager.css
new file mode 100644 (file)
index 0000000..a9973f8
--- /dev/null
@@ -0,0 +1,54 @@
+.pluginmanager_side {
+    vertical-align: top;
+    width: 33%;
+}
+
+.pluginmanager_container {
+    margin: 0px;
+    padding: 2px;
+    list-style-type: none;
+    background-color: #fdfdff;
+    height: 98%;
+}
+
+.pluginmanager_container li {
+   margin: 0px 0px 15px 0px;
+   padding: 3px;
+   position: relative;
+}
+
+.pluginmanager_side .heading {
+   margin: 0px 0px 15px 0px;
+   padding: 3px;
+   font-weight: bold;
+   font-size: 12pt;
+   padding: 20px;
+   text-align: center;
+   border-bottom: 1px dotted #FAFAFA;
+}
+
+.pluginmanager_item_even,  .pluginmanager_item_uneven {
+    background-color: #FAFAFA;
+    border: 1px solid #DADADA;
+}
+
+.pluginmanager_place,
+.pluginmanager_ownership {
+    text-align: right;
+    margin: 2px;
+}
+
+.pluginmanager_grablet {
+    display: block;
+    height: 10px;
+    width: 10%;
+    margin: 0px 0px 5px 0px;
+    padding: 0px;
+    background-image: url(img/grablet.gif);
+    background-repeat: repeat-x;
+    cursor:move;
+}
+
+.grablet:hover {
+    background-image: url(img/grablet_over.gif);
+}
\ No newline at end of file