biovision-base 0.34.190331.1 → 0.36.190526.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/biovision/base/biovision.js +52 -7
  3. data/app/assets/javascripts/biovision/base/components/carousel.js +127 -17
  4. data/app/assets/javascripts/biovision/base/components/video-stretcher.js +51 -0
  5. data/app/assets/stylesheets/biovision/base/biovision.scss +18 -2
  6. data/app/controllers/admin/settings_controller.rb +7 -12
  7. data/app/controllers/admin_controller.rb +4 -0
  8. data/app/controllers/concerns/authentication.rb +3 -0
  9. data/app/controllers/editable_pages_controller.rb +2 -0
  10. data/app/controllers/my/profiles_controller.rb +1 -1
  11. data/app/helpers/biovision_users_helper.rb +1 -1
  12. data/app/jobs/editable_page_body_parser_job.rb +16 -0
  13. data/app/models/biovision_component.rb +18 -11
  14. data/app/models/editable_page.rb +21 -16
  15. data/app/models/foreign_site.rb +9 -3
  16. data/app/services/biovision/components/base_component.rb +17 -40
  17. data/app/services/canonizer.rb +21 -14
  18. data/app/services/oembed_receiver.rb +92 -0
  19. data/app/services/user_bouncer.rb +3 -4
  20. data/app/uploaders/simple_file_uploader.rb +15 -0
  21. data/app/views/admin/editable_pages/entity/_in_list.html.erb +1 -1
  22. data/app/views/admin/editable_pages/show.html.erb +2 -2
  23. data/app/views/admin/index/_biovision_base.html.erb +2 -2
  24. data/app/views/admin/settings/component/_new_parameter.html.erb +5 -13
  25. data/app/views/admin/settings/component/_parameters.html.erb +6 -6
  26. data/app/views/admin/settings/show.html.erb +1 -1
  27. data/app/views/admin/users/entity/_in_list.html.erb +3 -0
  28. data/app/views/admin/users/show.html.erb +1 -1
  29. data/app/views/editable_pages/_editable_page.html.erb +1 -1
  30. data/app/views/editable_pages/_form.html.erb +2 -2
  31. data/app/views/editable_pages/entity/_content.html.erb +1 -1
  32. data/app/views/editable_pages/entity/_metadata.html.erb +12 -10
  33. data/app/views/index/index/_default_dashboard.html.erb +3 -1
  34. data/app/views/my/index/index/_dashboard.html.erb +16 -0
  35. data/app/views/shared/_breadcrumbs.html.erb +4 -2
  36. data/app/views/shared/_track.html.erb +8 -8
  37. data/app/views/shared/admin/_breadcrumbs.html.erb +4 -2
  38. data/app/views/shared/editable_pages/_body.html.erb +2 -2
  39. data/app/views/shared/entity/_metadata.html.erb +13 -0
  40. data/app/views/shared/forms/_toggle_wysiwyg.html.erb +24 -0
  41. data/app/views/shared/forms/_wysiwyg.html.erb +1 -1
  42. data/app/views/simple_blocks/_form.html.erb +12 -0
  43. data/app/views/users/_form.html.erb +22 -22
  44. data/config/locales/components-ru.yml +2 -3
  45. data/config/routes.rb +41 -90
  46. data/db/migrate/20181217000000_create_biovision_components.rb +16 -39
  47. data/db/migrate/20181217000110_create_editable_pages.rb +11 -40
  48. data/db/migrate/20190326120000_create_simple_blocks.rb +7 -1
  49. data/db/migrate/20190423101010_add_parameters_to_biovision_components.rb +29 -0
  50. data/db/migrate/20190429111111_add_parsed_body_to_editable_pages.rb +18 -0
  51. data/lib/biovision/base/base_methods.rb +10 -0
  52. data/lib/biovision/base/privilege_methods.rb +5 -0
  53. data/lib/biovision/base/version.rb +1 -1
  54. metadata +9 -3
  55. data/app/views/layouts/profile.html.erb +0 -30
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eaab273faa85e0bc922a32b591fa239f57f78d3391554d304afb1e1a32d60285
4
- data.tar.gz: f8e5837fdf1922adf3e6acaca5801de5e3f4d483ca8ef6c124a3382e1a1d7330
3
+ metadata.gz: 25a60b6a83c799dd28363fb20c326010de89318358e923459eee1a952e5c4581
4
+ data.tar.gz: 21c006b675139987f71e209ee9b5e49026152a15d0f4a3f5ba9332d6aab4febb
5
5
  SHA512:
