bootsy-rails3 2.0.5.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.
Files changed (49) hide show
  1. checksums.yaml +15 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +155 -0
  4. data/Rakefile +38 -0
  5. data/app/assets/images/bootsy/gallery-loader.gif +0 -0
  6. data/app/assets/images/bootsy/upload-loader.gif +0 -0
  7. data/app/assets/javascripts/bootsy.js +8 -0
  8. data/app/assets/javascripts/bootsy/bootstrap-wysihtml5.js +530 -0
  9. data/app/assets/javascripts/bootsy/bootstrap.file-input.js +122 -0
  10. data/app/assets/javascripts/bootsy/bootsy.js +305 -0
  11. data/app/assets/javascripts/bootsy/editor_options.js +80 -0
  12. data/app/assets/javascripts/bootsy/init.js +31 -0
  13. data/app/assets/javascripts/bootsy/locales/bootstrap-wysihtml5.pt-BR.js +50 -0
  14. data/app/assets/javascripts/bootsy/locales/bootsy.pt-BR.js +9 -0
  15. data/app/assets/javascripts/bootsy/translations.js +8 -0
  16. data/app/assets/javascripts/bootsy/wysihtml5.js +9564 -0
  17. data/app/assets/stylesheets/bootsy.css +3 -0
  18. data/app/assets/stylesheets/bootsy/bootstrap-submenu.css +47 -0
  19. data/app/assets/stylesheets/bootsy/bootstrap-wysihtml5.css +102 -0
  20. data/app/assets/stylesheets/bootsy/bootsy.css +161 -0
  21. data/app/controllers/bootsy/application_controller.rb +7 -0
  22. data/app/controllers/bootsy/images_controller.rb +80 -0
  23. data/app/helpers/bootsy/application_helper.rb +11 -0
  24. data/app/uploaders/bootsy/image_uploader.rb +35 -0
  25. data/app/views/bootsy/images/_image.html.erb +43 -0
  26. data/app/views/bootsy/images/_modal.html.erb +28 -0
  27. data/app/views/bootsy/images/_new.html.erb +8 -0
  28. data/config/cucumber.yml +8 -0
  29. data/config/locales/en.yml +27 -0
  30. data/config/locales/pt-BR.yml +27 -0
  31. data/config/routes.rb +11 -0
  32. data/db/migrate/20120624171333_create_bootsy_images.rb +9 -0
  33. data/db/migrate/20120628124845_create_bootsy_image_galleries.rb +8 -0
  34. data/lib/bootsy-rails3.rb +66 -0
  35. data/lib/bootsy/activerecord/image.rb +11 -0
  36. data/lib/bootsy/activerecord/image_gallery.rb +8 -0
  37. data/lib/bootsy/container.rb +31 -0
  38. data/lib/bootsy/core_ext.rb +2 -0
  39. data/lib/bootsy/engine.rb +31 -0
  40. data/lib/bootsy/form_builder.rb +7 -0
  41. data/lib/bootsy/form_helper.rb +68 -0
  42. data/lib/bootsy/simple_form/bootsy_input.rb +11 -0
  43. data/lib/bootsy/version.rb +3 -0
  44. data/lib/generators/bootsy/USAGE +12 -0
  45. data/lib/generators/bootsy/install_generator.rb +50 -0
  46. data/lib/generators/bootsy/templates/bootsy.rb +64 -0
  47. data/lib/tasks/bootsy_tasks.rake +4 -0
  48. data/lib/tasks/cucumber.rake +65 -0
  49. metadata +137 -0
