written 0.0.5 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/LICENSE +8 -0
- data/README.md +63 -0
- data/Rakefile +17 -27
- data/lib/written.rb +0 -8
- data/lib/written/app/assets/images/written/placeholder.png +0 -0
- data/lib/written/app/assets/javascripts/written.coffee +2 -0
- data/lib/written/app/assets/javascripts/written/core/content.coffee +53 -35
- data/lib/written/app/assets/javascripts/written/core/cursor.coffee +33 -12
- data/lib/written/app/assets/javascripts/written/core/document.coffee +16 -11
- data/lib/written/app/assets/javascripts/written/core/extensions/string.coffee +9 -0
- data/lib/written/app/assets/javascripts/written/core/extensions/text.coffee +2 -0
- data/lib/written/app/assets/javascripts/written/core/history.coffee +2 -0
- data/lib/written/app/assets/javascripts/written/core/observer.coffee +6 -2
- data/lib/written/app/assets/javascripts/written/core/stringify.coffee +15 -0
- data/lib/written/app/assets/javascripts/written/parsers/block.coffee +69 -0
- data/lib/written/app/assets/javascripts/written/parsers/block/code.coffee +79 -15
- data/lib/written/app/assets/javascripts/written/parsers/block/heading.coffee +60 -5
- data/lib/written/app/assets/javascripts/written/parsers/block/image.coffee +103 -9
- data/lib/written/app/assets/javascripts/written/parsers/block/olist.coffee +94 -12
- data/lib/written/app/assets/javascripts/written/parsers/block/paragraph.coffee +63 -5
- data/lib/written/app/assets/javascripts/written/parsers/block/quote.coffee +92 -0
- data/lib/written/app/assets/javascripts/written/parsers/block/ulist.coffee +93 -12
- data/lib/written/app/assets/javascripts/written/parsers/inline.coffee +81 -0
- data/lib/written/app/assets/javascripts/written/parsers/inline/code.coffee +57 -0
- data/lib/written/app/assets/javascripts/written/parsers/inline/italic.coffee +40 -7
- data/lib/written/app/assets/javascripts/written/parsers/inline/link.coffee +43 -13
- data/lib/written/app/assets/javascripts/written/parsers/inline/list.coffee +27 -0
- data/lib/written/app/assets/javascripts/written/parsers/inline/strong.coffee +41 -7
- data/lib/written/app/assets/javascripts/written/parsers/parsers.coffee +21 -107
- data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/CustomElements.js +32 -0
- data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/base.js +40 -0
- data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/boot.js +124 -0
- data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/observe.js +318 -0
- data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/register.js +369 -0
- data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/traverse.js +86 -0
- data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/upgrade.js +130 -0
- data/lib/written/app/assets/javascripts/written/polyfills/MutationObserver/MutationObserver.js +575 -0
- data/lib/written/app/assets/javascripts/written/polyfills/WeakMap/WeakMap.js +49 -0
- data/lib/written/app/assets/javascripts/written/polyfills/base.coffee +10 -0
- data/lib/written/app/assets/javascripts/written/polyfills/dom.js +104 -0
- data/lib/written/app/assets/javascripts/written/uploaders/aws.coffee +125 -0
- data/lib/written/app/assets/stylesheets/written.scss +80 -11
- data/lib/written/version.rb +1 -1
- data/test/server/app/assets/javascripts/application.coffee +20 -2
- data/test/server/app/assets/stylesheets/application.scss +2 -2
- data/test/server/app/assets/stylesheets/prism.css +0 -1
- data/test/server/app/views/posts/show.html.erb +10 -3
- metadata +26 -20
- data/lib/written/app/assets/javascripts/written/core/ext.coffee +0 -109
- data/lib/written/app/assets/javascripts/written/core/extensions.coffee +0 -2
- data/lib/written/document.rb +0 -42
- data/lib/written/node.rb +0 -21
- data/lib/written/nodes/code.rb +0 -65
- data/lib/written/nodes/heading.rb +0 -15
- data/lib/written/nodes/image.rb +0 -14
- data/lib/written/nodes/ordered_list.rb +0 -18
- data/lib/written/nodes/unordered_list.rb +0 -19
- data/lib/written/parsers.rb +0 -11
- data/lib/written/parsers/base.rb +0 -26
- data/lib/written/parsers/code.rb +0 -60
- data/lib/written/parsers/heading.rb +0 -19
- data/lib/written/parsers/image.rb +0 -19
- data/lib/written/parsers/link.rb +0 -12
- data/lib/written/parsers/list.rb +0 -33
- data/lib/written/parsers/word.rb +0 -16
@@ -0,0 +1,318 @@
|
|
1
|
+
/**
|
2
|
+
* @license
|
3
|
+
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
4
|
+
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
5
|
+
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
6
|
+
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
7
|
+
* Code distributed by Google as part of the polymer project is also
|
8
|
+
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
9
|
+
*/
|
10
|
+
|
11
|
+
/**
|
12
|
+
* Implements custom element observation and attached/detached callbacks
|
13
|
+
* @module observe
|
14
|
+
*/
|
15
|
+
|
16
|
+
window.CustomElements.addModule(function(scope){
|
17
|
+
|
18
|
+
// imports
|
19
|
+
var flags = scope.flags;
|
20
|
+
var forSubtree = scope.forSubtree;
|
21
|
+
var forDocumentTree = scope.forDocumentTree;
|
22
|
+
|
23
|
+
/*
|
24
|
+
Manage nodes attached to document trees
|
25
|
+
*/
|
26
|
+
|
27
|
+
// manage lifecycle on added node and it's subtree; upgrade the node and
|
28
|
+
// entire subtree if necessary and process attached for the node and entire
|
29
|
+
// subtree
|
30
|
+
function addedNode(node, isAttached) {
|
31
|
+
return added(node, isAttached) || addedSubtree(node, isAttached);
|
32
|
+
}
|
33
|
+
|
34
|
+
// manage lifecycle on added node; upgrade if necessary and process attached
|
35
|
+
function added(node, isAttached) {
|
36
|
+
if (scope.upgrade(node, isAttached)) {
|
37
|
+
// Return true to indicate
|
38
|
+
return true;
|
39
|
+
}
|
40
|
+
if (isAttached) {
|
41
|
+
attached(node);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
// manage lifecycle on added node's subtree only; allows the entire subtree
|
46
|
+
// to upgrade if necessary and process attached
|
47
|
+
function addedSubtree(node, isAttached) {
|
48
|
+
forSubtree(node, function(e) {
|
49
|
+
if (added(e, isAttached)) {
|
50
|
+
return true;
|
51
|
+
}
|
52
|
+
});
|
53
|
+
}
|
54
|
+
|
55
|
+
// On platforms without MutationObserver, mutations may not be
|
56
|
+
// reliable and therefore attached/detached are not reliable. We think this
|
57
|
+
// occurs sometimes under heavy DOM operation load, but it is not easy to
|
58
|
+
// reproduce.
|
59
|
+
// To make these callbacks less likely to fail in this scenario,
|
60
|
+
// we *optionally* defer all inserts and removes
|
61
|
+
// to give a chance for elements to be attached into dom.
|
62
|
+
// This helps ensure attachedCallback fires for elements that are created and
|
63
|
+
// immediately added to dom.
|
64
|
+
// This change can significantly alter the performance characteristics
|
65
|
+
// of attaching elements and therefore we only enable it if the user has
|
66
|
+
// explicitly provided the `throttle-attached` flag.
|
67
|
+
var hasThrottledAttached = (window.MutationObserver._isPolyfilled &&
|
68
|
+
flags['throttle-attached']);
|
69
|
+
// bc
|
70
|
+
scope.hasPolyfillMutations = hasThrottledAttached;
|
71
|
+
// exposed for testing
|
72
|
+
scope.hasThrottledAttached = hasThrottledAttached;
|
73
|
+
|
74
|
+
var isPendingMutations = false;
|
75
|
+
var pendingMutations = [];
|
76
|
+
function deferMutation(fn) {
|
77
|
+
pendingMutations.push(fn);
|
78
|
+
if (!isPendingMutations) {
|
79
|
+
isPendingMutations = true;
|
80
|
+
setTimeout(takeMutations);
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
function takeMutations() {
|
85
|
+
isPendingMutations = false;
|
86
|
+
var $p = pendingMutations;
|
87
|
+
for (var i=0, l=$p.length, p; (i<l) && (p=$p[i]); i++) {
|
88
|
+
p();
|
89
|
+
}
|
90
|
+
pendingMutations = [];
|
91
|
+
}
|
92
|
+
|
93
|
+
function attached(element) {
|
94
|
+
if (hasThrottledAttached) {
|
95
|
+
deferMutation(function() {
|
96
|
+
_attached(element);
|
97
|
+
});
|
98
|
+
} else {
|
99
|
+
_attached(element);
|
100
|
+
}
|
101
|
+
}
|
102
|
+
|
103
|
+
// NOTE: due to how MO works (see comments below), an element may be attached
|
104
|
+
// multiple times so we protect against extra processing here.
|
105
|
+
function _attached(element) {
|
106
|
+
// track element for insertion if it's upgraded and cares about insertion
|
107
|
+
// bail if the element is already marked as attached
|
108
|
+
if (element.__upgraded__ && !element.__attached) {
|
109
|
+
element.__attached = true;
|
110
|
+
if (element.attachedCallback) {
|
111
|
+
element.attachedCallback();
|
112
|
+
}
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
/*
|
117
|
+
Manage nodes detached from document trees
|
118
|
+
*/
|
119
|
+
|
120
|
+
// manage lifecycle on detached node and it's subtree; process detached
|
121
|
+
// for the node and entire subtree
|
122
|
+
function detachedNode(node) {
|
123
|
+
detached(node);
|
124
|
+
forSubtree(node, function(e) {
|
125
|
+
detached(e);
|
126
|
+
});
|
127
|
+
}
|
128
|
+
|
129
|
+
function detached(element) {
|
130
|
+
if (hasThrottledAttached) {
|
131
|
+
deferMutation(function() {
|
132
|
+
_detached(element);
|
133
|
+
});
|
134
|
+
} else {
|
135
|
+
_detached(element);
|
136
|
+
}
|
137
|
+
}
|
138
|
+
|
139
|
+
// NOTE: due to how MO works (see comments below), an element may be detached
|
140
|
+
// multiple times so we protect against extra processing here.
|
141
|
+
function _detached(element) {
|
142
|
+
// track element for removal if it's upgraded and cares about removal
|
143
|
+
// bail if the element is already marked as not attached
|
144
|
+
if (element.__upgraded__ && element.__attached) {
|
145
|
+
element.__attached = false;
|
146
|
+
if (element.detachedCallback) {
|
147
|
+
element.detachedCallback();
|
148
|
+
}
|
149
|
+
}
|
150
|
+
}
|
151
|
+
|
152
|
+
// recurse up the tree to check if an element is actually in the main document.
|
153
|
+
function inDocument(element) {
|
154
|
+
var p = element;
|
155
|
+
var doc = window.wrap(document);
|
156
|
+
while (p) {
|
157
|
+
if (p == doc) {
|
158
|
+
return true;
|
159
|
+
}
|
160
|
+
p = p.parentNode || ((p.nodeType === Node.DOCUMENT_FRAGMENT_NODE) && p.host);
|
161
|
+
}
|
162
|
+
}
|
163
|
+
|
164
|
+
// Install an element observer on all shadowRoots owned by node.
|
165
|
+
function watchShadow(node) {
|
166
|
+
if (node.shadowRoot && !node.shadowRoot.__watched) {
|
167
|
+
flags.dom && console.log('watching shadow-root for: ', node.localName);
|
168
|
+
// watch all unwatched roots...
|
169
|
+
var root = node.shadowRoot;
|
170
|
+
while (root) {
|
171
|
+
observe(root);
|
172
|
+
root = root.olderShadowRoot;
|
173
|
+
}
|
174
|
+
}
|
175
|
+
}
|
176
|
+
|
177
|
+
/*
|
178
|
+
NOTE: In order to process all mutations, it's necessary to recurse into
|
179
|
+
any added nodes. However, it's not possible to determine a priori if a node
|
180
|
+
will get its own mutation record. This means
|
181
|
+
*nodes can be seen multiple times*.
|
182
|
+
|
183
|
+
Here's an example:
|
184
|
+
|
185
|
+
(1) In this case, recursion is required to see `child`:
|
186
|
+
|
187
|
+
node.innerHTML = '<div><child></child></div>'
|
188
|
+
|
189
|
+
(2) In this case, child will get its own mutation record:
|
190
|
+
|
191
|
+
node.appendChild(div).appendChild(child);
|
192
|
+
|
193
|
+
We cannot know ahead of time if we need to walk into the node in (1) so we
|
194
|
+
do and see child; however, if it was added via case (2) then it will have its
|
195
|
+
own record and therefore be seen 2x.
|
196
|
+
*/
|
197
|
+
function handler(root, mutations) {
|
198
|
+
// for logging only
|
199
|
+
if (flags.dom) {
|
200
|
+
var mx = mutations[0];
|
201
|
+
if (mx && mx.type === 'childList' && mx.addedNodes) {
|
202
|
+
if (mx.addedNodes) {
|
203
|
+
var d = mx.addedNodes[0];
|
204
|
+
while (d && d !== document && !d.host) {
|
205
|
+
d = d.parentNode;
|
206
|
+
}
|
207
|
+
var u = d && (d.URL || d._URL || (d.host && d.host.localName)) || '';
|
208
|
+
u = u.split('/?').shift().split('/').pop();
|
209
|
+
}
|
210
|
+
}
|
211
|
+
console.group('mutations (%d) [%s]', mutations.length, u || '');
|
212
|
+
}
|
213
|
+
// handle mutations
|
214
|
+
// NOTE: do an `inDocument` check dynamically here. It's possible that `root`
|
215
|
+
// is a document in which case the answer here can never change; however
|
216
|
+
// `root` may be an element like a shadowRoot that can be added/removed
|
217
|
+
// from the main document.
|
218
|
+
var isAttached = inDocument(root);
|
219
|
+
mutations.forEach(function(mx) {
|
220
|
+
if (mx.type === 'childList') {
|
221
|
+
forEach(mx.addedNodes, function(n) {
|
222
|
+
if (!n.localName) {
|
223
|
+
return;
|
224
|
+
}
|
225
|
+
addedNode(n, isAttached);
|
226
|
+
});
|
227
|
+
forEach(mx.removedNodes, function(n) {
|
228
|
+
if (!n.localName) {
|
229
|
+
return;
|
230
|
+
}
|
231
|
+
detachedNode(n);
|
232
|
+
});
|
233
|
+
}
|
234
|
+
});
|
235
|
+
flags.dom && console.groupEnd();
|
236
|
+
};
|
237
|
+
|
238
|
+
|
239
|
+
/*
|
240
|
+
When elements are added to the dom, upgrade and attached/detached may be
|
241
|
+
asynchronous. `CustomElements.takeRecords` can be called to process any
|
242
|
+
pending upgrades and attached/detached callbacks synchronously.
|
243
|
+
*/
|
244
|
+
function takeRecords(node) {
|
245
|
+
node = window.wrap(node);
|
246
|
+
// If the optional node is not supplied, assume we mean the whole document.
|
247
|
+
if (!node) {
|
248
|
+
node = window.wrap(document);
|
249
|
+
}
|
250
|
+
// Find the root of the tree, which will be an Document or ShadowRoot.
|
251
|
+
while (node.parentNode) {
|
252
|
+
node = node.parentNode;
|
253
|
+
}
|
254
|
+
var observer = node.__observer;
|
255
|
+
if (observer) {
|
256
|
+
handler(node, observer.takeRecords());
|
257
|
+
takeMutations();
|
258
|
+
}
|
259
|
+
}
|
260
|
+
|
261
|
+
var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
|
262
|
+
|
263
|
+
|
264
|
+
// observe a node tree; bail if it's already being observed.
|
265
|
+
function observe(inRoot) {
|
266
|
+
if (inRoot.__observer) {
|
267
|
+
return;
|
268
|
+
}
|
269
|
+
// For each ShadowRoot, we create a new MutationObserver, so the root can be
|
270
|
+
// garbage collected once all references to the `inRoot` node are gone.
|
271
|
+
// Give the handler access to the root so that an 'in document' check can
|
272
|
+
// be done.
|
273
|
+
var observer = new MutationObserver(handler.bind(this, inRoot));
|
274
|
+
observer.observe(inRoot, {childList: true, subtree: true});
|
275
|
+
inRoot.__observer = observer;
|
276
|
+
}
|
277
|
+
|
278
|
+
// upgrade an entire document and observe it for elements changes.
|
279
|
+
function upgradeDocument(doc) {
|
280
|
+
doc = window.wrap(doc);
|
281
|
+
flags.dom && console.group('upgradeDocument: ', (doc.baseURI).split('/').pop());
|
282
|
+
var isMainDocument = (doc === window.wrap(document));
|
283
|
+
addedNode(doc, isMainDocument);
|
284
|
+
observe(doc);
|
285
|
+
flags.dom && console.groupEnd();
|
286
|
+
}
|
287
|
+
|
288
|
+
/*
|
289
|
+
This method is intended to be called when the document tree (including imports)
|
290
|
+
has pending custom elements to upgrade. It can be called multiple times and
|
291
|
+
should do nothing if no elements are in need of upgrade.
|
292
|
+
*/
|
293
|
+
function upgradeDocumentTree(doc) {
|
294
|
+
forDocumentTree(doc, upgradeDocument);
|
295
|
+
}
|
296
|
+
|
297
|
+
|
298
|
+
// Patch `createShadowRoot()` if Shadow DOM is available, otherwise leave
|
299
|
+
// undefined to aid feature detection of Shadow DOM.
|
300
|
+
var originalCreateShadowRoot = Element.prototype.createShadowRoot;
|
301
|
+
if (originalCreateShadowRoot) {
|
302
|
+
Element.prototype.createShadowRoot = function() {
|
303
|
+
var root = originalCreateShadowRoot.call(this);
|
304
|
+
window.CustomElements.watchShadow(this);
|
305
|
+
return root;
|
306
|
+
};
|
307
|
+
}
|
308
|
+
|
309
|
+
// exports
|
310
|
+
scope.watchShadow = watchShadow;
|
311
|
+
scope.upgradeDocumentTree = upgradeDocumentTree;
|
312
|
+
scope.upgradeDocument = upgradeDocument;
|
313
|
+
scope.upgradeSubtree = addedSubtree;
|
314
|
+
scope.upgradeAll = addedNode;
|
315
|
+
scope.attached = attached;
|
316
|
+
scope.takeRecords = takeRecords;
|
317
|
+
|
318
|
+
});
|
@@ -0,0 +1,369 @@
|
|
1
|
+
/**
|
2
|
+
* @license
|
3
|
+
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
4
|
+
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
5
|
+
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
6
|
+
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
7
|
+
* Code distributed by Google as part of the polymer project is also
|
8
|
+
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
9
|
+
*/
|
10
|
+
|
11
|
+
/**
|
12
|
+
* Implements `document.registerElement`
|
13
|
+
* @module register
|
14
|
+
*/
|
15
|
+
|
16
|
+
/**
|
17
|
+
* Polyfilled extensions to the `document` object.
|
18
|
+
* @class Document
|
19
|
+
*/
|
20
|
+
|
21
|
+
window.CustomElements.addModule(function(scope) {
|
22
|
+
|
23
|
+
// imports
|
24
|
+
var isIE = scope.isIE;
|
25
|
+
var upgradeDocumentTree = scope.upgradeDocumentTree;
|
26
|
+
var upgradeAll = scope.upgradeAll;
|
27
|
+
var upgradeWithDefinition = scope.upgradeWithDefinition;
|
28
|
+
var implementPrototype = scope.implementPrototype;
|
29
|
+
var useNative = scope.useNative;
|
30
|
+
|
31
|
+
/**
|
32
|
+
* Registers a custom tag name with the document.
|
33
|
+
*
|
34
|
+
* When a registered element is created, a `readyCallback` method is called
|
35
|
+
* in the scope of the element. The `readyCallback` method can be specified on
|
36
|
+
* either `options.prototype` or `options.lifecycle` with the latter taking
|
37
|
+
* precedence.
|
38
|
+
*
|
39
|
+
* @method register
|
40
|
+
* @param {String} name The tag name to register. Must include a dash ('-'),
|
41
|
+
* for example 'x-component'.
|
42
|
+
* @param {Object} options
|
43
|
+
* @param {String} [options.extends]
|
44
|
+
* (_off spec_) Tag name of an element to extend (or blank for a new
|
45
|
+
* element). This parameter is not part of the specification, but instead
|
46
|
+
* is a hint for the polyfill because the extendee is difficult to infer.
|
47
|
+
* Remember that the input prototype must chain to the extended element's
|
48
|
+
* prototype (or HTMLElement.prototype) regardless of the value of
|
49
|
+
* `extends`.
|
50
|
+
* @param {Object} options.prototype The prototype to use for the new
|
51
|
+
* element. The prototype must inherit from HTMLElement.
|
52
|
+
* @param {Object} [options.lifecycle]
|
53
|
+
* Callbacks that fire at important phases in the life of the custom
|
54
|
+
* element.
|
55
|
+
*
|
56
|
+
* @example
|
57
|
+
* FancyButton = document.registerElement("fancy-button", {
|
58
|
+
* extends: 'button',
|
59
|
+
* prototype: Object.create(HTMLButtonElement.prototype, {
|
60
|
+
* readyCallback: {
|
61
|
+
* value: function() {
|
62
|
+
* console.log("a fancy-button was created",
|
63
|
+
* }
|
64
|
+
* }
|
65
|
+
* })
|
66
|
+
* });
|
67
|
+
* @return {Function} Constructor for the newly registered type.
|
68
|
+
*/
|
69
|
+
function register(name, options) {
|
70
|
+
//console.warn('document.registerElement("' + name + '", ', options, ')');
|
71
|
+
// construct a defintion out of options
|
72
|
+
// TODO(sjmiles): probably should clone options instead of mutating it
|
73
|
+
var definition = options || {};
|
74
|
+
if (!name) {
|
75
|
+
throw new Error('document.registerElement: first argument `name` must not be empty');
|
76
|
+
}
|
77
|
+
if (name.indexOf('-') < 0) {
|
78
|
+
throw new Error('document.registerElement: first argument (\'name\') must contain a dash (\'-\'). Argument provided was \'' + String(name) + '\'.');
|
79
|
+
}
|
80
|
+
// prevent registering reserved names
|
81
|
+
if (isReservedTag(name)) {
|
82
|
+
throw new Error('Failed to execute \'registerElement\' on \'Document\': Registration failed for type \'' + String(name) + '\'. The type name is invalid.');
|
83
|
+
}
|
84
|
+
// elements may only be registered once
|
85
|
+
if (getRegisteredDefinition(name)) {
|
86
|
+
throw new Error('DuplicateDefinitionError: a type with name \'' + String(name) + '\' is already registered');
|
87
|
+
}
|
88
|
+
// prototype is optional, default to an extension of HTMLElement
|
89
|
+
if (!definition.prototype) {
|
90
|
+
definition.prototype = Object.create(HTMLElement.prototype);
|
91
|
+
}
|
92
|
+
// record name
|
93
|
+
definition.__name = name.toLowerCase();
|
94
|
+
// ensure extended name is also treated case-insensitively
|
95
|
+
if (definition.extends) {
|
96
|
+
definition.extends = definition.extends.toLowerCase();
|
97
|
+
}
|
98
|
+
// ensure a lifecycle object so we don't have to null test it
|
99
|
+
definition.lifecycle = definition.lifecycle || {};
|
100
|
+
// build a list of ancestral custom elements (for native base detection)
|
101
|
+
// TODO(sjmiles): we used to need to store this, but current code only
|
102
|
+
// uses it in 'resolveTagName': it should probably be inlined
|
103
|
+
definition.ancestry = ancestry(definition.extends);
|
104
|
+
// extensions of native specializations of HTMLElement require localName
|
105
|
+
// to remain native, and use secondary 'is' specifier for extension type
|
106
|
+
resolveTagName(definition);
|
107
|
+
// some platforms require modifications to the user-supplied prototype
|
108
|
+
// chain
|
109
|
+
resolvePrototypeChain(definition);
|
110
|
+
// overrides to implement attributeChanged callback
|
111
|
+
overrideAttributeApi(definition.prototype);
|
112
|
+
// 7.1.5: Register the DEFINITION with DOCUMENT
|
113
|
+
registerDefinition(definition.__name, definition);
|
114
|
+
// 7.1.7. Run custom element constructor generation algorithm with PROTOTYPE
|
115
|
+
// 7.1.8. Return the output of the previous step.
|
116
|
+
definition.ctor = generateConstructor(definition);
|
117
|
+
definition.ctor.prototype = definition.prototype;
|
118
|
+
// force our .constructor to be our actual constructor
|
119
|
+
definition.prototype.constructor = definition.ctor;
|
120
|
+
// if initial parsing is complete
|
121
|
+
if (scope.ready) {
|
122
|
+
// upgrade any pre-existing nodes of this type
|
123
|
+
upgradeDocumentTree(document);
|
124
|
+
}
|
125
|
+
return definition.ctor;
|
126
|
+
}
|
127
|
+
|
128
|
+
// attribute watching
|
129
|
+
function overrideAttributeApi(prototype) {
|
130
|
+
// overrides to implement callbacks
|
131
|
+
// TODO(sjmiles): should support access via .attributes NamedNodeMap
|
132
|
+
// TODO(sjmiles): preserves user defined overrides, if any
|
133
|
+
if (prototype.setAttribute._polyfilled) {
|
134
|
+
return;
|
135
|
+
}
|
136
|
+
var setAttribute = prototype.setAttribute;
|
137
|
+
prototype.setAttribute = function(name, value) {
|
138
|
+
changeAttribute.call(this, name, value, setAttribute);
|
139
|
+
};
|
140
|
+
var removeAttribute = prototype.removeAttribute;
|
141
|
+
prototype.removeAttribute = function(name) {
|
142
|
+
changeAttribute.call(this, name, null, removeAttribute);
|
143
|
+
};
|
144
|
+
prototype.setAttribute._polyfilled = true;
|
145
|
+
}
|
146
|
+
|
147
|
+
// https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/
|
148
|
+
// index.html#dfn-attribute-changed-callback
|
149
|
+
function changeAttribute(name, value, operation) {
|
150
|
+
name = name.toLowerCase();
|
151
|
+
var oldValue = this.getAttribute(name);
|
152
|
+
operation.apply(this, arguments);
|
153
|
+
var newValue = this.getAttribute(name);
|
154
|
+
if (this.attributeChangedCallback &&
|
155
|
+
(newValue !== oldValue)) {
|
156
|
+
this.attributeChangedCallback(name, oldValue, newValue);
|
157
|
+
}
|
158
|
+
}
|
159
|
+
|
160
|
+
function isReservedTag(name) {
|
161
|
+
for (var i = 0; i < reservedTagList.length; i++) {
|
162
|
+
if (name === reservedTagList[i]) {
|
163
|
+
return true;
|
164
|
+
}
|
165
|
+
}
|
166
|
+
}
|
167
|
+
|
168
|
+
var reservedTagList = [
|
169
|
+
'annotation-xml', 'color-profile', 'font-face', 'font-face-src',
|
170
|
+
'font-face-uri', 'font-face-format', 'font-face-name', 'missing-glyph'
|
171
|
+
];
|
172
|
+
|
173
|
+
function ancestry(extnds) {
|
174
|
+
var extendee = getRegisteredDefinition(extnds);
|
175
|
+
if (extendee) {
|
176
|
+
return ancestry(extendee.extends).concat([extendee]);
|
177
|
+
}
|
178
|
+
return [];
|
179
|
+
}
|
180
|
+
|
181
|
+
function resolveTagName(definition) {
|
182
|
+
// if we are explicitly extending something, that thing is our
|
183
|
+
// baseTag, unless it represents a custom component
|
184
|
+
var baseTag = definition.extends;
|
185
|
+
// if our ancestry includes custom components, we only have a
|
186
|
+
// baseTag if one of them does
|
187
|
+
for (var i=0, a; (a=definition.ancestry[i]); i++) {
|
188
|
+
baseTag = a.is && a.tag;
|
189
|
+
}
|
190
|
+
// our tag is our baseTag, if it exists, and otherwise just our name
|
191
|
+
definition.tag = baseTag || definition.__name;
|
192
|
+
if (baseTag) {
|
193
|
+
// if there is a base tag, use secondary 'is' specifier
|
194
|
+
definition.is = definition.__name;
|
195
|
+
}
|
196
|
+
}
|
197
|
+
|
198
|
+
function resolvePrototypeChain(definition) {
|
199
|
+
// if we don't support __proto__ we need to locate the native level
|
200
|
+
// prototype for precise mixing in
|
201
|
+
if (!Object.__proto__) {
|
202
|
+
// default prototype
|
203
|
+
var nativePrototype = HTMLElement.prototype;
|
204
|
+
// work out prototype when using type-extension
|
205
|
+
if (definition.is) {
|
206
|
+
var inst = document.createElement(definition.tag);
|
207
|
+
nativePrototype = Object.getPrototypeOf(inst);
|
208
|
+
}
|
209
|
+
// ensure __proto__ reference is installed at each point on the prototype
|
210
|
+
// chain.
|
211
|
+
// NOTE: On platforms without __proto__, a mixin strategy is used instead
|
212
|
+
// of prototype swizzling. In this case, this generated __proto__ provides
|
213
|
+
// limited support for prototype traversal.
|
214
|
+
var proto = definition.prototype, ancestor;
|
215
|
+
var foundPrototype = false;
|
216
|
+
while (proto) {
|
217
|
+
if (proto == nativePrototype) {
|
218
|
+
foundPrototype = true;
|
219
|
+
}
|
220
|
+
ancestor = Object.getPrototypeOf(proto);
|
221
|
+
if (ancestor) {
|
222
|
+
proto.__proto__ = ancestor;
|
223
|
+
}
|
224
|
+
proto = ancestor;
|
225
|
+
}
|
226
|
+
if (!foundPrototype) {
|
227
|
+
// Note the spec actually allows this, but it results in broken elements
|
228
|
+
// and is difficult to polyfill correctly, so we throw
|
229
|
+
console.warn(definition.tag + ' prototype not found in prototype chain for ' +
|
230
|
+
definition.is);
|
231
|
+
}
|
232
|
+
// cache this in case of mixin
|
233
|
+
definition.native = nativePrototype;
|
234
|
+
}
|
235
|
+
}
|
236
|
+
|
237
|
+
// SECTION 4
|
238
|
+
|
239
|
+
function instantiate(definition) {
|
240
|
+
// 4.a.1. Create a new object that implements PROTOTYPE
|
241
|
+
// 4.a.2. Let ELEMENT by this new object
|
242
|
+
//
|
243
|
+
// the custom element instantiation algorithm must also ensure that the
|
244
|
+
// output is a valid DOM element with the proper wrapper in place.
|
245
|
+
//
|
246
|
+
return upgradeWithDefinition(domCreateElement(definition.tag), definition);
|
247
|
+
}
|
248
|
+
|
249
|
+
// element registry (maps tag names to definitions)
|
250
|
+
|
251
|
+
var registry = {};
|
252
|
+
|
253
|
+
function getRegisteredDefinition(name) {
|
254
|
+
if (name) {
|
255
|
+
return registry[name.toLowerCase()];
|
256
|
+
}
|
257
|
+
}
|
258
|
+
|
259
|
+
function registerDefinition(name, definition) {
|
260
|
+
registry[name] = definition;
|
261
|
+
}
|
262
|
+
|
263
|
+
function generateConstructor(definition) {
|
264
|
+
return function() {
|
265
|
+
return instantiate(definition);
|
266
|
+
};
|
267
|
+
}
|
268
|
+
|
269
|
+
var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
|
270
|
+
function createElementNS(namespace, tag, typeExtension) {
|
271
|
+
// NOTE: we do not support non-HTML elements,
|
272
|
+
// just call createElementNS for non HTML Elements
|
273
|
+
if (namespace === HTML_NAMESPACE) {
|
274
|
+
return createElement(tag, typeExtension);
|
275
|
+
} else {
|
276
|
+
return domCreateElementNS(namespace, tag);
|
277
|
+
}
|
278
|
+
}
|
279
|
+
|
280
|
+
function createElement(tag, typeExtension) {
|
281
|
+
// TODO(sjmiles): ignore 'tag' when using 'typeExtension', we could
|
282
|
+
// error check it, or perhaps there should only ever be one argument
|
283
|
+
if (tag) {
|
284
|
+
tag = tag.toLowerCase();
|
285
|
+
}
|
286
|
+
if (typeExtension) {
|
287
|
+
typeExtension = typeExtension.toLowerCase();
|
288
|
+
}
|
289
|
+
var definition = getRegisteredDefinition(typeExtension || tag);
|
290
|
+
if (definition) {
|
291
|
+
if (tag == definition.tag && typeExtension == definition.is) {
|
292
|
+
return new definition.ctor();
|
293
|
+
}
|
294
|
+
// Handle empty string for type extension.
|
295
|
+
if (!typeExtension && !definition.is) {
|
296
|
+
return new definition.ctor();
|
297
|
+
}
|
298
|
+
}
|
299
|
+
var element;
|
300
|
+
if (typeExtension) {
|
301
|
+
element = createElement(tag);
|
302
|
+
element.setAttribute('is', typeExtension);
|
303
|
+
return element;
|
304
|
+
}
|
305
|
+
element = domCreateElement(tag);
|
306
|
+
// Custom tags should be HTMLElements even if not upgraded.
|
307
|
+
if (tag.indexOf('-') >= 0) {
|
308
|
+
implementPrototype(element, HTMLElement);
|
309
|
+
}
|
310
|
+
return element;
|
311
|
+
}
|
312
|
+
|
313
|
+
// capture native createElement before we override it
|
314
|
+
var domCreateElement = document.createElement.bind(document);
|
315
|
+
var domCreateElementNS = document.createElementNS.bind(document);
|
316
|
+
|
317
|
+
// Create a custom 'instanceof'. This is necessary when CustomElements
|
318
|
+
// are implemented via a mixin strategy, as for example on IE10.
|
319
|
+
var isInstance;
|
320
|
+
if (!Object.__proto__ && !useNative) {
|
321
|
+
isInstance = function(obj, ctor) {
|
322
|
+
// Allows instanceof(<div>, HTMLElement.prototype) to work
|
323
|
+
if (obj instanceof ctor) {
|
324
|
+
return true;
|
325
|
+
}
|
326
|
+
var p = obj;
|
327
|
+
while (p) {
|
328
|
+
// NOTE: this is not technically correct since we're not checking if
|
329
|
+
// an object is an instance of a constructor; however, this should
|
330
|
+
// be good enough for the mixin strategy.
|
331
|
+
if (p === ctor.prototype) {
|
332
|
+
return true;
|
333
|
+
}
|
334
|
+
p = p.__proto__;
|
335
|
+
}
|
336
|
+
return false;
|
337
|
+
};
|
338
|
+
} else {
|
339
|
+
isInstance = function(obj, base) {
|
340
|
+
return obj instanceof base;
|
341
|
+
};
|
342
|
+
}
|
343
|
+
|
344
|
+
// wrap a dom object method that works on nodes such that it forces upgrade
|
345
|
+
function wrapDomMethodToForceUpgrade(obj, methodName) {
|
346
|
+
var orig = obj[methodName];
|
347
|
+
obj[methodName] = function() {
|
348
|
+
var n = orig.apply(this, arguments);
|
349
|
+
upgradeAll(n);
|
350
|
+
return n;
|
351
|
+
};
|
352
|
+
}
|
353
|
+
|
354
|
+
wrapDomMethodToForceUpgrade(Node.prototype, 'cloneNode');
|
355
|
+
wrapDomMethodToForceUpgrade(document, 'importNode');
|
356
|
+
|
357
|
+
// exports
|
358
|
+
document.registerElement = register;
|
359
|
+
document.createElement = createElement; // override
|
360
|
+
document.createElementNS = createElementNS; // override
|
361
|
+
scope.registry = registry;
|
362
|
+
scope.instanceof = isInstance;
|
363
|
+
scope.reservedTagList = reservedTagList;
|
364
|
+
scope.getRegisteredDefinition = getRegisteredDefinition;
|
365
|
+
|
366
|
+
// bc
|
367
|
+
document.register = document.registerElement;
|
368
|
+
|
369
|
+
});
|