aaf-lipstick 1.1.0 → 2.0.0
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.
- 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
|