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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/biovision/base/biovision.js +52 -7
- data/app/assets/javascripts/biovision/base/components/carousel.js +127 -17
- data/app/assets/javascripts/biovision/base/components/video-stretcher.js +51 -0
- data/app/assets/stylesheets/biovision/base/biovision.scss +18 -2
- data/app/controllers/admin/settings_controller.rb +7 -12
- data/app/controllers/admin_controller.rb +4 -0
- data/app/controllers/concerns/authentication.rb +3 -0
- data/app/controllers/editable_pages_controller.rb +2 -0
- data/app/controllers/my/profiles_controller.rb +1 -1
- data/app/helpers/biovision_users_helper.rb +1 -1
- data/app/jobs/editable_page_body_parser_job.rb +16 -0
- data/app/models/biovision_component.rb +18 -11
- data/app/models/editable_page.rb +21 -16
- data/app/models/foreign_site.rb +9 -3
- data/app/services/biovision/components/base_component.rb +17 -40
- data/app/services/canonizer.rb +21 -14
- data/app/services/oembed_receiver.rb +92 -0
- data/app/services/user_bouncer.rb +3 -4
- data/app/uploaders/simple_file_uploader.rb +15 -0
- data/app/views/admin/editable_pages/entity/_in_list.html.erb +1 -1
- data/app/views/admin/editable_pages/show.html.erb +2 -2
- data/app/views/admin/index/_biovision_base.html.erb +2 -2
- data/app/views/admin/settings/component/_new_parameter.html.erb +5 -13
- data/app/views/admin/settings/component/_parameters.html.erb +6 -6
- data/app/views/admin/settings/show.html.erb +1 -1
- data/app/views/admin/users/entity/_in_list.html.erb +3 -0
- data/app/views/admin/users/show.html.erb +1 -1
- data/app/views/editable_pages/_editable_page.html.erb +1 -1
- data/app/views/editable_pages/_form.html.erb +2 -2
- data/app/views/editable_pages/entity/_content.html.erb +1 -1
- data/app/views/editable_pages/entity/_metadata.html.erb +12 -10
- data/app/views/index/index/_default_dashboard.html.erb +3 -1
- data/app/views/my/index/index/_dashboard.html.erb +16 -0
- data/app/views/shared/_breadcrumbs.html.erb +4 -2
- data/app/views/shared/_track.html.erb +8 -8
- data/app/views/shared/admin/_breadcrumbs.html.erb +4 -2
- data/app/views/shared/editable_pages/_body.html.erb +2 -2
- data/app/views/shared/entity/_metadata.html.erb +13 -0
- data/app/views/shared/forms/_toggle_wysiwyg.html.erb +24 -0
- data/app/views/shared/forms/_wysiwyg.html.erb +1 -1
- data/app/views/simple_blocks/_form.html.erb +12 -0
- data/app/views/users/_form.html.erb +22 -22
- data/config/locales/components-ru.yml +2 -3
- data/config/routes.rb +41 -90
- data/db/migrate/20181217000000_create_biovision_components.rb +16 -39
- data/db/migrate/20181217000110_create_editable_pages.rb +11 -40
- data/db/migrate/20190326120000_create_simple_blocks.rb +7 -1
- data/db/migrate/20190423101010_add_parameters_to_biovision_components.rb +29 -0
- data/db/migrate/20190429111111_add_parsed_body_to_editable_pages.rb +18 -0
- data/lib/biovision/base/base_methods.rb +10 -0
- data/lib/biovision/base/privilege_methods.rb +5 -0
- data/lib/biovision/base/version.rb +1 -1
- metadata +9 -3
- data/app/views/layouts/profile.html.erb +0 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 25a60b6a83c799dd28363fb20c326010de89318358e923459eee1a952e5c4581
|
4
|
+
data.tar.gz: 21c006b675139987f71e209ee9b5e49026152a15d0f4a3f5ba9332d6aab4febb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 480dc37953cba27517f9a99927ea704433a2af1241fe229c28d20a693b45cb8feea15ee39915a8639edebd1782c9a81ff80e01b4656c26da328d565483406425
|
7
|
+
data.tar.gz: b8a978a75a8be80078f11a3a6a57001eddf8a719ca97255b401420e09b79cb2848f7badcba3f512b35f027a094be0dffdcdddc551c2f820735d8d6ea0d123eb2
|
@@ -16,10 +16,45 @@ const Biovision = {
|
|
16
16
|
});
|
17
17
|
}
|
18
18
|
|
19
|
-
for (let
|
20
|
-
if (this.components.hasOwnProperty(
|
21
|
-
|
22
|
-
|
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;
|
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(
|
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
|
-
'å': '
|
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
|
-
|
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
|
-
|
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(
|
57
|
+
slider["prevButton"].addEventListener("click", component.clickedPrev);
|
32
58
|
}
|
33
59
|
if (slider["nextButton"]) {
|
34
|
-
slider["nextButton"].addEventListener(
|
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.
|
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.
|
105
|
+
const slider = component.getSlider(event.target);
|
63
106
|
component.nextItem(slider);
|
64
107
|
},
|
65
|
-
|
66
|
-
|
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 ===
|
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 =
|
94
|
-
const currentSlide = slider.container.querySelector(
|
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(
|
160
|
+
currentSlide.classList.remove("current");
|
97
161
|
}
|
98
|
-
slider.container.querySelector(selector).classList.add(
|
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(
|
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) +
|
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(
|
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
|
-
|
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
|
-
|
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
|
18
|
+
new_settings = params.dig(:component, :settings).permit!
|
19
19
|
@handler.settings = new_settings.to_h
|
20
|
-
flash[:notice]
|
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
|
27
|
-
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
|
-
|
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.
|
36
|
+
@handler.component.parameters.delete(params[:parameter_slug])
|
37
|
+
@handler.component.save
|
43
38
|
|
44
39
|
head :no_content
|
45
40
|
end
|