editor: prevent list action from operating across context root boundries
[fnpeditor.git] / src / editor / modules / documentCanvas / canvas / selection.js
1 define(function(require) {
2     
3 'use strict';
4
5 var $ = require('libs/jquery');
6
7 var Selection = function(canvas, params) {
8     this.canvas = canvas;
9     $.extend(this, params);
10 };
11
12 var CaretSelection = function(canvas, params) {
13     Selection.call(this, canvas, params);
14 };
15 CaretSelection.prototype = Object.create(Selection.prototype);
16 $.extend(CaretSelection.prototype, {
17     toDocumentFragment: function() {
18         var doc = this.canvas.wlxmlDocument;
19         return doc.createFragment(doc.CaretFragment, {node: this.element.wlxmlNode, offset: this.offset});
20     },
21     isAtEdge: function() {
22         return this.isAtBeginning() || this.isAtEnd();
23     },
24     isAtBeginning: function() {
25         return this.offset === 0;
26     },
27     isAtEnd: function() {
28         return this.offset === this.element.getText().length;
29     }
30 });
31
32 var TextSelection = function(canvas, params) {
33     var anchorFirst;
34
35     Selection.call(this, canvas, params);
36
37     if(this.anchorElement.sameNode(this.focusElement)) {
38         anchorFirst = this.anchorOffset <= this.focusOffset;
39     } else {
40         /*jshint bitwise: false*/
41         /* globals Node */
42         anchorFirst = this.anchorElement.dom[0].compareDocumentPosition(this.focusElement.dom[0]) & Node.DOCUMENT_POSITION_FOLLOWING;
43     }
44
45     if(anchorFirst) {
46         this.startElement = this.anchorElement;
47         this.startOffset = this.anchorOffset;
48         this.endElement = this.focusElement;
49         this.endOffset = this.focusOffset;
50
51     } else {
52         this.startElement = this.focusElement;
53         this.startOffset = this.focusOffset;
54         this.endElement = this.anchorElement;
55         this.endOffset = this.anchorOffset;
56     }
57 };
58 TextSelection.prototype = Object.create(Selection.prototype);
59 $.extend(TextSelection.prototype, {
60     toDocumentFragment: function() {
61         var doc = this.canvas.wlxmlDocument,
62             anchorNode = this.anchorElement ? this.anchorElement.wlxmlNode : null,
63             focusNode = this.focusElement ? this.focusElement.wlxmlNode : null,
64             node1, node2;
65         
66         if(!anchorNode || !focusNode) {
67             return;
68         }
69
70         if(anchorNode.isSiblingOf(focusNode)) {
71             return doc.createFragment(doc.TextRangeFragment, {
72                 node1: anchorNode,
73                 offset1: this.anchorOffset,
74                 node2: focusNode,
75                 offset2: this.focusOffset,
76             });
77         }
78         else {
79             if(anchorNode.hasSameContextRoot(focusNode)) {
80                 var siblingParents = doc.getSiblingParents({node1: anchorNode, node2: focusNode});
81                 node1 = siblingParents.node1;
82                 node2 = siblingParents.node2;
83             } else {
84                 node1 = focusNode;
85                 node2 = anchorNode;
86             }
87             return doc.createFragment(doc.RangeFragment, {
88                 node1: node1,
89                 node2: node2
90             });
91         }
92     },
93     startsAtBeginning: function() {
94         return this.startOffset === 0;
95     },
96     endsAtEnd: function() {
97         return this.endOffset === this.endElement.getText().length;
98     }
99 });
100
101 var NodeSelection = function(canvas, params) {
102     Selection.call(this, canvas, params);
103 };
104 NodeSelection.prototype = Object.create(Selection.prototype);
105 $.extend(NodeSelection.prototype, {
106     toDocumentFragment: function() {
107         var doc = this.canvas.wlxmlDocument;
108         doc.createFragment(doc.NodeFragment, {node: this.element.wlxmlNode});
109     }
110 });
111
112
113 var isText = function(node) {
114     /* globals Node */
115     return node && node.nodeType === Node.TEXT_NODE && $(node.parentNode).is('[document-text-element]');
116 };
117
118 var types = {
119     caret: CaretSelection,
120     textSelection: TextSelection,
121     nodeSelection: NodeSelection
122 };
123
124 return {
125     fromParams: function(canvas, params) {
126         return new types[params.type](canvas, params);
127     },
128     fromNativeSelection: function(canvas) {
129         /* globals window */
130         var nativeSelection =  window.getSelection(),
131             params = {},
132             element, anchorElement, focusElement;
133             
134         if(nativeSelection.focusNode) {
135             if(nativeSelection.isCollapsed && isText(nativeSelection.focusNode)) {
136                 element = canvas.getDocumentElement(nativeSelection.focusNode);
137                 params = {
138                     type: 'caret',
139                     element: element,
140                     offset: element.isEmpty() ? 0 : nativeSelection.getRangeAt(0).startOffset
141                 };
142             } else if(isText(nativeSelection.focusNode) && isText(nativeSelection.anchorNode)) {
143                 anchorElement = canvas.getDocumentElement(nativeSelection.anchorNode);
144                 focusElement = canvas.getDocumentElement(nativeSelection.focusNode);
145                 params = {
146                     type: 'textSelection',
147                     anchorElement: anchorElement,
148                     anchorOffset: anchorElement.isEmpty() ? 0 : nativeSelection.anchorOffset,
149                     focusElement: focusElement,
150                     focusOffset: focusElement.isEmpty() ? 0 : nativeSelection.focusOffset
151                 };
152             }
153         } else if((element = canvas.getCurrentNodeElement())) {
154             params = {
155                 type: 'nodeSelection',
156                 element: element
157             };
158         }
159         if(params.type) {
160             return this.fromParams(canvas, params);
161         }
162     }
163 };
164
165 });