sir-trevor-rails 0.1.3 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore CHANGED
@@ -1 +1,2 @@
1
+ *.gem
1
2
  .DS_Store
@@ -1,32 +1,32 @@
1
- // Sir Trevor, v0.1.3
1
+ // Sir Trevor, v0.2.1
2
2
 
3
3
  (function ($, _){
4
4
 
5
5
  var root = this,
6
6
  SirTrevor;
7
7
 
8
- SirTrevor = root.SirTrevor = {};
9
- SirTrevor.DEBUG = true;
8
+ SirTrevor = root.SirTrevor = {};
9
+ SirTrevor.DEBUG = false;
10
10
  SirTrevor.SKIP_VALIDATION = false;
11
11
 
12
- /*
12
+ /*
13
13
  Define default attributes that can be extended through an object passed to the
14
14
  initialize function of SirTrevor
15
15
  */
16
16
 
17
17
  SirTrevor.DEFAULTS = {
18
18
  baseCSSClass: "sir-trevor",
19
- errorClass: "sir-trevor-error",
19
+ errorClass: "error",
20
20
  defaultType: "Text",
21
21
  spinner: {
22
22
  className: 'spinner',
23
- lines: 9,
24
- length: 8,
25
- width: 3,
26
- radius: 6,
27
- color: '#000',
28
- speed: 1.4,
29
- trail: 57,
23
+ lines: 9,
24
+ length: 8,
25
+ width: 3,
26
+ radius: 6,
27
+ color: '#000',
28
+ speed: 1.4,
29
+ trail: 57,
30
30
  shadow: false,
31
31
  left: '50%',
32
32
  top: '50%'
@@ -45,7 +45,7 @@
45
45
  required: [],
46
46
  uploadUrl: '/attachments',
47
47
  baseImageUrl: '/sir-trevor-uploads/'
48
- };
48
+ };
49
49
 
50
50
  SirTrevor.Blocks = {};
51
51
  SirTrevor.Formatters = {};
@@ -261,7 +261,7 @@
261
261
  };
262
262
 
263
263
  $.fn.chars = function() {
264
- count = (this.attr('contenteditable')!==undefined) ? this.text().length : count = this.val().length;
264
+ count = (this.attr('contenteditable')!==undefined) ? this.text().length : this.val().length;
265
265
  return count;
266
266
  };
267
267
 
@@ -531,13 +531,11 @@
531
531
 
532
532
 
533
533
  var Block = SirTrevor.Block = function(instance, data) {
534
-
535
534
  this.instance = instance;
536
535
  this.type = this._getBlockType();
537
536
 
538
- this.store("create", this, { data: data });
537
+ this.store("create", this, { data: data });
539
538
 
540
- //this.data = data;
541
539
  this.uploadsCount = 0;
542
540
  this.blockID = _.uniqueId(this.className + '-');
543
541
 
@@ -550,21 +548,22 @@
550
548
  };
551
549
 
552
550
  var blockOptions = [
553
- "className",
551
+ "className",
554
552
  "toolbarEnabled",
555
553
  "formattingEnabled",
556
- "dropEnabled",
557
- "title",
558
- "limit",
559
- "editorHTML",
560
- "dropzoneHTML",
561
- "validate",
562
- "loadData",
554
+ "dropEnabled",
555
+ "title",
556
+ "limit",
557
+ "editorHTML",
558
+ "dropzoneHTML",
559
+ "validate",
560
+ "loadData",
563
561
  "toData",
564
562
  "onDrop",
565
563
  "onContentPasted",
566
564
  "onBlockRender",
567
565
  "beforeBlockRender",
566
+ "setTextLimit",
568
567
  "toMarkdown",
569
568
  "toHTML"
570
569
  ];
@@ -596,6 +595,7 @@
596
595
  loadData: function(data) {},
597
596
  onBlockRender: function(){},
598
597
  beforeBlockRender: function(){},
598
+ setTextLimit: function() {},
599
599
  toMarkdown: function(markdown){ return markdown; },
600
600
  toHTML: function(html){ return html; },
601
601
 
@@ -608,11 +608,10 @@
608
608
  this.beforeBlockRender();
609
609
 
610
610
  // Insert before the marker
611
- this.instance.formatBar.hide();
612
611
  this.instance.marker.hide();
613
612
  this.instance.marker.$el.before(this.$el);
614
613
 
615
- // Do we have a dropzone?
614
+ // Do we have a dropzone?
616
615
  if (this.dropEnabled) {
617
616
  this._initDragDrop();
618
617
  }
@@ -628,8 +627,8 @@
628
627
  this.save();
629
628
 
630
629
  // Add UI elements
631
- this.$el.append($('<span>',{ 'class': 'handle', draggable: true }));
632
- this.$el.append($('<span>',{ 'class': 'delete block-delete' }));
630
+ this.$el.append($('<span>',{ 'class': this.instance.baseCSS("drag-handle"), draggable: true }));
631
+ this.$el.append($('<span>',{ 'class': this.instance.baseCSS("remove-block") }));
633
632
 
634
633
  // Stop events propagating through to the container
635
634
  this.$el
@@ -645,20 +644,18 @@
645
644
  this._initPaste();
646
645
 
647
646
  // Delete
648
- this.$('.delete.block-delete').bind('click', this.onDeleteClick);
647
+ this.$('.' + this.instance.baseCSS("remove-block")).bind('click', this.onDeleteClick);
649
648
 
650
649
  // Handle text blocks
651
650
  if (this.$$('.text-block').length > 0) {
652
651
  document.execCommand("styleWithCSS", false, false);
653
652
  document.execCommand("insertBrOnReturn", false, true);
654
653
 
655
- // Bind our text block to show the format bar
656
- this.$$('.text-block')
657
- .focus(this.onBlockFocus)
658
- .blur(this.onBlockBlur);
659
-
660
654
  // Strip out all the HTML on paste
661
- this.$$('.text-block').bind('paste', this._handleContentPaste);
655
+ this.$$('.text-block')
656
+ .bind('paste', this._handleContentPaste)
657
+ .bind('focus', this.onBlockFocus)
658
+ .bind('blur', this.onBlockBlur);
662
659
 
663
660
  // Formatting
664
661
  this._initFormatting();
@@ -676,11 +673,10 @@
676
673
  // Reorderable
677
674
  this._initReordering();
678
675
 
679
- this._initTextLimits();
680
-
681
676
  // Set ready state
682
- this.$el.addClass('sir-trevor-item-ready');
677
+ this.$el.addClass(this.instance.baseCSS('item-ready'));
683
678
 
679
+ this.setTextLimit();
684
680
  this.onBlockRender();
685
681
  },
686
682
 
@@ -740,13 +736,14 @@
740
736
 
741
737
  if ((required && content.length === 0) || too_long) {
742
738
  // Error!
743
- field.addClass(this.instance.options.errorClass).before($("<div>", {
744
- 'class': 'error-marker',
745
- 'html': '!'
746
- }));
739
+ field.addClass(this.instance.baseCSS(this.instance.options.errorClass));
747
740
  errors++;
748
- }
741
+ }
749
742
  }, this));
