sir-trevor-rails 0.1.3 → 0.2.1

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