written 0.0.5 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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);