743
+
744
+ if (errors > 0) {
745
+ this.$el.addClass(this.instance.baseCSS('block-with-errors'));
746
+ }
750
747
 
751
748
  return (errors === 0);
752
749
  },
@@ -818,23 +815,7 @@
818
815
  var item = $(ev.target);
819
816
  item.parent().removeClass('dragging');
820
817
  this.instance.marker.hide();
821
- },
822
-
823
- onBlockFocus: function(ev) {
824
- _.delay(_.bind(function(){
825
- this.instance.formatBar.clicked = false;
826
- if(this.formattingEnabled) {
827
- this.instance.formatBar.show(this.$el);
828
- }
829
- }, this), 250);
830
- },
831
-
832
- onBlockBlur: function(ev) {
833
- _.delay(_.bind(function(){
834
- if(!this.instance.formatBar.clicked && this.formattingEnabled) {
835
- this.instance.formatBar.hide();
836
- }
837
- }, this), 250);
818
+ this.instance.formatBar.show();
838
819
  },
839
820
 
840
821
  onDeleteClick: function(ev) {
@@ -850,6 +831,14 @@
850
831
  textBlock.html(this.instance._toHTML(this.instance._toMarkdown(textBlock.html(), this.type),this.type));
851
832
  }
852
833
  },
834
+
835
+ onBlockFocus: function(e) {
836
+ this.$el.addClass('focussed');
837
+ },
838
+
839
+ onBlockBlur: function(e) {
840
+ this.$el.removeClass('focussed');
841
+ },
853
842
 
854
843
  /*
855
844
  Generic Upload Attachment Function
@@ -863,7 +852,6 @@
863
852
  /* Private methods */
864
853
 
865
854
  _loadData: function() {
866
-
867
855
  SirTrevor.log("loadData for " + this.blockID);
868
856
 
869
857
  this.loading();
@@ -880,14 +868,16 @@
880
868
  },
881
869
 
882
870
  _beforeValidate: function() {
883
- this.errors = [];
884
- this.$('.error').removeClass('error');
871
+ this.errors = [];
872
+ var errorClass = this.instance.baseCSS("error");
873
+ this.$el.removeClass(this.instance.baseCSS('block-with-errors'));
874
+ this.$('.' + errorClass).removeClass(errorClass);
885
875
  this.$('.error-marker').remove();
886
876
  },
887
877
 
