1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159 | 1×
1×
1×
1×
1×
2×
1×
215×
215×
1×
215×
215×
215×
215×
1×
1×
9×
9×
9×
9×
9×
2×
7×
7×
9×
7×
2×
1×
1×
1×
2×
2×
2×
2×
2×
1×
1×
2×
5×
1×
38×
38×
38×
38×
38×
38×
38×
38×
38×
38×
38×
38×
38×
38×
1×
| import $ from 'jquery';
import env from '../core/env';
import func from '../core/func';
import lists from '../core/lists';
import dom from '../core/dom';
export default class Style {
/**
* @method jQueryCSS
*
* [workaround] for old jQuery
* passing an array of style properties to .css()
* will result in an object of property-value pairs.
* (compability with version < 1.9)
*
* @private
* @param {jQuery} $obj
* @param {Array} propertyNames - An array of one or more CSS properties.
* @return {Object}
*/
jQueryCSS($obj, propertyNames) {
Iif (env.jqueryVersion < 1.9) {
const result = {};
$.each(propertyNames, (idx, propertyName) => {
result[propertyName] = $obj.css(propertyName);
});
return result;
}
return $obj.css(propertyNames);
}
/**
* returns style object from node
*
* @param {jQuery} $node
* @return {Object}
*/
fromNode($node) {
const properties = ['font-family', 'font-size', 'text-align', 'list-style-type', 'line-height'];
const styleInfo = this.jQueryCSS($node, properties) || {};
styleInfo['font-size'] = parseInt(styleInfo['font-size'], 10);
return styleInfo;
}
/**
* paragraph level style
*
* @param {WrappedRange} rng
* @param {Object} styleInfo
*/
stylePara(rng, styleInfo) {
$.each(rng.nodes(dom.isPara, {
includeAncestor: true
}), (idx, para) => {
$(para).css(styleInfo);
});
}
/**
* insert and returns styleNodes on range.
*
* @param {WrappedRange} rng
* @param {Object} [options] - options for styleNodes
* @param {String} [options.nodeName] - default: `SPAN`
* @param {Boolean} [options.expandClosestSibling] - default: `false`
* @param {Boolean} [options.onlyPartialContains] - default: `false`
* @return {Node[]}
*/
styleNodes(rng, options) {
rng = rng.splitText();
const nodeName = (options && options.nodeName) || 'SPAN';
const expandClosestSibling = !!(options && options.expandClosestSibling);
const onlyPartialContains = !!(options && options.onlyPartialContains);
if (rng.isCollapsed()) {
return [rng.insertNode(dom.create(nodeName))];
}
let pred = dom.makePredByNodeName(nodeName);
const nodes = rng.nodes(dom.isText, {
fullyContains: true
}).map((text) => {
return dom.singleChildAncestor(text, pred) || dom.wrap(text, nodeName);
});
if (expandClosestSibling) {
if (onlyPartialContains) {
const nodesInRange = rng.nodes();
// compose with partial contains predication
pred = func.and(pred, (node) => {
return lists.contains(nodesInRange, node);
});
}
return nodes.map((node) => {
const siblings = dom.withClosestSiblings(node, pred);
const head = lists.head(siblings);
const tails = lists.tail(siblings);
$.each(tails, (idx, elem) => {
dom.appendChildNodes(head, elem.childNodes);
dom.remove(elem);
});
return lists.head(siblings);
});
} else {
return nodes;
}
}
/**
* get current style on cursor
*
* @param {WrappedRange} rng
* @return {Object} - object contains style properties.
*/
current(rng) {
const $cont = $(!dom.isElement(rng.sc) ? rng.sc.parentNode : rng.sc);
let styleInfo = this.fromNode($cont);
// document.queryCommandState for toggle state
// [workaround] prevent Firefox nsresult: "0x80004005 (NS_ERROR_FAILURE)"
try {
styleInfo = $.extend(styleInfo, {
'font-bold': document.queryCommandState('bold') ? 'bold' : 'normal',
'font-italic': document.queryCommandState('italic') ? 'italic' : 'normal',
'font-underline': document.queryCommandState('underline') ? 'underline' : 'normal',
'font-subscript': document.queryCommandState('subscript') ? 'subscript' : 'normal',
'font-superscript': document.queryCommandState('superscript') ? 'superscript' : 'normal',
'font-strikethrough': document.queryCommandState('strikethrough') ? 'strikethrough' : 'normal',
'font-family': document.queryCommandValue('fontname') || styleInfo['font-family']
});
} catch (e) {}
// list-style-type to list-style(unordered, ordered)
Eif (!rng.isOnList()) {
styleInfo['list-style'] = 'none';
} else {
const orderedTypes = ['circle', 'disc', 'disc-leading-zero', 'square'];
const isUnordered = $.inArray(styleInfo['list-style-type'], orderedTypes) > -1;
styleInfo['list-style'] = isUnordered ? 'unordered' : 'ordered';
}
const para = dom.ancestor(rng.sc, dom.isPara);
Iif (para && para.style['line-height']) {
styleInfo['line-height'] = para.style.lineHeight;
} else {
const lineHeight = parseInt(styleInfo['line-height'], 10) / parseInt(styleInfo['font-size'], 10);
styleInfo['line-height'] = lineHeight.toFixed(1);
}
styleInfo.anchor = rng.isOnAnchor() && dom.ancestor(rng.sc, dom.isAnchor);
styleInfo.ancestors = dom.listAncestor(rng.sc, dom.isEditable);
styleInfo.range = rng;
return styleInfo;
}
}
|