liquid_cms 0.3.0.8 → 0.3.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.gitignore +1 -0
  2. data/CHANGELOG.rdoc +7 -0
  3. data/TODO.rdoc +1 -1
  4. data/app/controllers/cms/main_controller.rb +3 -2
  5. data/app/helpers/cms/common_helper.rb +9 -2
  6. data/app/helpers/cms/components_helper.rb +10 -4
  7. data/app/liquid/filters/cms_filters.rb +1 -0
  8. data/app/models/cms/component.rb +4 -0
  9. data/app/views/cms/assets/_list.html.erb +4 -4
  10. data/app/views/cms/components/_list.html.erb +5 -1
  11. data/app/views/cms/components/edit.html.erb +1 -1
  12. data/app/views/cms/pages/_list.html.erb +4 -4
  13. data/app/views/cms/shared/_sidebar.html.erb +31 -8
  14. data/app/views/layouts/cms.html.erb +5 -2
  15. data/config/initializers/cms/simple_form_updates.rb +2 -2
  16. data/config/locales/cms/en.yml +3 -2
  17. data/lib/generators/liquid_cms/templates/public/cms/codemirror/LICENSE +0 -0
  18. data/lib/generators/liquid_cms/templates/public/cms/codemirror/css/csscolors.css +0 -0
  19. data/lib/generators/liquid_cms/templates/public/cms/codemirror/css/docs.css +17 -3
  20. data/lib/generators/liquid_cms/templates/public/cms/codemirror/css/font.js +15 -0
  21. data/lib/generators/liquid_cms/templates/public/cms/codemirror/css/jscolors.css +0 -0
  22. data/lib/generators/liquid_cms/templates/public/cms/codemirror/css/sparqlcolors.css +0 -0
  23. data/lib/generators/liquid_cms/templates/public/cms/codemirror/css/xmlcolors.css +0 -0
  24. data/lib/generators/liquid_cms/templates/public/cms/codemirror/js/codemirror.js +59 -26
  25. data/lib/generators/liquid_cms/templates/public/cms/codemirror/js/editor.js +149 -71
  26. data/lib/generators/liquid_cms/templates/public/cms/codemirror/js/highlight.js +2 -2
  27. data/lib/generators/liquid_cms/templates/public/cms/codemirror/js/mirrorframe.js +2 -2
  28. data/lib/generators/liquid_cms/templates/public/cms/codemirror/js/parsecss.js +5 -3
  29. data/lib/generators/liquid_cms/templates/public/cms/codemirror/js/parsedummy.js +0 -0
  30. data/lib/generators/liquid_cms/templates/public/cms/codemirror/js/parsehtmlmixed.js +28 -9
  31. data/lib/generators/liquid_cms/templates/public/cms/codemirror/js/parsejavascript.js +0 -0
  32. data/lib/generators/liquid_cms/templates/public/cms/codemirror/js/parsesparql.js +0 -0
  33. data/lib/generators/liquid_cms/templates/public/cms/codemirror/js/parsexml.js +6 -1
  34. data/lib/generators/liquid_cms/templates/public/cms/codemirror/js/select.js +48 -21
  35. data/lib/generators/liquid_cms/templates/public/cms/codemirror/js/stringstream.js +15 -1
  36. data/lib/generators/liquid_cms/templates/public/cms/codemirror/js/tokenize.js +0 -0
  37. data/lib/generators/liquid_cms/templates/public/cms/codemirror/js/tokenizejavascript.js +1 -1
  38. data/lib/generators/liquid_cms/templates/public/cms/codemirror/js/undo.js +17 -14
  39. data/lib/generators/liquid_cms/templates/public/cms/codemirror/js/unittests.js +44 -0
  40. data/lib/generators/liquid_cms/templates/public/cms/codemirror/js/util.js +6 -3
  41. data/lib/generators/liquid_cms/templates/public/cms/javascripts/cms.js +15 -1
  42. data/lib/generators/liquid_cms/templates/public/cms/javascripts/livepipe.js +181 -0
  43. data/lib/generators/liquid_cms/templates/public/cms/javascripts/tabs.js +149 -0
  44. data/lib/generators/liquid_cms/templates/public/cms/stylesheets/ie9.css +4 -0
  45. data/lib/generators/liquid_cms/templates/public/cms/stylesheets/sidebar.css +132 -0
  46. data/lib/generators/liquid_cms/templates/public/cms/stylesheets/styles.css +1 -74
  47. data/lib/generators/liquid_cms/templates/public/cms/stylesheets/themes/dark.css +2 -1
  48. data/lib/liquid_cms/context.rb +4 -0
  49. data/lib/liquid_cms/version.rb +1 -1
  50. data/liquid_cms.gemspec +1 -1
  51. data/test/rails_app/Gemfile +1 -2
  52. data/test/rails_app/Gemfile.lock +56 -62
  53. metadata +24 -16
  54. data/Gemfile.lock +0 -122
  55. data/lib/generators/liquid_cms/templates/public/cms/codemirror/bigtest.html +0 -1296
  56. data/lib/generators/liquid_cms/templates/public/cms/codemirror/css/people.jpg +0 -0
  57. data/lib/generators/liquid_cms/templates/public/cms/codemirror/csstest.html +0 -60
  58. data/lib/generators/liquid_cms/templates/public/cms/codemirror/highlight.html +0 -82
  59. data/lib/generators/liquid_cms/templates/public/cms/codemirror/htmltest.html +0 -52
  60. data/lib/generators/liquid_cms/templates/public/cms/codemirror/index.html +0 -245
  61. data/lib/generators/liquid_cms/templates/public/cms/codemirror/jstest.html +0 -56
  62. data/lib/generators/liquid_cms/templates/public/cms/codemirror/manual.html +0 -759
  63. data/lib/generators/liquid_cms/templates/public/cms/codemirror/mixedtest.html +0 -52
  64. data/lib/generators/liquid_cms/templates/public/cms/codemirror/sparqltest.html +0 -41
  65. data/lib/generators/liquid_cms/templates/public/cms/codemirror/story.html +0 -671
