liquid_cms 0.2.0.11 → 0.2.0.12

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 (60) hide show
  1. data/CHANGELOG.rdoc +7 -0
  2. data/TODO.rdoc +1 -1
  3. data/app/controllers/cms/main_controller.rb +3 -2
  4. data/app/helpers/cms/common_helper.rb +9 -2
  5. data/app/helpers/cms/components_helper.rb +10 -4
  6. data/app/models/cms/component.rb +4 -0
  7. data/app/views/cms/assets/_list.html.erb +4 -4
  8. data/app/views/cms/components/_list.html.erb +5 -1
  9. data/app/views/cms/pages/_list.html.erb +4 -4
  10. data/app/views/cms/shared/_sidebar.html.erb +32 -8
  11. data/app/views/layouts/cms.html.erb +5 -2
  12. data/generators/liquid_cms/templates/config/locales/cms/en.yml +3 -2
  13. data/generators/liquid_cms/templates/public/cms/codemirror/LICENSE +0 -0
  14. data/generators/liquid_cms/templates/public/cms/codemirror/css/csscolors.css +0 -0
  15. data/generators/liquid_cms/templates/public/cms/codemirror/css/docs.css +17 -3
  16. data/generators/liquid_cms/templates/public/cms/codemirror/css/font.js +15 -0
  17. data/generators/liquid_cms/templates/public/cms/codemirror/css/jscolors.css +0 -0
  18. data/generators/liquid_cms/templates/public/cms/codemirror/css/sparqlcolors.css +0 -0
  19. data/generators/liquid_cms/templates/public/cms/codemirror/css/xmlcolors.css +0 -0
  20. data/generators/liquid_cms/templates/public/cms/codemirror/js/codemirror.js +59 -26
  21. data/generators/liquid_cms/templates/public/cms/codemirror/js/editor.js +149 -71
  22. data/generators/liquid_cms/templates/public/cms/codemirror/js/highlight.js +2 -2
  23. data/generators/liquid_cms/templates/public/cms/codemirror/js/mirrorframe.js +2 -2
  24. data/generators/liquid_cms/templates/public/cms/codemirror/js/parsecss.js +5 -3
  25. data/generators/liquid_cms/templates/public/cms/codemirror/js/parsedummy.js +0 -0
  26. data/generators/liquid_cms/templates/public/cms/codemirror/js/parsehtmlmixed.js +28 -9
  27. data/generators/liquid_cms/templates/public/cms/codemirror/js/parsejavascript.js +0 -0
  28. data/generators/liquid_cms/templates/public/cms/codemirror/js/parsesparql.js +0 -0
  29. data/generators/liquid_cms/templates/public/cms/codemirror/js/parsexml.js +6 -1
  30. data/generators/liquid_cms/templates/public/cms/codemirror/js/select.js +48 -21
  31. data/generators/liquid_cms/templates/public/cms/codemirror/js/stringstream.js +15 -1
  32. data/generators/liquid_cms/templates/public/cms/codemirror/js/tokenize.js +0 -0
  33. data/generators/liquid_cms/templates/public/cms/codemirror/js/tokenizejavascript.js +1 -1
  34. data/generators/liquid_cms/templates/public/cms/codemirror/js/undo.js +17 -14
  35. data/generators/liquid_cms/templates/public/cms/codemirror/js/unittests.js +44 -0
  36. data/generators/liquid_cms/templates/public/cms/codemirror/js/util.js +6 -3
  37. data/generators/liquid_cms/templates/public/cms/javascripts/cms.js +15 -1
  38. data/generators/liquid_cms/templates/public/cms/javascripts/livepipe.js +181 -0
  39. data/generators/liquid_cms/templates/public/cms/javascripts/tabs.js +149 -0
  40. data/generators/liquid_cms/templates/public/cms/stylesheets/ie9.css +4 -0
  41. data/generators/liquid_cms/templates/public/cms/stylesheets/sidebar.css +132 -0
  42. data/generators/liquid_cms/templates/public/cms/stylesheets/styles.css +1 -74
  43. data/generators/liquid_cms/templates/public/cms/stylesheets/themes/dark.css +2 -1
  44. data/lib/liquid_cms/context.rb +4 -0
  45. data/lib/liquid_cms/version.rb +1 -1
  46. data/liquid_cms.gemspec +1 -1
  47. data/test/functional/assets_controller_test.rb +3 -3
  48. data/test/rails_app/config/locales/cms/en.yml +8 -0
  49. metadata +11 -16
  50. data/generators/liquid_cms/templates/public/cms/codemirror/bigtest.html +0 -1296
  51. data/generators/liquid_cms/templates/public/cms/codemirror/css/people.jpg +0 -0
  52. data/generators/liquid_cms/templates/public/cms/codemirror/csstest.html +0 -60
  53. data/generators/liquid_cms/templates/public/cms/codemirror/highlight.html +0 -82
  54. data/generators/liquid_cms/templates/public/cms/codemirror/htmltest.html +0 -52
  55. data/generators/liquid_cms/templates/public/cms/codemirror/index.html +0 -245
  56. data/generators/liquid_cms/templates/public/cms/codemirror/jstest.html +0 -56
  57. data/generators/liquid_cms/templates/public/cms/codemirror/manual.html +0 -759
  58. data/generators/liquid_cms/templates/public/cms/codemirror/mixedtest.html +0 -52
  59. data/generators/liquid_cms/templates/public/cms/codemirror/sparqltest.html +0 -41
  60. data/generators/liquid_cms/templates/public/cms/codemirror/story.html +0 -671