6
- metadata.gz: 1ee9f0baf217333f237339d555681d334c37cf27d9a822b92e6f1e0bc15fe4b3c654ba73b79bece91b97a80d3226d70cfe33c334323f5b6b18ebc764a0cc079b
7
- data.tar.gz: 17bb0eef033b0bffe97faa5e4cb4eafefc177a592062298680caa67b286332b1f47cc925928cd9bf78fc48f34a331d48b93e8decfafc7bb4ab3571968bd4ea11
6
+ metadata.gz: 480dc37953cba27517f9a99927ea704433a2af1241fe229c28d20a693b45cb8feea15ee39915a8639edebd1782c9a81ff80e01b4656c26da328d565483406425
7
+ data.tar.gz: b8a978a75a8be80078f11a3a6a57001eddf8a719ca97255b401420e09b79cb2848f7badcba3f512b35f027a094be0dffdcdddc551c2f820735d8d6ea0d123eb2
@@ -16,10 +16,45 @@ const Biovision = {
16
16
  });
17
17
  }
18
18
 
19
- for (let component in this.components) {
20
- if (this.components.hasOwnProperty(component)) {
21
- if (this.components[component].hasOwnProperty('init')) {
22
- this.components[component].init();
19
+ for (let componentName in this.components) {
20
+ if (this.components.hasOwnProperty(componentName)) {
21
+ const component = this.components[componentName];
22
+ if (component.hasOwnProperty('init')) {
23
+ component.init();
24
+ }
25
+
26
+ if (component.hasOwnProperty("autoInitComponents")) {
27
+ if (component.autoInitComponents) {
28
+ this.initChildComponents(component);
29
+ }
30
+ }
31
+ }
32
+ }
33
+ },
34
+ /**
35
+ * Init child components of given component
36
+ *
37
+ * @type {function}
38
+ * @param {Object} parent
39
+ */
40
+ initChildComponents: function (parent) {
41
+ if (!parent.hasOwnProperty("components")) {
42
+ return;
43
+ }
44
+
45
+ for (let componentName in parent.components) {
46
+ if (parent.components.hasOwnProperty(componentName)) {
47
+ const child = parent.components[componentName];
48
+
49
+ if (child.hasOwnProperty("init")) {
50
+ let initialized = false;
51
+ if (child.hasOwnProperty("initialized")) {
52
+ initialized = child.initialized;
53
+ }
54
+
55
+ if (!initialized) {
56
+ child.init();
57
+ }
23
58
  }
24
59
  }
25
60
  }
@@ -32,7 +67,7 @@ const Biovision = {
32
67
  * @param [onFailure]
33
68
  */
34
69
  new_ajax_request: function (method, url, onSuccess, onFailure) {
35
- console.log("Biovision.new_ajax_request is deprecated; user Biovision.newAjaxRequest instead");
70
+ console.log("Biovision.new_ajax_request is deprecated; use Biovision.newAjaxRequest instead");
36
71
  return Biovision.newAjaxRequest(method, url, onSuccess, onFailure);
37
72
  },
38
73
  /**
@@ -64,10 +99,20 @@ const Biovision = {
64
99
 
65
100
  return request;
66
101
  },
102
+ /**
103
+ * Initialize new AJAX request with JSON content-type and accept headers
104
+ *
105
+ * @param {string} method
106
+ * @param {string} url
107
+ * @param {function} onSuccess
108
+ * @param {function} onFailure
109
+ * @returns {XMLHttpRequest}
110
+ */
67
111
  jsonAjaxRequest: function (method, url, onSuccess, onFailure) {
68
112
  const request = Biovision.newAjaxRequest(method, url, onSuccess, onFailure);
69
113
 
70
- request.setRequestHeader('Content-Type', 'application/json');
114
+ request.setRequestHeader("Content-Type", "application/json");
115
+ request.setRequestHeader("Accept", "application/json");
71
116
 
72
117
  return request;
73
118
  },
@@ -405,7 +450,7 @@ Biovision.components.transliterator = {
405
450
  'у': 'u', 'ф': 'f', 'х': 'kh', 'ц': 'c', 'ч': 'ch',
406
451
  'ш': 'sh', 'щ': 'shh', 'ъ': '', 'ы': 'y', 'ь': '',
407
452
  'э': 'e', 'ю': 'yu', 'я': 'ya',
408
- 'å': 'ao', 'ä': 'ae', 'ö': 'oe', 'é': 'e'
453
+ 'å': 'aa', 'ä': 'ae', 'ö': 'oe', 'é': 'e'
409
454
  };
410
455
  let string = input.toLowerCase();
411
456
 
@@ -1,13 +1,36 @@
1
1
  "use strict";
2
2
 
3
3
  Biovision.components.carousel = {
4
+ /**
5
+ * Component is initialized
6
+ *
7
+ * @type {Boolean}
8
+ */
4
9
  initialized: false,
5
- selector: '.js-biovision-carousel',
10
+ /**
11
+ * Selector string for matching carousel containers
12
+ *
13
+ * @type {String}
14
+ */
15
+ selector: ".js-biovision-carousel",
16
+ /**
17
+ * Wrappers for found carousel containers
18
+ *
19
+ * @type {Array<Object>}
20
+ */
6
21
  sliders: [],
22
+ /**
23
+ * Initializer
24
+ */
7
25
  init: function () {
8
26
  document.querySelectorAll(this.selector).forEach(this.apply);
9
27
  this.initialized = true;
10
28
  },
29
+ /**
30
+ * Apply carousel behavior to container
31
+ *
32
+ * @param {HTMLElement} element
33
+ */
11
34
  apply: function (element) {
12
35
  const component = Biovision.components.carousel;
13
36
  const slider = {
@@ -16,7 +39,8 @@ Biovision.components.carousel = {
16
39
  "items": element.querySelectorAll(".carousel-item"),
17
40
  "prevButton": element.querySelector("button.prev"),
18
41
  "nextButton": element.querySelector("button.next"),
19
- "current": 0
42
+ "current": 0,
43
+ "touchData": {"x": null, "y": null}
20
44
  };
21
45
  if (element.hasAttribute("data-type")) {
22
46
  slider["type"] = element.getAttribute("data-type");
@@ -25,18 +49,27 @@ Biovision.components.carousel = {
25
49
  }
26
50
  if (element.hasAttribute("data-timeout")) {
27
51
  slider["timeout"] = parseInt(element.getAttribute("data-timeout"));
28
- slider["timeout_handler"] = window.setInterval(component.nextItem, slider["timeout"], slider);
52
+ if (slider["timeout"] > 0) {
53
+ slider["timeout_handler"] = window.setInterval(component.nextItem, slider["timeout"], slider);
54
+ }
29
55
  }
30
56
  if (slider["prevButton"]) {
31
- slider["prevButton"].addEventListener('click', component.clickedPrev);
57
+ slider["prevButton"].addEventListener("click", component.clickedPrev);
32
58
  }
33
59
  if (slider["nextButton"]) {
34
- slider["nextButton"].addEventListener('click', component.clickedNext);
60
+ slider["nextButton"].addEventListener("click", component.clickedNext);
35
61
  }
36
62
  slider["maxItem"] = slider["items"].length - 1;
63
+ element.addEventListener("touchstart", component.touchStart, false);
64
+ element.addEventListener("touchend", component.touchEnd, false);
37
65
  component.sliders.push(slider);
38
66
  component.rearrange(slider);
39
67
  },
68
+ /**
69
+ * Rearrange items in carousel
70
+ *
71
+ * @param {Object} slider
72
+ */
40
73
  rearrange: function (slider) {
41
74
  const component = Biovision.components.carousel;
42
75
  switch (slider["type"]) {
@@ -52,24 +85,45 @@ Biovision.components.carousel = {
52
85
  component.newOffset(slider);
53
86
  }
54
87
  },
88
+ /**
89
+ * Handler for clicking "Previous" button
90
+ *
91
+ * @param {Event} event
92
+ */
55
93
  clickedPrev: function (event) {
56
94
  const component = Biovision.components.carousel;
57
- const slider = component.sliderForButton(event.target);
95
+ const slider = component.getSlider(event.target);
58
96
  component.prevItem(slider);
59
97
  },
98
+ /**
99
+ * Handler for clicking "Next" button
100
+ *
101
+ * @param {Event} event
102
+ */
60
103
  clickedNext: function (event) {
61
104
  const component = Biovision.components.carousel;
62
- const slider = component.sliderForButton(event.target);
105
+ const slider = component.getSlider(event.target);
63
106
  component.nextItem(slider);
64
107
  },
65
- sliderForButton: function (button) {
66
- const element = button.closest(this.selector);
108
+ /**
109
+ * Get wrapper for slider
110
+ *
111
+ * @param {HTMLElement|EventTarget} element
112
+ * @returns {Object}
113
+ */
114
+ getSlider: function (element) {
115
+ const slider = element.closest(this.selector);
67
116
  for (let i = 0; i < this.sliders.length; i++) {
68
- if (this.sliders[i].element === element) {
117
+ if (this.sliders[i].element === slider) {
69
118
  return this.sliders[i];
70
119
  }
71
120
  }
72
121
  },
122
+ /**
123
+ * Slide to next item
124
+ *
125
+ * @param {Object} slider
126
+ */
73
127
  nextItem: function (slider) {
74
128
  const component = Biovision.components.carousel;
75
129
 
@@ -80,6 +134,11 @@ Biovision.components.carousel = {
80
134
 
81
135
  component.rearrange(slider);
82
136
  },
137
+ /**
138
+ * Slide to previous item
139
+ *
140
+ * @param {Object} slider
141
+ */
83
142
  prevItem: function (slider) {
84
143
  const component = Biovision.components.carousel;
85
144
  slider["current"]--;
@@ -89,16 +148,26 @@ Biovision.components.carousel = {
89
148
 
90
149
  component.rearrange(slider);
91
150
  },
151
+ /**
152
+ * Mark new item as current
153
+ *
154
+ * @param {Object} slider
155
+ */
92
156
  newCurrentItem: function (slider) {
93
- const selector = '.carousel-item:nth-of-type(' + (slider.current + 1) + ')';
94
- const currentSlide = slider.container.querySelector('.carousel-item.current');
157
+ const selector = ".carousel-item:nth-of-type(" + (slider.current + 1) + ")";
158
+ const currentSlide = slider.container.querySelector(".carousel-item.current");
95
159
  if (currentSlide) {
96
- currentSlide.classList.remove('current');
160
+ currentSlide.classList.remove("current");
97
161
  }
98
- slider.container.querySelector(selector).classList.add('current');
162
+ slider.container.querySelector(selector).classList.add("current");
99
163
  },
164
+ /**
165
+ * Change margin of the leftmost slide
166
+ *
167
+ * @param {Object} slider
168
+ */
100
169
  newOffset: function (slider) {
101
- const firstSlide = slider.container.querySelector('.carousel-item:first-of-type');
170
+ const firstSlide = slider.container.querySelector(".carousel-item:first-of-type");
102
171
  const rightMargin = window.getComputedStyle(firstSlide).marginRight;
103
172
  const slideWidth = firstSlide.offsetWidth + parseInt(rightMargin);
104
173
  let newMargin = -(slideWidth * slider.current);
@@ -111,13 +180,54 @@ Biovision.components.carousel = {
111
180
  slider["current"] = slider["maxItem"];
112
181
  }
113
182
 
114
- firstSlide.style.marginLeft = String(newMargin) + 'px';
183
+ firstSlide.style.marginLeft = String(newMargin) + "px";
115
184
  },
185
+ /**
186
+ * Determine maximum item number so that right margin remains minimal
187
+ *
188
+ * @param {Object} slider
189
+ */
116
190
  setMaxItem: function (slider) {
117
- const firstSlide = slider.container.querySelector('.carousel-item:first-of-type');
191
+ const firstSlide = slider.container.querySelector(".carousel-item:first-of-type");
118
192
  const rightMargin = window.getComputedStyle(firstSlide).marginRight;
119
193
  const slideWidth = firstSlide.offsetWidth + parseInt(rightMargin);
120
194
  const maxCount = slider.container.offsetWidth / slideWidth;
121
195
  slider["maxItem"] = slider.items.length - Math.floor(maxCount);
196
+ },
197
+ /**
198
+ * Handler for start of swipe
199
+ *
200
+ * @param {TouchEvent} event
201
+ * @type {Function}
202
+ */
203
+ touchStart: function (event) {
204
+ const component = Biovision.components.carousel;
205
+ const slider = component.getSlider(event.target);
206
+ slider["touchData"] = {
207
+ "x": event.changedTouches[0].pageX,
208
+ "y": event.changedTouches[0].pageY
209
+ }
210
+ },
211
+ /**
212
+ * Handler for end of swipe
213
+ *
214
+ * @param {TouchEvent} event
215
+ * @type {Function}
216
+ */
217
+ touchEnd: function (event) {
218
+ const component = Biovision.components.carousel;
219
+ const slider = component.getSlider(event.target);
220
+ const x = event.changedTouches[0].pageX;
221
+ const y = event.changedTouches[0].pageY;
222
+ const deltaX = Math.abs(x - slider["touchData"]["x"]);
223
+ const deltaY = Math.abs(y - slider["touchData"]["y"]);
224
+ if (deltaX > deltaY) {
225
+ if (x < slider["touchData"]["x"]) {
226
+ component.nextItem(slider);
227
+ } else if (x > slider["touchData"]["x"]) {
228
+ component.prevItem(slider);
229
+ }
230
+ }
231
+ slider["touchData"] = {"x": null, "y": null}
122
232
  }
123
233
  };
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Stretch videos inserted by WYSIWYG applying proportional-container
5
+ *
6
+ * @author Maxim Khan-Magomedov <maxim.km@gmail.com>
7
+ * @type {Object}
8
+ */
9
+ Biovision.components.videoStretcher = {
10
+ /**
11
+ * Component is initialized
12
+ *
13
+ * @type {boolean}
14
+ */
15
+ initialized: false,
16
+ /**
17
+ * Selector for matching embedded videos
18
+ *
19
+ * @type {string}
20
+ */
21
+ selector: 'figure.media iframe[src^="https://www.you"]',
22
+ /**
23
+ * List of matched items
24
+ *
25
+ * @type {array<HTMLElement>}
26
+ */
27
+ items: [],
28
+ /**
29
+ * Initializer
30
+ *
31
+ * @type {function}
32
+ */
33
+ init: function () {
34
+ document.querySelectorAll(this.selector).forEach(this.apply);
35
+ this.initialized = true;
36
+ },
37
+ /**
38
+ * Callback for applying stretch
39
+ *
40
+ * @param {HTMLElement} element
41
+ */
42
+ apply: function (element) {
43
+ const component = Biovision.components.videoStretcher;
44
+ const figure = element.closest("figure.media");
45
+ if (figure) {
46
+ component.items.push(figure);
47
+ figure.classList.add("proportional-container");
48
+ figure.classList.add("r-16x9");
49
+ }
50
+ }
51
+ };
@@ -426,12 +426,18 @@ form {
426
426
  }
427
427
 
428
428
  .visually-hidden {
429
- height: .1rem;
429
+ border: none;
430
+ clip: rect(0 0 0 0);
431
+ height: auto;
430
432
  left: -10000rem;
433
+ margin: 0;
431
434
  opacity: .05;
432
435
  overflow: hidden;
436
+ padding: 0;
437
+ pointer-events: none;
433
438
  position: absolute;
434
- width: .1rem;
439
+ white-space: nowrap;
440
+ width: 1px;
435
441
  }
436
442
 
437
443
  .proportional-container {
@@ -491,3 +497,13 @@ input.control:not(:checked) + * {
491
497
  width: 100%;
492
498
  }
493
499
  }
500
+
501
+ .containing {
502
+ overflow: hidden;
503
+
504
+ > * {
505
+ height: 100%;
506
+ object-fit: contain;
507
+ width: 100%;
508
+ }
509
+ }
@@ -15,31 +15,26 @@ class Admin::SettingsController < AdminController
15
15
 
16
16
  # patch /admin/settings/:slug
17
17
  def update
18
- new_settings = params.dig(:component, :settings).permit!
18
+ new_settings = params.dig(:component, :settings).permit!
19
19
  @handler.settings = new_settings.to_h
20
- flash[:notice] = t('admin.settings.update.success')
20
+ flash[:notice] = t('admin.settings.update.success')
21
21
  redirect_to(admin_component_path(slug: params[:slug]))
22
22
  end
23
23
 
24
24
  # put /admin/settings/:slug/parameter
25
25
  def set_parameter
26
- slug = param_from_request(:key, :slug).downcase
27
- value = param_from_request(:key, :value)
28
- name = param_from_request(:key, :name)
29
- description = param_from_request(:key, :description)
26
+ slug = param_from_request(:key, :slug).downcase
27
+ value = param_from_request(:key, :value)
30
28
 
31
- if name.blank? && description.blank?
32
- @handler[slug] = value
33
- else
34
- @handler.set_parameter(slug, value, name, description)
35
- end
29
+ @handler[slug] = value
36
30
 
37
31
  head :no_content
38
32
  end
39
33
 
40
34
  # delete /admin/settings/:slug/:parameter_slug
41
35
  def delete_parameter
42
- @handler.delete_parameter(params[:parameter_slug])
36
+ @handler.component.parameters.delete(params[:parameter_slug])
37
+ @handler.component.save
43
38
 
44
39
  head :no_content
45
40
  end