aaf-lipstick 1.1.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +7 -15
- data/app/assets/images/aaf-icon.png +0 -0
- data/app/assets/images/logo.png +0 -0
- data/app/assets/javascripts/aaf-lipstick.js +90 -0
- data/app/assets/stylesheets/aaf-lipstick.css.scss +304 -0
- data/app/views/layouts/email_branding.html.erb +35 -36
- data/lib/aaf-lipstick.rb +1 -0
- data/lib/lipstick.rb +4 -2
- data/lib/lipstick/auto_validation.rb +26 -22
- data/lib/lipstick/email_message.rb +1 -0
- data/lib/lipstick/engine.rb +3 -0
- data/lib/lipstick/filterable.rb +57 -0
- data/lib/lipstick/helpers.rb +6 -2
- data/lib/lipstick/helpers/bootstrap_form_builder.rb +61 -0
- data/lib/lipstick/helpers/compatibility_hacks.rb +39 -0
- data/lib/lipstick/helpers/form_helper.rb +117 -136
- data/lib/lipstick/helpers/form_validation_builder.rb +70 -0
- data/lib/lipstick/helpers/layout_helper.rb +98 -57
- data/lib/lipstick/helpers/nav_helper.rb +24 -29
- data/lib/lipstick/helpers/pagination_link_renderer.rb +131 -0
- data/lib/lipstick/images.rb +1 -0
- data/lib/lipstick/images/email_banner.rb +10 -4
- data/lib/lipstick/images/processor.rb +10 -4
- data/lib/lipstick/sprockets_asset_data_url_helper.rb +14 -0
- data/lib/lipstick/version.rb +2 -1
- metadata +75 -14
- data/app/assets/javascripts/aaf-layout.js +0 -35
- data/app/assets/stylesheets/aaf-layout.css.scss +0 -204
- data/lib/lipstick/action_view_tilt_template.rb +0 -17
- data/lib/lipstick/helpers/semantic_form_builder.rb +0 -2
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Lipstick
|
3
|
+
module Helpers
|
4
|
+
class FormValidationBuilder
|
5
|
+
def initialize(sym)
|
6
|
+
@rules = {}
|
7
|
+
@messages = {}
|
8
|
+
@sym = sym
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_h
|
12
|
+
{ rules: @rules, messages: @messages }
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate_field(field, opts)
|
16
|
+
target = wrap_name(field)
|
17
|
+
|
18
|
+
opts.each do |k, v|
|
19
|
+
next validate_with_hash(target, k, v) if v.is_a?(Hash)
|
20
|
+
validate_with_param(target, k, v)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def auto_validate(obj, *fields)
|
25
|
+
unless obj.class.respond_to?(:lipstick_auto_validators)
|
26
|
+
raise("#{obj.class.name} does not include Lipstick::AutoValidation")
|
27
|
+
end
|
28
|
+
|
29
|
+
validators = obj.class.lipstick_auto_validators
|
30
|
+
validators.slice(*fields).each do |field, opts|
|
31
|
+
validate_field(field, opts)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def validate_with_hash(target, validation, opts)
|
38
|
+
message = opts.delete(:message)
|
39
|
+
add_message(target, validation, message) if message
|
40
|
+
|
41
|
+
return validate_with_param(target, validation, true) if opts.empty?
|
42
|
+
|
43
|
+
if opts.keys == [:param]
|
44
|
+
return validate_with_param(target, validation, opts[:param])
|
45
|
+
end
|
46
|
+
|
47
|
+
add_rule(target, validation, opts)
|
48
|
+
end
|
49
|
+
|
50
|
+
def validate_with_param(target, validation, param)
|
51
|
+
add_rule(target, validation, param)
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_rule(target, key, value)
|
55
|
+
@rules[target] ||= {}
|
56
|
+
@rules[target][key] = value
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_message(target, key, value)
|
60
|
+
@messages[target] ||= {}
|
61
|
+
@messages[target][key] = value
|
62
|
+
end
|
63
|
+
|
64
|
+
def wrap_name(name)
|
65
|
+
return name if @sym.nil?
|
66
|
+
"#{@sym}[#{name}]"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -1,23 +1,20 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
3
2
|
module Lipstick
|
4
3
|
module Helpers
|
5
4
|
module LayoutHelper
|
6
|
-
|
7
|
-
include ActionView::Helpers::TextHelper
|
8
|
-
include ActionView::Helpers::CaptureHelper
|
9
|
-
|
10
|
-
def aaf_header(title:, environment: nil, &bl)
|
5
|
+
def aaf_header(title:, environment: nil, auth: nil, &bl)
|
11
6
|
content_tag('div', class: 'aaf-header') do
|
12
|
-
concat(aaf_banner(title, environment))
|
7
|
+
concat(aaf_banner(title, environment, auth))
|
13
8
|
concat(capture(&bl))
|
14
9
|
end
|
15
10
|
end
|
16
11
|
|
17
|
-
def aaf_footer
|
12
|
+
def aaf_footer
|
18
13
|
content_tag('footer') do
|
19
|
-
|
20
|
-
|
14
|
+
content_tag('div', class: 'footer-content') do
|
15
|
+
concat(aaf_logo)
|
16
|
+
concat(capture { yield })
|
17
|
+
end
|
21
18
|
end
|
22
19
|
end
|
23
20
|
|
@@ -31,25 +28,23 @@ module Lipstick
|
|
31
28
|
end
|
32
29
|
|
33
30
|
def page_header(header, subheader = nil)
|
34
|
-
content_tag('
|
35
|
-
|
36
|
-
|
37
|
-
concat(
|
31
|
+
content_tag('div', class: 'page-header') do
|
32
|
+
content_tag('h1') do
|
33
|
+
concat(header)
|
34
|
+
concat(' ')
|
35
|
+
concat(content_tag('small', subheader)) if subheader
|
38
36
|
end
|
39
37
|
end
|
40
38
|
end
|
41
39
|
|
42
|
-
def divider_tag
|
43
|
-
content_tag('div', '', class: 'ui divider')
|
44
|
-
end
|
45
|
-
|
46
40
|
def yes_no_string(boolean)
|
47
41
|
boolean ? 'Yes' : 'No'
|
48
42
|
end
|
49
43
|
|
50
44
|
def icon_tag(icon_class, html_opts = {})
|
51
|
-
html_opts[:class] =
|
52
|
-
|
45
|
+
html_opts[:class] =
|
46
|
+
"#{html_opts[:class]} glyphicon glyphicon-#{icon_class}".strip
|
47
|
+
content_tag('span', '', html_opts)
|
53
48
|
end
|
54
49
|
|
55
50
|
# button_link_to(url_opts) { 'Link Text' }
|
@@ -60,77 +55,123 @@ module Lipstick
|
|
60
55
|
args.unshift(capture(&block)) if block_given?
|
61
56
|
text, url_opts, html_opts = args
|
62
57
|
html_opts ||= {}
|
63
|
-
html_opts[:class]
|
58
|
+
html_opts[:class] ||= 'btn-default'
|
59
|
+
html_opts[:class] = "#{html_opts[:class]} btn".strip
|
64
60
|
link_to(text, url_opts, html_opts)
|
65
61
|
end
|
66
62
|
|
67
63
|
def info_message(title, &block)
|
68
|
-
|
64
|
+
alert_block(title, 'info', &block)
|
69
65
|
end
|
70
66
|
|
71
67
|
def error_message(title, &block)
|
72
|
-
|
68
|
+
alert_block(title, 'danger', &block)
|
73
69
|
end
|
74
70
|
|
75
71
|
def success_message(title, &block)
|
76
|
-
|
72
|
+
alert_block(title, 'success', &block)
|
77
73
|
end
|
78
74
|
|
79
75
|
def warning_message(title, &block)
|
80
|
-
|
76
|
+
alert_block(title, 'warning', &block)
|
81
77
|
end
|
82
78
|
|
83
79
|
def breadcrumbs(*links)
|
84
|
-
content_tag('
|
80
|
+
content_tag('ol', class: 'breadcrumb') do
|
85
81
|
last = links.pop
|
86
82
|
|
87
83
|
links.each do |link|
|
88
|
-
concat(content_tag('
|
89
|
-
concat(icon_tag('angle double right divider'))
|
84
|
+
concat(content_tag('li', breadcrumb_link(link)))
|
90
85
|
end
|
91
86
|
|
92
|
-
concat(content_tag('
|
93
|
-
class: 'active
|
87
|
+
concat(content_tag('li', breadcrumb_link(last),
|
88
|
+
class: 'active'))
|
94
89
|
end
|
95
90
|
end
|
96
91
|
|
92
|
+
def will_paginate(_ = nil, options = {})
|
93
|
+
options[:renderer] ||= Lipstick::Helpers::PaginationLinkRenderer
|
94
|
+
super
|
95
|
+
end
|
96
|
+
|
97
|
+
DISABLE_ANIMATIONS_CSS = <<-EOF
|
98
|
+
.ui, .ui * {
|
99
|
+
-webkit-animation-duration: 0ms !important;
|
100
|
+
-moz-animation-duration: 0ms !important;
|
101
|
+
-ms-animation-duration: 0ms !important;
|
102
|
+
animation-duration: 0ms !important;
|
103
|
+
}
|
104
|
+
EOF
|
105
|
+
private_constant :DISABLE_ANIMATIONS_CSS
|
106
|
+
|
107
|
+
def disable_animations
|
108
|
+
content_tag('style', DISABLE_ANIMATIONS_CSS, type: 'text/css')
|
109
|
+
end
|
110
|
+
|
97
111
|
private
|
98
112
|
|
99
|
-
def aaf_banner(title, environment)
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
concat(tag('img', src: image_path('logo.png'), class: 'logo',
|
106
|
-
width: 138, height: 80))
|
107
|
-
concat(content_tag('div', title, class: 'content'))
|
113
|
+
def aaf_banner(title, environment, auth)
|
114
|
+
capture do
|
115
|
+
text = content_tag('header') do
|
116
|
+
concat(aaf_links(auth))
|
117
|
+
concat(title)
|
118
|
+
concat(aaf_environment_string(environment))
|
108
119
|
end
|
109
|
-
concat(
|
120
|
+
concat(text)
|
110
121
|
end
|
111
122
|
end
|
112
123
|
|
113
|
-
def
|
114
|
-
|
115
|
-
content_tag('div', environment, class: 'environment')
|
124
|
+
def aaf_logo
|
125
|
+
content_tag('span', '', class: 'pull-right logo hidden-xs hidden-sm')
|
116
126
|
end
|
117
127
|
|
118
|
-
def
|
119
|
-
|
120
|
-
|
121
|
-
|
128
|
+
def aaf_links(auth)
|
129
|
+
content_tag('div', class: 'aaf-links pull-right') do
|
130
|
+
concat(aaf_home_link)
|
131
|
+
concat(aaf_support_link)
|
132
|
+
concat(aaf_auth_link(auth))
|
122
133
|
end
|
123
|
-
super
|
124
134
|
end
|
125
135
|
|
126
|
-
def
|
127
|
-
content_tag('
|
128
|
-
concat(icon_tag(
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
136
|
+
def aaf_home_link
|
137
|
+
content_tag('a', href: 'https://aaf.edu.au') do
|
138
|
+
concat(icon_tag('aaf'))
|
139
|
+
concat(' AAF Home')
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def aaf_support_link
|
144
|
+
content_tag('a', href: 'https://support.aaf.edu.au') do
|
145
|
+
concat(icon_tag('user'))
|
146
|
+
concat(' Support')
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def aaf_auth_link(auth)
|
151
|
+
return if auth.nil?
|
152
|
+
|
153
|
+
text = ' Log In'
|
154
|
+
text = ' Log Out' if auth == :logout
|
155
|
+
|
156
|
+
icon = 'log-in'
|
157
|
+
icon = 'log-out' if auth == :logout
|
158
|
+
|
159
|
+
content_tag('a', href: "/auth/#{auth}") do
|
160
|
+
concat(icon_tag(icon))
|
161
|
+
concat(text)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def aaf_environment_string(environment)
|
166
|
+
return unless environment&.present?
|
167
|
+
content_tag('span', environment, class: 'environment')
|
168
|
+
end
|
169
|
+
|
170
|
+
def alert_block(title, color_class, &block)
|
171
|
+
opts = { class: "alert alert-#{color_class}", role: 'alert' }
|
172
|
+
content_tag('div', opts) do
|
173
|
+
concat(content_tag('h4', title))
|
174
|
+
concat(capture(&block))
|
134
175
|
end
|
135
176
|
end
|
136
177
|
|
@@ -1,48 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Lipstick
|
2
3
|
module Helpers
|
3
4
|
module NavHelper
|
4
|
-
|
5
|
-
|
5
|
+
def nav_bar
|
6
|
+
content_tag('nav', class: 'navbar shrink') { yield }
|
7
|
+
end
|
6
8
|
|
7
|
-
def
|
8
|
-
content_tag('nav') do
|
9
|
-
|
10
|
-
content_tag('
|
11
|
-
concat(nav_collapse_button)
|
12
|
-
concat(capture(&block))
|
13
|
-
end
|
9
|
+
def nav_first_item(text, url)
|
10
|
+
content_tag('div', class: 'nav navbar-header') do
|
11
|
+
concat(nav_collapse_button)
|
12
|
+
concat(content_tag('a', text, href: url, class: 'navbar-brand'))
|
14
13
|
end
|
15
14
|
end
|
16
15
|
|
17
|
-
def nav_collapsing_items
|
18
|
-
|
16
|
+
def nav_collapsing_items
|
17
|
+
attrs = { class: 'collapse navbar-collapse', id: 'aaf-nav-collapse' }
|
18
|
+
content_tag('div', attrs) do
|
19
|
+
content_tag('ul', class: 'nav navbar-nav') { yield }
|
20
|
+
end
|
19
21
|
end
|
20
22
|
|
21
23
|
def nav_item(text, url, html_opts = {})
|
22
|
-
|
23
|
-
|
24
|
-
end
|
25
|
-
|
26
|
-
def nav_items_right(&block)
|
27
|
-
content_tag('div', class: 'right menu', &block)
|
28
|
-
end
|
29
|
-
|
30
|
-
def nav_dropdown(title, &block)
|
31
|
-
content_tag('div', class: 'ui simple dropdown item') do
|
32
|
-
concat(content_tag('i', '', class: 'dropdown icon'))
|
33
|
-
concat(title)
|
34
|
-
concat(content_tag('div', class: 'menu', &block))
|
24
|
+
content_tag('li') do
|
25
|
+
content_tag('a', text, html_opts.merge(href: url))
|
35
26
|
end
|
36
27
|
end
|
37
28
|
|
38
29
|
private
|
39
30
|
|
40
31
|
def nav_collapse_button
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
32
|
+
attrs = {
|
33
|
+
type: 'button', class: 'navbar-toggle collapsed',
|
34
|
+
'data-toggle': 'collapse', 'data-target': '#aaf-nav-collapse',
|
35
|
+
'aria-expanded': 'false'
|
36
|
+
}
|
37
|
+
|
38
|
+
content_tag('button', attrs) do
|
39
|
+
concat(content_tag('span', 'Toggle navigation', class: 'sr-only'))
|
40
|
+
3.times { concat(content_tag('span', '', class: 'icon-bar')) }
|
46
41
|
end
|
47
42
|
end
|
48
43
|
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'will_paginate'
|
3
|
+
|
4
|
+
module Lipstick
|
5
|
+
module Helpers
|
6
|
+
class PaginationLinkRenderer
|
7
|
+
attr_reader :items, :current, :total, :template
|
8
|
+
private :current, :total, :template
|
9
|
+
|
10
|
+
def prepare(collection, _options, template)
|
11
|
+
@current = collection.current_page
|
12
|
+
@total = collection.total_pages
|
13
|
+
|
14
|
+
@items = [*prefix, *middle, *suffix]
|
15
|
+
@template = template
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_html
|
19
|
+
template.content_tag('nav', class: 'pagination-wrapper') do
|
20
|
+
template.content_tag('ul', class: 'pagination') do
|
21
|
+
items.each do |item|
|
22
|
+
template.concat(render_item(item))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def prefix
|
31
|
+
return [:prev] if !show_first? || short_list?
|
32
|
+
return [:prev, 1, :gap] if prefix_gap?
|
33
|
+
[:prev, 1]
|
34
|
+
end
|
35
|
+
|
36
|
+
def middle
|
37
|
+
return page_range(1, total) if short_list?
|
38
|
+
page_range(*page_window)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Decides which pages to show in the links surrounding the "current" page.
|
42
|
+
def page_window
|
43
|
+
# If we're close to the start/end we can show a few extra page links.
|
44
|
+
return [1, 8] if current < 5
|
45
|
+
return [total - 7, total] if current > total - 4
|
46
|
+
|
47
|
+
# Otherwise show 3 pages either side of the current page
|
48
|
+
[current - 3, current + 3]
|
49
|
+
end
|
50
|
+
|
51
|
+
def suffix
|
52
|
+
return [:next] if !show_last? || short_list?
|
53
|
+
return [:gap, total, :next] if suffix_gap?
|
54
|
+
[total, :next]
|
55
|
+
end
|
56
|
+
|
57
|
+
def page_range(start, finish)
|
58
|
+
[start, 1].max.upto([finish, total].min)
|
59
|
+
end
|
60
|
+
|
61
|
+
def short_list?
|
62
|
+
total <= 10
|
63
|
+
end
|
64
|
+
|
65
|
+
def show_first?
|
66
|
+
!middle.include?(1)
|
67
|
+
end
|
68
|
+
|
69
|
+
def show_last?
|
70
|
+
!middle.include?(total)
|
71
|
+
end
|
72
|
+
|
73
|
+
def prefix_gap?
|
74
|
+
!middle.include?(2)
|
75
|
+
end
|
76
|
+
|
77
|
+
def suffix_gap?
|
78
|
+
!middle.include?(total - 1)
|
79
|
+
end
|
80
|
+
|
81
|
+
def render_item(item)
|
82
|
+
case item
|
83
|
+
when :prev
|
84
|
+
prev_item
|
85
|
+
when :next
|
86
|
+
next_item
|
87
|
+
when :gap
|
88
|
+
gap_item
|
89
|
+
else
|
90
|
+
link_to_page(item, page: item)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def prev_item
|
95
|
+
icon = template.content_tag('span', "\u25C0", 'aria-hidden': 'true')
|
96
|
+
|
97
|
+
return disabled_item(icon) if current < 2
|
98
|
+
link_to_page(icon, page: current - 1, 'aria-label': 'Previous')
|
99
|
+
end
|
100
|
+
|
101
|
+
def next_item
|
102
|
+
icon = template.content_tag('span', "\u25B6", 'aria-hidden': 'true')
|
103
|
+
|
104
|
+
return disabled_item(icon) if current >= total
|
105
|
+
link_to_page(icon, page: current + 1, 'aria-label': 'Next')
|
106
|
+
end
|
107
|
+
|
108
|
+
def gap_item
|
109
|
+
template.content_tag('li') do
|
110
|
+
template.content_tag('span', "\u2026", 'aria-hidden': 'true')
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def link_to_page(content, page:, **opts)
|
115
|
+
li_opts = {}
|
116
|
+
li_opts[:class] = 'active' if page == current
|
117
|
+
|
118
|
+
url_opts = template.params.merge(page: page)
|
119
|
+
opts = opts.merge(href: template.url_for(url_opts))
|
120
|
+
|
121
|
+
template.content_tag('li', li_opts) do
|
122
|
+
template.content_tag('a', content, opts)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def disabled_item(content)
|
127
|
+
template.content_tag('li', content, class: 'disabled')
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|