@@ -0,0 +1,122 @@
1
+ /*
2
+ Bootstrap - File Input
3
+ ======================
4
+
5
+ This is meant to convert all file input tags into a set of elements that displays consistently in all browsers.
6
+
7
+ Converts all
8
+ <input type="file">
9
+ into Bootstrap buttons
10
+ <a class="btn">Browse</a>
11
+
12
+ */
13
+ $(function() {
14
+
15
+ $.fn.bootstrapFileInput = function() {
16
+
17
+ this.each(function(i,elem){
18
+
19
+ var $elem = $(elem);
20
+
21
+ // Maybe some fields don't need to be standardized.
22
+ if (typeof $elem.attr('data-bfi-disabled') != 'undefined') {
23
+ return;
24
+ }
25
+
26
+ // Set the word to be displayed on the button
27
+ var buttonWord = 'Browse';
28
+
29
+ if (typeof $elem.attr('title') != 'undefined') {
30
+ buttonWord = $elem.attr('title');
31
+ }
32
+
33
+ // Start by getting the HTML of the input element.
34
+ // Thanks for the tip http://stackoverflow.com/a/1299069
35
+ var input = $('<div>').append( $elem.eq(0).clone() ).html();
36
+ var className = '';
37
+
38
+ if (!!$elem.attr('class')) {
39
+ className = ' ' + $elem.attr('class');
40
+ }
41
+
42
+ // Now we're going to replace that input field with a Bootstrap button.
43
+ // The input will actually still be there, it will just be float above and transparent (done with the CSS).
44
+ $elem.replaceWith('<a class="file-input-wrapper btn btn-default ' + className + '">'+buttonWord+input+'</a>');
45
+ })
46
+
47
+ // After we have found all of the file inputs let's apply a listener for tracking the mouse movement.
48
+ // This is important because the in order to give the illusion that this is a button in FF we actually need to move the button from the file input under the cursor. Ugh.
49
+ .promise().done( function(){
50
+
51
+ // As the cursor moves over our new Bootstrap button we need to adjust the position of the invisible file input Browse button to be under the cursor.
52
+ // This gives us the pointer cursor that FF denies us
53
+ $('.file-input-wrapper').mousemove(function(cursor) {
54
+
55
+ var input, wrapper,
56
+ wrapperX, wrapperY,
57
+ inputWidth, inputHeight,
58
+ cursorX, cursorY;
59
+
60
+ // This wrapper element (the button surround this file input)
61
+ wrapper = $(this);
62
+ // The invisible file input element
63
+ input = wrapper.find("input");
64
+ // The left-most position of the wrapper
65
+ wrapperX = wrapper.offset().left;
66
+ // The top-most position of the wrapper
67
+ wrapperY = wrapper.offset().top;
68
+ // The with of the browsers input field
69
+ inputWidth= input.width();
70
+ // The height of the browsers input field
71
+ inputHeight= input.height();
72
+ //The position of the cursor in the wrapper
73
+ cursorX = cursor.pageX;
74
+ cursorY = cursor.pageY;
75
+
76
+ //The positions we are to move the invisible file input
77
+ // The 20 at the end is an arbitrary number of pixels that we can shift the input such that cursor is not pointing at the end of the Browse button but somewhere nearer the middle
78
+ moveInputX = cursorX - wrapperX - inputWidth + 20;
79
+ // Slides the invisible input Browse button to be positioned middle under the cursor
80
+ moveInputY = cursorY- wrapperY - (inputHeight/2);
81
+
82
+ // Apply the positioning styles to actually move the invisible file input
83
+ input.css({
84
+ left:moveInputX,
85
+ top:moveInputY
86
+ });
87
+ });
88
+
89
+ $('.file-input-wrapper input[type=file]').change(function(){
90
+
91
+ var fileName;
92
+ fileName = $(this).val();
93
+
94
+ // Remove any previous file names
95
+ $(this).parent().next('.file-input-name').remove();
96
+ if (!!$(this).prop('files') && $(this).prop('files').length > 1) {
97
+ fileName = $(this)[0].files.length+' files';
98
+ //$(this).parent().after('<span class="file-input-name">'+$(this)[0].files.length+' files</span>');
99
+ }
100
+ else {
101
+ // var fakepath = 'C:\\fakepath\\';
102
+ // fileName = $(this).val().replace('C:\\fakepath\\','');
103
+ fileName = fileName.substring(fileName.lastIndexOf('\\')+1,fileName.length);
104
+ }
105
+
106
+ $(this).parent().after('<span class="file-input-name">'+fileName+'</span>');
107
+ });
108
+
109
+ });
110
+
111
+ };
112
+
113
+ // Add the styles before the first stylesheet
114
+ // This ensures they can be easily overridden with developer styles
115
+ var cssHtml = '<style>'+
116
+ '.file-input-wrapper { overflow: hidden; position: relative; cursor: pointer; z-index: 1; }'+
117
+ '.file-input-wrapper input[type=file], .file-input-wrapper input[type=file]:focus, .file-input-wrapper input[type=file]:hover { position: absolute; top: 0; left: 0; cursor: pointer; opacity: 0; filter: alpha(opacity=0); z-index: 99; outline: 0; }'+
118
+ '.file-input-name { margin-left: 8px; }'+
119
+ '</style>';
120
+ $('link[rel=stylesheet]').eq(0).before(cssHtml);
121
+
122
+ });
@@ -0,0 +1,305 @@
1
+ /* global Bootsy */
2
+
3
+ window.Bootsy = window.Bootsy || {};
4
+
5
+ Bootsy.Area = function($el) {
6
+ this.$el = $el;
7
+ this.modal = $el.siblings('.bootsy-modal');
8
+ this.locale = $el.data('bootsy-locale') || $('html').attr('lang') || 'en';
9
+ this.unsavedChanges = false;
10
+ this.initialized = false;
11
+
12
+ this.options = {
13
+ locale: this.locale,
14
+ alertUnsavedChanges: $el.data('bootsy-alert-unsaved'),
15
+ image: $el.data('bootsy-image'),
16
+ uploader: $el.data('bootsy-uploader'),
17
+ };
18
+
19
+ // Set default values to editor options
20
+ if ($el.data('bootsy-font-styles') === false) {
21
+ this.options['font-styles'] = false;
22
+ }
23
+
24
+ if ($el.data('bootsy-emphasis') === false) {
25
+ this.options.emphasis = false;
26
+ }
27
+
28
+ if ($el.data('bootsy-lists') === false) {
29
+ this.options.lists = false;
30
+ }
31
+
32
+ if ($el.data('bootsy-html') === true) {
33
+ this.options.html = true;
34
+ }
35
+
36
+ if ($el.data('bootsy-link') === false) {
37
+ this.options.link = false;
38
+ }
39
+
40
+ if ($el.data('bootsy-color') === false) {
41
+ this.options.color = false;
42
+ }
43
+
44
+ // Delegate find to the modal
45
+ this.find = this.modal.find.bind(this.modal);
46
+ };
47
+
48
+
49
+ // Gallery loading
50
+ Bootsy.Area.prototype.showGalleryLoadingAnimation = function() {
51
+ this.find('.bootsy-gallery-loader').fadeIn(200);
52
+ };
53
+
54
+ Bootsy.Area.prototype.hideGalleryLoadingAnimation = function() {
55
+ this.find('.bootsy-gallery-loader').fadeOut(200);
56
+ };
57
+
58
+
59
+ // Upload loading animation
60
+ Bootsy.Area.prototype.showUploadLoadingAnimation = function() {
61
+ this.find('.bootsy-upload-loader').fadeIn(200);
62
+ };
63
+
64
+ Bootsy.Area.prototype.hideUploadLoadingAnimation = function() {
65
+ this.find('.bootsy-upload-loader').fadeOut(200);
66
+ };
67
+
68
+ // Alert for empty gallery
69
+ Bootsy.Area.prototype.showEmptyAlert = function() {
70
+ this.find('.bootsy-empty-alert').fadeIn(200);
71
+ };
72
+
73
+ Bootsy.Area.prototype.hideEmptyAlert = function() {
74
+ this.find('.bootsy-empty-alert').fadeOut(200);
75
+ };
76
+
77
+ // Manual refresh button
78
+ Bootsy.Area.prototype.showRefreshButton = function() {
79
+ this.find('.refresh-btn').fadeIn(200);
80
+ };
81
+
82
+ Bootsy.Area.prototype.hideRefreshButton = function() {
83
+ this.find('.refresh-btn').fadeOut(200);
84
+ };
85
+
86
+
87
+ // Set upload form
88
+ Bootsy.Area.prototype.setUploadForm = function(html) {
89
+ this.find('.modal-footer').html(html);
90
+
91
+ this.hideUploadLoadingAnimation();
92
+
93
+ this.find('.bootsy-upload-form input[type="file"]').bootstrapFileInput();
94
+
95
+ this.uploadInput = this.find('.bootsy-upload-form input[type="file"]');
96
+
97
+ this.uploadInput.change(function() {
98
+ this.showUploadLoadingAnimation();
99
+
100
+ this.uploadInput.closest('form').submit();
101
+ }.bind(this));
102
+ };
103
+
104
+
105
+ // Set image gallery
106
+ Bootsy.Area.prototype.setImageGallery = function() {
107
+ this.showGalleryLoadingAnimation();
108
+
109
+ $.ajax({
110
+ url: '/bootsy/images',
111
+ type: 'GET',
112
+ cache: false,
113
+ data: {
114
+ image_gallery_id: this.modal.data('gallery-id')
115
+ },
116
+ dataType: 'json',
117
+ success: function(data) {
118
+ this.hideGalleryLoadingAnimation();
119
+
120
+ $.each(data.images, function(index, value) {
121
+ this.addImage(value);
122
+ }.bind(this));
123
+
124
+ if (data.images.length === 0) {
125
+ this.showEmptyAlert();
126
+ }
127
+
128
+ this.setUploadForm(data.form);
129
+
130
+ this.modal.data('gallery-loaded', true);
131
+ }.bind(this),
132
+ error: function() {
133
+ alert(Bootsy.translations[this.locale].error);
134
+
135
+ this.showRefreshButton();
136
+ }.bind(this)
137
+ });
138
+ };
139
+
140
+ // Delete image
141
+ Bootsy.Area.prototype.deleteImage = function (id) {
142
+ var image = this.find('.bootsy-image[data-id="' + id + '"]');
143
+
144
+ this.hideGalleryLoadingAnimation();
145
+
146
+ image.hide(200, function() {
147
+ image.remove();
148
+
149
+ // Put message back if 0 images
150
+ if (this.find('.bootsy-image').length === 0 ) {
151
+ this.showEmptyAlert();
152
+ }
153
+ }.bind(this));
154
+ };
155
+
156
+
157
+ // Add image to gallery
158
+ Bootsy.Area.prototype.addImage = function(html) {
159
+ this.hideEmptyAlert();
160
+
161
+ $(html).hide().appendTo(this.find('.bootsy-gallery')).fadeIn(200);
162
+ };
163
+
164
+ // Insert image in the text
165
+ Bootsy.Area.prototype.insertImage = function(image) {
166
+ this.modal.modal('hide');
167
+
168
+ this.editor.currentView.element.focus();
169
+
170
+ if (this.caretBookmark) {
171
+ this.editor.composer.selection.setBookmark(this.caretBookmark);
172
+ this.caretBookmark = null;
173
+ }
174
+
175
+ this.editor.composer.commands.exec('insertImage', image);
176
+ };
177
+
178
+ // Open Bootsy modal
179
+ Bootsy.Area.prototype.openImagesModal = function(editor) {
180
+ editor.currentView.element.focus(false);
181
+
182
+ this.caretBookmark = editor.composer.selection.getBookmark();
183
+
184
+ this.modal.modal('show');
185
+ };
186
+
187
+ // Alert for unsaved changes
188
+ Bootsy.Area.prototype.unsavedChangesAlert = function () {
189
+ if (this.unsavedChanges) {
190
+ return Bootsy.translations[this.locale].alertUnsaved;
191
+ }
192
+ };
193
+
194
+ // Clear everything
195
+ Bootsy.Area.prototype.clear = function () {
196
+ this.editor.clear();
197
+ this.setImageGalleryId('');
198
+ this.modal.data('gallery-loaded', false);
199
+ };
200
+
201
+ // Set the image gallery id
202
+ Bootsy.Area.prototype.setImageGalleryId = function(id) {
203
+ this.modal.data('gallery-id', id);
204
+
205
+ this.$el.siblings('.bootsy_image_gallery_id').val(id);
206
+ };
207
+
208
+
209
+ // Init components
210
+ Bootsy.Area.prototype.init = function() {
211
+ var insert;
212
+
213
+ if (!this.initialized) {
214
+ insert = this.insertImage.bind(this);
215
+
216
+ if ((this.options.image === true) && (this.options.uploader === true)) {
217
+ this.modal.on('click', '.bootsy-image .insert', function(e) {
218
+ var img, imageObject;
219
+ var imagePrefix = '/' + $(this).attr('data-image-size') + '_';
220
+
221
+ e.preventDefault();
222
+
223
+ if ($(this).data('image-size') === 'original') {
224
+ imagePrefix = '/';
225
+ }
226
+
227
+ img = $(this).parents('.bootsy-image').find('img');
228
+
229
+ imageObject = {
230
+ src: img.attr('src').replace('/thumb_', imagePrefix),
231
+ alt: img.attr('alt').replace('Thumb_', '')
232
+ };
233
+
234
+ imageObject.align = $(this).data('position');
235
+
236
+ insert(imageObject);
237
+ });
238
+
239
+ // Let's use the uploader, not the static image modal
240
+ this.options.image = false;
241
+ this.options.customCommand = true;
242
+ this.options.customCommandCallback = this.openImagesModal.bind(this);
243
+ this.options.customTemplates = {
244
+ customCommand: function(locale, options) {
245
+ var size = (options && options.size) ? ' btn-'+options.size : '';
246
+
247
+ return '<li>' +
248
+ '<a class="btn btn-default ' + size + '" data-wysihtml5-command="customCommand" title="' + locale.image.insert + '" tabindex="-1">' +
249
+ '<span class="glyphicon glyphicon-picture"></span>' +
250
+ '</a>' +
251
+ '</li>';
252
+ }
253
+ };
254
+
255
+ // In order to avoid form nesting
256
+ this.modal.parents('form').after(this.modal);
257
+
258
+ this.modal.on('click', 'a[href="#refresh-gallery"]', this.setImageGallery.bind(this));
259
+
260
+ this.modal.on('ajax:before', '.destroy-btn', this.showGalleryLoadingAnimation.bind(this));
261
+
262
+ this.modal.on('ajax:success', '.destroy-btn', function(evt, data) {
263
+ this.deleteImage(data.id);
264
+ }.bind(this));
265
+
266
+ this.modal.on('ajax:success', '.bootsy-upload-form', function(evt, data) {
267
+ this.setImageGalleryId(data.gallery_id);
268
+ this.addImage(data.image);
269
+ this.setUploadForm(data.form);
270
+ }.bind(this));
271
+ }
272
+
273
+ this.editor = this.$el.wysihtml5($.extend(Bootsy.options, this.options)).data('wysihtml5').editor;
274
+
275
+ // Mechanism for unsaved changes alert
276
+ if (this.options.alertUnsavedChanges !== false) {
277
+ window.onbeforeunload = this.unsavedChangesAlert.bind(this);
278
+ }
279
+
280
+ this.$el.closest('form').submit(function() {
281
+ this.unsavedChanges = false;
282
+
283
+ return true;
284
+ }.bind(this));
285
+
286
+ this.editor.on('change', function() {
287
+ this.unsavedChanges = true;
288
+ }.bind(this));
289
+
290
+ this.modal.modal({ show: false });
291
+
292
+ this.modal.on('shown.bs.modal', function() {
293
+ if (this.modal.data('gallery-loaded') !== true) {
294
+ this.setImageGallery();
295
+ }
296
+ }.bind(this));
297
+
298
+ this.modal.on('hide.bs.modal', this.editor.currentView.element.focus);
299
+
300
+ this.hideRefreshButton();
301
+ this.hideEmptyAlert();
302
+
303
+ this.initialized = true;
304
+ }
305
+ };
@@ -0,0 +1,80 @@
1
+ window.Bootsy = window.Bootsy || {};
2
+
3
+ var pageStylesheets = [];
4
+ $('link[rel="stylesheet"]').each(function () {
5
+ pageStylesheets.push($(this).attr('href'));
6
+ });
7
+
8
+ window.Bootsy.options = {
9
+ parserRules: {
10
+ classes: {
11
+ "wysiwyg-color-silver" : 1,
12
+ "wysiwyg-color-gray" : 1,
13
+ "wysiwyg-color-white" : 1,
14
+ "wysiwyg-color-maroon" : 1,
15
+ "wysiwyg-color-red" : 1,
16
+ "wysiwyg-color-purple" : 1,
17
+ "wysiwyg-color-fuchsia" : 1,
18
+ "wysiwyg-color-green" : 1,
19
+ "wysiwyg-color-lime" : 1,
20
+ "wysiwyg-color-olive" : 1,
21
+ "wysiwyg-color-yellow" : 1,
22
+ "wysiwyg-color-navy" : 1,
23
+ "wysiwyg-color-blue" : 1,
24
+ "wysiwyg-color-teal" : 1,
25
+ "wysiwyg-color-aqua" : 1,
26
+ "wysiwyg-color-orange" : 1,
27
+ "wysiwyg-float-left": 1,
28
+ "wysiwyg-float-right": 1
29
+ },
30
+
31
+ tags: {
32
+ "b": {},
33
+ "i": {},
34
+ "br": {},
35
+ "ol": {},
36
+ "ul": {},
37
+ "li": {},
38
+ "h1": {},
39
+ "h2": {},
40
+ "h3": {},
41
+ "small": {},
42
+ "p": {},
43
+ "blockquote": {},
44
+ "u": 1,
45
+ "cite": {
46
+ "check_attributes": {
47
+ "title": "alt"
48
+ }
49
+ },
50
+ "img": {
51
+ "check_attributes": {
52
+ "width": "numbers",
53
+ "alt": "alt",
54
+ "src": "src",
55
+ "height": "numbers"
56
+ },
57
+ "add_class": {
58
+ "align": "align_img"
59
+ }
60
+ },
61
+
62
+ "a": {
63
+ set_attributes: {
64
+ target: "_blank",
65
+ rel: "nofollow"
66
+ },
67
+ check_attributes: {
68
+ href: "url" // important to avoid XSS
69
+ }
70
+ },
71
+ "span": 1,
72
+ "div": 1,
73
+ // to allow save and edit files with code tag hacks
74
+ "code": 1,
75
+ "pre": 1
76
+ }
77
+ },
78
+ color: true,
79
+ stylesheets: pageStylesheets
80
+ };