@@ -41,7 +41,7 @@ var indentUnit = 2;
41
41
  callback = function(line) {
42
42
  for (var i = 0; i < line.length; i++)
43
43
  node.appendChild(line[i]);
44
- node.appendChild(document.createElement("BR"));
44
+ node.appendChild(document.createElement("br"));
45
45
  };
46
46
  }
47
47
 
@@ -53,7 +53,7 @@ var indentUnit = 2;
53
53
  line = [];
54
54
  }
55
55
  else {
56
- var span = document.createElement("SPAN");
56
+ var span = document.createElement("span");
57
57
  span.className = token.style;
58
58
  span.appendChild(document.createTextNode(token.value));
59
59
  line.push(span);
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  function MirrorFrame(place, options) {
7
- this.home = document.createElement("DIV");
7
+ this.home = document.createElement("div");
8
8
  if (place.appendChild)
9
9
  place.appendChild(this.home);
10
10
  else
@@ -12,7 +12,7 @@ function MirrorFrame(place, options) {
12
12
 
13
13
  var self = this;
14
14
  function makeButton(name, action) {
15
- var button = document.createElement("INPUT");
15
+ var button = document.createElement("input");
16
16
  button.type = "button";
17
17
  button.value = name;
18
18
  self.home.appendChild(button);
@@ -128,7 +128,9 @@ var CSSParser = Editor.Parser = (function() {
128
128
  if (content == "\n")
129
129
  token.indentation = indentCSS(inBraces, inRule, basecolumn);
130
130
 
131
- if (content == "{")
131
+ if (content == "{" && inDecl == "@media")
132
+ inDecl = false;
133
+ else if (content == "{")
132
134
  inBraces = true;
133
135
  else if (content == "}")
134
136
  inBraces = inRule = inDecl = false;
@@ -136,8 +138,8 @@ var CSSParser = Editor.Parser = (function() {
136
138
  inRule = inDecl = false;
137
139
  else if (inBraces && style != "css-comment" && style != "whitespace")
138
140
  inRule = true;
139
- else if (!inBraces && style == "css-at" && content != "@media")
140
- inDecl = true;
141
+ else if (!inBraces && style == "css-at")
142
+ inDecl = content;
141
143
 
142
144
  return token;
143
145
  },
@@ -1,9 +1,22 @@
1
1
  var HTMLMixedParser = Editor.Parser = (function() {
2
- if (!(CSSParser && JSParser && XMLParser))
3
- throw new Error("CSS, JS, and XML parsers must be loaded for HTML mixed mode to work.");
4
- XMLParser.configure({useHTMLKludges: true});
2
+
3
+ // tags that trigger seperate parsers
4
+ var triggers = {
5
+ "script": "JSParser",
6
+ "style": "CSSParser"
7
+ };
8
+
9
+ function checkDependencies() {
10
+ var parsers = ['XMLParser'];
11
+ for (var p in triggers) parsers.push(triggers[p]);
12
+ for (var i in parsers) {
13
+ if (!window[parsers[i]]) throw new Error(parsers[i] + " parser must be loaded for HTML mixed mode to work.");
14
+ }
15
+ XMLParser.configure({useHTMLKludges: true});
16
+ }
5
17
 
6
18
  function parseMixed(stream) {
19
+ checkDependencies();
7
20
  var htmlParser = XMLParser.make(stream), localParser = null, inTag = false;
8
21
  var iter = {next: top, copy: copy};
9
22
 
@@ -14,10 +27,10 @@ var HTMLMixedParser = Editor.Parser = (function() {
14
27
  else if (token.style == "xml-tagname" && inTag === true)
15
28
  inTag = token.content.toLowerCase();
16
29
  else if (token.content == ">") {
17
- if (inTag == "script")
18
- iter.next = local(JSParser, "</script");
19
- else if (inTag == "style")
20
- iter.next = local(CSSParser, "</style");
30
+ if (triggers[inTag]) {
31
+ var parser = window[triggers[inTag]];
32
+ iter.next = local(parser, "</" + inTag);
33
+ }
21
34
  inTag = false;
22
35
  }
23
36
  return token;
@@ -47,7 +60,7 @@ var HTMLMixedParser = Editor.Parser = (function() {
47
60
  return baseIndent;
48
61
  else
49
62
  return oldIndent(chars);
50
- }
63
+ };
51
64
  }
52
65
 
53
66
  return token;
@@ -69,6 +82,12 @@ var HTMLMixedParser = Editor.Parser = (function() {
69
82
  return iter;
70
83
  }
71
84
 
72
- return {make: parseMixed, electricChars: "{}/:"};
85
+ return {
86
+ make: parseMixed,
87
+ electricChars: "{}/:",
88
+ configure: function(obj) {
89
+ if (obj.triggers) triggers = obj.triggers;
90
+ }
91
+ };
73
92
 
74
93
  })();
@@ -38,6 +38,11 @@ var XMLParser = Editor.Parser = (function() {
38
38
  setState(inBlock("xml-comment", "-->"));
39
39
  return null;
40
40
  }
41
+ else if (source.lookAhead("DOCTYPE", true)) {
42
+ source.nextWhileMatches(/[\w\._\-]/);
43
+ setState(inBlock("xml-doctype", ">"));
44
+ return "xml-doctype";
45
+ }
41
46
  else {
42
47
  return "xml-text";
43
48
  }
@@ -182,7 +187,7 @@ var XMLParser = Editor.Parser = (function() {
182
187
  function base() {
183
188
  return pass(element, base);
184
189
  }
185
- var harmlessTokens = {"xml-text": true, "xml-entity": true, "xml-comment": true, "xml-processing": true};
190
+ var harmlessTokens = {"xml-text": true, "xml-entity": true, "xml-comment": true, "xml-processing": true, "xml-doctype": true};
186
191
  function element(style, content) {
187
192
  if (content == "<") cont(tagname, attributes, endtag(tokenNr == 1));
188
193
  else if (content == "</") cont(closetagname, expect(">"));
@@ -97,6 +97,21 @@ var select = {};
97
97
  if (currentSelection) currentSelection.changed = true;
98
98
  };
99
99
 
100
+ // Find the 'leaf' node (BR or text) after the given one.
101
+ function baseNodeAfter(node) {
102
+ var next = node.nextSibling;
103
+ if (next) {
104
+ while (next.firstChild) next = next.firstChild;
105
+ if (next.nodeType == 3 || isBR(next)) return next;
106
+ else return baseNodeAfter(next);
107
+ }
108
+ else {
109
+ var parent = node.parentNode;
110
+ while (parent && !parent.nextSibling) parent = parent.parentNode;
111
+ return parent && baseNodeAfter(parent);
112
+ }
113
+ }
114
+
100
115
  // This is called by the code in editor.js whenever it is replacing
101
116
  // a text node. The function sees whether the given oldNode is part
102
117
  // of the current selection, and updates this selection if it is.
@@ -121,6 +136,9 @@ var select = {};
121
136
  point.offset += (offset || 0);
122
137
  }
123
138
  }
139
+ else if (select.ie_selection && point.offset == 0 && point.node == baseNodeAfter(from)) {
140
+ currentSelection.changed = true;
141
+ }
124
142
  }
125
143
  replace(currentSelection.start);
126
144
  replace(currentSelection.end);
@@ -144,8 +162,15 @@ var select = {};
144
162
  // Most functions are defined in two ways, one for the IE selection
145
163
  // model, one for the W3C one.
146
164
  if (select.ie_selection) {
165
+ function selRange() {
166
+ var sel = document.selection;
167
+ if (!sel) return null;
168
+ if (sel.createRange) return sel.createRange();
169
+ else return sel.createTextRange();
170
+ }
171
+
147
172
  function selectionNode(start) {
148
- var range = document.selection.createRange();
173
+ var range = selRange();
149
174
  range.collapse(start);
150
175
 
151
176
  function nodeAfter(node) {
@@ -232,9 +257,9 @@ var select = {};
232
257
  };
233
258
 
234
259
  select.offsetInNode = function(node) {
235
- var sel = document.selection;
236
- if (!sel) return 0;
237
- var range = sel.createRange(), range2 = range.duplicate();
260
+ var range = selRange();
261
+ if (!range) return 0;
262
+ var range2 = range.duplicate();
238
263
  try {range2.moveToElementText(node);} catch(e){return 0;}
239
264
  range.setEndPoint("StartToStart", range2);
240
265
  return range.text.length;
@@ -244,10 +269,9 @@ var select = {};
244
269
  // after. Note that this returns false for 'no cursor', and null
245
270
  // for 'start of document'.
246
271
  select.selectionTopNode = function(container, start) {
247
- var selection = document.selection;
248
- if (!selection) return false;
249
-
250
- var range = selection.createRange(), range2 = range.duplicate();
272
+ var range = selRange();
273
+ if (!range) return false;
274
+ var range2 = range.duplicate();
251
275
  range.collapse(start);
252
276
  var around = range.parentElement();
253
277
  if (around && isAncestor(container, around)) {
@@ -296,8 +320,12 @@ var select = {};
296
320
  }
297
321
 
298
322
  if (start == 0) {
299
- var test1 = selection.createRange(), test2 = test1.duplicate();
300
- test2.moveToElementText(container);
323
+ var test1 = selRange(), test2 = test1.duplicate();
324
+ try {
325
+ test2.moveToElementText(container);
326
+ } catch(exception) {
327
+ return null;
328
+ }
301
329
  if (test1.compareEndPoints("StartToStart", test2) == 0)
302
330
  return null;
303
331
  }
@@ -315,14 +343,13 @@ var select = {};
315
343
  };
316
344
 
317
345
  select.somethingSelected = function() {
318
- var sel = document.selection;
319
- return sel && (sel.createRange().text != "");
346
+ var range = selRange();
347
+ return range && (range.text != "");
320
348
  };
321
349
 
322
350
  function insertAtCursor(html) {
323
- var selection = document.selection;
324
- if (selection) {
325
- var range = selection.createRange();
351
+ var range = selRange();
352
+ if (range) {
326
353
  range.pasteHTML(html);
327
354
  range.collapse(false);
328
355
  range.select();
@@ -343,14 +370,14 @@ var select = {};
343
370
  // currently is, and the offset into the line. Returns null as
344
371
  // node if cursor is on first line.
345
372
  select.cursorPos = function(container, start) {
346
- var selection = document.selection;
347
- if (!selection) return null;
373
+ var range = selRange();
374
+ if (!range) return null;
348
375
 
349
376
  var topNode = select.selectionTopNode(container, start);
350
377
  while (topNode && !isBR(topNode))
351
378
  topNode = topNode.previousSibling;
352
379
 
353
- var range = selection.createRange(), range2 = range.duplicate();
380
+ var range2 = range.duplicate();
354
381
  range.collapse(start);
355
382
  if (topNode) {
356
383
  range2.moveToElementText(topNode);
@@ -565,7 +592,7 @@ var select = {};
565
592
  return range.toString().length;
566
593
  };
567
594
 
568
- function insertNodeAtCursor(node) {
595
+ select.insertNodeAtCursor = function(node) {
569
596
  var range = selectionRange();
570
597
  if (!range) return;
571
598
 
@@ -593,11 +620,11 @@ var select = {};
593
620
  }
594
621
 
595
622
  select.insertNewlineAtCursor = function() {
596
- insertNodeAtCursor(document.createElement("BR"));
623
+ select.insertNodeAtCursor(document.createElement("BR"));
597
624
  };
598
625
 
599
626
  select.insertTabAtCursor = function() {
600
- insertNodeAtCursor(document.createTextNode(fourSpaces));
627
+ select.insertNodeAtCursor(document.createTextNode(fourSpaces));
601
628
  };
602
629
 
603
630
  select.cursorPos = function(container, start) {
@@ -92,7 +92,7 @@ var stringStream = function(source){
92
92
  else if (str.slice(0, left) == cased(current.slice(pos))) {
93
93
  accum += current; current = "";
94
94
  try {current = source.next();}
95
- catch (e) {break;}
95
+ catch (e) {if (e != StopIteration) throw e; break;}
96
96
  pos = 0;
97
97
  str = str.slice(left);
98
98
  }
@@ -109,6 +109,20 @@ var stringStream = function(source){
109
109
 
110
110
  return found;
111
111
  },
112
+ // Wont't match past end of line.
113
+ lookAheadRegex: function(regex, consume) {
114
+ if (regex.source.charAt(0) != "^")
115
+ throw new Error("Regexps passed to lookAheadRegex must start with ^");
116
+
117
+ // Fetch the rest of the line
118
+ while (current.indexOf("\n", pos) == -1) {
119
+ try {current += source.next();}
120
+ catch (e) {if (e != StopIteration) throw e; break;}
121
+ }
122
+ var matched = current.slice(pos).match(regex);
123
+ if (matched && consume) pos += matched[0].length;
124
+ return matched;
125
+ },
112
126
 
113
127
  // Utils built on top of the above
114
128
  // more: -> boolean
@@ -101,7 +101,7 @@ var tokenizeJavaScript = (function() {
101
101
  }
102
102
  function readRegexp() {
103
103
  nextUntilUnescaped(source, "/");
104
- source.nextWhileMatches(/[gi]/);
104
+ source.nextWhileMatches(/[gimy]/); // 'y' is "sticky" option in Mozilla
105
105
  return {type: "regexp", style: "js-string"};
106
106
  }
107
107
  // Mutli-line comments are tricky. We want to return the newlines
@@ -29,7 +29,7 @@
29
29
  function UndoHistory(container, maxDepth, commitDelay, editor) {
30
30
  this.container = container;
31
31
  this.maxDepth = maxDepth; this.commitDelay = commitDelay;
32
- this.editor = editor; this.parent = editor.parent;
32
+ this.editor = editor;
33
33
  // This line object represents the initial, empty editor.
34
34
  var initial = {text: "", from: null, to: null};
35
35
  // As the borders between lines are represented by BR elements, the
@@ -44,7 +44,7 @@ function UndoHistory(container, maxDepth, commitDelay, editor) {
44
44
  this.firstTouched = false;
45
45
  // History is the set of committed changes, touched is the set of
46
46
  // nodes touched since the last commit.
47
- this.history = []; this.redoHistory = []; this.touched = [];
47
+ this.history = []; this.redoHistory = []; this.touched = []; this.lostundo = 0;
48
48
  }
49
49
 
50
50
  UndoHistory.prototype = {
@@ -52,8 +52,8 @@ UndoHistory.prototype = {
52
52
  // milliseconds).
53
53
  scheduleCommit: function() {
54
54
  var self = this;
55
- this.parent.clearTimeout(this.commitTimeout);
56
- this.commitTimeout = this.parent.setTimeout(function(){self.tryCommit();}, this.commitDelay);
55
+ parent.clearTimeout(this.commitTimeout);
56
+ this.commitTimeout = parent.setTimeout(function(){self.tryCommit();}, this.commitDelay);
57
57
  },
58
58
 
59
59
  // Mark a node as touched. Null is a valid argument.
@@ -92,18 +92,19 @@ UndoHistory.prototype = {
92
92
  clear: function() {
93
93
  this.history = [];
94
94
  this.redoHistory = [];
95
+ this.lostundo = 0;
95
96
  },
96
97
 
97
98
  // Ask for the size of the un/redo histories.
98
99
  historySize: function() {
99
- return {undo: this.history.length, redo: this.redoHistory.length};
100
+ return {undo: this.history.length, redo: this.redoHistory.length, lostundo: this.lostundo};
100
101
  },
101
102
 
102
103
  // Push a changeset into the document.
103
104
  push: function(from, to, lines) {
104
105
  var chain = [];
105
106
  for (var i = 0; i < lines.length; i++) {
106
- var end = (i == lines.length - 1) ? to : this.container.ownerDocument.createElement("BR");
107
+ var end = (i == lines.length - 1) ? to : document.createElement("br");
107
108
  chain.push({from: from, to: end, text: cleanText(lines[i])});
108
109
  from = end;
109
110
  }
@@ -128,7 +129,7 @@ UndoHistory.prototype = {
128
129
  // Clear the undo history, make the current document the start
129
130
  // position.
130
131
  reset: function() {
131
- this.history = []; this.redoHistory = [];
132
+ this.history = []; this.redoHistory = []; this.lostundo = 0;
132
133
  },
133
134
 
134
135
  textAfter: function(br) {
@@ -145,7 +146,7 @@ UndoHistory.prototype = {
145
146
 
146
147
  // Commit unless there are pending dirty nodes.
147
148
  tryCommit: function() {
148
- if (!window.parent || !window.UndoHistory) return; // Stop when frame has been unloaded
149
+ if (!window || !window.parent || !window.UndoHistory) return; // Stop when frame has been unloaded
149
150
  if (this.editor.highlightDirty()) this.commit(true);
150
151
  else this.scheduleCommit();
151
152
  },
@@ -153,7 +154,7 @@ UndoHistory.prototype = {
153
154
  // Check whether the touched nodes hold any changes, if so, commit
154
155
  // them.
155
156
  commit: function(doNotHighlight) {
156
- this.parent.clearTimeout(this.commitTimeout);
157
+ parent.clearTimeout(this.commitTimeout);
157
158
  // Make sure there are no pending dirty nodes.
158
159
  if (!doNotHighlight) this.editor.highlightDirty(true);
159
160
  // Build set of chains.
@@ -192,7 +193,7 @@ UndoHistory.prototype = {
192
193
  },
193
194
 
194
195
  notifyEnvironment: function() {
195
- if (this.onChange) this.onChange();
196
+ if (this.onChange) this.onChange(this.editor);
196
197
  // Used by the line-wrapping line-numbering code.
197
198
  if (window.frameElement && window.frameElement.CodeMirror.updateNumbers)
198
199
  window.frameElement.CodeMirror.updateNumbers();
@@ -235,8 +236,10 @@ UndoHistory.prototype = {
235
236
  // it than allowed.
236
237
  addUndoLevel: function(diffs) {
237
238
  this.history.push(diffs);
238
- if (this.history.length > this.maxDepth)
239
+ if (this.history.length > this.maxDepth) {
239
240
  this.history.shift();
241
+ lostundo += 1;
242
+ }
240
243
  },
241
244
 
242
245
  // Build chains from a set of touched nodes.
@@ -257,8 +260,8 @@ UndoHistory.prototype = {
257
260
  function buildLine(node) {
258
261
  var text = [];
259
262
  for (var cur = node ? node.nextSibling : self.container.firstChild;
260
- cur && !isBR(cur); cur = cur.nextSibling)
261
- if (cur.currentText) text.push(cur.currentText);
263
+ cur && (!isBR(cur) || cur.hackBR); cur = cur.nextSibling)
264
+ if (!cur.hackBR && cur.currentText) text.push(cur.currentText);
262
265
  return {from: node, to: cur, text: cleanText(text.join(""))};
263
266
  }
264
267
 
@@ -267,7 +270,7 @@ UndoHistory.prototype = {
267
270
  var lines = [];
268
271
  if (self.firstTouched) self.touched.push(null);
269
272
  forEach(self.touched, function(node) {
270
- if (node && node.parentNode != self.container) return;
273
+ if (node && (node.parentNode != self.container || node.hackBR)) return;
271
274
 
272
275
  if (node) node.historyTouched = false;
273
276
  else self.firstTouched = false;