editor: selection fix - handle Zero Width Space in an empty text element
[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         
65         if(!anchorNode || !focusNode) {
66             return;
67         }
68
69         if(anchorNode.isSiblingOf(focusNode)) {
70             return doc.createFragment(doc.TextRangeFragment, {
71                 node1: anchorNode,
72                 offset1: this.anchorOffset,
73                 node2: focusNode,
74                 offset2: this.focusOffset,
75             });
76         }
77         else {
78             var siblingParents = doc.getSiblingParents({node1: anchorNode, node2: focusNode});
79             return doc.createFragment(doc.RangeFragment, {
80                 node1: siblingParents.node1,
81                 node2: siblingParents.node2
82             });
83         }
84     },
85     startsAtBeginning: function() {
86         return this.startOffset === 0;
87     },
88     endsAtEnd: function() {
89         return this.endOffset === this.endElement.getText().length;
90     }
91 });
92
93 var NodeSelection = function(canvas, params) {
94     Selection.call(this, canvas, params);
95 };
96 NodeSelection.prototype = Object.create(Selection.prototype);
97 $.extend(NodeSelection.prototype, {
98     toDocumentFragment: function() {
99         var doc = this.canvas.wlxmlDocument;
100         doc.createFragment(doc.NodeFragment, {node: this.element.wlxmlNode});
101     }
102 });
103
104
105 var isText = function(node) {
106     /* globals Node */
107     return node && node.nodeType === Node.TEXT_NODE && $(node.parentNode).is('[document-text-element]');
108 };
109
110 var types = {
111     caret: CaretSelection,
112     textSelection: TextSelection,
113     nodeSelection: NodeSelection
114 };
115
116 return {
117     fromParams: function(canvas, params) {
118         return new types[params.type](canvas, params);
119     },
120     fromNativeSelection: function(canvas) {
121         /* globals window */
122         var nativeSelection =  window.getSelection(),
123             params = {},
124             element, anchorElement, focusElement;
125             
126         if(nativeSelection.focusNode) {
127             if(nativeSelection.isCollapsed && isText(nativeSelection.focusNode)) {
128                 element = canvas.getDocumentElement(nativeSelection.focusNode);
129                 params = {
130                     type: 'caret',
131                     element: element,
132                     offset: element.isEmpty() ? 0 : nativeSelection.focusOffset
133                 };
134             } else if(isText(nativeSelection.focusNode) && isText(nativeSelection.anchorNode)) {
135                 anchorElement = canvas.getDocumentElement(nativeSelection.anchorNode);
136                 focusElement = canvas.getDocumentElement(nativeSelection.focusNode);
137                 params = {
138                     type: 'textSelection',
139                     anchorElement: anchorElement,
140                     anchorOffset: anchorElement.isEmpty() ? 0 : nativeSelection.anchorOffset,
141                     focusElement: focusElement,
142                     focusOffset: focusElement.isEmpty() ? 0 : nativeSelection.focusOffset
143                 };
144             }
145         } else if((element = canvas.getCurrentNodeElement())) {
146             params = {
147                 type: 'nodeSelection',
148                 element: element
149             };
150         }
151         if(params.type) {
152             return this.fromParams(canvas, params);
153         }
154     }
155 };
156
157 });