prevent empty span in a new node
[fnpeditor.git] / src / smartxml / fragments.js
1 define(function(require) {
2     
3 'use strict';
4
5 var $ = require('libs/jquery'),
6     _ = require('libs/underscore');
7
8
9 var Fragment = function(document) {
10     this.document = document;
11 };
12 $.extend(Fragment.prototype, {
13     isValid: function() {
14         return false;
15     }
16 });
17
18
19 var NodeFragment = function(document, params) {
20     Fragment.call(this, document);
21     this.node = params.node;
22     this.nodePath = this.isValid() ? params.node.getPath() : null;
23 };
24 NodeFragment.prototype = Object.create(Fragment.prototype);
25 $.extend(NodeFragment.prototype, {
26     isValid: function() {
27         return this.document.containsNode(this.node);
28     },
29     restoreFromPaths: function() {
30         if(this.nodePath) {
31             this.node = this.document.getNodeByPath(this.nodePath);
32         }
33     }
34 });
35
36
37 var CaretFragment = function(document, params) {
38     this.offset = params.offset;
39     NodeFragment.call(this, document, params);
40
41 };
42 CaretFragment.prototype = Object.create(NodeFragment.prototype);
43 $.extend(CaretFragment.prototype, {
44     isValid: function() {
45         /* globals Node */
46         return NodeFragment.prototype.isValid.call(this) &&
47                 this.node.nodeType === Node.TEXT_NODE &&
48                 _.isNumber(this.offset);
49     }
50 });
51
52
53
54 var RangeFragment = function(document, params) {
55     Fragment.call(this, document);
56
57     if(params.node1.sameNode(params.node2)) {
58         this.startNode = this.endNode = params.node1;
59     } else {
60         /*jshint bitwise: false*/
61         /* globals Node */
62         var node1First = params.node1.nativeNode.compareDocumentPosition(params.node2.nativeNode) & Node.DOCUMENT_POSITION_FOLLOWING;
63         (node1First ? ['start', 'end'] : ['end','start']).forEach(function(prefix, idx) {
64             this[prefix + 'Node'] = params['node'+(idx+1)];
65         }.bind(this));
66     }
67     this.startNodePath = this.startNode.getPath();
68     this.endNodePath = this.endNode.getPath();
69 };
70 RangeFragment.prototype = Object.create(Fragment.prototype);
71 $.extend(RangeFragment.prototype, {
72     isValid: function() {
73         return this.document.containsNode(this.startNode) && this.document.containsNode(this.endNode);
74     },
75     restoreFromPaths: function() {
76         this.startNode = this.document.getNodeByPath(this.startNodePath);
77         this.endNode = this.document.getNodeByPath(this.endNodePath);
78     },
79     hasSiblingBoundaries: function() {
80         return this.isValid() && this.startNode.isSiblingOf(this.endNode);
81     },
82     hasSameBoundaries: function() {
83         return this.isValid() && this.startNode.sameNode(this.endNode);
84     },
85     boundariesSiblingParents: function() {
86         return this.startNode.document.getSiblingParents({
87             node1: this.startNode,
88             node2: this.endNode
89         });
90     },
91     getCommonParent: function() {
92         var siblingParents = this.boundariesSiblingParents();
93         if(siblingParents) {
94             return siblingParents.node1.parent();
95         }
96     },
97 });
98
99 var TextRangeFragment = function(document, params) {
100     var orderChanged;
101
102     RangeFragment.call(this, document, params);
103
104     if(this.startNode.sameNode(this.endNode)) {
105         this.startOffset = Math.min(params.offset1, params.offset2);
106         this.endOffset = Math.max(params.offset1, params.offset2);
107     } else {
108         orderChanged =  !params.node1.sameNode(this.startNode);
109         this.startOffset = orderChanged ? params.offset2 : params.offset1;
110         this.endOffset = orderChanged ? params.offset1 : params.offset2;
111     }
112 };
113 TextRangeFragment.prototype = Object.create(RangeFragment.prototype);
114 $.extend(TextRangeFragment.prototype, {
115     isValid: function() {
116         return RangeFragment.prototype.isValid.call(this) &&
117             _.isNumber(this.startOffset) &&
118             _.isNumber(this.endOffset);
119     }
120 });
121
122 var FragmentTypes = {
123     Fragment: Fragment,
124     NodeFragment: NodeFragment,
125     CaretFragment: CaretFragment,
126     RangeFragment: RangeFragment,
127     TextRangeFragment: TextRangeFragment
128 };
129 _.values(FragmentTypes).forEach(function(Type) {
130     $.extend(Type.prototype, FragmentTypes);
131 });
132
133 return FragmentTypes;
134
135 });