list fixes: headers, invalid elements, easier ending
[fnpeditor.git] / src / editor / plugins / core / lists.js
index 3cf1192..151103d 100644 (file)
@@ -8,10 +8,14 @@ var getBoundriesForAList = function(fragment) {
     var node;
 
     if(fragment instanceof fragment.RangeFragment && fragment.hasSiblingBoundries()) {
-        return fragment.boundriesSiblingParents();
+        return fragment.startNode.hasSameContextRoot(fragment.endNode) && fragment.boundriesSiblingParents();
     }
     if(fragment instanceof fragment.NodeFragment) {
         node = fragment.node.getNearestElementNode();
+        if(node.isContextRoot()) {
+            node = fragment.node;
+        }
+
         return {
             node1: node,
             node2: node
@@ -32,32 +36,123 @@ var countItems = function(boundries) {
 var toggleListAction = function(type) {
     
     var execute = {
-        add: function(params) {
+        add: function(callback, params) {
             var boundries = getBoundriesForAList(params.fragment),
-                listParams = {klass: type === 'Bullet' ? 'list' : 'list.enum'};
+                listParams = {klass: type === 'Bullet' ? 'list' : 'list.enum'},
+                action = this;
+
             if(boundries && boundries.node1) {
-                listParams.node1 = boundries.node1;
-                listParams.node2 = boundries.node2;
-                boundries.node1.document.createList(listParams);
+                boundries.node1.document.transaction(function() {
+                    var iterNode = boundries.node1;
+                    while(true) {
+                        if(!iterNode.is({tagName: 'div', klass: 'p'})) {
+                            if(iterNode.is({tagName: 'header'})) {
+                                var newNode = iterNode.setTag('div');
+                                newNode.setClass('p');
+                                if(iterNode.sameNode(boundries.node1)) {
+                                    boundries.node1 = newNode;
+                                }
+                                if(iterNode.sameNode(boundries.node2)) {
+                                    boundries.node2 = newNode;
+                                }
+                                iterNode = newNode;
+                            } else {
+                                throw new Error('Invalid element');
+                            }
+                        }
+                        if(iterNode.sameNode(boundries.node2))
+                            break;
+                        iterNode = iterNode.next();
+                    }
+                    listParams.node1 = boundries.node1;
+                    listParams.node2 = boundries.node2;
+                    var list = boundries.node1.document.createList(listParams),
+                        item1 = list.object.getItem(0),
+                        text = item1 ? item1.contents()[0] : undefined, //
+                        doc = boundries.node1.document;
+                    if(text) {
+                        return doc.createFragment(doc.CaretFragment, {node: text, offset:0});
+                    }
+                }, {
+                    metadata: {
+                        description: action.getState().description,
+                        fragment: params.fragment
+                    },
+                    success: callback
+                });
             } else {
                 throw new Error('Invalid boundries');
             }
         },
-        remove: function(params) {
+        remove: function(callback, params) {
             /* globals Node */
-            var current = params.fragment.node;
+            var current = params.fragment.node,
+                action = this;
+
+            if(current.parent().is('item') && current.parent().parent().is('list') && current.parent().next() === null) {
+                var item = current.parent();
+                var list = item.parent();
+                current.document.transaction(function() {
+                    var p = list.after({tagName: 'div', attrs: {'class': 'p'}});
+                    p.append({text: current.getText()});
+                    item.detach();
+                    if(list.contents().length === 0) {
+                        list.detach();
+                    }
+                    return current.document.createFragment(current.document.NodeFragment, {node: p});
+                }, {
+                    metadata: {
+                        description: action.getState().description,
+                        fragment: params.fragment
+                    },
+                    success: callback
+                });
+                return;
+            }
 
             var toSearch = current.nodeType === Node.ELEMENT_NODE ? [current] : [];
             toSearch = toSearch.concat(current.parents());
             toSearch.some(function(node) {
                 if(node.is('list')) {
-                    node.object.extractListItems();
+                    node.document.transaction(function() {
+                        var firstItem = node.object.extractListItems(),
+                            toret;
+                        if(params.fragment.isValid()) {
+                            toret = params.fragment;
+                        } else {
+                            toret = node.document.createFragment(node.document.NodeFragment, {node: firstItem});
+                        }
+                        return toret;
+                    }, {
+                        metadata: {
+                            description: action.getState().description,
+                            fragment: params.fragment
+                        },
+                        success: callback
+                    });
+                    
                     return true; // break
                 }
-            });
+            }.bind(this));
         },
-        changeType: function(params) {
-            params.fragment.node.getParent('list').setClass(type === 'Bullet' ? 'list' : 'list.enum');
+        changeType: function(callback, params) {
+            var node = params.fragment.node,
+                action = this;
+            node.document.transaction(function() {
+                var list = node.getParent('list');
+                list.setClass(type === 'Bullet' ? 'list' : 'list.enum');
+                if(params.fragment.isValid()) {
+                    return params.fragment;
+                } else {
+                    return node.document.createFragment(node.document.NodeFragment, {node: list.contents()[0]});
+                }
+            }, {
+                metadata: {
+                    description: action.getState().description,
+                    fragment: params.fragment
+                },
+                success: callback
+            });
         }
     };
 
@@ -69,6 +164,7 @@ var toggleListAction = function(type) {
         return false;
     };
 
+    var label = type === 'Bullet' ? gettext('bull. list') : gettext('num. list');
 
     return {
         name: 'toggle' + type + 'List',
@@ -77,7 +173,7 @@ var toggleListAction = function(type) {
             fragment: {type: 'context', name: 'fragment'}
         },
         stateDefaults: {
-            label: type === 'Bullet' ? gettext('bull. list') : gettext('num. list')
+            label: label
         },
         getState: function(params) {
             if(!params.fragment || !params.fragment.isValid()) {
@@ -89,7 +185,7 @@ var toggleListAction = function(type) {
                 if((list.getClass() === 'list' && type === 'Enum') || (list.getClass() === 'list.enum' && type === 'Bullet')) {
                     return {
                         allowed: true,
-                        description: interpolate(gettext('Change list type to %s'), [type]),
+                        description: interpolate(gettext('Change list type to %s'), [label]),
                         execute: execute.changeType
                     };
                 }
@@ -102,7 +198,20 @@ var toggleListAction = function(type) {
 
             }
             var boundries = getBoundriesForAList(params.fragment);
-            if(boundries) {
+            if(boundries && boundries.node1.hasSameContextRoot(boundries.node2)) {
+                var iterNode = boundries.node1;
+                while(true) {
+                    if(!iterNode.is({tagName: 'div', klass: 'p'}) && !iterNode.is({tagName: 'header'})) {
+                        return {
+                            allowed: false,
+                            description: gettext('Invalid element for a list item')
+                        }
+                    }
+                    if(iterNode.sameNode(boundries.node2))
+                        break;
+                    iterNode = iterNode.next();
+                }
+
                 return {
                     allowed: true,
                     description: interpolate(gettext('Make %s fragment(s) into list'), [countItems(getBoundriesForAList(params.fragment))]),