888
878
  _handleContentPaste: function(ev) {
889
879
  // We need a little timeout here
890
- var timed = function(ev){
880
+ var timed = function(ev){
891
881
  // Delegate this off to the super method that can be overwritten
892
882
  this.onContentPasted(ev);
893
883
  };
@@ -905,21 +895,18 @@
905
895
  types = e.dataTransfer.types,
906
896
  type, data = [];
907
897
 
908
- this.instance.formatBar.hide();
909
898
  this.instance.marker.hide();
910
- this.$dropzone.removeClass('dragOver');
899
+ this.$dropzone.removeClass('drag-enter');
911
900
 
912
901
  /*
913
902
  Check the type we just received,
914
903
  delegate it away to our blockTypes to process
915
- */
904
+ */
916
905
 
917
- if (!_.isUndefined(types))
918
- {
919
- if (_.include(types, 'Files') || _.include(types, 'text/plain') || _.include(types, 'text/uri-list'))
920
- {
906
+ if (!_.isUndefined(types)) {
907
+ if (_.include(types, 'Files') || _.include(types, 'text/plain') || _.include(types, 'text/uri-list')) {
921
908
  this.onDrop(e.dataTransfer);
922
- }
909
+ }
923
910
  }
924
911
  },
925
912
 
@@ -928,12 +915,12 @@
928
915
 
929
916
  // Set
930
917
  var editor = $('<div>', {
931
- 'class': 'block-editor ' + this.className + '-block',
918
+ 'class': this.instance.baseCSS("editor-block") + ' ' + this._getBlockClass(),
932
919
  html: el
933
920
  });
934
921
 
935
- this.$el = $('<div>', {
936
- 'class': this.instance.options.baseCSSClass + "-block",
922
+ this.$el = $('<div>', {
923
+ 'class': this.instance.baseCSS("block"),
937
924
  id: this.blockID,
938
925
  "data-type": this.type,
939
926
  "data-instance": this.instance.ID,
@@ -951,13 +938,16 @@
951
938
  if (SirTrevor.Blocks[block].prototype == Object.getPrototypeOf(this)) {
952
939
  objName = block;
953
940
  }
954
- }
941
+ }
955
942
  return objName;
956
943
  },
944
+
945
+ _getBlockClass: function() {
946
+ return this.className + '-block';
947
+ },
957
948
 
958
949
  /*
959
950
  * Init functions for adding functionality
960
- *
961
951
  */
962
952
 
963
953
  _initDragDrop: function() {
@@ -965,18 +955,24 @@
965
955
 
966
956
  this.$dropzone = $("<div>", {
967
957
  html: this.dropzoneHTML,
968
- class: "dropzone " + this.className + '-block'
958
+ 'class': "dropzone " + this._getBlockClass()
969
959
  });
970
960
  this.$el.append(this.$dropzone);
971
961
  this.$editor.hide();
972
-
962
+
973
963
  // Bind our drop event
974
- this.$dropzone.dropArea();
975
- this.$dropzone.bind('drop', this._handleDrop);
964
+ this.$dropzone.bind('drop', this._handleDrop)
965
+ .bind('dragenter', function(e) { halt(e); $(this).addClass('drag-enter'); })
966
+ .bind('dragover', function(e) {
967
+ e.originalEvent.dataTransfer.dropEffect = "copy";
968
+ halt(e);
969
+ $(this).addClass('drag-enter');
970
+ })
971
+ .bind('dragleave', function(e) { halt(e); $(this).removeClass('drag-enter'); });
976
972
  },
977
973
 
978
974
  _initReordering: function() {
979
- this.$('.handle')
975
+ this.$('.' + this.instance.baseCSS("drag-handle"))
980
976
  .bind('dragstart', this.onDragStart)
981
977
  .bind('dragend', this.onDragEnd)
982
978
  .bind('drag', this.instance.marker.show);
@@ -1013,7 +1009,6 @@
1013
1009
  var Format = SirTrevor.Formatter = function(options){
1014
1010
  this.formatId = _.uniqueId('format-');
1015
1011
  this._configure(options || {});
1016
- this.className = SirTrevor.DEFAULTS.baseCSSClass + "-format-" + this.options.className;
1017
1012
  this.initialize.apply(this, arguments);
1018
1013
  };
1019
1014
 
@@ -1073,7 +1068,7 @@
1073
1068
  SirTrevor.Blocks.Quote = SirTrevor.Block.extend({
1074
1069
 
1075
1070
  title: "Quote",
1076
- className: "block-quote",
1071
+ className: "quote",
1077
1072
  limit: 0,
1078
1073
 
1079
1074
  editorHTML: function() {
@@ -1328,9 +1323,6 @@
1328
1323
  /*
1329
1324
  Text Block
1330
1325
  */
1331
-
1332
- var tb_template =
1333
-
1334
1326
  SirTrevor.Blocks.Text = SirTrevor.Block.extend({
1335
1327
 
1336
1328
  title: "Text",
@@ -1344,7 +1336,7 @@
1344
1336
  }
1345
1337
  });
1346
1338
  var t_template = '<p>Drop tweet link here</p><div class="input text"><label>or paste URL:</label><input type="text" class="paste-block"></div>';
1347
- var tweet_template = '<div class="tweet media"><div class="img"><img src="<%= user.profile_image_url %>" class="tweet-avatar"></div><div class="bd tweet-body"><p><a href="http://twitter.com/#!/<%= user.screen_name %>">@<%= user.screen_name %></a>: <%= text %></p><time><%= created_at %></time></div></div>';
1339
+ var tweet_template = '<div class="tweet"><img src="<%= user.profile_image_url %>" class="tweet-avatar"><div class="tweet-body"><p class="tweet-user"><a href="http://twitter.com/#!/<%= user.screen_name %>" class="tweet-user">@<%= user.screen_name %></a> on Twitter</p><p class="tweet-text"><%= text %></p><time><%= created_at %></time></div></div>';
1348
1340
 
1349
1341
  SirTrevor.Blocks.Tweet = SirTrevor.Block.extend({
1350
1342
 
@@ -1407,7 +1399,7 @@
1407
1399
 
1408
1400
  // Make our AJAX call
1409
1401
  $.ajax({
1410
- url: "//api.twitter.com/1/statuses/show/" + tweetID + ".json",
1402
+ url: "http://api.twitter.com/1/statuses/show/" + tweetID + ".json",
1411
1403
  dataType: "JSONP",
1412
1404
  success: _.bind(tweetCallbackSuccess, this),
1413
1405
  error: _.bind(tweetCallbackFail, this)
@@ -1576,8 +1568,8 @@
1576
1568
 
1577
1569
  if(link && link.length > 0) {
1578
1570
 
1579
- if (!link_regex.test(link)) {
1580
- link = "http://" + link;
1571
+ if (!link_regex.test(link)) {
1572
+ link = "http://" + link;
1581
1573
  }
1582
1574
 
1583
1575
  document.execCommand(this.cmd, false, link);
@@ -1591,37 +1583,6 @@
1591
1583
  cmd: "unlink"
1592
1584
  });
1593
1585
 
1594
- var Heading1 = SirTrevor.Formatter.extend({
1595
-
1596
- title: "H1",
1597
- className: "heading h1",
1598
- cmd: "formatBlock",
1599
- param: "H1",
1600
-
1601
- toMarkdown: function(markdown) {
1602
- return markdown.replace(/<h1>([^*|_]+)<\/h1>/mg,"#$1#\n");
1603
- },
1604
-
1605
- toHTML: function(html) {
1606
- return html.replace(/(?:#)([^*|_]+)(?:#)/mg,"<h1>$1</h1>");
1607
- }
1608
- });
1609
-
1610
- var Heading2 = SirTrevor.Formatter.extend({
1611
- title: "H2",
1612
- className: "heading h2",
1613
- cmd: "formatBlock",
1614
- param: "H2",
1615
-
1616
- toMarkdown: function(markdown) {
1617
- return markdown.replace(/<h2>([^*|_]+)<\/h2>/mg,"##$1##\n");
1618
- },
1619
-
1620
- toHTML: function(html) {
1621
- return html.replace(/(?:##)([^*|_]+)(?:##)/mg,"<h2>$1</h2>");
1622
- }
1623
- });
1624
-
1625
1586
  /*
1626
1587
  Create our formatters and add a static reference to them
1627
1588
  */
@@ -1629,8 +1590,6 @@
1629
1590
  SirTrevor.Formatters.Italic = new Italic();
1630
1591
  SirTrevor.Formatters.Link = new Link();
1631
1592
  SirTrevor.Formatters.Unlink = new UnLink();
1632
- //SirTrevor.Formatters.Heading1 = new Heading1();
1633
- //SirTrevor.Formatters.Heading2 = new Heading2();
1634
1593
  /* Marker */
1635
1594
  /*
1636
1595
  SirTrevor Marker
@@ -1649,22 +1608,28 @@
1649
1608
  bound: ["onButtonClick", "show", "hide", "onDrop"],
1650
1609
 
1651
1610
  render: function() {
1652
-
1653
- var marker = $('<span>', {
1654
- 'class': this.instance.options.baseCSSClass + "-" + this.options.baseCSSClass,
1655
- html: '<p>' + this.options.addText + '</p><div class="buttons"></div>'
1611
+
1612
+ var marker = $('<div>', {
1613
+ 'class': this.instance.baseCSS(this.options.baseCSSClass),
1614
+ html: '<p>' + this.options.addText + '</p>'
1615
+ });
1616
+
1617
+ var btns_cont = $("<div>", {
1618
+ 'class': this.instance.baseCSS("buttons")
1656
1619
  });
1620
+
1621
+ marker.append(btns_cont);
1657
1622
 
1658
1623
  // Bind to the wrapper
1659
1624
  this.instance.$wrapper.append(marker);
1660
1625
 
1661
1626
  // Cache our elements for later use
1662
1627
  this.$el = marker;
1663
- this.$btns = this.$el.find('.buttons');
1628
+ this.$btns = btns_cont;
1664
1629
  this.$p = this.$el.find('p');
1665
1630
 
1666
1631
  // Add all of our buttons
1667
- var blockName, block;
1632
+ var blockName, block;
1668
1633
 
1669
1634
  for (blockName in this.instance.blockTypes) {
1670
1635
  if (SirTrevor.Blocks.hasOwnProperty(blockName)) {
@@ -1673,11 +1638,11 @@
1673
1638
  this.$btns.append(
1674
1639
  $("<a>", {
1675
1640
  "href": "#",
1676
- "class": this.options.buttonClass + " new-" + block.prototype.className,
1641
+ "class": this.instance.baseCSS(this.options.buttonClass) + " new-" + block.prototype.className,
1677
1642
  "data-type": blockName,
1678
1643
  "text": block.prototype.title,
1679
1644
  click: this.onButtonClick
1680
- })
1645
+ })
1681
1646
  );
1682
1647
  }
1683
1648
  }
@@ -1687,64 +1652,69 @@
1687
1652
  if(this.$btns.children().length === 0) this.$el.addClass('hidden');
1688
1653
 
1689
1654
  // Bind our marker to the wrapper
1690
- this.instance.$outer.bind('mouseover', this.show);
1691
- this.instance.$outer.bind('mouseout', this.hide);
1692
- this.instance.$outer.bind('dragover', this.show);
1693
- this.$el.bind('dragover',halt);
1655
+ var throttled_show = _.throttle(this.show, 0),
1656
+ throttled_hide = _.throttle(this.hide, 0);
1657
+
1658
+ this.instance.$outer.bind('mouseover', throttled_show)
1659
+ .bind('mouseout', throttled_hide)
1660
+ .bind('dragover', throttled_show);
1661
+
1662
+ this.$el.bind('dragover', halt);
1694
1663
 
1695
1664
  // Bind the drop function onto here
1696
- this.instance.$outer.dropArea();
1697
- this.instance.$outer.bind('dragleave', this.hide);
1698
- this.instance.$outer.bind('drop', this.onDrop);
1665
+ this.instance.$outer.dropArea()
1666
+ .bind('dragleave', throttled_hide)
1667
+ .bind('drop', this.onDrop);
1699
1668
 
1700
- this.$el.addClass('sir-trevor-item-ready');
1669
+ this.$el.addClass(this.instance.baseCSS("item-ready"));
1701
1670
  },
1702
1671
 
1703
- show: function(ev){
1704
-
1672
+ show: function(ev) {
1673
+ var target = $(ev.target),
1674
+ target_parent = target.parent();
1675
+
1676
+ if (target.is(this.$el) || target.is(this.$btns) || target_parent.is(this.$el) || target_parent.is(this.$btns)) {
1677
+ this.$el.addClass(this.instance.baseCSS("item-ready"));
1678
+ return;
1679
+ }
1680
+
1705
1681
  if(ev.type == 'drag' || ev.type == 'dragover') {
1682
+ this.$el.addClass('drop-zone');
1706
1683
  this.$p.text(this.options.dropText);
1707
1684
  this.$btns.hide();
1708
1685
  } else {
1686
+ this.$el.removeClass('drop-zone');
1709
1687
  this.$p.text(this.options.addText);
1710
1688
  this.$btns.show();
1711
1689
  }
1690
+
1691
+ // Check to see we're not over the formatting bar
1692
+ if (target.is(this.instance.formatBar.$el) || target_parent.is(this.instance.formatBar.$el)) {
1693
+ return this.hide();
1694
+ }
1712
1695
 
1713
1696
  var mouse_enter = (ev) ? ev.originalEvent.pageY - this.instance.$wrapper.offset().top : 0;
1714
-
1697
+
1715
1698
  // Do we have any sedit blocks?
1716
1699
  if (this.instance.blocks.length > 0) {
1717
1700
 
1718
1701
  // Find the closest block to this position
1719
- var closest_block = false,
1720
- wrapper = this.instance.$wrapper,
1721
- blockClass = "." + this.instance.options.baseCSSClass + "-block";
1722
-
1723
- var blockIterator = function(block, index) {
1724
- block = $(block);
1725
- var block_top = block.position().top - 40,
1726
- block_bottom = block.position().top + block.outerHeight(true) - 40;
1727
-
1728
- if(block_top <= mouse_enter && mouse_enter < block_bottom) {
1729
- closest_block = block;
1730
- }
1731
- };
1732
- _.each(wrapper.find(blockClass), _.bind(blockIterator, this));
1702
+ var closest_block = this.findClosestBlock(mouse_enter);
1733
1703
 
1734
1704
  // Position it
1735
1705
  if (closest_block) {
1736
1706
  this.$el.insertBefore(closest_block);
1737
1707
  } else if(mouse_enter > 0) {
1738
- this.$el.insertAfter(wrapper.find(blockClass).last());
1708
+ this.$el.insertAfter(this.instance.cachedDomBlocks.last());
1739
1709
  } else {
1740
- this.$el.insertBefore(wrapper.find(blockClass).first());
1710
+ this.$el.insertBefore(this.instance.cachedDomBlocks.first());
1741
1711
  }
1742
1712
  }
1743
- this.$el.addClass('sir-trevor-item-ready');
1713
+ this.$el.addClass(this.instance.baseCSS("item-ready"));
1744
1714
  },
1745
1715
 
1746
- hide: function(ev){
1747
- this.$el.removeClass('sir-trevor-item-ready');
1716
+ hide: function(ev){
1717
+ this.$el.removeClass(this.instance.baseCSS("item-ready"));
1748
1718
  },
1749
1719
 
1750
1720
  onDrop: function(ev){
@@ -1758,9 +1728,27 @@
1758
1728
  marker.after(block);
1759
1729
  }
1760
1730
  },
1731
+
1732
+ findClosestBlock: function(mouse_enter) {
1733
+ var closest_block = false;
1734
+
1735
+ var blockIterator = function(block, index) {
1736
+ block = $(block);
1737
+
1738
+ var block_top = block.offset().top - 40,
1739
+ block_bottom = block.offset().top + block.outerHeight(true) - 40;
1740
+
1741
+ if(block_top <= mouse_enter && mouse_enter < block_bottom) {
1742
+ closest_block = block;
1743
+ }
1744
+ };
1745
+ _.each(this.instance.cachedDomBlocks, _.bind(blockIterator, this));
1746
+
1747
+ return closest_block;
1748
+ },
1761
1749
 
1762
1750
  remove: function(){ this.$el.remove(); },
1763
-
1751
+
1764
1752
  onButtonClick: function(ev){
1765
1753
  halt(ev);
1766
1754
  var button = $(ev.target);
@@ -1774,11 +1762,9 @@
1774
1762
  },
1775
1763
 
1776
1764
  move: function(top) {
1777
- this.$el.css({
1778
- top: top
1779
- });
1780
- this.$el.show();
1781
- this.$el.addClass('sir-trevor-item-ready');
1765
+ this.$el.css({ top: top })
1766
+ .show()
1767
+ .addClass(this.instance.baseCSS("item-ready"));
1782
1768
  }
1783
1769
  });
1784
1770
 
@@ -1796,7 +1782,7 @@
1796
1782
  var FormatBar = SirTrevor.FormatBar = function(options, editorInstance) {
1797
1783
  this.instance = editorInstance;
1798
1784
  this.options = _.extend({}, SirTrevor.DEFAULTS.formatBar, options || {});
1799
- this.className = this.instance.options.baseCSSClass + "-" + this.options.baseCSSClass;
1785
+ this.className = this.instance.baseCSS(this.options.baseCSSClass);
1800
1786
  this.clicked = false;
1801
1787
  this._bindFunctions();
1802
1788
  };
@@ -1806,7 +1792,6 @@
1806
1792
  bound: ["onFormatButtonClick"],
1807
1793
 
1808
1794
  render: function(){
1809
-
1810
1795
  var bar = $("<div>", {
1811
1796
  "class": this.className
1812
1797
  });
@@ -1821,7 +1806,7 @@
1821
1806
  if (SirTrevor.Formatters.hasOwnProperty(formatName)) {
1822
1807
  format = SirTrevor.Formatters[formatName];
1823
1808
  $("<button>", {
1824
- 'class': 'format-button ' + format.className,
1809
+ 'class': this.instance.baseCSS("format-button"),
1825
1810
  'text': format.title,
1826
1811
  'data-type': formatName,
1827
1812
  'data-cmd': format.cmd,
@@ -1830,45 +1815,56 @@
1830
1815
  }
1831
1816
  }
1832
1817
 
1818
+ var throttled_scroll = _.throttle(_.bind(this.handleDocumentScroll, this), 150);
1819
+ $(document).bind('scroll', throttled_scroll);
1820
+
1833
1821
  if(this.$el.find('button').length === 0) this.$el.addClass('hidden');
1834
-
1835
- this.hide();
1836
- this.$el.bind('mouseout', _.bind(function(ev){ halt(ev); this.clicked = false; }, this));
1837
- this.$el.bind('mouseover', halt);
1822
+ this.show();
1838
1823
  },
1839
-
1840
- /* Convienience methods */
1841
- show: function(relativeEl){
1842
- this.$el.css({
1843
- top: relativeEl.position().top
1844
- });
1845
- this.$el.addClass('sir-trevor-item-ready');
1846
- this.$el.show();
1824
+
1825
+ handleDocumentScroll: function() {
1826
+ var instance_height = this.instance.$outer.height(),
1827
+ instance_offset = this.instance.$outer.offset().top,
1828
+ viewport_top = $(document).scrollTop();
1829
+
1830
+ if (this.$el.hasClass('fixed')) {
1831
+ instance_offset = this.$el.offset().top;
1832
+ }
1833
+
1834
+ if ((viewport_top > 5) && viewport_top >= instance_offset) {
1835
+ this.$el.addClass('fixed');
1836
+ this.instance.$wrapper.css({ 'padding-top': '104px' });
1837
+ } else {
1838
+ this.$el.removeClass('fixed');
1839
+ this.instance.$wrapper.css({ 'padding-top': '16px' });
1840
+ }
1847
1841
  },
1848
1842
 
1849
- hide: function(){
1850
- this.clicked = false;
1851
- this.$el.removeClass('sir-trevor-item-ready');
1852
- this.$el.hide();
1843
+ hide: function() {
1844
+ this.$el.removeClass(this.instance.baseCSS('item-ready'));
1853
1845
  },
1854
-
1846
+
1847
+ show: function() {
1848
+ this.$el.addClass(this.instance.baseCSS('item-ready'));
1849
+ },
1850
+
1855
1851
  remove: function(){ this.$el.remove(); },
1856
1852
 
1857
1853
  onFormatButtonClick: function(ev){
1858
1854
  halt(ev);
1859
- this.clicked = true;
1855
+
1860
1856
  var btn = $(ev.target),
1861
1857
  format = SirTrevor.Formatters[btn.attr('data-type')];
1862
1858
 
1863
- // Do we have a click function defined on this formatter?
1859
+ // Do we have a click function defined on this formatter?
1864
1860
  if(!_.isUndefined(format.onClick) && _.isFunction(format.onClick)) {
1865
1861
  format.onClick(); // Delegate
1866
1862
  } else {
1867
1863
  // Call default
1868
1864
  document.execCommand(btn.attr('data-cmd'), false, format.param);
1869
- }
1865
+ }
1870
1866
  // Make sure we still show the bar
1871
- this.$el.addClass('sir-trevor-item-ready');
1867
+ this.show();
1872
1868
  }
1873
1869
 
1874
1870
  });
@@ -1889,6 +1885,7 @@
1889
1885
  this.blockCounts = {}; // Cached block type counts
1890
1886
  this.blocks = []; // Block references
1891
1887
  this.errors = [];
1888
+ this.cachedDomBlocks = [];
1892
1889
  this.options = _.extend({}, SirTrevor.DEFAULTS, options || {});
1893
1890
  this.ID = _.uniqueId(this.options.baseCSSClass + "-");
1894
1891
 
@@ -1948,7 +1945,7 @@
1948
1945
 
1949
1946
  if(!_.isUndefined(this.onEditorRender)) {
1950
1947
  this.onEditorRender();
1951
- }
1948
+ }
1952
1949
  },
1953
1950
 
1954
1951
  store: function(){
@@ -1978,7 +1975,7 @@
1978
1975
  return false;
1979
1976
  }
1980
1977
 
1981
- var block = new blockType(this, data || {});
1978
+ var block = new blockType(this, data || {});
1982
1979
 
1983
1980
  if (_.isUndefined(this.blockCounts[type])) {
1984
1981
  this.blockCounts[type] = 0;
@@ -1998,18 +1995,19 @@
1998
1995
  this.marker.$el.find('[data-type="' + type + '"]')
1999
1996
  .addClass('inactive')
2000
1997
  .attr('title','You have reached the limit for this type of block');
2001
- }
1998
+ }
2002
1999
 
2003
2000
  SirTrevor.publish("editor/block/createBlock");
2004
2001
 
2005
2002
  SirTrevor.log("Block created of type " + type);
2003
+ this.cachedDomBlocks = this.$wrapper.find('.' + this.baseCSS("block"));
2006
2004
  } else {
2007
2005
  SirTrevor.log("Block type not available " + type);
2008
2006
  }
2009
2007
  },
2010
2008
 
2011
2009
  removeBlock: function(block) {
2012
- // Blocks exist purely on the dom.
2010
+ // Blocks exist purely on the dom.
2013
2011
  // Remove the block and decrement the blockCount
2014
2012
  block.remove();
2015
2013
  this.blockCounts[block.type] = this.blockCounts[block.type] - 1;
@@ -2017,9 +2015,9 @@
2017
2015
  // Remove the block from our store
2018
2016
  this.blocks = _.reject(this.blocks, function(item){ return (item.blockID == block.blockID); });
2019
2017
  if(_.isUndefined(this.blocks)) this.blocks = [];
2020
- this.formatBar.hide();
2021
2018
 
2022
2019
  SirTrevor.publish("editor/block/removeBlock");
2020
+ this.cachedDomBlocks = this.$wrapper.find('.' + this.baseCSS("block"));
2023
2021
 
2024
2022
  // Remove our inactive class if it's no longer relevant
2025
2023
  if(this._getBlockTypeLimit(block.type) > this.blockCounts[block.type]) {
@@ -2066,8 +2064,7 @@
2066
2064
  SirTrevor.log("Handling form submission for Editor " + this.ID);
2067
2065
 
2068
2066
  var blockLength, block, result, errors = 0;
2069
-
2070
- this.formatBar.hide();
2067
+
2071
2068
  this.removeErrors();
2072
2069
  // Reset our store
2073
2070
  this.store("reset", this);
@@ -2100,7 +2097,7 @@
2100
2097
  errors++;
2101
2098
 
2102
2099
  } else {
2103
- // We need to also validate that we have some data of this type too.
2100
+ // We need to also validate that we have some data of this type too.
2104
2101
  // This is ugly, but necessary for proper validation on blocks that don't have required fields.
2105
2102
  var blocks = _.filter(this.blocks, function(b){ return (b.type == type && !_.isEmpty(b.getData())); });
2106
2103
 
@@ -2128,7 +2125,7 @@
2128
2125
 
2129
2126
  if (_.isUndefined(this.$errors)) {
2130
2127
  this.$errors = $("<div>", {
2131
- class: this.options.baseCSSClass + "-errors",
2128
+ 'class': this.baseCSS("errors"),
2132
2129
  html: "<p>You have the following errors: </p><ul></ul>"
2133
2130
  });
2134
2131
  this.$outer.prepend(this.$errors);
@@ -2138,7 +2135,7 @@
2138
2135
 
2139
2136
  _.each(this.errors, _.bind(function(error) {
2140
2137
  list.append($("<li>", {
2141
- class: "error-msg",
2138
+ 'class': this.baseCSS("error-msg"),
2142
2139
  html: error.text
2143
2140
  }));
2144
2141
  }, this));
@@ -2182,7 +2179,6 @@
2182
2179
  },
2183
2180
 
2184
2181
  _ensureAndSetElements: function() {
2185
-
2186
2182
  if(_.isUndefined(this.options.el) || _.isEmpty(this.options.el)) {
2187
2183
  SirTrevor.log("You must provide an el");
2188
2184
  return false;
@@ -2192,19 +2188,15 @@
2192
2188
  this.el = this.options.el[0];
2193
2189
  this.$form = this.$el.parents('form');
2194
2190
 
2191
+ var blockCSSClass = this.baseCSS("blocks");
2192
+
2195
2193
  // Wrap our element in lots of containers *eww*
2196
- this.$el.wrap($('<div>', {
2197
- id: this.ID,
2198
- 'class': this.options.baseCSSClass,
2199
- dropzone: 'copy link move'
2200
- })
2201
- )
2202
- .wrap($("<div>", {
2203
- class: this.options.baseCSSClass + "-blocks"
2204
- }));
2194
+ this.$el.wrap($('<div>', { id: this.ID, 'class': this.options.baseCSSClass, dropzone: 'copy link move' }))
2195
+ .wrap($("<div>", { 'class': blockCSSClass }));
2205
2196
 
2206
- this.$outer = this.$form.find('#' + this.ID);
2207
- this.$wrapper = this.$outer.find("." + this.options.baseCSSClass + "-blocks");
2197
+ this.$outer = this.$form.find('#' + this.ID);
2198
+ this.$wrapper = this.$outer.find("." + blockCSSClass);
2199
+
2208
2200
  return true;
2209
2201
  },
2210
2202
 
@@ -2215,7 +2207,7 @@
2215
2207
  */
2216
2208
  _setBlocksAndFormatters: function() {
2217
2209
  this.blockTypes = flattern((_.isUndefined(this.options.blockTypes)) ? SirTrevor.Blocks : this.options.blockTypes);
2218
- this.formatters = flattern((_.isUndefined(this.options.formatters)) ? SirTrevor.Formatters : this.options.formatters);
2210
+ this.formatters = flattern((_.isUndefined(this.options.formatters)) ? SirTrevor.Formatters : this.options.formatters);
2219
2211
  },
2220
2212
 
2221
2213
  /* Get our required blocks (if any) */
@@ -2256,7 +2248,7 @@
2256
2248
  .replace(/(?:<div>)(?:<br>)?([^<>]+)(?:<br>)?(?:<\/div>)/g,"$1\n\n") // ^ (handle content inside divs)
2257
2249
  .replace(/<\/p>/g,"\n\n\n\n") // P tags as line breaks
2258
2250
  .replace(/<(.)?br(.)?>/g,"\n\n") // Convert normal line breaks
2259
- .replace(/&nbsp;/g," ") // Strip white-space entities
2251
+ .replace(/&nbsp;/g," ") // Strip white-space entities
2260
2252
  .replace(/&lt;/g,"<").replace(/&gt;/g,">"); // Encoding
2261
2253
 
2262
2254
 
@@ -2271,9 +2263,8 @@
2271
2263
  }
2272
2264
 
2273
2265
  // Strip remaining HTML
2274
- markdown = markdown.replace(/<\/?[^>]+(>|$)/g, "");
2266
+ markdown = markdown.replace(/<\/?[^>]+(>|$)/g, "");
2275
2267
 
2276
-
2277
2268
  return markdown;
2278
2269
  },
2279
2270
 
@@ -2313,7 +2304,11 @@
2313
2304
  .replace(/(?:_)([^*|_(http)]+)(?:_)/g,"<i>$1</i>") // Italic, avoid italicizing two links with underscores next to each other
2314
2305
  .replace(/(?:\*\*)([^*|_]+)(?:\*\*)/g,"<b>$1</b>"); // Bold
2315
2306
 
2316
- return html;
2307
+ return html;
2308
+ },
2309
+
2310
+ baseCSS: function(additional) {
2311
+ return this.options.baseCSSClass + "-" + additional;
2317
2312
  }
2318
2313
  });
2319
2314
 
@@ -2346,7 +2341,7 @@
2346
2341
  if(errors > 0) {
2347
2342
  SirTrevor.publish("onError");
2348
2343
  ev.preventDefault();
2349
- }
2344
+ }
2350
2345
  };
2351
2346
 
2352
2347
  SirTrevor.runOnAllInstances = function(method) {