@@ -6,8 +6,9 @@
6
6
 
7
7
  var internetExplorer = document.selection && window.ActiveXObject && /MSIE/.test(navigator.userAgent);
8
8
  var webkit = /AppleWebKit/.test(navigator.userAgent);
9
- var safari = /Apple Computers, Inc/.test(navigator.vendor);
10
- var gecko = /gecko\/(\d{8})/i.test(navigator.userAgent);
9
+ var safari = /Apple Computer, Inc/.test(navigator.vendor);
10
+ var gecko = navigator.userAgent.match(/gecko\/(\d{8})/i);
11
+ if (gecko) gecko = Number(gecko[1]);
11
12
  var mac = /Mac/.test(navigator.platform);
12
13
 
13
14
  // TODO this is related to the backspace-at-end-of-line bug. Remove
@@ -37,7 +38,7 @@ function fixSpaces(string) {
37
38
  }
38
39
 
39
40
  function cleanText(text) {
40
- return text.replace(/\u00a0/g, " ");
41
+ return text.replace(/\u00a0/g, " ").replace(/\u200b/g, "");
41
42
  }
42
43
 
43
44
  // Create a SPAN node with the expected properties for document part
@@ -47,13 +48,15 @@ function makePartSpan(value) {
47
48
  if (value.nodeType == 3) text = value.nodeValue;
48
49
  else value = document.createTextNode(text);
49
50
 
50
- var span = document.createElement("SPAN");
51
+ var span = document.createElement("span");
51
52
  span.isPart = true;
52
53
  span.appendChild(value);
53
54
  span.currentText = text;
54
55
  return span;
55
56
  }
56
57
 
58
+ function alwaysZero() {return 0;}
59
+
57
60
  // On webkit, when the last BR of the document does not have text
58
61
  // behind it, the cursor can not be put on the line after it. This
59
62
  // makes pressing enter at the end of the document occasionally do
@@ -67,22 +70,22 @@ var webkitLastLineHack = webkit ?
67
70
  function(container) {
68
71
  var last = container.lastChild;
69
72
  if (!last || !last.hackBR) {
70
- var br = document.createElement("BR");
73
+ var br = document.createElement("br");
71
74
  br.hackBR = true;
72
75
  container.appendChild(br);
73
76
  }
74
77
  } : function() {};
75
78
 
