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,86 @@
|
|
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
|
+
// helper methods for traversing through element trees
|
12
|
+
window.CustomElements.addModule(function(scope){
|
13
|
+
|
14
|
+
// imports
|
15
|
+
var IMPORT_LINK_TYPE = window.HTMLImports ? window.HTMLImports.IMPORT_LINK_TYPE : 'none';
|
16
|
+
|
17
|
+
// walk the subtree rooted at node, including descent into shadow-roots,
|
18
|
+
// applying 'cb' to each element
|
19
|
+
function forSubtree(node, cb) {
|
20
|
+
//flags.dom && node.childNodes && node.childNodes.length && console.group('subTree: ', node);
|
21
|
+
findAllElements(node, function(e) {
|
22
|
+
if (cb(e)) {
|
23
|
+
return true;
|
24
|
+
}
|
25
|
+
forRoots(e, cb);
|
26
|
+
});
|
27
|
+
forRoots(node, cb);
|
28
|
+
//flags.dom && node.childNodes && node.childNodes.length && console.groupEnd();
|
29
|
+
}
|
30
|
+
|
31
|
+
|
32
|
+
// walk the subtree rooted at node, applying 'find(element, data)' function
|
33
|
+
// to each element
|
34
|
+
// if 'find' returns true for 'element', do not search element's subtree
|
35
|
+
function findAllElements(node, find, data) {
|
36
|
+
var e = node.firstElementChild;
|
37
|
+
if (!e) {
|
38
|
+
e = node.firstChild;
|
39
|
+
while (e && e.nodeType !== Node.ELEMENT_NODE) {
|
40
|
+
e = e.nextSibling;
|
41
|
+
}
|
42
|
+
}
|
43
|
+
while (e) {
|
44
|
+
if (find(e, data) !== true) {
|
45
|
+
findAllElements(e, find, data);
|
46
|
+
}
|
47
|
+
e = e.nextElementSibling;
|
48
|
+
}
|
49
|
+
return null;
|
50
|
+
}
|
51
|
+
|
52
|
+
// walk all shadowRoots on a given node.
|
53
|
+
function forRoots(node, cb) {
|
54
|
+
var root = node.shadowRoot;
|
55
|
+
while(root) {
|
56
|
+
forSubtree(root, cb);
|
57
|
+
root = root.olderShadowRoot;
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
function forDocumentTree(doc, cb) {
|
62
|
+
_forDocumentTree(doc, cb, []);
|
63
|
+
}
|
64
|
+
|
65
|
+
|
66
|
+
function _forDocumentTree(doc, cb, processingDocuments) {
|
67
|
+
doc = window.wrap(doc);
|
68
|
+
if (processingDocuments.indexOf(doc) >= 0) {
|
69
|
+
return;
|
70
|
+
}
|
71
|
+
processingDocuments.push(doc);
|
72
|
+
var imports = doc.querySelectorAll('link[rel=' + IMPORT_LINK_TYPE + ']');
|
73
|
+
for (var i=0, l=imports.length, n; (i<l) && (n=imports[i]); i++) {
|
74
|
+
if (n.import) {
|
75
|
+
_forDocumentTree(n.import, cb, processingDocuments);
|
76
|
+
}
|
77
|
+
}
|
78
|
+
cb(doc);
|
79
|
+
}
|
80
|
+
|
81
|
+
// exports
|
82
|
+
scope.forDocumentTree = forDocumentTree;
|
83
|
+
scope.forSubtree = forSubtree;
|
84
|
+
|
85
|
+
|
86
|
+
});
|
@@ -0,0 +1,130 @@
|
|
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 upgrading
|
13
|
+
* @module upgrade
|
14
|
+
*/
|
15
|
+
|
16
|
+
window.CustomElements.addModule(function(scope) {
|
17
|
+
|
18
|
+
// imports
|
19
|
+
var flags = scope.flags;
|
20
|
+
|
21
|
+
/**
|
22
|
+
* Upgrade an element to a custom element. Upgrading an element
|
23
|
+
* causes the custom prototype to be applied, an `is` attribute
|
24
|
+
* to be attached (as needed), and invocation of the `readyCallback`.
|
25
|
+
* If the element is in the main document, the `attachedkCallback` method
|
26
|
+
* will be invoked.
|
27
|
+
* `upgrade` does nothing if the element is already upgraded, or
|
28
|
+
* if it matches no registered custom tag name.
|
29
|
+
*
|
30
|
+
* @method ugprade
|
31
|
+
* @param {Element} element The element to upgrade.
|
32
|
+
* @return {Element} The upgraded element.
|
33
|
+
*/
|
34
|
+
// Upgrade a node if it can be upgraded and is not already.
|
35
|
+
function upgrade(node, isAttached) {
|
36
|
+
// upgrade template elements before custom elements
|
37
|
+
if (node.localName === 'template') {
|
38
|
+
if (window.HTMLTemplateElement && HTMLTemplateElement.decorate) {
|
39
|
+
HTMLTemplateElement.decorate(node);
|
40
|
+
}
|
41
|
+
}
|
42
|
+
if (!node.__upgraded__ && (node.nodeType === Node.ELEMENT_NODE)) {
|
43
|
+
var is = node.getAttribute('is');
|
44
|
+
// find definition first by localName and secondarily by is attribute
|
45
|
+
var definition = scope.getRegisteredDefinition(node.localName) ||
|
46
|
+
scope.getRegisteredDefinition(is);
|
47
|
+
if (definition) {
|
48
|
+
// upgrade with is iff the definition tag matches the element tag
|
49
|
+
// and don't upgrade if there's an is and the definition does not extend
|
50
|
+
// a native element
|
51
|
+
if ((is && definition.tag == node.localName) ||
|
52
|
+
(!is && !definition.extends)) {
|
53
|
+
return upgradeWithDefinition(node, definition, isAttached);
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
function upgradeWithDefinition(element, definition, isAttached) {
|
60
|
+
flags.upgrade && console.group('upgrade:', element.localName);
|
61
|
+
// some definitions specify an 'is' attribute
|
62
|
+
if (definition.is) {
|
63
|
+
element.setAttribute('is', definition.is);
|
64
|
+
}
|
65
|
+
// make 'element' implement definition.prototype
|
66
|
+
implementPrototype(element, definition);
|
67
|
+
// flag as upgraded
|
68
|
+
element.__upgraded__ = true;
|
69
|
+
// lifecycle management
|
70
|
+
created(element);
|
71
|
+
// attachedCallback fires in tree order, call before recursing
|
72
|
+
if (isAttached) {
|
73
|
+
scope.attached(element);
|
74
|
+
}
|
75
|
+
// there should never be a shadow root on element at this point
|
76
|
+
scope.upgradeSubtree(element, isAttached);
|
77
|
+
flags.upgrade && console.groupEnd();
|
78
|
+
// OUTPUT
|
79
|
+
return element;
|
80
|
+
}
|
81
|
+
|
82
|
+
// Set __proto__ on supported platforms and use a mixin strategy when
|
83
|
+
// this is not supported; e.g. on IE10.
|
84
|
+
function implementPrototype(element, definition) {
|
85
|
+
// prototype swizzling is best
|
86
|
+
if (Object.__proto__) {
|
87
|
+
element.__proto__ = definition.prototype;
|
88
|
+
} else {
|
89
|
+
// where above we can re-acquire inPrototype via
|
90
|
+
// getPrototypeOf(Element), we cannot do so when
|
91
|
+
// we use mixin, so we install a magic reference
|
92
|
+
customMixin(element, definition.prototype, definition.native);
|
93
|
+
element.__proto__ = definition.prototype;
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
function customMixin(inTarget, inSrc, inNative) {
|
98
|
+
// TODO(sjmiles): 'used' allows us to only copy the 'youngest' version of
|
99
|
+
// any property. This set should be precalculated. We also need to
|
100
|
+
// consider this for supporting 'super'.
|
101
|
+
var used = {};
|
102
|
+
// start with inSrc
|
103
|
+
var p = inSrc;
|
104
|
+
// The default is HTMLElement.prototype, so we add a test to avoid mixing in
|
105
|
+
// native prototypes
|
106
|
+
while (p !== inNative && p !== HTMLElement.prototype) {
|
107
|
+
var keys = Object.getOwnPropertyNames(p);
|
108
|
+
for (var i=0, k; k=keys[i]; i++) {
|
109
|
+
if (!used[k]) {
|
110
|
+
Object.defineProperty(inTarget, k,
|
111
|
+
Object.getOwnPropertyDescriptor(p, k));
|
112
|
+
used[k] = 1;
|
113
|
+
}
|
114
|
+
}
|
115
|
+
p = Object.getPrototypeOf(p);
|
116
|
+
}
|
117
|
+
}
|
118
|
+
|
119
|
+
function created(element) {
|
120
|
+
// invoke createdCallback
|
121
|
+
if (element.createdCallback) {
|
122
|
+
element.createdCallback();
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
scope.upgrade = upgrade;
|
127
|
+
scope.upgradeWithDefinition = upgradeWithDefinition;
|
128
|
+
scope.implementPrototype = implementPrototype;
|
129
|
+
|
130
|
+
});
|
data/lib/written/app/assets/javascripts/written/polyfills/MutationObserver/MutationObserver.js
ADDED
@@ -0,0 +1,575 @@
|
|
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
|
+
(function(global) {
|
12
|
+
|
13
|
+
// Don't allow this object to be redefined.
|
14
|
+
if (global.JsMutationObserver) {
|
15
|
+
return;
|
16
|
+
}
|
17
|
+
|
18
|
+
var registrationsTable = new WeakMap();
|
19
|
+
|
20
|
+
var setImmediate;
|
21
|
+
|
22
|
+
// As much as we would like to use the native implementation, IE
|
23
|
+
// (all versions) suffers a rather annoying bug where it will drop or defer
|
24
|
+
// callbacks when heavy DOM operations are being performed concurrently.
|
25
|
+
//
|
26
|
+
// For a thorough discussion on this, see:
|
27
|
+
// http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/
|
28
|
+
if (/Trident|Edge/.test(navigator.userAgent)) {
|
29
|
+
// Sadly, this bug also affects postMessage and MessageQueues.
|
30
|
+
//
|
31
|
+
// We would like to use the onreadystatechange hack for IE <= 10, but it is
|
32
|
+
// dangerous in the polyfilled environment due to requiring that the
|
33
|
+
// observed script element be in the document.
|
34
|
+
setImmediate = setTimeout;
|
35
|
+
|
36
|
+
// If some other browser ever implements it, let's prefer their native
|
37
|
+
// implementation:
|
38
|
+
} else if (window.setImmediate) {
|
39
|
+
setImmediate = window.setImmediate;
|
40
|
+
|
41
|
+
// Otherwise, we fall back to postMessage as a means of emulating the next
|
42
|
+
// task semantics of setImmediate.
|
43
|
+
} else {
|
44
|
+
var setImmediateQueue = [];
|
45
|
+
var sentinel = String(Math.random());
|
46
|
+
window.addEventListener('message', function(e) {
|
47
|
+
if (e.data === sentinel) {
|
48
|
+
var queue = setImmediateQueue;
|
49
|
+
setImmediateQueue = [];
|
50
|
+
queue.forEach(function(func) {
|
51
|
+
func();
|
52
|
+
});
|
53
|
+
}
|
54
|
+
});
|
55
|
+
setImmediate = function(func) {
|
56
|
+
setImmediateQueue.push(func);
|
57
|
+
window.postMessage(sentinel, '*');
|
58
|
+
};
|
59
|
+
}
|
60
|
+
|
61
|
+
// This is used to ensure that we never schedule 2 callas to setImmediate
|
62
|
+
var isScheduled = false;
|
63
|
+
|
64
|
+
// Keep track of observers that needs to be notified next time.
|
65
|
+
var scheduledObservers = [];
|
66
|
+
|
67
|
+
/**
|
68
|
+
* Schedules |dispatchCallback| to be called in the future.
|
69
|
+
* @param {MutationObserver} observer
|
70
|
+
*/
|
71
|
+
function scheduleCallback(observer) {
|
72
|
+
scheduledObservers.push(observer);
|
73
|
+
if (!isScheduled) {
|
74
|
+
isScheduled = true;
|
75
|
+
setImmediate(dispatchCallbacks);
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
function wrapIfNeeded(node) {
|
80
|
+
return window.ShadowDOMPolyfill &&
|
81
|
+
window.ShadowDOMPolyfill.wrapIfNeeded(node) ||
|
82
|
+
node;
|
83
|
+
}
|
84
|
+
|
85
|
+
function dispatchCallbacks() {
|
86
|
+
// http://dom.spec.whatwg.org/#mutation-observers
|
87
|
+
|
88
|
+
isScheduled = false; // Used to allow a new setImmediate call above.
|
89
|
+
|
90
|
+
var observers = scheduledObservers;
|
91
|
+
scheduledObservers = [];
|
92
|
+
// Sort observers based on their creation UID (incremental).
|
93
|
+
observers.sort(function(o1, o2) {
|
94
|
+
return o1.uid_ - o2.uid_;
|
95
|
+
});
|
96
|
+
|
97
|
+
var anyNonEmpty = false;
|
98
|
+
observers.forEach(function(observer) {
|
99
|
+
|
100
|
+
// 2.1, 2.2
|
101
|
+
var queue = observer.takeRecords();
|
102
|
+
// 2.3. Remove all transient registered observers whose observer is mo.
|
103
|
+
removeTransientObserversFor(observer);
|
104
|
+
|
105
|
+
// 2.4
|
106
|
+
if (queue.length) {
|
107
|
+
observer.callback_(queue, observer);
|
108
|
+
anyNonEmpty = true;
|
109
|
+
}
|
110
|
+
});
|
111
|
+
|
112
|
+
// 3.
|
113
|
+
if (anyNonEmpty)
|
114
|
+
dispatchCallbacks();
|
115
|
+
}
|
116
|
+
|
117
|
+
function removeTransientObserversFor(observer) {
|
118
|
+
observer.nodes_.forEach(function(node) {
|
119
|
+
var registrations = registrationsTable.get(node);
|
120
|
+
if (!registrations)
|
121
|
+
return;
|
122
|
+
registrations.forEach(function(registration) {
|
123
|
+
if (registration.observer === observer)
|
124
|
+
registration.removeTransientObservers();
|
125
|
+
});
|
126
|
+
});
|
127
|
+
}
|
128
|
+
|
129
|
+
/**
|
130
|
+
* This function is used for the "For each registered observer observer (with
|
131
|
+
* observer's options as options) in target's list of registered observers,
|
132
|
+
* run these substeps:" and the "For each ancestor ancestor of target, and for
|
133
|
+
* each registered observer observer (with options options) in ancestor's list
|
134
|
+
* of registered observers, run these substeps:" part of the algorithms. The
|
135
|
+
* |options.subtree| is checked to ensure that the callback is called
|
136
|
+
* correctly.
|
137
|
+
*
|
138
|
+
* @param {Node} target
|
139
|
+
* @param {function(MutationObserverInit):MutationRecord} callback
|
140
|
+
*/
|
141
|
+
function forEachAncestorAndObserverEnqueueRecord(target, callback) {
|
142
|
+
for (var node = target; node; node = node.parentNode) {
|
143
|
+
var registrations = registrationsTable.get(node);
|
144
|
+
|
145
|
+
if (registrations) {
|
146
|
+
for (var j = 0; j < registrations.length; j++) {
|
147
|
+
var registration = registrations[j];
|
148
|
+
var options = registration.options;
|
149
|
+
|
150
|
+
// Only target ignores subtree.
|
151
|
+
if (node !== target && !options.subtree)
|
152
|
+
continue;
|
153
|
+
|
154
|
+
var record = callback(options);
|
155
|
+
if (record)
|
156
|
+
registration.enqueue(record);
|
157
|
+
}
|
158
|
+
}
|
159
|
+
}
|
160
|
+
}
|
161
|
+
|
162
|
+
var uidCounter = 0;
|
163
|
+
|
164
|
+
/**
|
165
|
+
* The class that maps to the DOM MutationObserver interface.
|
166
|
+
* @param {Function} callback.
|
167
|
+
* @constructor
|
168
|
+
*/
|
169
|
+
function JsMutationObserver(callback) {
|
170
|
+
this.callback_ = callback;
|
171
|
+
this.nodes_ = [];
|
172
|
+
this.records_ = [];
|
173
|
+
this.uid_ = ++uidCounter;
|
174
|
+
}
|
175
|
+
|
176
|
+
JsMutationObserver.prototype = {
|
177
|
+
observe: function(target, options) {
|
178
|
+
target = wrapIfNeeded(target);
|
179
|
+
|
180
|
+
// 1.1
|
181
|
+
if (!options.childList && !options.attributes && !options.characterData ||
|
182
|
+
|
183
|
+
// 1.2
|
184
|
+
options.attributeOldValue && !options.attributes ||
|
185
|
+
|
186
|
+
// 1.3
|
187
|
+
options.attributeFilter && options.attributeFilter.length &&
|
188
|
+
!options.attributes ||
|
189
|
+
|
190
|
+
// 1.4
|
191
|
+
options.characterDataOldValue && !options.characterData) {
|
192
|
+
|
193
|
+
throw new SyntaxError();
|
194
|
+
}
|
195
|
+
|
196
|
+
var registrations = registrationsTable.get(target);
|
197
|
+
if (!registrations)
|
198
|
+
registrationsTable.set(target, registrations = []);
|
199
|
+
|
200
|
+
// 2
|
201
|
+
// If target's list of registered observers already includes a registered
|
202
|
+
// observer associated with the context object, replace that registered
|
203
|
+
// observer's options with options.
|
204
|
+
var registration;
|
205
|
+
for (var i = 0; i < registrations.length; i++) {
|
206
|
+
if (registrations[i].observer === this) {
|
207
|
+
registration = registrations[i];
|
208
|
+
registration.removeListeners();
|
209
|
+
registration.options = options;
|
210
|
+
break;
|
211
|
+
}
|
212
|
+
}
|
213
|
+
|
214
|
+
// 3.
|
215
|
+
// Otherwise, add a new registered observer to target's list of registered
|
216
|
+
// observers with the context object as the observer and options as the
|
217
|
+
// options, and add target to context object's list of nodes on which it
|
218
|
+
// is registered.
|
219
|
+
if (!registration) {
|
220
|
+
registration = new Registration(this, target, options);
|
221
|
+
registrations.push(registration);
|
222
|
+
this.nodes_.push(target);
|
223
|
+
}
|
224
|
+
|
225
|
+
registration.addListeners();
|
226
|
+
},
|
227
|
+
|
228
|
+
disconnect: function() {
|
229
|
+
this.nodes_.forEach(function(node) {
|
230
|
+
var registrations = registrationsTable.get(node);
|
231
|
+
for (var i = 0; i < registrations.length; i++) {
|
232
|
+
var registration = registrations[i];
|
233
|
+
if (registration.observer === this) {
|
234
|
+
registration.removeListeners();
|
235
|
+
registrations.splice(i, 1);
|
236
|
+
// Each node can only have one registered observer associated with
|
237
|
+
// this observer.
|
238
|
+
break;
|
239
|
+
}
|
240
|
+
}
|
241
|
+
}, this);
|
242
|
+
this.records_ = [];
|
243
|
+
},
|
244
|
+
|
245
|
+
takeRecords: function() {
|
246
|
+
var copyOfRecords = this.records_;
|
247
|
+
this.records_ = [];
|
248
|
+
return copyOfRecords;
|
249
|
+
}
|
250
|
+
};
|
251
|
+
|
252
|
+
/**
|
253
|
+
* @param {string} type
|
254
|
+
* @param {Node} target
|
255
|
+
* @constructor
|
256
|
+
*/
|
257
|
+
function MutationRecord(type, target) {
|
258
|
+
this.type = type;
|
259
|
+
this.target = target;
|
260
|
+
this.addedNodes = [];
|
261
|
+
this.removedNodes = [];
|
262
|
+
this.previousSibling = null;
|
263
|
+
this.nextSibling = null;
|
264
|
+
this.attributeName = null;
|
265
|
+
this.attributeNamespace = null;
|
266
|
+
this.oldValue = null;
|
267
|
+
}
|
268
|
+
|
269
|
+
function copyMutationRecord(original) {
|
270
|
+
var record = new MutationRecord(original.type, original.target);
|
271
|
+
record.addedNodes = original.addedNodes.slice();
|
272
|
+
record.removedNodes = original.removedNodes.slice();
|
273
|
+
record.previousSibling = original.previousSibling;
|
274
|
+
record.nextSibling = original.nextSibling;
|
275
|
+
record.attributeName = original.attributeName;
|
276
|
+
record.attributeNamespace = original.attributeNamespace;
|
277
|
+
record.oldValue = original.oldValue;
|
278
|
+
return record;
|
279
|
+
};
|
280
|
+
|
281
|
+
// We keep track of the two (possibly one) records used in a single mutation.
|
282
|
+
var currentRecord, recordWithOldValue;
|
283
|
+
|
284
|
+
/**
|
285
|
+
* Creates a record without |oldValue| and caches it as |currentRecord| for
|
286
|
+
* later use.
|
287
|
+
* @param {string} oldValue
|
288
|
+
* @return {MutationRecord}
|
289
|
+
*/
|
290
|
+
function getRecord(type, target) {
|
291
|
+
return currentRecord = new MutationRecord(type, target);
|
292
|
+
}
|
293
|
+
|
294
|
+
/**
|
295
|
+
* Gets or creates a record with |oldValue| based in the |currentRecord|
|
296
|
+
* @param {string} oldValue
|
297
|
+
* @return {MutationRecord}
|
298
|
+
*/
|
299
|
+
function getRecordWithOldValue(oldValue) {
|
300
|
+
if (recordWithOldValue)
|
301
|
+
return recordWithOldValue;
|
302
|
+
recordWithOldValue = copyMutationRecord(currentRecord);
|
303
|
+
recordWithOldValue.oldValue = oldValue;
|
304
|
+
return recordWithOldValue;
|
305
|
+
}
|
306
|
+
|
307
|
+
function clearRecords() {
|
308
|
+
currentRecord = recordWithOldValue = undefined;
|
309
|
+
}
|
310
|
+
|
311
|
+
/**
|
312
|
+
* @param {MutationRecord} record
|
313
|
+
* @return {boolean} Whether the record represents a record from the current
|
314
|
+
* mutation event.
|
315
|
+
*/
|
316
|
+
function recordRepresentsCurrentMutation(record) {
|
317
|
+
return record === recordWithOldValue || record === currentRecord;
|
318
|
+
}
|
319
|
+
|
320
|
+
/**
|
321
|
+
* Selects which record, if any, to replace the last record in the queue.
|
322
|
+
* This returns |null| if no record should be replaced.
|
323
|
+
*
|
324
|
+
* @param {MutationRecord} lastRecord
|
325
|
+
* @param {MutationRecord} newRecord
|
326
|
+
* @param {MutationRecord}
|
327
|
+
*/
|
328
|
+
function selectRecord(lastRecord, newRecord) {
|
329
|
+
if (lastRecord === newRecord)
|
330
|
+
return lastRecord;
|
331
|
+
|
332
|
+
// Check if the the record we are adding represents the same record. If
|
333
|
+
// so, we keep the one with the oldValue in it.
|
334
|
+
if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord))
|
335
|
+
return recordWithOldValue;
|
336
|
+
|
337
|
+
return null;
|
338
|
+
}
|
339
|
+
|
340
|
+
/**
|
341
|
+
* Class used to represent a registered observer.
|
342
|
+
* @param {MutationObserver} observer
|
343
|
+
* @param {Node} target
|
344
|
+
* @param {MutationObserverInit} options
|
345
|
+
* @constructor
|
346
|
+
*/
|
347
|
+
function Registration(observer, target, options) {
|
348
|
+
this.observer = observer;
|
349
|
+
this.target = target;
|
350
|
+
this.options = options;
|
351
|
+
this.transientObservedNodes = [];
|
352
|
+
}
|
353
|
+
|
354
|
+
Registration.prototype = {
|
355
|
+
enqueue: function(record) {
|
356
|
+
var records = this.observer.records_;
|
357
|
+
var length = records.length;
|
358
|
+
|
359
|
+
// There are cases where we replace the last record with the new record.
|
360
|
+
// For example if the record represents the same mutation we need to use
|
361
|
+
// the one with the oldValue. If we get same record (this can happen as we
|
362
|
+
// walk up the tree) we ignore the new record.
|
363
|
+
if (records.length > 0) {
|
364
|
+
var lastRecord = records[length - 1];
|
365
|
+
var recordToReplaceLast = selectRecord(lastRecord, record);
|
366
|
+
if (recordToReplaceLast) {
|
367
|
+
records[length - 1] = recordToReplaceLast;
|
368
|
+
return;
|
369
|
+
}
|
370
|
+
} else {
|
371
|
+
scheduleCallback(this.observer);
|
372
|
+
}
|
373
|
+
|
374
|
+
records[length] = record;
|
375
|
+
},
|
376
|
+
|
377
|
+
addListeners: function() {
|
378
|
+
this.addListeners_(this.target);
|
379
|
+
},
|
380
|
+
|
381
|
+
addListeners_: function(node) {
|
382
|
+
var options = this.options;
|
383
|
+
if (options.attributes)
|
384
|
+
node.addEventListener('DOMAttrModified', this, true);
|
385
|
+
|
386
|
+
if (options.characterData)
|
387
|
+
node.addEventListener('DOMCharacterDataModified', this, true);
|
388
|
+
|
389
|
+
if (options.childList)
|
390
|
+
node.addEventListener('DOMNodeInserted', this, true);
|
391
|
+
|
392
|
+
if (options.childList || options.subtree)
|
393
|
+
node.addEventListener('DOMNodeRemoved', this, true);
|
394
|
+
},
|
395
|
+
|
396
|
+
removeListeners: function() {
|
397
|
+
this.removeListeners_(this.target);
|
398
|
+
},
|
399
|
+
|
400
|
+
removeListeners_: function(node) {
|
401
|
+
var options = this.options;
|
402
|
+
if (options.attributes)
|
403
|
+
node.removeEventListener('DOMAttrModified', this, true);
|
404
|
+
|
405
|
+
if (options.characterData)
|
406
|
+
node.removeEventListener('DOMCharacterDataModified', this, true);
|
407
|
+
|
408
|
+
if (options.childList)
|
409
|
+
node.removeEventListener('DOMNodeInserted', this, true);
|
410
|
+
|
411
|
+
if (options.childList || options.subtree)
|
412
|
+
node.removeEventListener('DOMNodeRemoved', this, true);
|
413
|
+
},
|
414
|
+
|
415
|
+
/**
|
416
|
+
* Adds a transient observer on node. The transient observer gets removed
|
417
|
+
* next time we deliver the change records.
|
418
|
+
* @param {Node} node
|
419
|
+
*/
|
420
|
+
addTransientObserver: function(node) {
|
421
|
+
// Don't add transient observers on the target itself. We already have all
|
422
|
+
// the required listeners set up on the target.
|
423
|
+
if (node === this.target)
|
424
|
+
return;
|
425
|
+
|
426
|
+
this.addListeners_(node);
|
427
|
+
this.transientObservedNodes.push(node);
|
428
|
+
var registrations = registrationsTable.get(node);
|
429
|
+
if (!registrations)
|
430
|
+
registrationsTable.set(node, registrations = []);
|
431
|
+
|
432
|
+
// We know that registrations does not contain this because we already
|
433
|
+
// checked if node === this.target.
|
434
|
+
registrations.push(this);
|
435
|
+
},
|
436
|
+
|
437
|
+
removeTransientObservers: function() {
|
438
|
+
var transientObservedNodes = this.transientObservedNodes;
|
439
|
+
this.transientObservedNodes = [];
|
440
|
+
|
441
|
+
transientObservedNodes.forEach(function(node) {
|
442
|
+
// Transient observers are never added to the target.
|
443
|
+
this.removeListeners_(node);
|
444
|
+
|
445
|
+
var registrations = registrationsTable.get(node);
|
446
|
+
for (var i = 0; i < registrations.length; i++) {
|
447
|
+
if (registrations[i] === this) {
|
448
|
+
registrations.splice(i, 1);
|
449
|
+
// Each node can only have one registered observer associated with
|
450
|
+
// this observer.
|
451
|
+
break;
|
452
|
+
}
|
453
|
+
}
|
454
|
+
}, this);
|
455
|
+
},
|
456
|
+
|
457
|
+
handleEvent: function(e) {
|
458
|
+
// Stop propagation since we are managing the propagation manually.
|
459
|
+
// This means that other mutation events on the page will not work
|
460
|
+
// correctly but that is by design.
|
461
|
+
e.stopImmediatePropagation();
|
462
|
+
|
463
|
+
switch (e.type) {
|
464
|
+
case 'DOMAttrModified':
|
465
|
+
// http://dom.spec.whatwg.org/#concept-mo-queue-attributes
|
466
|
+
|
467
|
+
var name = e.attrName;
|
468
|
+
var namespace = e.relatedNode.namespaceURI;
|
469
|
+
var target = e.target;
|
470
|
+
|
471
|
+
// 1.
|
472
|
+
var record = new getRecord('attributes', target);
|
473
|
+
record.attributeName = name;
|
474
|
+
record.attributeNamespace = namespace;
|
475
|
+
|
476
|
+
// 2.
|
477
|
+
var oldValue =
|
478
|
+
e.attrChange === MutationEvent.ADDITION ? null : e.prevValue;
|
479
|
+
|
480
|
+
forEachAncestorAndObserverEnqueueRecord(target, function(options) {
|
481
|
+
// 3.1, 4.2
|
482
|
+
if (!options.attributes)
|
483
|
+
return;
|
484
|
+
|
485
|
+
// 3.2, 4.3
|
486
|
+
if (options.attributeFilter && options.attributeFilter.length &&
|
487
|
+
options.attributeFilter.indexOf(name) === -1 &&
|
488
|
+
options.attributeFilter.indexOf(namespace) === -1) {
|
489
|
+
return;
|
490
|
+
}
|
491
|
+
// 3.3, 4.4
|
492
|
+
if (options.attributeOldValue)
|
493
|
+
return getRecordWithOldValue(oldValue);
|
494
|
+
|
495
|
+
// 3.4, 4.5
|
496
|
+
return record;
|
497
|
+
});
|
498
|
+
|
499
|
+
break;
|
500
|
+
|
501
|
+
case 'DOMCharacterDataModified':
|
502
|
+
// http://dom.spec.whatwg.org/#concept-mo-queue-characterdata
|
503
|
+
var target = e.target;
|
504
|
+
|
505
|
+
// 1.
|
506
|
+
var record = getRecord('characterData', target);
|
507
|
+
|
508
|
+
// 2.
|
509
|
+
var oldValue = e.prevValue;
|
510
|
+
|
511
|
+
|
512
|
+
forEachAncestorAndObserverEnqueueRecord(target, function(options) {
|
513
|
+
// 3.1, 4.2
|
514
|
+
if (!options.characterData)
|
515
|
+
return;
|
516
|
+
|
517
|
+
// 3.2, 4.3
|
518
|
+
if (options.characterDataOldValue)
|
519
|
+
return getRecordWithOldValue(oldValue);
|
520
|
+
|
521
|
+
// 3.3, 4.4
|
522
|
+
return record;
|
523
|
+
});
|
524
|
+
|
525
|
+
break;
|
526
|
+
|
527
|
+
case 'DOMNodeRemoved':
|
528
|
+
this.addTransientObserver(e.target);
|
529
|
+
// Fall through.
|
530
|
+
case 'DOMNodeInserted':
|
531
|
+
// http://dom.spec.whatwg.org/#concept-mo-queue-childlist
|
532
|
+
var changedNode = e.target;
|
533
|
+
var addedNodes, removedNodes;
|
534
|
+
if (e.type === 'DOMNodeInserted') {
|
535
|
+
addedNodes = [changedNode];
|
536
|
+
removedNodes = [];
|
537
|
+
} else {
|
538
|
+
|
539
|
+
addedNodes = [];
|
540
|
+
removedNodes = [changedNode];
|
541
|
+
}
|
542
|
+
var previousSibling = changedNode.previousSibling;
|
543
|
+
var nextSibling = changedNode.nextSibling;
|
544
|
+
|
545
|
+
// 1.
|
546
|
+
var record = getRecord('childList', e.target.parentNode);
|
547
|
+
record.addedNodes = addedNodes;
|
548
|
+
record.removedNodes = removedNodes;
|
549
|
+
record.previousSibling = previousSibling;
|
550
|
+
record.nextSibling = nextSibling;
|
551
|
+
|
552
|
+
forEachAncestorAndObserverEnqueueRecord(e.relatedNode, function(options) {
|
553
|
+
// 2.1, 3.2
|
554
|
+
if (!options.childList)
|
555
|
+
return;
|
556
|
+
|
557
|
+
// 2.2, 3.3
|
558
|
+
return record;
|
559
|
+
});
|
560
|
+
|
561
|
+
}
|
562
|
+
|
563
|
+
clearRecords();
|
564
|
+
}
|
565
|
+
};
|
566
|
+
|
567
|
+
global.JsMutationObserver = JsMutationObserver;
|
568
|
+
|
569
|
+
if (!global.MutationObserver) {
|
570
|
+
global.MutationObserver = JsMutationObserver;
|
571
|
+
// Explicltly mark MO as polyfilled for user reference.
|
572
|
+
JsMutationObserver._isPolyfilled = true;
|
573
|
+
}
|
574
|
+
|
575
|
+
})(self);
|