editor: lists fix - do not touch context root on list creation
[fnpeditor.git] / src / editor / plugins / core / lists.js
1 define(function() {
2     
3 'use strict';
4 /* globals gettext, interpolate */
5
6
7 var getBoundriesForAList = function(fragment) {
8     var node;
9
10     if(fragment instanceof fragment.RangeFragment && fragment.hasSiblingBoundries()) {
11         return fragment.boundriesSiblingParents();
12     }
13     if(fragment instanceof fragment.NodeFragment) {
14         node = fragment.node.getNearestElementNode();
15         if(node.isContextRoot()) {
16             node = fragment.node;
17         }
18
19         return {
20             node1: node,
21             node2: node
22         };
23     }
24 };
25
26 var countItems = function(boundries) {
27     var ptr = boundries.node1,
28         c = 1;
29     while(ptr && !ptr.sameNode(boundries.node2)) {
30         c++;
31         ptr = ptr.next();
32     }
33     return c;
34 };
35
36 var toggleListAction = function(type) {
37     
38     var execute = {
39         add: function(callback, params) {
40             var boundries = getBoundriesForAList(params.fragment),
41                 listParams = {klass: type === 'Bullet' ? 'list' : 'list.enum'},
42                 action = this;
43
44             if(boundries && boundries.node1) {
45                 listParams.node1 = boundries.node1;
46                 listParams.node2 = boundries.node2;
47                 boundries.node1.document.transaction(function() {
48                     var list = boundries.node1.document.createList(listParams),
49                         item1 = list.object.getItem(0),
50                         text = item1 ? item1.contents()[0] : undefined, //
51                         doc = boundries.node1.document;
52                     if(text) {
53                         return doc.createFragment(doc.CaretFragment, {node: text, offset:0});
54                     }
55                 }, {
56                     metadata: {
57                         description: action.getState().description,
58                         fragment: params.fragment
59                     },
60                     success: callback
61                 });
62             } else {
63                 throw new Error('Invalid boundries');
64             }
65         },
66         remove: function(callback, params) {
67             /* globals Node */
68             var current = params.fragment.node,
69                 action = this;
70
71             var toSearch = current.nodeType === Node.ELEMENT_NODE ? [current] : [];
72             toSearch = toSearch.concat(current.parents());
73             toSearch.some(function(node) {
74                 if(node.is('list')) {
75                     node.document.transaction(function() {
76                         var firstItem = node.object.extractListItems(),
77                             toret;
78                         if(params.fragment.isValid()) {
79                             toret = params.fragment;
80                         } else {
81                             toret = node.document.createFragment(node.document.NodeFragment, {node: firstItem});
82                         }
83                         return toret;
84                     }, {
85                         metadata: {
86                             description: action.getState().description,
87                             fragment: params.fragment
88                         },
89                         success: callback
90                     });
91                     
92                     return true; // break
93                 }
94             }.bind(this));
95         },
96         changeType: function(callback, params) {
97             var node = params.fragment.node,
98                 action = this;
99             node.document.transaction(function() {
100                 var list = node.getParent('list');
101                 list.setClass(type === 'Bullet' ? 'list' : 'list.enum');
102                 if(params.fragment.isValid()) {
103                     return params.fragment;
104                 } else {
105                     return node.document.createFragment(node.document.NodeFragment, {node: list.contents()[0]});
106                 }
107             }, {
108                 metadata: {
109                     description: action.getState().description,
110                     fragment: params.fragment
111                 },
112                 success: callback
113             });
114         }
115     };
116
117     var isToggled = function(params) {
118         if(params.fragment && params.fragment.node && params.fragment.node.isInside('list')) {
119             var list = params.fragment.node.getParent('list');
120             return list.getClass() === (type === 'Bullet' ? 'list' : 'list.enum');
121         }
122         return false;
123     };
124
125     var label = type === 'Bullet' ? gettext('bull. list') : gettext('num. list');
126
127     return {
128         name: 'toggle' + type + 'List',
129         context: ['fragment'],
130         params: {
131             fragment: {type: 'context', name: 'fragment'}
132         },
133         stateDefaults: {
134             label: label
135         },
136         getState: function(params) {
137             if(!params.fragment || !params.fragment.isValid()) {
138                 return false;
139             }
140
141             if(params.fragment instanceof params.fragment.CaretFragment && params.fragment.node.isInside('list')) {
142                 var list = params.fragment.node.getParent('list');
143                 if((list.getClass() === 'list' && type === 'Enum') || (list.getClass() === 'list.enum' && type === 'Bullet')) {
144                     return {
145                         allowed: true,
146                         description: interpolate(gettext('Change list type to %s'), [label]),
147                         execute: execute.changeType
148                     };
149                 }
150                 return {
151                     allowed: true,
152                     toggled: isToggled(params),
153                     description: gettext('Remove list'),
154                     execute: execute.remove
155                 };
156
157             }
158             var boundries = getBoundriesForAList(params.fragment);
159             if(boundries) {
160                 return {
161                     allowed: true,
162                     description: interpolate(gettext('Make %s fragment(s) into list'), [countItems(getBoundriesForAList(params.fragment))]),
163                     execute: execute.add
164                 };
165             }
166         }
167     };
168 };
169
170
171 return {
172     actions: [toggleListAction('Bullet'), toggleListAction('Enum')]
173 };
174
175 });