frontpack 0.1.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +51 -0
- data/Rakefile +5 -0
- data/app/assets/config/frontpack_manifest.js +1 -0
- data/app/assets/images/logo.svg +141 -0
- data/app/assets/javascripts/frontpack/auto-complete.js +173 -0
- data/app/assets/javascripts/frontpack/base-webcomponent.js +160 -0
- data/app/assets/javascripts/frontpack/expandable-list.js +99 -0
- data/app/assets/javascripts/frontpack/image-preview.js +160 -0
- data/app/assets/javascripts/frontpack/image-uploader.js +34 -0
- data/app/assets/javascripts/frontpack/localized-time.js +17 -0
- data/app/assets/javascripts/frontpack/modal-dialog.js +206 -0
- data/app/assets/javascripts/frontpack/shortcuts.js +63 -0
- data/app/assets/javascripts/frontpack/tab-control.js +77 -0
- data/app/assets/javascripts/frontpack/text-box.js +70 -0
- data/app/assets/javascripts/frontpack/toggle-button.js +73 -0
- data/app/assets/stylesheets/frontpack/000-reset.scss +15 -0
- data/app/assets/stylesheets/frontpack/010-config.scss +148 -0
- data/app/assets/stylesheets/frontpack/020-mixins.scss +218 -0
- data/app/assets/stylesheets/frontpack/030-consts.scss +13 -0
- data/app/assets/stylesheets/frontpack/031-margins.scss +5 -0
- data/app/assets/stylesheets/frontpack/100-layout.scss +113 -0
- data/app/assets/stylesheets/frontpack/101-container.scss +131 -0
- data/app/assets/stylesheets/frontpack/200-design.scss +75 -0
- data/app/assets/stylesheets/frontpack/201-box.scss +6 -0
- data/app/assets/stylesheets/frontpack/202-borders.scss +5 -0
- data/app/assets/stylesheets/frontpack/300-typography.scss +128 -0
- data/app/assets/stylesheets/frontpack/800-components.scss +85 -0
- data/app/assets/stylesheets/frontpack/801-card.scss +53 -0
- data/app/assets/stylesheets/frontpack/802-buttons.scss +58 -0
- data/app/assets/stylesheets/frontpack/803-form-controls.scss +161 -0
- data/app/assets/stylesheets/frontpack/804-table.scss +60 -0
- data/app/assets/stylesheets/frontpack/805-switch-toggle.scss +43 -0
- data/app/assets/stylesheets/frontpack/frontpack.scss +18 -0
- data/app/controllers/concerns/frontpack/searchable.rb +33 -0
- data/app/controllers/frontpack/autocomplete_controller.rb +33 -0
- data/app/helpers/frontpack/application_helper.rb +7 -0
- data/app/helpers/frontpack/form_builder.rb +130 -0
- data/app/models/concerns/frontpack/auto_completable.rb +35 -0
- data/config/importmap.rb +1 -0
- data/config/routes.rb +3 -0
- data/lib/frontpack/button_to_patch.rb +64 -0
- data/lib/frontpack/engine.rb +28 -0
- data/lib/frontpack/extended_model_translations.rb +31 -0
- data/lib/frontpack/form_builder_options.rb +5 -0
- data/lib/frontpack/version.rb +5 -0
- data/lib/frontpack.rb +9 -0
- data/lib/generators/frontpack/install_generator.rb +37 -0
- data/lib/generators/frontpack/locale_generator.rb +11 -0
- data/lib/generators/templates/initializers/customize_form_with_errors.rb +31 -0
- data/lib/generators/templates/locales/frontpack.en.yml +43 -0
- data/lib/generators/templates/views/layouts/_form-errors.html.slim +3 -0
- data/lib/generators/templates/views/layouts/_navigation.html.slim +1 -0
- data/lib/generators/templates/views/layouts/application.html.slim +37 -0
- data/lib/generators/templates/views/layouts/errors.html.slim +19 -0
- data/lib/generators/templates/views/layouts/mailer.html.slim +6 -0
- data/lib/generators/templates/views/layouts/mailer.text.slim +1 -0
- data/lib/tasks/frontpack_tasks.rake +6 -0
- data/lib/templates/rails/file_utils/searchable.rb +30 -0
- data/lib/templates/rails/scaffold_controller/controller.rb.tt +93 -0
- data/lib/templates/slim/scaffold/_form.html.slim.tt +10 -0
- data/lib/templates/slim/scaffold/edit.html.slim.tt +10 -0
- data/lib/templates/slim/scaffold/index.html.slim.tt +20 -0
- data/lib/templates/slim/scaffold/new.html.slim.tt +8 -0
- data/lib/templates/slim/scaffold/partial.html.slim.tt +6 -0
- data/lib/templates/slim/scaffold/show.html.slim.tt +12 -0
- data/lib/templates/test_unit/model/fixtures.yml.tt +18 -0
- data/lib/templates/test_unit/model/unit_test.rb.tt +41 -0
- metadata +143 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Frontpack
|
4
|
+
# Adds dynamic search functionality
|
5
|
+
module Searchable
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
def basic_search(model, approximate_fields = [], exact_fields = [], include_list = nil)
|
9
|
+
search_conditions = prepare_search_fields model.arel_table, exact_fields, exact: true
|
10
|
+
search_conditions += prepare_search_fields model.arel_table, approximate_fields
|
11
|
+
|
12
|
+
query = include_list.present? ? model.includes(include_list) : model
|
13
|
+
query = query.where(*search_conditions) if search_conditions.length.positive?
|
14
|
+
|
15
|
+
query.page(params[:page]).per(params[:per_page])
|
16
|
+
end
|
17
|
+
|
18
|
+
def prepare_search_fields(arel_table, fields, **options)
|
19
|
+
[].tap do |search_conditions|
|
20
|
+
fields.each do |field|
|
21
|
+
val = params[field]
|
22
|
+
next if val.blank?
|
23
|
+
|
24
|
+
if options[:exact]
|
25
|
+
search_conditions.push(arel_table[field].eq(val))
|
26
|
+
else
|
27
|
+
search_conditions.push(arel_table[field].matches(Arel::Nodes::SqlLiteral.new("'%#{val}%'")))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Frontpack
|
2
|
+
# Autocomplete controller
|
3
|
+
class AutocompleteController < ApplicationController
|
4
|
+
before_action :set_model
|
5
|
+
|
6
|
+
def show
|
7
|
+
query = @model.autocomplete(params[:keyword], **autocomplete_params)
|
8
|
+
data = query.map do |row|
|
9
|
+
if row.is_a? String
|
10
|
+
{ id: row, value: row }
|
11
|
+
else
|
12
|
+
{
|
13
|
+
id: row.shift,
|
14
|
+
value: row.join(' ')
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
respond_to do |format|
|
19
|
+
format.json { render json: data }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def set_model
|
26
|
+
@model = params[:model].split('/').map(&:classify).join('::').constantize
|
27
|
+
end
|
28
|
+
|
29
|
+
def autocomplete_params
|
30
|
+
params.permit(*@model.autocomplete_fields)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Frontpack
|
4
|
+
# Custom input builders
|
5
|
+
class FormBuilder < ActionView::Helpers::FormBuilder
|
6
|
+
include Rails.application.routes.url_helpers
|
7
|
+
include ActionView::Helpers::UrlHelper
|
8
|
+
|
9
|
+
def enum_field(method, options = {})
|
10
|
+
excluded_keys = options.key?(:except) ? options[:except] : []
|
11
|
+
|
12
|
+
if @object
|
13
|
+
opts = { selected: @object.send(method) }
|
14
|
+
select method, @object.enum_options(method, excluded_keys), opts, options
|
15
|
+
elsif options.key?(:model)
|
16
|
+
model = options[:model]
|
17
|
+
select method, model.enum_options(method, excluded_keys), options.slice(:selected, :include_blank, :prompt), options
|
18
|
+
else
|
19
|
+
select method, [], opts, options
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def autocomplete_field(method, options = {})
|
24
|
+
relation = @object.class.reflect_on_association(method)
|
25
|
+
uri = relation.class_name.underscore.sub('::', '/')
|
26
|
+
display_field = relation.klass.autocomplete_display_fields[1]
|
27
|
+
text_field(relation.foreign_key, options.merge('data-url': "/autocomplete/#{uri}", is: 'auto-complete', 'display-value': @object.send(method)&.send(display_field)))
|
28
|
+
end
|
29
|
+
|
30
|
+
def text_field(method, options = {})
|
31
|
+
set_options(method, options) if @object
|
32
|
+
super(method, options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def password_field(method, options = {})
|
36
|
+
set_options(method, options) if @object
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
def email_field(method, options = {})
|
41
|
+
set_options(method, options) if @object
|
42
|
+
super
|
43
|
+
end
|
44
|
+
|
45
|
+
def text_area(method, options = {})
|
46
|
+
set_options(method, options) if @object
|
47
|
+
super
|
48
|
+
end
|
49
|
+
|
50
|
+
def label(method, text = nil, options = {}, &)
|
51
|
+
set_label_options method, options if @object
|
52
|
+
super(method, text, options, &)
|
53
|
+
end
|
54
|
+
|
55
|
+
def select(method, choices = nil, options = {}, html_options = {}, &)
|
56
|
+
set_options(method, html_options, css_class: Frontpack::FormBuilderOptions.select_class) if @object
|
57
|
+
super
|
58
|
+
end
|
59
|
+
|
60
|
+
def number_field(method, options = {})
|
61
|
+
set_options(method, options) if @object
|
62
|
+
|
63
|
+
super(method, options)
|
64
|
+
end
|
65
|
+
|
66
|
+
def date_field(method, options = {}, _html_options = {})
|
67
|
+
@template.text_field(@object_name, method, objectify_options(options.merge(type: :date)))
|
68
|
+
end
|
69
|
+
|
70
|
+
def switch_field(method, options = {})
|
71
|
+
@template.content_tag(:div, class: 'switch-toggle') do
|
72
|
+
toggle_box = check_box(method, { checked: @object.send(method) }.merge(options))
|
73
|
+
opts = options.merge(for: "#{@object_name}_#{method}")
|
74
|
+
opts[:style] = "--on-icon: '#{options[:'data-on-icon']}';" if options.key? :'data-on-icon'
|
75
|
+
opts[:style] += "--off-icon: '#{options[:'data-off-icon']}';" if options.key? :'data-off-icon'
|
76
|
+
toggle_box + @template.content_tag(:label, nil, opts)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def set_options(method, options, css_class: Frontpack::FormBuilderOptions.input_class)
|
83
|
+
@object.class.validators.map do |validator|
|
84
|
+
next unless validator.attributes.include?(method)
|
85
|
+
|
86
|
+
option_required(options) if validator.is_a? ::ActiveRecord::Validations::PresenceValidator
|
87
|
+
options[:maxlength] = validator.options[:maximum] if validator.is_a? ActiveRecord::Validations::LengthValidator
|
88
|
+
options[:size] = nil
|
89
|
+
set_numericality_options(validator, options) if validator.is_a? ActiveModel::Validations::NumericalityValidator
|
90
|
+
end
|
91
|
+
merge_css_classes(options, css_class)
|
92
|
+
end
|
93
|
+
|
94
|
+
def merge_css_classes(options, css_class)
|
95
|
+
classes = []
|
96
|
+
classes += options[:class].to_s.split if options.key? :class
|
97
|
+
classes << css_class
|
98
|
+
options[:class] = classes.join ' '
|
99
|
+
end
|
100
|
+
|
101
|
+
# @param [Hash] options
|
102
|
+
def option_required(options)
|
103
|
+
if options.key?(:required) && options[:required] == false
|
104
|
+
options.delete(:required)
|
105
|
+
return options
|
106
|
+
end
|
107
|
+
|
108
|
+
options[:required] = true
|
109
|
+
end
|
110
|
+
|
111
|
+
def set_numericality_options(validator, options)
|
112
|
+
if (gt = validator.options[:greater_than])
|
113
|
+
options[:min] = gt
|
114
|
+
end
|
115
|
+
if (lt = validator.options[:less_than])
|
116
|
+
options[:max] = lt
|
117
|
+
end
|
118
|
+
options[:step] = :any
|
119
|
+
end
|
120
|
+
|
121
|
+
def set_label_options(method, options)
|
122
|
+
@object.class.validators.map do |validator|
|
123
|
+
next unless validator.attributes.include?(method)
|
124
|
+
|
125
|
+
options[:'data-required'] = true if validator.is_a?(ActiveRecord::Validations::PresenceValidator)
|
126
|
+
end
|
127
|
+
merge_css_classes options, Frontpack::FormBuilderOptions.label_class
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Frontpack
|
2
|
+
module AutoCompletable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
cattr_reader :autocomplete_keyword, :autocomplete_fields
|
7
|
+
@autocomplete_display_fields = []
|
8
|
+
@autocomplete_key = :id
|
9
|
+
end
|
10
|
+
|
11
|
+
class_methods do
|
12
|
+
def autocomplete_by(keyword, *extra_filters)
|
13
|
+
@autocomplete_keyword = keyword.to_sym
|
14
|
+
@autocomplete_fields = extra_filters.map(&:to_sym)
|
15
|
+
end
|
16
|
+
|
17
|
+
def autocomplete_with(key, *display_fields)
|
18
|
+
@autocomplete_key = key.to_sym
|
19
|
+
@autocomplete_display_fields = display_fields.map(&:to_sym)
|
20
|
+
end
|
21
|
+
|
22
|
+
def autocomplete(keyword, **params)
|
23
|
+
keyword_field = arel_table[@autocomplete_keyword]
|
24
|
+
condition = Arel::Nodes::SqlLiteral.new("'%#{keyword}%'")
|
25
|
+
query = where(keyword_field.matches(condition))
|
26
|
+
params.each { |key, value| query = query.where(arel_table[key].eq(value)) }
|
27
|
+
query.select(autocomplete_display_fields).pluck(autocomplete_display_fields)
|
28
|
+
end
|
29
|
+
|
30
|
+
def autocomplete_display_fields
|
31
|
+
[@autocomplete_key, *@autocomplete_display_fields]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/config/importmap.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pin_all_from File.expand_path('../app/assets/javascripts/frontpack', __dir__), under: 'frontpack'
|
data/config/routes.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
module ActionView
|
2
|
+
module Helpers
|
3
|
+
module UrlHelper
|
4
|
+
def button_to(name = nil, options = nil, html_options = nil, &block)
|
5
|
+
html_options, options = options, name if block_given?
|
6
|
+
html_options ||= {}
|
7
|
+
html_options = html_options.stringify_keys
|
8
|
+
|
9
|
+
url = case options
|
10
|
+
when FalseClass then nil
|
11
|
+
else url_for(options)
|
12
|
+
end
|
13
|
+
|
14
|
+
remote = html_options.delete("remote")
|
15
|
+
params = html_options.delete("params")
|
16
|
+
|
17
|
+
authenticity_token = html_options.delete("authenticity_token")
|
18
|
+
|
19
|
+
method = (html_options.delete("method").presence || method_for_options(options)).to_s
|
20
|
+
method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : "".html_safe
|
21
|
+
|
22
|
+
form_id = html_options.delete("form_id") || SecureRandom.hex(8)
|
23
|
+
form_method = method == "get" ? "get" : "post"
|
24
|
+
form_options = html_options.delete("form") || {}
|
25
|
+
form_options[:class] ||= html_options.delete("form_class") || "button_to"
|
26
|
+
form_options[:method] = form_method
|
27
|
+
form_options[:action] = url
|
28
|
+
form_options[:id] = form_id
|
29
|
+
form_options[:'data-remote'] = true if remote
|
30
|
+
|
31
|
+
request_token_tag = if form_method == "post"
|
32
|
+
request_method = method.empty? ? "post" : method
|
33
|
+
token_tag(authenticity_token, form_options: { action: url, method: request_method })
|
34
|
+
else
|
35
|
+
""
|
36
|
+
end
|
37
|
+
|
38
|
+
html_options = convert_options_to_data_attributes(options, html_options)
|
39
|
+
html_options["type"] = "submit"
|
40
|
+
|
41
|
+
|
42
|
+
html_options["form"] = form_id
|
43
|
+
button = if block_given?
|
44
|
+
content_tag("button", html_options, &block)
|
45
|
+
elsif button_to_generates_button_tag
|
46
|
+
content_tag("button", name || url, html_options, &block)
|
47
|
+
else
|
48
|
+
html_options["value"] = name || url
|
49
|
+
tag("input", html_options)
|
50
|
+
end
|
51
|
+
|
52
|
+
inner_tags = method_tag.safe_concat(request_token_tag)
|
53
|
+
if params
|
54
|
+
to_form_params(params).each do |param|
|
55
|
+
inner_tags.safe_concat tag(:input, type: "hidden", name: param[:name], value: param[:value],
|
56
|
+
autocomplete: "off")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
html = content_tag("form", inner_tags, form_options) + button
|
60
|
+
prevent_content_exfiltration(html)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'importmap-rails'
|
4
|
+
|
5
|
+
module Frontpack
|
6
|
+
class Engine < ::Rails::Engine
|
7
|
+
isolate_namespace Frontpack
|
8
|
+
|
9
|
+
initializer 'frontpack.importmap', before: 'importmap' do |app|
|
10
|
+
app.config.importmap.paths << root.join('config/importmap.rb')
|
11
|
+
app.config.importmap.cache_sweepers << root.join('app/assets/javascripts')
|
12
|
+
end
|
13
|
+
|
14
|
+
initializer 'frontpack.assets' do |app|
|
15
|
+
app.config.assets.precompile += %w[frontpack_manifest]
|
16
|
+
end
|
17
|
+
|
18
|
+
initializer 'frontpack.action_controller' do |app|
|
19
|
+
ActiveSupport.on_load :action_controller do
|
20
|
+
helper Frontpack::ApplicationHelper
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
initializer 'frontpack.active_record' do
|
25
|
+
ActiveRecord::Base.include Frontpack::ExtendedModelTranslations
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Frontpack
|
2
|
+
module ExtendedModelTranslations
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include ActiveModel::Naming
|
5
|
+
|
6
|
+
class_methods do
|
7
|
+
def human_enum_value(enum_name, enum_value)
|
8
|
+
I18n.t("#{i18n_scope}.attributes.#{model_name.i18n_key}.#{enum_name.to_s.pluralize}.#{enum_value}")
|
9
|
+
end
|
10
|
+
|
11
|
+
def enum_options(enum_name, excluded_keys = [])
|
12
|
+
keys = send(enum_name.to_s.pluralize).keys.reject { |item| excluded_keys.include? item.to_sym }
|
13
|
+
keys.collect { |item| [human_enum_value(enum_name, item), item] }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
delegate :human_attribute_name, to: :class
|
18
|
+
|
19
|
+
def enum_options(enum_name, excluded_keys = [])
|
20
|
+
self.class.enum_options(enum_name, excluded_keys)
|
21
|
+
end
|
22
|
+
|
23
|
+
def human_enum_name(enum_name)
|
24
|
+
self.human_enum_name(enum_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
def human_enum_value(enum_name)
|
28
|
+
self.class.human_enum_value(enum_name, self[enum_name])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/frontpack.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Frontpack
|
4
|
+
class InstallGenerator < Rails::Generators::Base
|
5
|
+
source_root File.expand_path('../templates', __dir__)
|
6
|
+
|
7
|
+
def patch_app_base_classes
|
8
|
+
inject_into_class 'app/controllers/application_controller.rb', ApplicationController, <<-RUBY
|
9
|
+
# Frontpack form builder
|
10
|
+
default_form_builder Frontpack::FormBuilder
|
11
|
+
RUBY
|
12
|
+
copy_file 'initializers/customize_form_with_errors.rb', 'config/initializers/customize_form_with_errors.rb'
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_frontpack_assets
|
16
|
+
prepend_file 'app/assets/stylesheets/application.scss', <<~SCSS
|
17
|
+
@use 'frontpack/frontpack' with (
|
18
|
+
$theme-mode: 'dark',
|
19
|
+
$background: #2c0408,
|
20
|
+
$highlight: #dcac0b,
|
21
|
+
);
|
22
|
+
|
23
|
+
SCSS
|
24
|
+
end
|
25
|
+
|
26
|
+
def copy_layout
|
27
|
+
copy_file 'views/layouts/_form-errors.html.slim', 'app/views/layouts/_form-errors.html.slim'
|
28
|
+
copy_file 'views/layouts/_navigation.html.slim', 'app/views/layouts/_navigation.html.slim'
|
29
|
+
copy_file 'views/layouts/errors.html.slim', 'app/views/layouts/errors.html.slim'
|
30
|
+
template 'views/layouts/application.html.slim', 'app/views/layouts/application.html.slim'
|
31
|
+
end
|
32
|
+
|
33
|
+
def copy_locale
|
34
|
+
copy_file 'locales/frontpack.en.yml', 'config/locales/frontpack.en.yml'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Frontpack
|
4
|
+
class LocaleGenerator < Rails::Generators::Base
|
5
|
+
source_root File.expand_path('../templates', __dir__)
|
6
|
+
|
7
|
+
def install
|
8
|
+
copy_file 'locale/frontpack.en_US.yml', 'config/locale/frontpack.en_US.yml'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Disable the form_with_errors feature
|
4
|
+
module ActionView
|
5
|
+
module Helpers
|
6
|
+
module Tags
|
7
|
+
# Allows access to method name
|
8
|
+
class Base
|
9
|
+
attr_reader :method_name
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
ActionView::Base.field_error_proc = proc do |html_tag, instance|
|
16
|
+
if instance.is_a? ActionView::Helpers::Tags::Label
|
17
|
+
html_tag
|
18
|
+
else
|
19
|
+
class_attr_index = html_tag.index 'class="'
|
20
|
+
|
21
|
+
if class_attr_index
|
22
|
+
html_tag.insert class_attr_index + 7, 'error '
|
23
|
+
else
|
24
|
+
html_tag.insert html_tag.index('>'), ' class=error'
|
25
|
+
end
|
26
|
+
|
27
|
+
# rubocop:disable Rails/OutputSafety
|
28
|
+
html_tag.safe_concat "<i>#{instance.object.errors[instance.method_name][0].capitalize}</i>"
|
29
|
+
# rubocop:enable Rails/OutputSafety
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
---
|
2
|
+
en:
|
3
|
+
sign_in: Sign in
|
4
|
+
sign_out: Sign out
|
5
|
+
sign_up: Create account
|
6
|
+
recover_password: Recover password
|
7
|
+
resend_confirmation: Resend confirmation instructions
|
8
|
+
resend_unlock: Resend unlock instructions
|
9
|
+
home: Home
|
10
|
+
new: New
|
11
|
+
edit: Edit
|
12
|
+
update: Update
|
13
|
+
save: Save
|
14
|
+
delete: Delete
|
15
|
+
cancel: Cancel
|
16
|
+
back: Back
|
17
|
+
copy: Copy
|
18
|
+
created_successfully: "%{resource_name} created"
|
19
|
+
updated_successfully: "%{resource_name} updated"
|
20
|
+
deleted_successfully: "%{resource_name} destroyed"
|
21
|
+
admin_privileges_required: This resource requires admin rights
|
22
|
+
are_you_sure?: Are you sure?
|
23
|
+
ago: ago
|
24
|
+
|
25
|
+
activerecord:
|
26
|
+
attributes:
|
27
|
+
default_fields: &default_fields
|
28
|
+
created_at: Created at
|
29
|
+
updated_at: Updated at
|
30
|
+
views:
|
31
|
+
pagination:
|
32
|
+
last: '>>'
|
33
|
+
next: '>'
|
34
|
+
previous: '<'
|
35
|
+
first: '<<'
|
36
|
+
truncate: ...
|
37
|
+
layouts:
|
38
|
+
application:
|
39
|
+
loading: Loading...
|
40
|
+
|
41
|
+
status:
|
42
|
+
404: The resource you are looking for does not exist
|
43
|
+
500: The server encountered an internal error and was unable to complete your request
|
@@ -0,0 +1 @@
|
|
1
|
+
= link_to t(:home), root_url
|
@@ -0,0 +1,37 @@
|
|
1
|
+
doctype html
|
2
|
+
html
|
3
|
+
head
|
4
|
+
/ TODO: Replace "Frontpack" with the desired default application title
|
5
|
+
title = content_for?(:title) ? yield(:title) : 'Frontpack'
|
6
|
+
meta(content = 'text/html;charset=UTF-8' http-equiv = 'Content-Type')
|
7
|
+
meta(content = 'width=device-width,initial-scale=1' name = 'viewport')
|
8
|
+
= csrf_meta_tags
|
9
|
+
= csp_meta_tag
|
10
|
+
= stylesheet_link_tag 'application', 'data-turbo-track': 'reload'
|
11
|
+
= javascript_importmap_tags
|
12
|
+
body
|
13
|
+
header#header
|
14
|
+
toggle-button#btn-menu.md-hidden.lg-hidden(type='toggle' data-icon="menu" target="main-menu" visible-class="-" hidden-class="sm-hidden")
|
15
|
+
= link_to root_url, id: :logo do
|
16
|
+
/ TODO: Replace this image_tag with your logo
|
17
|
+
= image_tag 'logo.svg'
|
18
|
+
nav#main-menu.sm-hidden
|
19
|
+
= render 'layouts/navigation'
|
20
|
+
#profile
|
21
|
+
- if content_for? :breadcrumbs
|
22
|
+
nav#breadcrumbs
|
23
|
+
= link_to t(:home), root_url, class: 'no-underscore'
|
24
|
+
| /
|
25
|
+
= yield :breadcrumbs
|
26
|
+
#messages
|
27
|
+
- flash.each do |type, message|
|
28
|
+
text-box(class=type) = message
|
29
|
+
section#content
|
30
|
+
- if content_for? :actions
|
31
|
+
#actions = yield :actions
|
32
|
+
= yield
|
33
|
+
footer#footer
|
34
|
+
/ TODO: Replace "Frontpack" with the desired copyright text
|
35
|
+
| © #{Time.zone.today.year} - Frontpack
|
36
|
+
|
37
|
+
#loading= t('.loading')
|
@@ -0,0 +1,19 @@
|
|
1
|
+
html
|
2
|
+
head
|
3
|
+
meta(content = 'text/html;charset=UTF-8' http-equiv = 'Content-Type')
|
4
|
+
title Genesis
|
5
|
+
meta( content = 'width=device-width,initial-scale=1' name = 'viewport' )
|
6
|
+
= csrf_meta_tags
|
7
|
+
= csp_meta_tag
|
8
|
+
= stylesheet_link_tag 'application', 'data-turbo-track': 'reload'
|
9
|
+
= javascript_include_tag 'application', 'data-turbo-track': 'reload', defer: true
|
10
|
+
body
|
11
|
+
header#header
|
12
|
+
= link_to root_url do
|
13
|
+
= image_tag 'logo.svg', id: :logo
|
14
|
+
nav#main-menu
|
15
|
+
#profile
|
16
|
+
#messages
|
17
|
+
section#content= yield
|
18
|
+
footer#footer
|
19
|
+
| © #{Time.zone.today.year} - Genesis
|
@@ -0,0 +1 @@
|
|
1
|
+
= yield
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Adds dynamic search functionality
|
4
|
+
module Searchable
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
def basic_search(model, approximate_fields = [], exact_fields = [], include_list = nil)
|
8
|
+
search_conditions = prepare_search_fields model.arel_table, exact_fields, exact: true
|
9
|
+
search_conditions += prepare_search_fields model.arel_table, approximate_fields
|
10
|
+
|
11
|
+
query = include_list.present? ? model.includes(include_list) : model
|
12
|
+
query = query.where(*search_conditions) if search_conditions.length.positive?
|
13
|
+
|
14
|
+
query.page(params[:page])
|
15
|
+
end
|
16
|
+
|
17
|
+
def prepare_search_fields(arel_table, fields, **options)
|
18
|
+
[].tap do |search_conditions|
|
19
|
+
fields.each do |field|
|
20
|
+
next if (val = params[field]).blank?
|
21
|
+
|
22
|
+
if options[:exact]
|
23
|
+
search_conditions.push(arel_table[field].eq(val))
|
24
|
+
else
|
25
|
+
search_conditions.push(arel_table[field].matches(Arel::Nodes::SqlLiteral.new("'%#{val}%'")))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|