liquid_cms 0.2.0.11 → 0.2.0.12

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