biovision-base 0.8.170916 → 0.8.171029
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/images/biovision/base/placeholders/image.svg +3 -18
- data/app/assets/javascripts/biovision/base/biovision.js +228 -124
- data/app/assets/stylesheets/biovision/base/admin.scss +8 -7
- data/app/assets/stylesheets/biovision/base/biovision.scss +1 -1
- data/app/assets/stylesheets/biovision/base/default.scss +6 -3
- data/app/assets/stylesheets/biovision/base/default_admin.scss +30 -0
- data/app/assets/stylesheets/biovision/base/layout.scss +22 -14
- data/app/assets/stylesheets/biovision/base/message-box.scss +8 -1
- data/app/controllers/admin/regions_controller.rb +0 -9
- data/app/controllers/admin/users_controller.rb +2 -2
- data/app/controllers/authentication_controller.rb +4 -1
- data/app/controllers/concerns/toggleable_entity.rb +12 -3
- data/app/controllers/regions_controller.rb +1 -1
- data/app/helpers/biovision_users_helper.rb +18 -8
- data/app/models/region.rb +6 -1
- data/app/models/user.rb +3 -3
- data/app/models/user_profile.rb +8 -0
- data/app/services/canonizer.rb +9 -8
- data/app/views/admin/metrics/show.html.erb +20 -12
- data/app/views/admin/privileges/entity/_groups.html.erb +12 -10
- data/app/views/admin/regions/entity/_in_list.html.erb +1 -1
- data/app/views/admin/regions/show.html.erb +1 -1
- data/app/views/admin/users/_filter.html.erb +2 -14
- data/app/views/admin/users/index.html.erb +1 -1
- data/app/views/admin/users/privileges.html.erb +53 -38
- data/app/views/agents/_filter.html.erb +28 -28
- data/app/views/authentication/new.jbuilder +6 -0
- data/app/views/editable_pages/_editable_page.html.erb +6 -0
- data/app/views/editable_pages/form/_ckeditor.html.erb +3 -3
- data/app/views/layouts/admin.html.erb +1 -1
- data/app/views/my/recoveries/show.html.erb +1 -1
- data/app/views/shared/_breadcrumbs.html.erb +6 -0
- data/app/views/shared/_list_of_errors.html.erb +5 -9
- data/config/locales/common-ru.yml +22 -22
- data/config/locales/users-ru.yml +9 -0
- data/config/routes.rb +1 -1
- data/db/migrate/20170301000001_create_metrics.rb +1 -1
- data/db/migrate/20170301000002_create_metric_values.rb +1 -1
- data/db/migrate/20170301000101_create_browsers.rb +1 -1
- data/db/migrate/20170301000102_create_agents.rb +1 -1
- data/db/migrate/20170301000201_create_regions.rb +4 -1
- data/db/migrate/20170302000001_create_users.rb +2 -1
- data/db/migrate/20170302000003_create_tokens.rb +1 -1
- data/db/migrate/20170302000005_create_codes.rb +1 -1
- data/db/migrate/20170302000101_create_privileges.rb +1 -1
- data/db/migrate/20170302000102_create_user_privileges.rb +1 -1
- data/db/migrate/20170302000103_create_privilege_groups.rb +1 -1
- data/db/migrate/20170302000104_create_privilege_group_privileges.rb +1 -1
- data/db/migrate/20170320000000_create_editable_pages.rb +4 -1
- data/db/migrate/20170425000001_create_foreign_sites.rb +1 -1
- data/db/migrate/20170425000002_create_foreign_users.rb +1 -1
- data/db/migrate/20171025222222_add_new_fields_171026.rb +29 -0
- data/lib/biovision/base/engine.rb +5 -0
- data/lib/biovision/base/version.rb +1 -1
- metadata +8 -4
@@ -1,13 +1,13 @@
|
|
1
1
|
html {
|
2
|
-
background-image: linear-gradient(to bottom, #dedfe3 2%, #aeb3b9 95%), radial-gradient(33% 100%, rgba(255, 255, 255, 0.50) 0%, rgba(0, 0, 0, 0.50) 100%);
|
3
|
-
background-blend-mode: screen;
|
4
2
|
background-attachment: fixed;
|
5
|
-
|
3
|
+
background-blend-mode: screen;
|
4
|
+
background-image: linear-gradient(to bottom, #dedfe3 2%, #aeb3b9 95%), radial-gradient(farthest-corner at 33% 100%, rgba(255, 255, 255, .5) 0%, rgba(0, 0, 0, .5) 100%);
|
5
|
+
font: 300 10px $font-family-main;
|
6
6
|
margin: 0;
|
7
7
|
padding: 0;
|
8
|
-
-webkit-text-size-adjust: none;
|
9
8
|
-moz-text-size-adjust: none;
|
10
9
|
-ms-text-size-adjust: none;
|
10
|
+
-webkit-text-size-adjust: none;
|
11
11
|
}
|
12
12
|
|
13
13
|
* {
|
@@ -17,6 +17,7 @@ html {
|
|
17
17
|
a:link,
|
18
18
|
a:visited {
|
19
19
|
color: $link-color;
|
20
|
+
font-weight: 400;
|
20
21
|
text-decoration: none;
|
21
22
|
}
|
22
23
|
|
@@ -29,6 +30,7 @@ img {
|
|
29
30
|
}
|
30
31
|
|
31
32
|
h1, h2, h3, h4, h5, h6 {
|
33
|
+
color: $text-color-heading;
|
32
34
|
font-family: $font-family-heading;
|
33
35
|
font-weight: 500;
|
34
36
|
margin: 0;
|
@@ -121,6 +123,7 @@ article {
|
|
121
123
|
|
122
124
|
&.preview {
|
123
125
|
img {
|
126
|
+
box-shadow: $block-shadow;
|
124
127
|
max-width: 32rem;
|
125
128
|
}
|
126
129
|
}
|
@@ -132,7 +135,7 @@ article {
|
|
132
135
|
|
133
136
|
dt {
|
134
137
|
border-top: $border-secondary;
|
135
|
-
font:
|
138
|
+
font: 400 $font-size-increased $font-family-main;
|
136
139
|
margin: .8rem 0 0 0;
|
137
140
|
padding: .4rem 0;
|
138
141
|
}
|
@@ -181,8 +184,6 @@ article {
|
|
181
184
|
}
|
182
185
|
|
183
186
|
nav.admin-breadcrumbs {
|
184
|
-
background: linear-gradient(to right, rgba(192, 230, 192, .125), rgba(255, 255, 255, 0));
|
185
|
-
border-left: .2rem solid rgba(80, 230, 80, .5);
|
186
187
|
margin-bottom: .8rem;
|
187
188
|
padding: .4rem;
|
188
189
|
|
@@ -17,11 +17,14 @@ $text-color-heading: #000 !default;
|
|
17
17
|
$text-color-primary: rgb(28, 28, 28) !default;
|
18
18
|
$text-color-secondary: #777 !default;
|
19
19
|
|
20
|
-
$block-shadow:
|
20
|
+
$block-shadow: .2rem .2rem .4rem .2rem rgba(0, 0, 0, .125) !default;
|
21
21
|
|
22
|
-
$border-primary:
|
23
|
-
$border-secondary:
|
22
|
+
$border-color-primary: rgb(192, 192, 192) !default;
|
23
|
+
$border-color-secondary: rgb(230, 230, 230) !default;
|
24
|
+
$border-primary: .1rem solid $border-color-primary !default;
|
25
|
+
$border-secondary: .1rem solid $border-color-secondary !default;
|
24
26
|
|
27
|
+
$background-body: #f4f4f4 linear-gradient(to top, #f4f4f4 0%, #dfdedc 100%) no-repeat center / cover !default;
|
25
28
|
$background-header: #fff !default;
|
26
29
|
$background-main: #fff !default;
|
27
30
|
$background-footer: #fff !default;
|
@@ -1,3 +1,33 @@
|
|
1
|
+
$font-family-heading: "Cormorant Garamond", serif !default;
|
2
|
+
$font-family-main: "Roboto", sans-serif !default;
|
3
|
+
|
4
|
+
$font-size-large: 2rem !default;
|
5
|
+
$font-size-increased: 1.6rem !default;
|
6
|
+
$font-size-normal: 1.4rem !default;
|
7
|
+
$font-size-decreased: 1.2rem !default;
|
8
|
+
$font-size-small: 1rem !default;
|
9
|
+
|
10
|
+
$content-width: 100rem !default;
|
11
|
+
$content-width-min: 32rem !default;
|
12
|
+
|
13
|
+
$row-background-even: hsl(0, 0%, 95%) !default;
|
14
|
+
$row-background-odd: hsl(0, 0%, 98%) !default;
|
15
|
+
|
16
|
+
$text-color-heading: #000 !default;
|
17
|
+
$text-color-primary: rgb(28, 28, 28) !default;
|
18
|
+
$text-color-secondary: #777 !default;
|
19
|
+
|
20
|
+
$block-shadow: .2rem .2rem .4rem .2rem rgba(0, 0, 0, .125) !default;
|
21
|
+
|
22
|
+
$border-color-primary: rgb(192, 192, 192) !default;
|
23
|
+
$border-color-secondary: rgb(230, 230, 230) !default;
|
24
|
+
$border-primary: .1rem solid $border-color-primary !default;
|
25
|
+
$border-secondary: .1rem solid $border-color-secondary !default;
|
26
|
+
|
27
|
+
$link-color: rgb(20, 127, 255) !default;
|
28
|
+
$link-color-visited: rgb(20, 77, 250) !default;
|
29
|
+
$link-color-hover: rgb(255, 77, 20) !default;
|
30
|
+
|
1
31
|
@import "biovision/base/toggleable";
|
2
32
|
@import "biovision/base/buttons";
|
3
33
|
@import "biovision/base/biovision";
|
@@ -1,3 +1,7 @@
|
|
1
|
+
* {
|
2
|
+
box-sizing: border-box;
|
3
|
+
}
|
4
|
+
|
1
5
|
html {
|
2
6
|
font: 10px $font-family-main;
|
3
7
|
margin: 0;
|
@@ -7,10 +11,6 @@ html {
|
|
7
11
|
-ms-text-size-adjust: none;
|
8
12
|
}
|
9
13
|
|
10
|
-
* {
|
11
|
-
box-sizing: border-box;
|
12
|
-
}
|
13
|
-
|
14
14
|
h1, h2, h3, h4, h6, h6 {
|
15
15
|
color: $text-color-heading;
|
16
16
|
font-family: $font-family-heading;
|
@@ -48,6 +48,7 @@ img {
|
|
48
48
|
}
|
49
49
|
|
50
50
|
body {
|
51
|
+
background: $background-body;
|
51
52
|
color: $text-color-primary;
|
52
53
|
display: flex;
|
53
54
|
flex-direction: column;
|
@@ -60,10 +61,14 @@ body {
|
|
60
61
|
|
61
62
|
> footer,
|
62
63
|
> header {
|
64
|
+
padding: .4rem 0;
|
65
|
+
|
63
66
|
> div {
|
67
|
+
display: flex;
|
68
|
+
flex-wrap: wrap;
|
64
69
|
margin: 0 auto;
|
65
|
-
padding: .4rem;
|
66
70
|
max-width: $content-width;
|
71
|
+
padding: .4rem;
|
67
72
|
|
68
73
|
.authentication {
|
69
74
|
> div {
|
@@ -95,8 +100,6 @@ body {
|
|
95
100
|
|
96
101
|
> div {
|
97
102
|
align-items: center;
|
98
|
-
display: flex;
|
99
|
-
flex-wrap: wrap;
|
100
103
|
|
101
104
|
.logo {
|
102
105
|
margin-right: auto;
|
@@ -110,18 +113,22 @@ body {
|
|
110
113
|
margin: auto 0 0 0;
|
111
114
|
|
112
115
|
> div {
|
113
|
-
align-items: center;
|
114
116
|
color: $text-color-secondary;
|
115
|
-
display: flex;
|
116
|
-
flex-wrap: wrap;
|
117
117
|
font-size: $font-size-decreased;
|
118
118
|
|
119
|
-
.copyright {
|
120
|
-
margin-right: auto;
|
121
|
-
}
|
122
|
-
|
123
119
|
nav {
|
124
120
|
margin: 0 auto;
|
121
|
+
|
122
|
+
ul {
|
123
|
+
margin: 0;
|
124
|
+
padding: 0;
|
125
|
+
|
126
|
+
li {
|
127
|
+
list-style: none;
|
128
|
+
margin: 0;
|
129
|
+
padding: 0;
|
130
|
+
}
|
131
|
+
}
|
125
132
|
}
|
126
133
|
}
|
127
134
|
}
|
@@ -133,6 +140,7 @@ body {
|
|
133
140
|
margin: 0 auto;
|
134
141
|
max-width: $content-width;
|
135
142
|
padding: .8rem;
|
143
|
+
width: 100%;
|
136
144
|
}
|
137
145
|
|
138
146
|
form {
|
@@ -32,7 +32,6 @@ section.errors {
|
|
32
32
|
padding: 1.6rem;
|
33
33
|
|
34
34
|
> h2 {
|
35
|
-
background: #fee image_url('biovision/base/icons/alert.svg') no-repeat center left 1.6rem / 1.6rem 1.6rem;
|
36
35
|
color: #400;
|
37
36
|
font-size: $font-size-large;
|
38
37
|
padding: 1.6rem 1.6rem 1.6rem 4.8rem;
|
@@ -43,6 +42,14 @@ section.errors {
|
|
43
42
|
}
|
44
43
|
}
|
45
44
|
|
45
|
+
ol.errors {
|
46
|
+
background: #fee image_url('biovision/base/icons/alert.svg') no-repeat top 1rem left 1rem / 1.6rem 1.6rem;
|
47
|
+
box-shadow: .2rem .2rem .2rem 0 hsla(0, 75%, 50%, .5);
|
48
|
+
color: hsl(0, 75%, 50%);
|
49
|
+
margin: 1rem 1.6rem;
|
50
|
+
padding: 1rem 1rem 1rem 5.4rem;
|
51
|
+
}
|
52
|
+
|
46
53
|
.field_with_errors {
|
47
54
|
background: #fee;
|
48
55
|
display: inline-block;
|
@@ -3,7 +3,6 @@ class Admin::RegionsController < AdminController
|
|
3
3
|
include LockableEntity
|
4
4
|
|
5
5
|
before_action :set_entity, except: [:index]
|
6
|
-
before_action :check_entity_lock, only: [:toggle]
|
7
6
|
|
8
7
|
# get /admin/regions
|
9
8
|
def index
|
@@ -20,18 +19,10 @@ class Admin::RegionsController < AdminController
|
|
20
19
|
require_privilege_group :region_managers
|
21
20
|
end
|
22
21
|
|
23
|
-
def restrict_editing
|
24
|
-
unless @entity.editable_by?(current_user)
|
25
|
-
handle_http_401('Current user cannot edit region')
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
22
|
def set_entity
|
30
23
|
@entity = Region.find_by(id: params[:id])
|
31
24
|
if @entity.nil?
|
32
25
|
handle_http_404("Cannot find region #{params[:id]}")
|
33
|
-
else
|
34
|
-
restrict_editing
|
35
26
|
end
|
36
27
|
end
|
37
28
|
end
|
@@ -7,8 +7,8 @@ class Admin::UsersController < AdminController
|
|
7
7
|
|
8
8
|
# get /admin/users
|
9
9
|
def index
|
10
|
-
@
|
11
|
-
@collection = User.page_for_administration current_page, @
|
10
|
+
@search = param_from_request(:q)
|
11
|
+
@collection = User.page_for_administration current_page, @search
|
12
12
|
end
|
13
13
|
|
14
14
|
# get /admin/users/:id
|
@@ -66,6 +66,9 @@ class AuthenticationController < ApplicationController
|
|
66
66
|
return_path = my_path unless return_path[0] == '/'
|
67
67
|
cookies.delete 'return_path', domain: :all
|
68
68
|
|
69
|
-
|
69
|
+
respond_to do |format|
|
70
|
+
format.json { render(json: { data: { url: return_path } }) }
|
71
|
+
format.html { redirect_to(return_path) }
|
72
|
+
end
|
70
73
|
end
|
71
74
|
end
|
@@ -3,7 +3,9 @@ module ToggleableEntity
|
|
3
3
|
|
4
4
|
# Toggle entity flag when allowed
|
5
5
|
def toggle
|
6
|
-
if
|
6
|
+
if entity_is_locked?
|
7
|
+
render json: { errors: { locked: true } }, status: :forbidden
|
8
|
+
elsif entity_is_editable?
|
7
9
|
render json: { data: @entity.toggle_parameter(params[:parameter].to_s) }
|
8
10
|
else
|
9
11
|
head :unauthorized
|
@@ -12,12 +14,19 @@ module ToggleableEntity
|
|
12
14
|
|
13
15
|
private
|
14
16
|
|
15
|
-
|
16
|
-
def allow_toggle?
|
17
|
+
def entity_is_editable?
|
17
18
|
if @entity.respond_to?(:editable_by?)
|
18
19
|
@entity.editable_by?(current_user)
|
19
20
|
else
|
20
21
|
true
|
21
22
|
end
|
22
23
|
end
|
24
|
+
|
25
|
+
def entity_is_locked?
|
26
|
+
if @entity.respond_to?(:locked?)
|
27
|
+
@entity.locked?
|
28
|
+
else
|
29
|
+
false
|
30
|
+
end
|
31
|
+
end
|
23
32
|
end
|
@@ -49,7 +49,7 @@ class RegionsController < AdminController
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def restrict_editing
|
52
|
-
|
52
|
+
if @entity.locked? || !@entity.editable_by?(current_user)
|
53
53
|
redirect_to admin_region_path(@entity.id), alert: t('regions.edit.forbidden')
|
54
54
|
end
|
55
55
|
end
|
@@ -2,7 +2,7 @@ module BiovisionUsersHelper
|
|
2
2
|
def genders_for_select
|
3
3
|
genders = [[t(:not_selected), '']]
|
4
4
|
prefix = 'activerecord.attributes.user_profile.genders.'
|
5
|
-
genders + UserProfile.genders.keys.to_a.map { |o| [
|
5
|
+
genders + UserProfile.genders.keys.to_a.map { |o| [t("#{prefix}#{o}"), o] }
|
6
6
|
end
|
7
7
|
|
8
8
|
# @param [User] entity
|
@@ -26,23 +26,33 @@ module BiovisionUsersHelper
|
|
26
26
|
link_to entity.name, admin_token_path(entity.id)
|
27
27
|
end
|
28
28
|
|
29
|
-
# @param [User]
|
30
|
-
def profile_avatar(
|
31
|
-
if
|
32
|
-
|
29
|
+
# @param [User] entity
|
30
|
+
def profile_avatar(entity)
|
31
|
+
if entity.is_a?(User) && !entity.image.blank? && !entity.deleted?
|
32
|
+
user_image_profile(entity)
|
33
33
|
else
|
34
|
-
image_tag
|
34
|
+
image_tag('biovision/base/placeholders/user.svg')
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
38
|
# @param [User] entity
|
39
39
|
def user_image_preview(entity)
|
40
|
-
return image_tag('biovision/base/placeholders/user.svg') if entity.image.blank?
|
41
|
-
|
42
40
|
versions = "#{entity.image.preview_2x.url} 2x"
|
43
41
|
image_tag(entity.image.preview.url, alt: entity.profile_name, srcset: versions)
|
44
42
|
end
|
45
43
|
|
44
|
+
# @param [User] entity
|
45
|
+
def user_image_profile(entity)
|
46
|
+
versions = "#{entity.image.profile_2x.url} 2x"
|
47
|
+
image_tag(entity.image.profile.url, alt: entity.profile_name, srcset: versions)
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param [User] entity
|
51
|
+
def user_image_big(entity)
|
52
|
+
versions = "#{entity.image.big_2x.url} 2x"
|
53
|
+
image_tag(entity.image.big.url, alt: entity.profile_name, srcset: versions)
|
54
|
+
end
|
55
|
+
|
46
56
|
# @param [ForeignSite] foreign_site
|
47
57
|
def foreign_login_link(foreign_site)
|
48
58
|
image = "biovision/base/icons/foreign/#{foreign_site.slug}.svg"
|
data/app/models/region.rb
CHANGED
@@ -47,7 +47,7 @@ class Region < ApplicationRecord
|
|
47
47
|
def editable_by?(user)
|
48
48
|
administrator = UserPrivilege.user_has_privilege?(user, :administrator)
|
49
49
|
manager = UserPrivilege.user_has_privilege?(user, :region_manager, self)
|
50
|
-
|
50
|
+
administrator || manager
|
51
51
|
end
|
52
52
|
|
53
53
|
def parents
|
@@ -78,6 +78,11 @@ class Region < ApplicationRecord
|
|
78
78
|
"#{parents.map(&:name).join('/')}/#{name}"
|
79
79
|
end
|
80
80
|
|
81
|
+
def branch_name
|
82
|
+
return short_name if parents.blank?
|
83
|
+
"#{parents.map(&:short_name).join('/')}/#{short_name}"
|
84
|
+
end
|
85
|
+
|
81
86
|
def cache_parents!
|
82
87
|
return if parent.nil?
|
83
88
|
self.parents_cache = "#{parent.parents_cache},#{parent_id}".gsub(/\A,/, '')
|
data/app/models/user.rb
CHANGED
@@ -55,9 +55,9 @@ class User < ApplicationRecord
|
|
55
55
|
scope :filtered, ->(f) { email_like(f[:email]).screen_name_like(f[:screen_name]) }
|
56
56
|
|
57
57
|
# @param [Integer] page
|
58
|
-
# @param [
|
59
|
-
def self.page_for_administration(page,
|
60
|
-
|
58
|
+
# @param [String] search_query
|
59
|
+
def self.page_for_administration(page, search_query)
|
60
|
+
search(search_query).order('id desc').page(page).per(PER_PAGE)
|
61
61
|
end
|
62
62
|
|
63
63
|
def self.profile_parameters
|
data/app/models/user_profile.rb
CHANGED
@@ -18,4 +18,12 @@ class UserProfile < ApplicationRecord
|
|
18
18
|
def search_string
|
19
19
|
"#{name} #{surname}"
|
20
20
|
end
|
21
|
+
|
22
|
+
def age
|
23
|
+
now = Time.now
|
24
|
+
bd = birthday || now
|
25
|
+
result = now.year - bd.year
|
26
|
+
result = result - 1 if (bd.month > now.month || (bd.month >= now.month && bd.day > now.day))
|
27
|
+
result
|
28
|
+
end
|
21
29
|
end
|
data/app/services/canonizer.rb
CHANGED
@@ -1,29 +1,30 @@
|
|
1
1
|
class Canonizer
|
2
2
|
TRANSLITERATION_MAP = {
|
3
|
-
'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', 'е' => 'e',
|
4
|
-
'ж' => 'zh', 'з' => 'z', 'и' => 'i', 'й' => 'j', 'к' => 'k',
|
5
|
-
'
|
6
|
-
'
|
7
|
-
'
|
3
|
+
'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', 'е' => 'e',
|
4
|
+
'ё' => 'yo', 'ж' => 'zh', 'з' => 'z', 'и' => 'i', 'й' => 'j', 'к' => 'k',
|
5
|
+
'л' => 'l', 'м' => 'm', 'н' => 'n', 'о' => 'o', 'п' => 'p', 'р' => 'r',
|
6
|
+
'с' => 's', 'т' => 't', 'у' => 'u', 'ф' => 'f', 'х' => 'kh', 'ц' => 'c',
|
7
|
+
'ч' => 'ch', 'ш' => 'sh', 'щ' => 'shh', 'ъ' => '', 'ы' => 'y', 'ь' => '',
|
8
|
+
'э' => 'e', 'ю' => 'yu', 'я' => 'ya',
|
8
9
|
}
|
9
10
|
|
10
11
|
# @param [String] text
|
11
12
|
def self.transliterate(text)
|
12
|
-
result = text.
|
13
|
+
result = text.downcase
|
13
14
|
TRANSLITERATION_MAP.each { |r, e| result.gsub!(r, e) }
|
14
15
|
result.downcase.gsub(/[^-a-z0-9_]/, '-').gsub(/^[-_]*([-a-z0-9_]*[a-z0-9]+)[-_]*$/, '\1').gsub(/--+/, '-')
|
15
16
|
end
|
16
17
|
|
17
18
|
# @param [String] input
|
18
19
|
def self.canonize(input)
|
19
|
-
lowered = input.
|
20
|
+
lowered = input.downcase.strip
|
20
21
|
canonized = lowered.gsub(/[^a-zа-я0-9ё]/, '')
|
21
22
|
canonized.empty? ? lowered : canonized
|
22
23
|
end
|
23
24
|
|
24
25
|
# @param [String] input
|
25
26
|
def self.urlize(input)
|
26
|
-
lowered = input.
|
27
|
+
lowered = input.downcase.squish
|
27
28
|
lowered.gsub(/[^a-zа-я0-9ё]/, '-').gsub(/-\z/, '')
|
28
29
|
end
|
29
30
|
end
|