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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/LICENSE +8 -0
  4. data/README.md +63 -0
  5. data/Rakefile +17 -27
  6. data/lib/written.rb +0 -8
  7. data/lib/written/app/assets/images/written/placeholder.png +0 -0
  8. data/lib/written/app/assets/javascripts/written.coffee +2 -0
  9. data/lib/written/app/assets/javascripts/written/core/content.coffee +53 -35
  10. data/lib/written/app/assets/javascripts/written/core/cursor.coffee +33 -12
  11. data/lib/written/app/assets/javascripts/written/core/document.coffee +16 -11
  12. data/lib/written/app/assets/javascripts/written/core/extensions/string.coffee +9 -0
  13. data/lib/written/app/assets/javascripts/written/core/extensions/text.coffee +2 -0
  14. data/lib/written/app/assets/javascripts/written/core/history.coffee +2 -0
  15. data/lib/written/app/assets/javascripts/written/core/observer.coffee +6 -2
  16. data/lib/written/app/assets/javascripts/written/core/stringify.coffee +15 -0
  17. data/lib/written/app/assets/javascripts/written/parsers/block.coffee +69 -0
  18. data/lib/written/app/assets/javascripts/written/parsers/block/code.coffee +79 -15
  19. data/lib/written/app/assets/javascripts/written/parsers/block/heading.coffee +60 -5
  20. data/lib/written/app/assets/javascripts/written/parsers/block/image.coffee +103 -9
  21. data/lib/written/app/assets/javascripts/written/parsers/block/olist.coffee +94 -12
  22. data/lib/written/app/assets/javascripts/written/parsers/block/paragraph.coffee +63 -5
  23. data/lib/written/app/assets/javascripts/written/parsers/block/quote.coffee +92 -0
  24. data/lib/written/app/assets/javascripts/written/parsers/block/ulist.coffee +93 -12
  25. data/lib/written/app/assets/javascripts/written/parsers/inline.coffee +81 -0
  26. data/lib/written/app/assets/javascripts/written/parsers/inline/code.coffee +57 -0
  27. data/lib/written/app/assets/javascripts/written/parsers/inline/italic.coffee +40 -7
  28. data/lib/written/app/assets/javascripts/written/parsers/inline/link.coffee +43 -13
  29. data/lib/written/app/assets/javascripts/written/parsers/inline/list.coffee +27 -0
  30. data/lib/written/app/assets/javascripts/written/parsers/inline/strong.coffee +41 -7
  31. data/lib/written/app/assets/javascripts/written/parsers/parsers.coffee +21 -107
  32. data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/CustomElements.js +32 -0
  33. data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/base.js +40 -0
  34. data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/boot.js +124 -0
  35. data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/observe.js +318 -0
  36. data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/register.js +369 -0
  37. data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/traverse.js +86 -0
  38. data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/upgrade.js +130 -0
  39. data/lib/written/app/assets/javascripts/written/polyfills/MutationObserver/MutationObserver.js +575 -0
  40. data/lib/written/app/assets/javascripts/written/polyfills/WeakMap/WeakMap.js +49 -0
  41. data/lib/written/app/assets/javascripts/written/polyfills/base.coffee +10 -0
  42. data/lib/written/app/assets/javascripts/written/polyfills/dom.js +104 -0
  43. data/lib/written/app/assets/javascripts/written/uploaders/aws.coffee +125 -0
  44. data/lib/written/app/assets/stylesheets/written.scss +80 -11
  45. data/lib/written/version.rb +1 -1
  46. data/test/server/app/assets/javascripts/application.coffee +20 -2
  47. data/test/server/app/assets/stylesheets/application.scss +2 -2
  48. data/test/server/app/assets/stylesheets/prism.css +0 -1
  49. data/test/server/app/views/posts/show.html.erb +10 -3
  50. metadata +26 -20
  51. data/lib/written/app/assets/javascripts/written/core/ext.coffee +0 -109
  52. data/lib/written/app/assets/javascripts/written/core/extensions.coffee +0 -2
  53. data/lib/written/document.rb +0 -42
  54. data/lib/written/node.rb +0 -21
  55. data/lib/written/nodes/code.rb +0 -65
  56. data/lib/written/nodes/heading.rb +0 -15
  57. data/lib/written/nodes/image.rb +0 -14
  58. data/lib/written/nodes/ordered_list.rb +0 -18
  59. data/lib/written/nodes/unordered_list.rb +0 -19
  60. data/lib/written/parsers.rb +0 -11
  61. data/lib/written/parsers/base.rb +0 -26
  62. data/lib/written/parsers/code.rb +0 -60
  63. data/lib/written/parsers/heading.rb +0 -19
  64. data/lib/written/parsers/image.rb +0 -19
  65. data/lib/written/parsers/link.rb +0 -12
  66. data/lib/written/parsers/list.rb +0 -33
  67. 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
+ });
@@ -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);