79
+ function asEditorLines(string) {
80
+ var tab = makeWhiteSpace(indentUnit);
81
+ return map(string.replace(/\t/g, tab).replace(/\u00a0/g, " ").replace(/\r\n?/g, "\n").split("\n"), fixSpaces);
82
+ }
83
+
76
84
  var Editor = (function(){
77
85
  // The HTML elements whose content should be suffixed by a newline
78
86
  // when converting them to flat text.
79
87
  var newlineElements = {"P": true, "DIV": true, "LI": true};
80
88
 
81
- function asEditorLines(string) {
82
- var tab = makeWhiteSpace(indentUnit);
83
- return map(string.replace(/\t/g, tab).replace(/\u00a0/g, " ").replace(/\r\n?/g, "\n").split("\n"), fixSpaces);
84
- }
85
-
86
89
  // Helper function for traverseDOM. Flattens an arbitrary DOM node
87
90
  // into an array of textnodes and <br> tags.
88
91
  function simplifyDOM(root, atEnd) {
@@ -91,7 +94,7 @@ var Editor = (function(){
91
94
 
92
95
  function simplifyNode(node, top) {
93
96
  if (node.nodeType == 3) {
94
- var text = node.nodeValue = fixSpaces(node.nodeValue.replace(/\r/g, "").replace(/\n/g, " "));
97
+ var text = node.nodeValue = fixSpaces(node.nodeValue.replace(/[\r\u200b]/g, "").replace(/\n/g, " "));
95
98
  if (text.length) leaving = false;
96
99
  result.push(node);
97
100
  }
@@ -104,7 +107,7 @@ var Editor = (function(){
104
107
  if (!leaving && newlineElements.hasOwnProperty(node.nodeName.toUpperCase())) {
105
108
  leaving = true;
106
109
  if (!atEnd || !top)
107
- result.push(document.createElement("BR"));
110
+ result.push(document.createElement("br"));
108
111
  }
109
112
  }
110
113
  }
@@ -175,7 +178,9 @@ var Editor = (function(){
175
178
  // Check whether a node is a normalized <span> element.
176
179
  function partNode(node){
177
180
  if (node.isPart && node.childNodes.length == 1 && node.firstChild.nodeType == 3) {
178
- node.currentText = node.firstChild.nodeValue;
181
+ var text = node.firstChild.nodeValue;
182
+ node.dirty = node.dirty || text != node.currentText;
183
+ node.currentText = text;
179
184
  return !/[\n\t\r]/.test(node.currentText);
180
185
  }
181
186
  return false;
@@ -240,20 +245,20 @@ var Editor = (function(){
240
245
  // indicating whether anything was found, and can be called again to
241
246
  // skip to the next find. Use the select and replace methods to
242
247
  // actually do something with the found locations.
243
- function SearchCursor(editor, string, from, caseFold) {
248
+ function SearchCursor(editor, pattern, from, caseFold) {
244
249
  this.editor = editor;
245
250
  this.history = editor.history;
246
251
  this.history.commit();
247
- this.valid = !!string;
252
+ this.valid = !!pattern;
248
253
  this.atOccurrence = false;
249
- if (caseFold == undefined) caseFold = string == string.toLowerCase();
254
+ if (caseFold == undefined) caseFold = typeof pattern == "string" && pattern == pattern.toLowerCase();
250
255
 
251
256
  function getText(node){
252
257
  var line = cleanText(editor.history.textAfter(node));
253
258
  return (caseFold ? line.toLowerCase() : line);
254
259
  }
255
260
 
256
- var topPos = {node: null, offset: 0};
261
+ var topPos = {node: null, offset: 0}, self = this;
257
262
  if (from && typeof from == "object" && typeof from.character == "number") {
258
263
  editor.checkLine(from.line);
259
264
  var pos = {node: from.line, offset: from.character};
@@ -267,16 +272,42 @@ var Editor = (function(){
267
272
  this.pos = {from: topPos, to: topPos};
268
273
  }
269
274
 
270
- if (caseFold) string = string.toLowerCase();
275
+ if (typeof pattern != "string") { // Regexp match
276
+ this.matches = function(reverse, node, offset) {
277
+ if (reverse) {
278
+ var line = getText(node).slice(0, offset), match = line.match(pattern), start = 0;
279
+ while (match) {
280
+ var ind = line.indexOf(match[0]);
281
+ start += ind;
282
+ line = line.slice(ind + 1);
283
+ var newmatch = line.match(pattern);
284
+ if (newmatch) match = newmatch;
285
+ else break;
286
+ }
287
+ }
288
+ else {
289
+ var line = getText(node).slice(offset), match = line.match(pattern),
290
+ start = match && offset + line.indexOf(match[0]);
291
+ }
292
+ if (match) {
293
+ self.currentMatch = match;
294
+ return {from: {node: node, offset: start},
295
+ to: {node: node, offset: start + match[0].length}};
296
+ }
297
+ };
298
+ return;
299
+ }
300
+
301
+ if (caseFold) pattern = pattern.toLowerCase();
271
302
  // Create a matcher function based on the kind of string we have.
272
- var target = string.split("\n");
303
+ var target = pattern.split("\n");
273
304
  this.matches = (target.length == 1) ?
274
305
  // For one-line strings, searching can be done simply by calling
275
306
  // indexOf or lastIndexOf on the current line.
276
307
  function(reverse, node, offset) {
277
- var line = getText(node), len = string.length, match;
278
- if (reverse ? (offset >= len && (match = line.lastIndexOf(string, offset - len)) != -1)
279
- : (match = line.indexOf(string, offset)) != -1)
308
+ var line = getText(node), len = pattern.length, match;
309
+ if (reverse ? (offset >= len && (match = line.lastIndexOf(pattern, offset - len)) != -1)
310
+ : (match = line.indexOf(pattern, offset)) != -1)
280
311
  return {from: {node: node, offset: match},
281
312
  to: {node: node, offset: match + len}};
282
313
  } :
@@ -364,6 +395,9 @@ var Editor = (function(){
364
395
 
365
396
  replace: function(string) {
366
397
  if (this.atOccurrence) {
398
+ var fragments = this.currentMatch;
399
+ if (fragments)
400
+ string = string.replace(/\\(\d)/, function(m, i){return fragments[i];});
367
401
  var end = this.editor.replaceRange(this.pos.from, this.pos.to, string);
368
402
  this.pos.to = end;
369
403
  this.atOccurrence = false;
@@ -380,7 +414,6 @@ var Editor = (function(){
380
414
  function Editor(options) {
381
415
  this.options = options;
382
416
  window.indentUnit = options.indentUnit;
383
- this.parent = parent;
384
417
  var container = this.container = document.body;
385
418
  this.history = new UndoHistory(container, options.undoDepth, options.undoDelay, this);
386
419
  var self = this;
@@ -390,7 +423,7 @@ var Editor = (function(){
390
423
  if (options.parserConfig && Editor.Parser.configure)
391
424
  Editor.Parser.configure(options.parserConfig);
392
425
 
393
- if (!options.readOnly)
426
+ if (!options.readOnly && !internetExplorer)
394
427
  select.setCursorPos(container, {node: null, offset: 0});
395
428
 
396
429
  this.dirty = [];
@@ -417,7 +450,8 @@ var Editor = (function(){
417
450
  // body of the document to focus it in IE, making focusing
418
451
  // hard when the document is small.
419
452
  if (internetExplorer && options.height != "dynamic")
420
- document.body.style.minHeight = (frameElement.clientHeight - 2 * document.body.offsetTop - 5) + "px";
453
+ document.body.style.minHeight = (
454
+ window.frameElement.clientHeight - 2 * document.body.offsetTop - 5) + "px";
421
455
 
422
456
  document.documentElement.style.borderWidth = "0";
423
457
  if (!options.textWrapping)
@@ -442,7 +476,7 @@ var Editor = (function(){
442
476
  addEventHandler(document, "keyup", method(this, "keyUp"));
443
477
 
444
478
  function cursorActivity() {self.cursorActivity(false);}
445
- addEventHandler(document.body, "mouseup", cursorActivity);
479
+ addEventHandler(internetExplorer ? document.body : window, "mouseup", cursorActivity);
446
480
  addEventHandler(document.body, "cut", cursorActivity);
447
481
 
448
482
  // workaround for a gecko bug [?] where going forward and then
@@ -481,8 +515,24 @@ var Editor = (function(){
481
515
  Editor.prototype = {
482
516
  // Import a piece of code into the editor.
483
517
  importCode: function(code) {
484
- this.history.push(null, null, asEditorLines(code));
485
- this.history.reset();
518
+ var lines = asEditorLines(code), chunk = 1000;
519
+ if (!this.options.incrementalLoading || lines.length < chunk) {
520
+ this.history.push(null, null, lines);
521
+ this.history.reset();
522
+ }
523
+ else {
524
+ var cur = 0, self = this;
525
+ function addChunk() {
526
+ var chunklines = lines.slice(cur, cur + chunk);
527
+ chunklines.push("");
528
+ self.history.push(self.history.nodeBefore(null), null, chunklines);
529
+ self.history.reset();
530
+ cur += chunk;
531
+ if (cur < lines.length)
532
+ parent.setTimeout(addChunk, 1000);
533
+ }
534
+ addChunk();
535
+ }
486
536
  },
487
537
 
488
538
  // Extract the code from the editor.
@@ -493,16 +543,16 @@ var Editor = (function(){
493
543
  var accum = [];
494
544
  select.markSelection();
495
545
  forEach(traverseDOM(this.container.firstChild), method(accum, "push"));
496
- webkitLastLineHack(this.container);
497
546
  select.selectMarked();
498
547
  // On webkit, don't count last (empty) line if the webkitLastLineHack BR is present
499
548
  if (webkit && this.container.lastChild.hackBR)
500
549
  accum.pop();
550
+ webkitLastLineHack(this.container);
501
551
  return cleanText(accum.join(""));
502
552
  },
503
553
 
504
554
  checkLine: function(node) {
505
- if (node === false || !(node == null || node.parentNode == this.container))
555
+ if (node === false || !(node == null || node.parentNode == this.container || node.hackBR))
506
556
  throw parent.CodeMirror.InvalidLineHandle;
507
557
  },
508
558
 
@@ -518,14 +568,17 @@ var Editor = (function(){
518
568
  },
519
569
 
520
570
  lastLine: function() {
521
- if (this.container.lastChild) return startOfLine(this.container.lastChild);
522
- else return null;
571
+ var last = this.container.lastChild;
572
+ if (last) last = startOfLine(last);
573
+ if (last && last.hackBR) last = startOfLine(last.previousSibling);
574
+ return last;
523
575
  },
524
576
 
525
577
  nextLine: function(line) {
526
578
  this.checkLine(line);
527
579
  var end = endOfLine(line, this.container);
528
- return end || false;
580
+ if (!end || end.hackBR) return false;
581
+ else return end;
529
582
  },
530
583
 
531
584
  prevLine: function(line) {
@@ -647,14 +700,14 @@ var Editor = (function(){
647
700
  webkitLastLineHack(this.container);
648
701
  },
649
702
 
650
- cursorCoords: function(start) {
703
+ cursorCoords: function(start, internal) {
651
704
  var sel = select.cursorPos(this.container, start);
652
705
  if (!sel) return null;
653
706
  var off = sel.offset, node = sel.node, self = this;
654
707
  function measureFromNode(node, xOffset) {
655
708
  var y = -(document.body.scrollTop || document.documentElement.scrollTop || 0),
656
709
  x = -(document.body.scrollLeft || document.documentElement.scrollLeft || 0) + xOffset;
657
- forEach([node, window.frameElement], function(n) {
710
+ forEach([node, internal ? null : window.frameElement], function(n) {
658
711
  while (n) {x += n.offsetLeft; y += n.offsetTop;n = n.offsetParent;}
659
712
  });
660
713
  return {x: x, y: y, yBot: y + node.offsetHeight};
@@ -691,15 +744,21 @@ var Editor = (function(){
691
744
  },
692
745
 
693
746
  reroutePasteEvent: function() {
694
- if (this.capturingPaste || window.opera) return;
747
+ if (this.capturingPaste || window.opera || (gecko && gecko >= 20101026)) return;
695
748
  this.capturingPaste = true;
696
749
  var te = window.frameElement.CodeMirror.textareaHack;
750
+ var coords = this.cursorCoords(true, true);
751
+ te.style.top = coords.y + "px";
752
+ if (internetExplorer) {
753
+ var snapshot = select.getBookmark(this.container);
754
+ if (snapshot) this.selectionSnapshot = snapshot;
755
+ }
697
756
  parent.focus();
698
757
  te.value = "";
699
758
  te.focus();
700
759
 
701
760
  var self = this;
702
- this.parent.setTimeout(function() {
761
+ parent.setTimeout(function() {
703
762
  self.capturingPaste = false;
704
763
  window.focus();
705
764
  if (self.selectionSnapshot) // IE hack
@@ -741,7 +800,7 @@ var Editor = (function(){
741
800
  var start = select.selectionTopNode(this.container, true),
742
801
  end = select.selectionTopNode(this.container, false);
743
802
  if (start === false || end === false) return;
744
- this.indentRegion(start, end, direction);
803
+ this.indentRegion(start, end, direction, true);
745
804
  }
746
805
  },
747
806
 
@@ -870,7 +929,7 @@ var Editor = (function(){
870
929
  this.reroutePasteEvent();
871
930
  }
872
931
  else if (electric && electric.indexOf(event.character) != -1)
873
- this.parent.setTimeout(function(){self.indentAtCursor(null);}, 0);
932
+ parent.setTimeout(function(){self.indentAtCursor(null);}, 0);
874
933
  // Work around a bug where pressing backspace at the end of a
875
934
  // line, or delete at the start, often causes the cursor to jump
876
935
  // to the start of the line in Opera 10.60.
@@ -879,7 +938,7 @@ var Editor = (function(){
879
938
  var sel = select.selectionTopNode(this.container), self = this,
880
939
  next = sel ? sel.nextSibling : this.container.firstChild;
881
940
  if (sel !== false && next && isBR(next))
882
- this.parent.setTimeout(function(){
941
+ parent.setTimeout(function(){
883
942
  if (select.selectionTopNode(self.container) == next)
884
943
  select.focusAfterNode(next.previousSibling, self.container);
885
944
  }, 20);
@@ -887,7 +946,7 @@ var Editor = (function(){
887
946
  else if (event.code == 46) { // delete
888
947
  var sel = select.selectionTopNode(this.container), self = this;
889
948
  if (sel && isBR(sel)) {
890
- this.parent.setTimeout(function(){
949
+ parent.setTimeout(function(){
891
950
  if (select.selectionTopNode(self.container) != sel)
892
951
  select.focusAfterNode(sel, self.container);
893
952
  }, 20);
@@ -908,12 +967,23 @@ var Editor = (function(){
908
967
  if (sel && next && isBR(next) && !isBR(sel)) {
909
968
  var cheat = document.createTextNode("\u200b");
910
969
  this.container.insertBefore(cheat, next);
911
- this.parent.setTimeout(function() {
970
+ parent.setTimeout(function() {
912
971
  if (cheat.nodeValue == "\u200b") removeElement(cheat);
913
972
  else cheat.nodeValue = cheat.nodeValue.replace("\u200b", "");
914
973
  }, 20);
915
974
  }
916
975
  }
976
+
977
+ // Magic incantation that works abound a webkit bug when you
978
+ // can't type on a blank line following a line that's wider than
979
+ // the window.
980
+ if (webkit && !this.options.textWrapping)
981
+ setTimeout(function () {
982
+ var node = select.selectionTopNode(self.container, true);
983
+ if (node && node.nodeType == 3 && node.previousSibling && isBR(node.previousSibling)
984
+ && node.nextSibling && isBR(node.nextSibling))
985
+ node.parentNode.replaceChild(document.createElement("BR"), node.previousSibling);
986
+ }, 50);
917
987
  },
918
988
 
919
989
  // Mark the node at the cursor dirty when a non-safe key is
@@ -938,6 +1008,7 @@ var Editor = (function(){
938
1008
  var self = this, whiteSpace = whiteSpaceAfter(start);
939
1009
  var newIndent = 0, curIndent = whiteSpace ? whiteSpace.currentText.length : 0;
940
1010
 
1011
+ var firstText = whiteSpace ? whiteSpace.nextSibling : (start ? start.nextSibling : this.container.firstChild);
941
1012
  if (direction == "keep") {
942
1013
  if (start) {
943
1014
  var prevWS = whiteSpaceAfter(startOfLine(start.previousSibling))
@@ -947,17 +1018,16 @@ var Editor = (function(){
947
1018
  else {
948
1019
  // Sometimes the start of the line can influence the correct
949
1020
  // indentation, so we retrieve it.
950
- var firstText = whiteSpace ? whiteSpace.nextSibling : (start ? start.nextSibling : this.container.firstChild);
951
1021
  var nextChars = (start && firstText && firstText.currentText) ? firstText.currentText : "";
952
1022
 
953
1023
  // Ask the lexical context for the correct indentation, and
954
1024
  // compute how much this differs from the current indentation.
955
- if (direction != null && this.options.tabMode == "shift")
1025
+ if (direction != null && this.options.tabMode != "indent")
956
1026
  newIndent = direction ? curIndent + indentUnit : Math.max(0, curIndent - indentUnit)
957
1027
  else if (start)
958
- newIndent = start.indentation(nextChars, curIndent, direction);
1028
+ newIndent = start.indentation(nextChars, curIndent, direction, firstText);
959
1029
  else if (Editor.Parser.firstIndentation)
960
- newIndent = Editor.Parser.firstIndentation(nextChars, curIndent, direction);
1030
+ newIndent = Editor.Parser.firstIndentation(nextChars, curIndent, direction, firstText);
961
1031
  }
962
1032
 
963
1033
  var indentDiff = newIndent - curIndent;
@@ -1017,7 +1087,7 @@ var Editor = (function(){
1017
1087
  // re-indented, when nothing is selected, the line with the cursor
1018
1088
  // is re-indented.
1019
1089
  handleTab: function(direction) {
1020
- if (this.options.tabMode == "spaces")
1090
+ if (this.options.tabMode == "spaces" && !select.somethingSelected())
1021
1091
  select.insertTabAtCursor();
1022
1092
  else
1023
1093
  this.reindentSelection(direction);
@@ -1086,9 +1156,9 @@ var Editor = (function(){
1086
1156
 
1087
1157
  // Delay (or initiate) the next paren highlight event.
1088
1158
  scheduleParenHighlight: function() {
1089
- if (this.parenEvent) this.parent.clearTimeout(this.parenEvent);
1159
+ if (this.parenEvent) parent.clearTimeout(this.parenEvent);
1090
1160
  var self = this;
1091
- this.parenEvent = this.parent.setTimeout(function(){self.highlightParens();}, 300);
1161
+ this.parenEvent = parent.setTimeout(function(){self.highlightParens();}, 300);
1092
1162
  },
1093
1163
 
1094
1164
  // Take the token before the cursor. If it contains a character in
@@ -1096,23 +1166,24 @@ var Editor = (function(){
1096
1166
  // highlight them in green for a moment, or red if no proper match
1097
1167
  // was found.
1098
1168
  highlightParens: function(jump, fromKey) {
1099
- var self = this;
1169
+ var self = this, mark = this.options.markParen;
1170
+ if (typeof mark == "string") mark = [mark, mark];
1100
1171
  // give the relevant nodes a colour.
1101
1172
  function highlight(node, ok) {
1102
1173
  if (!node) return;
1103
- if (self.options.markParen) {
1104
- self.options.markParen(node, ok);
1105
- }
1106
- else {
1174
+ if (!mark) {
1107
1175
  node.style.fontWeight = "bold";
1108
1176
  node.style.color = ok ? "#8F8" : "#F88";
1109
1177
  }
1178
+ else if (mark.call) mark(node, ok);
1179
+ else node.className += " " + mark[ok ? 0 : 1];
1110
1180
  }
1111
1181
  function unhighlight(node) {
1112
1182
  if (!node) return;
1113
- if (self.options.unmarkParen) {
1183
+ if (mark && !mark.call)
1184
+ removeClass(removeClass(node, mark[0]), mark[1]);
1185
+ else if (self.options.unmarkParen)
1114
1186
  self.options.unmarkParen(node);
1115
- }
1116
1187
  else {
1117
1188
  node.style.fontWeight = "";
1118
1189
  node.style.color = "";
@@ -1123,9 +1194,9 @@ var Editor = (function(){
1123
1194
  unhighlight(self.highlighted[1]);
1124
1195
  }
1125
1196
 
1126
- if (!window.parent || !window.select) return;
1197
+ if (!window || !window.parent || !window.select) return;
1127
1198
  // Clear the event property.
1128
- if (this.parenEvent) this.parent.clearTimeout(this.parenEvent);
1199
+ if (this.parenEvent) parent.clearTimeout(this.parenEvent);
1129
1200
  this.parenEvent = null;
1130
1201
 
1131
1202
  // Extract a 'paren' from a piece of text.
@@ -1184,7 +1255,7 @@ var Editor = (function(){
1184
1255
  highlight(cursor, found.status);
1185
1256
  highlight(found.node, found.status);
1186
1257
  if (fromKey)
1187
- self.parent.setTimeout(function() {unhighlight(cursor); unhighlight(found.node);}, 500);
1258
+ parent.setTimeout(function() {unhighlight(cursor); unhighlight(found.node);}, 500);
1188
1259
  else
1189
1260
  self.highlighted = [cursor, found.node];
1190
1261
  if (jump && found.node)
@@ -1213,7 +1284,7 @@ var Editor = (function(){
1213
1284
 
1214
1285
  // Indent all lines whose start falls inside of the current
1215
1286
  // selection.
1216
- indentRegion: function(start, end, direction) {
1287
+ indentRegion: function(start, end, direction, selectAfter) {
1217
1288
  var current = (start = startOfLine(start)), before = start && startOfLine(start.previousSibling);
1218
1289
  if (!isBR(end)) end = endOfLine(end, this.container);
1219
1290
  this.addDirtyNode(start);
@@ -1225,7 +1296,8 @@ var Editor = (function(){
1225
1296
  before = current;
1226
1297
  current = next;
1227
1298
  } while (current != end);
1228
- select.setCursorPos(this.container, {node: start, offset: 0}, {node: end, offset: 0});
1299
+ if (selectAfter)
1300
+ select.setCursorPos(this.container, {node: start, offset: 0}, {node: end, offset: 0});
1229
1301
  },
1230
1302
 
1231
1303
  // Find the node that the cursor is in, mark it as dirty, and make
@@ -1240,10 +1312,15 @@ var Editor = (function(){
1240
1312
 
1241
1313
  if (internetExplorer) {
1242
1314
  this.container.createTextRange().execCommand("unlink");
1243
- this.selectionSnapshot = select.getBookmark(this.container);
1315
+ clearTimeout(this.saveSelectionSnapshot);
1316
+ var self = this;
1317
+ this.saveSelectionSnapshot = setTimeout(function() {
1318
+ var snapshot = select.getBookmark(self.container);
1319
+ if (snapshot) self.selectionSnapshot = snapshot;
1320
+ }, 200);
1244
1321
  }
1245
1322
 
1246
- var activity = this.options.cursorActivity;
1323
+ var activity = this.options.onCursorActivity;
1247
1324
  if (!safe || activity) {
1248
1325
  var cursor = select.selectionTopNode(this.container, false);
1249
1326
  if (cursor === false || !this.container.firstChild) return;
@@ -1288,8 +1365,8 @@ var Editor = (function(){
1288
1365
  // Timeouts are routed through the parent window, because on
1289
1366
  // some browsers designMode windows do not fire timeouts.
1290
1367
  var self = this;
1291
- this.parent.clearTimeout(this.highlightTimeout);
1292
- this.highlightTimeout = this.parent.setTimeout(function(){self.highlightDirty();}, this.options.passDelay);
1368
+ parent.clearTimeout(this.highlightTimeout);
1369
+ this.highlightTimeout = parent.setTimeout(function(){self.highlightDirty();}, this.options.passDelay);
1293
1370
  },
1294
1371
 
1295
1372
  // Fetch one dirty node, and remove it from the dirty set.
@@ -1319,7 +1396,7 @@ var Editor = (function(){
1319
1396
  highlightDirty: function(force) {
1320
1397
  // Prevent FF from raising an error when it is firing timeouts
1321
1398
  // on a page that's no longer loaded.
1322
- if (!window.parent || !window.select) return false;
1399
+ if (!window || !window.parent || !window.select) return false;
1323
1400
 
1324
1401
  if (!this.options.readOnly) select.markSelection();
1325
1402
  var start, endTime = force ? null : time() + this.options.passTime;
@@ -1339,7 +1416,7 @@ var Editor = (function(){
1339
1416
  var self = this, pos = null;
1340
1417
  return function() {
1341
1418
  // FF timeout weirdness workaround.
1342
- if (!window.parent || !window.select) return;
1419
+ if (!window || !window.parent || !window.select) return;
1343
1420
  // If the current node is no longer in the document... oh
1344
1421
  // well, we start over.
1345
1422
  if (pos && pos.parentNode != self.container)
@@ -1357,8 +1434,8 @@ var Editor = (function(){
1357
1434
  // a given interval.
1358
1435
  delayScanning: function() {
1359
1436
  if (this.scanner) {
1360
- this.parent.clearTimeout(this.documentScan);
1361
- this.documentScan = this.parent.setTimeout(this.scanner, this.options.continuousScanning);
1437
+ parent.clearTimeout(this.documentScan);
1438
+ this.documentScan = parent.setTimeout(this.scanner, this.options.continuousScanning);
1362
1439
  }
1363
1440
  },
1364
1441
 
@@ -1516,7 +1593,7 @@ var Editor = (function(){
1516
1593
  // later resume parsing from this point, the second is used
1517
1594
  // for indentation.
1518
1595
  part.parserFromHere = parsed.copy();
1519
- part.indentation = token.indentation;
1596
+ part.indentation = token.indentation || alwaysZero;
1520
1597
  part.dirty = false;
1521
1598
 
1522
1599
  // If the target argument wasn't an integer, go at least
@@ -1540,6 +1617,7 @@ var Editor = (function(){
1540
1617
 
1541
1618
  // If the part matches the token, we can leave it alone.
1542
1619
  if (correctPart(token, part)){
1620
+ if (active && part.dirty) active(part, token, self);
1543
1621
  part.dirty = false;
1544
1622
  parts.next();
1545
1623
  }
@@ -1589,5 +1667,5 @@ var Editor = (function(){
1589
1667
  addEventHandler(window, "load", function() {
1590
1668
  var CodeMirror = window.frameElement.CodeMirror;
1591
1669
  var e = CodeMirror.editor = new Editor(CodeMirror.options);
1592
- this.parent.setTimeout(method(CodeMirror, "init"), 0);
1670
+ parent.setTimeout(method(CodeMirror, "init"), 0);
1593
1671
  });