express_admin 1.7.8 → 1.7.9
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/app/assets/javascripts/ace/mode-css.js +829 -0
- data/app/assets/stylesheets/express_admin/screen.sass +1 -0
- data/app/assets/stylesheets/express_admin/shared/_buttons.sass +2 -0
- data/app/assets/stylesheets/express_admin/shared/_forms.sass +1 -1
- data/app/components/express_admin/file_upload.rb +131 -0
- data/app/components/express_admin/media_form.rb +27 -0
- data/app/components/express_admin/oauth_sign_in_links.rb +28 -0
- data/app/components/express_admin/smart_table.rb +51 -5
- data/app/helpers/express_admin/admin_helper.rb +0 -4
- data/app/views/devise/sessions/new.html.et +2 -5
- data/lib/express_admin/engine.rb +14 -14
- data/lib/express_admin/standard_actions.rb +13 -1
- data/lib/express_admin/version.rb +1 -1
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/test/components/smart_table_test.rb +37 -1
- metadata +7 -2
@@ -0,0 +1,131 @@
|
|
1
|
+
module ExpressAdmin
|
2
|
+
module Components
|
3
|
+
class FileUpload < ExpressTemplates::Components::Forms::FormComponent
|
4
|
+
|
5
|
+
has_option :action, 'The form action containing the resource path or url.'
|
6
|
+
has_option :max_file_size, 'The maximum file size a user can upload', type: :int
|
7
|
+
|
8
|
+
contains -> {
|
9
|
+
div(class: 'dropzone-previews') {
|
10
|
+
div(class: 'dz-default dz-message') {
|
11
|
+
span(class: 'dz-message-instruction') {'Drop files here or click to upload.'}
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
15
|
+
div(class: 'dz-preview dz-file-preview', id: 'preview-template'){
|
16
|
+
div(class: 'dz-error-message hide') {
|
17
|
+
span('data-dz-errormessage' => 'true')
|
18
|
+
}
|
19
|
+
div(class: 'dz-image'){
|
20
|
+
img('data-dz-thumbnail' => true)
|
21
|
+
}
|
22
|
+
div(class: 'dz-close hide') {
|
23
|
+
icon_link('close-round', 'data-dz-remove' => true)
|
24
|
+
}
|
25
|
+
div(class: 'dz-details hide'){
|
26
|
+
div(class: 'dz-filename'){
|
27
|
+
span('data-dz-name' => true)
|
28
|
+
}
|
29
|
+
div(class: 'dz-size', 'data-dz-size' => true)
|
30
|
+
}
|
31
|
+
div(class: 'dz-progress') {
|
32
|
+
span(class: 'dz-upload', 'data-dz-uploadprogress' => true)
|
33
|
+
}
|
34
|
+
div(class: 'dz-success-mark hide') {
|
35
|
+
icon('checkmark-circled')
|
36
|
+
}
|
37
|
+
div(class: 'dz-error-mark hide') {
|
38
|
+
icon('close-circled')
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
content_for(:page_javascript) {
|
43
|
+
script {
|
44
|
+
%Q(
|
45
|
+
$(function() {
|
46
|
+
previewNode = $('#preview-template').get(0).outerHTML;
|
47
|
+
$('#preview-template').remove();
|
48
|
+
var submitButton;
|
49
|
+
Dropzone.options.#{config[:id].to_s.camelize(:lower)} = {
|
50
|
+
url: window.location.origin + '#{config[:action]}',
|
51
|
+
paramName: '#{config[:id]}[file]',
|
52
|
+
autoProcessQueue: false,
|
53
|
+
uploadMultiple: false,
|
54
|
+
maxFiles: 1,
|
55
|
+
maxFilesize: #{max_file_size},
|
56
|
+
previewTemplate: previewNode,
|
57
|
+
previewsContainer: '.dropzone-previews',
|
58
|
+
clickable: '.dropzone-previews',
|
59
|
+
init: function() {
|
60
|
+
var myDropzone = this;
|
61
|
+
submitButton = this.element.querySelector('input[type=submit]');
|
62
|
+
|
63
|
+
$(submitButton).on('click', function(e) {
|
64
|
+
e.preventDefault();
|
65
|
+
myDropzone.processQueue();
|
66
|
+
});
|
67
|
+
|
68
|
+
this.on('addedfile', function(file) {
|
69
|
+
if (this.files[1]) {
|
70
|
+
this.removeFile(this.files[0]);
|
71
|
+
}
|
72
|
+
$('.dz-message.dz-default').addClass('hide');
|
73
|
+
});
|
74
|
+
|
75
|
+
this.on('removedfile', function() {
|
76
|
+
$('.dz-message.dz-default').removeClass('hide');
|
77
|
+
$(submitButton).removeClass('disabled');
|
78
|
+
});
|
79
|
+
|
80
|
+
this.on('processing', function() {
|
81
|
+
$('.dz-image').zIndex('-999');
|
82
|
+
$('.dz-image > img').addClass('blur');
|
83
|
+
$(submitButton).addClass('disabled');
|
84
|
+
});
|
85
|
+
|
86
|
+
this.on('maxfilesexceeded', function(file) {
|
87
|
+
this.removeAllFiles();
|
88
|
+
this.addFile(file);
|
89
|
+
});
|
90
|
+
|
91
|
+
this.on('success', function(file) {
|
92
|
+
$('.dz-progress').css('opacity', 0);
|
93
|
+
$('.dz-success-mark').removeClass('hide');
|
94
|
+
setTimeout( function() {
|
95
|
+
$('.dz-image').zIndex('999');
|
96
|
+
$('.dz-image > img').removeClass('blur');
|
97
|
+
location.reload();
|
98
|
+
}, 2000);
|
99
|
+
});
|
100
|
+
|
101
|
+
this.on('complete', function(file) {
|
102
|
+
if (file.accepted === false) {
|
103
|
+
$('.dz-progress').css('opacity', 0);
|
104
|
+
setTimeout(function() { $('.dz-error-mark').removeClass('hide') }, 500);
|
105
|
+
|
106
|
+
setTimeout(function() {
|
107
|
+
$('.dz-image').zIndex('999');
|
108
|
+
$('.dz-image > img').removeClass('blur');
|
109
|
+
$('.dz-error-message').removeClass('hide');
|
110
|
+
$(submitButton).addClass('disabled');
|
111
|
+
}, 3000);
|
112
|
+
}
|
113
|
+
});
|
114
|
+
|
115
|
+
this.on('drop', function() {
|
116
|
+
$('.dz-message.dz-default').addClass('hide');
|
117
|
+
});
|
118
|
+
},
|
119
|
+
}
|
120
|
+
});
|
121
|
+
).html_safe
|
122
|
+
}
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
def max_file_size
|
127
|
+
config[:max_file_size]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ExpressAdmin
|
2
|
+
module Components
|
3
|
+
class MediaForm < ExpressTemplates::Components::Configurable
|
4
|
+
|
5
|
+
has_option :max_file_size, 'The maximum file size a user can upload', type: :int, default: 3
|
6
|
+
|
7
|
+
contains -> {
|
8
|
+
express_form(config[:id], enctype: 'multipart/form-data', class: 'dropzone'){
|
9
|
+
text :title
|
10
|
+
text :description
|
11
|
+
label_tag('tags', "Tags")
|
12
|
+
select_tag("media_item[tags]", helpers.options_for_select(ExpressSite::Tag.all.map(&:name), media_item.tags.map(&:name)), class: 'select2', multiple: true)
|
13
|
+
file_upload config[:id], action: form_action, max_file_size: max_file_size
|
14
|
+
submit class: 'button', value: 'Upload'
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
def form_action
|
19
|
+
config[:action] || (resource.try(:persisted?) ? resource_path(resource) : collection_path)
|
20
|
+
end
|
21
|
+
|
22
|
+
def max_file_size
|
23
|
+
config[:max_file_size]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ExpressAdmin
|
2
|
+
class OauthSignInLinks < ExpressTemplates::Components::Configurable
|
3
|
+
|
4
|
+
has_argument :providers, "OAuth providers to generate links for", as: :providers, type: :array
|
5
|
+
|
6
|
+
contains -> {
|
7
|
+
span(class: 'text-gray') { "Login with:" }
|
8
|
+
providers.each do |provider|
|
9
|
+
sign_in_link(provider)
|
10
|
+
end
|
11
|
+
}
|
12
|
+
|
13
|
+
def sign_in_link(provider)
|
14
|
+
link_to "#{provider_name(provider)}", user_omniauth_authorize_path(provider), class: 'hollow button button-login'
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def provider_name(provider)
|
20
|
+
provider.to_s.humanize
|
21
|
+
end
|
22
|
+
|
23
|
+
def providers
|
24
|
+
config[:providers]
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -8,6 +8,11 @@ module ExpressAdmin
|
|
8
8
|
|
9
9
|
MAX_COLS_TO_SHOW_IDX = 5
|
10
10
|
MAX_ROWS_TO_SHOW_IDX = 10
|
11
|
+
COLUMN_REGEX_LIST = {
|
12
|
+
link: /(\w+)_link$/,
|
13
|
+
checkmark: /(\w+)_checkmark/,
|
14
|
+
in_words: /(\w+)_in_words/
|
15
|
+
}
|
11
16
|
|
12
17
|
attr :columns
|
13
18
|
|
@@ -33,6 +38,9 @@ module ExpressAdmin
|
|
33
38
|
end
|
34
39
|
options = options + resource_class.instance_methods.grep(/_count$/).map(&:to_s)
|
35
40
|
}
|
41
|
+
has_option :sortable, 'Specify the target column. Provide column sort with caret display to signify column is sortable',
|
42
|
+
type: :array,
|
43
|
+
default: nil
|
36
44
|
has_option :rows, 'Specify the number of rows to show', type: :integer
|
37
45
|
|
38
46
|
has_option :pagination, 'Add pagination to the bottom of the table', type: :string, default: 'bottom'
|
@@ -50,7 +58,7 @@ module ExpressAdmin
|
|
50
58
|
tr {
|
51
59
|
display_columns.each do |column|
|
52
60
|
th(class: column.name) {
|
53
|
-
column.title
|
61
|
+
config[:sortable].present? ? sortable(column) : column.title
|
54
62
|
}
|
55
63
|
end
|
56
64
|
actions_header if should_show_actions?
|
@@ -82,6 +90,31 @@ module ExpressAdmin
|
|
82
90
|
_initialize_columns
|
83
91
|
}
|
84
92
|
|
93
|
+
def sortable(column)
|
94
|
+
table_column = column.table_column
|
95
|
+
if config[:sortable].include?(column.title) && table_column.present?
|
96
|
+
a(href: sort_link(column)) {
|
97
|
+
text_node column.title
|
98
|
+
if helpers.params[:asc].eql?(table_column)
|
99
|
+
i(class: 'icon ion-ios-arrow-up')
|
100
|
+
else
|
101
|
+
i(class: 'icon ion-ios-arrow-down')
|
102
|
+
end
|
103
|
+
}
|
104
|
+
else
|
105
|
+
column.title
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def sort_link(column)
|
110
|
+
uri = URI.parse(config[:id].to_s)
|
111
|
+
table_column = column.table_column
|
112
|
+
param_asc = helpers.params[:asc]
|
113
|
+
sort_direction = param_asc.eql?(table_column) ? :desc : :asc
|
114
|
+
uri.query = { sort_direction => table_column }.to_param
|
115
|
+
uri.to_s
|
116
|
+
end
|
117
|
+
|
85
118
|
def pagination
|
86
119
|
paginate collection, :route_set => route_set
|
87
120
|
end
|
@@ -154,12 +187,12 @@ module ExpressAdmin
|
|
154
187
|
rescue
|
155
188
|
'Error: '+$!.to_s
|
156
189
|
end
|
157
|
-
elsif attrib = accessor.to_s.match(
|
190
|
+
elsif attrib = accessor.to_s.match(COLUMN_REGEX_LIST[:link]).try(:[], 1)
|
158
191
|
# TODO: only works with non-namespaced routes
|
159
192
|
helpers.link_to item.send(attrib), resource_path(item)
|
160
|
-
elsif attrib = accessor.to_s.match(
|
193
|
+
elsif attrib = accessor.to_s.match(COLUMN_REGEX_LIST[:checkmark]).try(:[], 1)
|
161
194
|
"<i class='ion-checkmark-round'></i>".html_safe if item.send(attrib)
|
162
|
-
elsif attrib = accessor.to_s.match(
|
195
|
+
elsif attrib = accessor.to_s.match(COLUMN_REGEX_LIST[:in_words]).try(:[], 1)
|
163
196
|
if item.send(attrib)
|
164
197
|
if item.send(attrib) < DateTime.now
|
165
198
|
"#{helpers.time_ago_in_words(item.send(attrib))} ago"
|
@@ -193,11 +226,24 @@ module ExpressAdmin
|
|
193
226
|
end
|
194
227
|
|
195
228
|
class Column
|
196
|
-
attr :name, :title, :accessor
|
229
|
+
attr :name, :title, :accessor, :table_column
|
197
230
|
def initialize(accessor, title = nil)
|
198
231
|
@name = accessor.kind_of?(Symbol) ? accessor.to_s.underscore : title.titleize.gsub(/\s+/,'').underscore
|
199
232
|
@accessor = accessor
|
200
233
|
@title = title || @name.titleize
|
234
|
+
@table_column = table_column
|
235
|
+
end
|
236
|
+
|
237
|
+
def table_column
|
238
|
+
if accessor.kind_of?(Symbol)
|
239
|
+
COLUMN_REGEX_LIST.map do |key, value|
|
240
|
+
accessor_match(COLUMN_REGEX_LIST[key])
|
241
|
+
end.compact.first
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def accessor_match(regex)
|
246
|
+
accessor.to_s.match(regex).try(:[], 1)
|
201
247
|
end
|
202
248
|
end
|
203
249
|
|
@@ -1,9 +1,5 @@
|
|
1
1
|
module ExpressAdmin
|
2
2
|
module AdminHelper
|
3
|
-
|
4
|
-
# TODO move this out
|
5
|
-
#include ExpressMedia::Admin::MediaHelper if Kernel.const_defined?("ExpressMedia::Engine")
|
6
|
-
|
7
3
|
def title_partial
|
8
4
|
(ExpressAdmin::Engine.config.title_partial rescue nil) || 'shared/express_admin/title'
|
9
5
|
end
|
@@ -51,13 +51,10 @@ div(class: 'grid-container') {
|
|
51
51
|
}
|
52
52
|
|
53
53
|
hr
|
54
|
-
|
55
|
-
a(href: "/users/auth/appexpress") { "Sign in with appexpress" }
|
56
|
-
br
|
57
|
-
}
|
54
|
+
oauth_sign_in_links(Devise.omniauth_providers)
|
58
55
|
}
|
59
56
|
}
|
60
57
|
}
|
61
58
|
}
|
62
59
|
}
|
63
|
-
}
|
60
|
+
}
|
data/lib/express_admin/engine.rb
CHANGED
@@ -26,19 +26,19 @@ components.each {|component| require component }
|
|
26
26
|
module ExpressAdmin
|
27
27
|
class Engine < ::Rails::Engine
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
29
|
+
initializer :assets do |config|
|
30
|
+
engine_assets_path = File.join(File.dirname(__FILE__), '..', '..', 'app', 'assets')
|
31
|
+
all_assets = Dir.glob File.join(engine_assets_path, 'stylesheets', '**', '*.css*')
|
32
|
+
all_assets += Dir.glob File.join(engine_assets_path, 'javascripts', '**', '*.js*')
|
33
|
+
all_assets.each {|path| path.gsub!("#{engine_assets_path}/stylesheets/", '')}
|
34
|
+
all_assets.each {|path| path.gsub!("#{engine_assets_path}/javascripts/", '')}
|
35
|
+
all_assets.each {|path| path.gsub!("#{engine_assets_path}/fonts/", '')}
|
36
|
+
all_assets.each {|path| path.gsub!(/.(scss|coffee)$/, '')}
|
37
|
+
Rails.application.config.assets.paths << Rails.root.join('app', 'assets', 'fonts')
|
38
|
+
Rails.application.config.assets.precompile << /\.(?:svg|eot|woff|ttf|png|jpg|jpeg|gif)$/
|
39
|
+
Rails.application.config.assets.precompile << /\.(?:mp4|webm|mp3)$/
|
40
|
+
Rails.application.config.assets.precompile += all_assets
|
41
|
+
end
|
42
42
|
|
43
43
|
def all_rails_engines
|
44
44
|
Rails.application.eager_load!
|
@@ -59,4 +59,4 @@ module ExpressAdmin
|
|
59
59
|
class Railtie < ::Rails::Railtie
|
60
60
|
config.app_generators.template_engine :et
|
61
61
|
end
|
62
|
-
end
|
62
|
+
end
|
@@ -238,13 +238,25 @@ module ExpressAdmin
|
|
238
238
|
resource_class
|
239
239
|
end
|
240
240
|
scope = search?(scope)
|
241
|
-
|
241
|
+
scope = instance_variable(scope)
|
242
|
+
scope = params[:asc] || params[:desc] ? sort_table(scope) : scope
|
243
|
+
self.instance_variable_set(collection_ivar, scope)
|
242
244
|
end
|
243
245
|
|
244
246
|
def instance_variable(scope)
|
245
247
|
scope.kind_of?(Array) ? scope : scope.all
|
246
248
|
end
|
247
249
|
|
250
|
+
def sort_table(scope)
|
251
|
+
if params.keys.include?('asc')
|
252
|
+
scope.sort{ |a, b| a.send(params[:asc]) <=> b.send(params[:asc])}
|
253
|
+
elsif params.keys.include?('desc')
|
254
|
+
scope.sort{ |a, b| b.send(params[:desc]) <=> a.send(params[:desc])}
|
255
|
+
else
|
256
|
+
scope
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
248
260
|
def search?(scope)
|
249
261
|
params[:search_string].present? ? search(scope) : scope
|
250
262
|
end
|
data/test/dummy/db/test.sqlite3
CHANGED
Binary file
|
@@ -100,6 +100,42 @@ module Components
|
|
100
100
|
assert_match /class="created_at.*less than a minute ago/, fragment
|
101
101
|
end
|
102
102
|
|
103
|
-
|
103
|
+
test 'assign column to be sortable which generates a header link' do
|
104
|
+
fragment = arbre(widget: Widget.first, widgets: Widget.all){
|
105
|
+
smart_table(:widgets,
|
106
|
+
sortable: ['Created'],
|
107
|
+
columns: {
|
108
|
+
'Created' => :created_at_in_words
|
109
|
+
})
|
110
|
+
}
|
111
|
+
|
112
|
+
assert_match "href=\"widgets?asc=created_at\"", fragment
|
113
|
+
end
|
114
|
+
|
115
|
+
test 'assign unknown column to sortable which should not generate a header link' do
|
116
|
+
fragment = arbre(widget: Widget.first, widgets: Widget.all){
|
117
|
+
smart_table(:widgets,
|
118
|
+
sortable: ['Ghost'],
|
119
|
+
columns: {
|
120
|
+
'Created' => :created_at_in_words
|
121
|
+
})
|
122
|
+
}
|
104
123
|
|
124
|
+
assert_no_match "href=\"widgets?asc=ghost\"", fragment
|
125
|
+
end
|
126
|
+
|
127
|
+
test 'assign column with proc value to sortable which should not generate header link' do
|
128
|
+
fragment = arbre(widget: Widget.first, widgets: Widget.all){
|
129
|
+
smart_table(:widgets,
|
130
|
+
sortable: ['This column in will not be a link'],
|
131
|
+
columns: {
|
132
|
+
'Created' => :created_at_in_words,
|
133
|
+
"This column will not be a link" => -> (widget) { widget.column2.upcase },
|
134
|
+
})
|
135
|
+
}
|
136
|
+
|
137
|
+
assert_match 'This column will not be a link', fragment
|
138
|
+
assert_no_match 'href=', fragment
|
139
|
+
end
|
140
|
+
end
|
105
141
|
end
|