locomotivecms 4.0.3 → 4.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +3 -0
  3. data/app/api/locomotive/api/entities/content_type_entity.rb +1 -1
  4. data/app/api/locomotive/api/entities/site_entity.rb +1 -1
  5. data/app/api/locomotive/api/forms/content_type_form.rb +2 -1
  6. data/app/api/locomotive/api/forms/site_form.rb +1 -0
  7. data/app/api/locomotive/api/helpers/persistence_helper.rb +1 -1
  8. data/app/api/locomotive/api/resources/content_asset_resource.rb +6 -0
  9. data/app/api/locomotive/api/resources/content_type_resource.rb +2 -0
  10. data/app/api/locomotive/api/resources/current_site_resource.rb +1 -1
  11. data/app/api/locomotive/api/resources/site_resource.rb +3 -1
  12. data/app/assets/javascripts/locomotive/editor.js +7819 -100
  13. data/app/assets/javascripts/locomotive/views/content_entry_imports/new_view.js.coffee +12 -0
  14. data/app/assets/javascripts/locomotive/views/content_entry_imports/show_view.js.coffee +8 -0
  15. data/app/assets/stylesheets/locomotive/application.scss +2 -0
  16. data/app/assets/stylesheets/locomotive/editor.css +105 -12
  17. data/app/assets/stylesheets/locomotive/new/_dashboard.scss +20 -0
  18. data/app/assets/stylesheets/locomotive/old/_content_assets.scss +1 -1
  19. data/app/controllers/locomotive/content_assets_controller.rb +1 -1
  20. data/app/controllers/locomotive/content_entry_imports_controller.rb +50 -0
  21. data/app/helpers/locomotive/base_helper.rb +1 -1
  22. data/app/helpers/locomotive/shared/pages_helper.rb +1 -1
  23. data/app/jobs/locomotive/import_content_entry_job.rb +12 -0
  24. data/app/mailers/locomotive/notifications.rb +37 -11
  25. data/app/models/locomotive/account.rb +1 -0
  26. data/app/models/locomotive/concerns/asset/vignette.rb +7 -5
  27. data/app/models/locomotive/concerns/content_type/import.rb +124 -0
  28. data/app/models/locomotive/concerns/site/metafields.rb +26 -0
  29. data/app/models/locomotive/content_asset.rb +9 -2
  30. data/app/models/locomotive/content_type.rb +1 -0
  31. data/app/models/locomotive/section.rb +3 -1
  32. data/app/models/locomotive/site.rb +2 -0
  33. data/app/policies/locomotive/content_entry_policy.rb +1 -2
  34. data/app/policies/locomotive/content_type_policy.rb +3 -0
  35. data/app/policies/locomotive/site_policy.rb +5 -3
  36. data/app/services/locomotive/content_asset_service.rb +27 -4
  37. data/app/services/locomotive/content_entry_import_service.rb +107 -0
  38. data/app/services/locomotive/content_entry_service.rb +1 -1
  39. data/app/uploaders/locomotive/picture_uploader.rb +1 -1
  40. data/app/uploaders/locomotive/theme_asset_uploader.rb +1 -1
  41. data/app/views/locomotive/content_assets/_dropzone.html.slim +5 -1
  42. data/app/views/locomotive/content_entries/index.html.slim +8 -0
  43. data/app/views/locomotive/content_entry_imports/new.html.slim +23 -0
  44. data/app/views/locomotive/content_entry_imports/show.html.slim +54 -0
  45. data/app/views/locomotive/current_site/form/_advanced.html.slim +2 -0
  46. data/app/views/locomotive/page_content/edit.html.erb +1 -0
  47. data/config/locales/editor.en.yml +22 -0
  48. data/config/locales/editor.fr.yml +22 -0
  49. data/config/locales/en.yml +19 -0
  50. data/config/locales/es.yml +2 -0
  51. data/config/locales/flash.en.yml +6 -0
  52. data/config/locales/mongoid.en.yml +5 -1
  53. data/config/locales/mongoid.es.yml +4 -0
  54. data/config/locales/simple_form.en.yml +13 -1
  55. data/config/locales/simple_form.es.yml +2 -0
  56. data/config/routes.rb +3 -2
  57. data/lib/locomotive/configuration.rb +1 -0
  58. data/lib/locomotive/dragonfly.rb +6 -4
  59. data/lib/locomotive/version.rb +1 -1
  60. metadata +38 -29
@@ -0,0 +1,124 @@
1
+ module Locomotive
2
+ module Concerns
3
+ module ContentType
4
+ module Import
5
+
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ field :import_enabled, type: Boolean, default: false
10
+ field :raw_import_state, type: Hash, default: nil
11
+ end
12
+
13
+ def import_state
14
+ @import_state ||= ImportState.new(raw_import_state)
15
+ end
16
+
17
+ def can_import?
18
+ import_enabled? && import_state.can_start?
19
+ end
20
+
21
+ def importing?
22
+ import_state.running?
23
+ end
24
+
25
+ def import_status
26
+ import_state.status
27
+ end
28
+
29
+ def start_import(total:)
30
+ change_import_state({
31
+ 'status' => 'in_progress', 'total_rows' => total
32
+ }, true)
33
+ end
34
+
35
+ def finish_import
36
+ change_import_state({ 'status' => 'done' })
37
+ end
38
+
39
+ def cancel_import(error_message)
40
+ change_import_state({ 'status' => 'canceled', 'error' => error_message })
41
+ end
42
+
43
+ def on_imported_row(index, row_status)
44
+ change_import_state({
45
+ row_status.to_s => import_state.rows_count(row_status.to_s) + 1,
46
+ 'failed_ids' => import_state.failed_rows_ids + (row_status.to_s == 'failed' ? [index] : []),
47
+ })
48
+ end
49
+
50
+ def change_import_state(attributes, clear_state = false)
51
+ new_attributes = ((clear_state ? nil : raw_import_state) || {}).merge(attributes)
52
+ update(raw_import_state: new_attributes.merge('updated_at' => Time.zone.now)).tap do
53
+ @import_state = nil # reset the state object
54
+ end
55
+ end
56
+
57
+ class ImportState
58
+
59
+ def initialize(raw_state)
60
+ @raw_state = raw_state || { 'status' => 'ready' }
61
+ end
62
+
63
+ def can_start?
64
+ !running?
65
+ end
66
+
67
+ def running?
68
+ status == :in_progress
69
+ end
70
+
71
+ def status
72
+ @raw_state['status'].to_sym
73
+ end
74
+
75
+ def total_rows
76
+ @raw_state['total_rows']
77
+ end
78
+
79
+ def processed_rows_count
80
+ created_rows_count + updated_rows_count + failed_rows_count
81
+ end
82
+
83
+ def rows_count(topic)
84
+ @raw_state[topic] || 0
85
+ end
86
+
87
+ def created_rows_count
88
+ rows_count('created')
89
+ end
90
+
91
+ def updated_rows_count
92
+ rows_count('updated')
93
+ end
94
+
95
+ def failed_rows_count
96
+ rows_count('failed')
97
+ end
98
+
99
+ def failed_rows_ids
100
+ @raw_state['failed_ids'] || []
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ class ContentEntryImport
108
+ include ActiveModel::Model
109
+
110
+ attr_accessor :file, :col_sep, :quote_char
111
+
112
+ validates_each :file do |record, attr, value|
113
+ record.errors.add attr, :blank if value.blank?
114
+ end
115
+
116
+ def options
117
+ { col_sep: col_sep || ',', quote_char: quote_char || "\"" }
118
+ end
119
+
120
+ def file?
121
+ file.present?
122
+ end
123
+ end
124
+ end
@@ -41,6 +41,21 @@ module Locomotive
41
41
  end
42
42
  end
43
43
 
44
+ def cast_metafields(namespace)
45
+ return nil if namespace.blank? || !has_metafields?
46
+
47
+ schema = self.metafields_schema.find { |s| s['name'] == namespace }
48
+ values = self.metafields[namespace]
49
+
50
+ return nil if schema.blank? || values.blank?
51
+
52
+ values.map do |name, value|
53
+ field = schema['fields'].find { |f| f['name'] == name }
54
+ next unless field
55
+ [name, cast_metafield_value(field, value)]
56
+ end.compact.to_h
57
+ end
58
+
44
59
  protected
45
60
 
46
61
  def _metafields_schema_schema
@@ -75,6 +90,17 @@ module Locomotive
75
90
  }
76
91
  end
77
92
 
93
+ def cast_metafield_value(field, value)
94
+ case field['type']
95
+ when 'boolean'
96
+ ['1', 'true', true].include?(value)
97
+ when 'integer'
98
+ Integer(value)
99
+ else
100
+ value
101
+ end
102
+ end
103
+
78
104
  end
79
105
 
80
106
  end
@@ -1,12 +1,13 @@
1
1
  module Locomotive
2
2
  class ContentAsset
3
3
 
4
- include Locomotive::Mongoid::Document
4
+ include Locomotive::Mongoid::Document
5
5
 
6
6
  ## extensions ##
7
7
  include Concerns::Asset::Types
8
8
  include Concerns::Asset::Vignette
9
9
  include Concerns::Asset::Checksum
10
+ include ActionView::Helpers::NumberHelper
10
11
 
11
12
  ## fields ##
12
13
  field :content_type, type: String
@@ -20,6 +21,7 @@ module Locomotive
20
21
 
21
22
  ## validations ##
22
23
  validates_presence_of :source
24
+ validate :maximum_file_size
23
25
 
24
26
  ## behaviours ##
25
27
  mount_uploader :source, ContentAssetUploader, mount_on: :source_filename
@@ -27,6 +29,7 @@ module Locomotive
27
29
  ## scopes ##
28
30
  scope :ordered, -> { order_by(created_at: :desc) }
29
31
  scope :by_filename, ->(query) { where(source_filename: /.*#{query}.*/i) }
32
+ scope :by_exact_filename, ->(filename) { where(source_filename: filename) }
30
33
 
31
34
  ## methods ##
32
35
 
@@ -39,9 +42,13 @@ module Locomotive
39
42
 
40
43
  def as_json(options = nil)
41
44
  super.merge(
42
- thumbnail_url: self.big_vignette_url
45
+ thumbnail_url: self.big_vignette_url,
46
+ size_to_human: number_to_human_size(self.size)
43
47
  )
44
48
  end
45
49
 
50
+ def maximum_file_size
51
+ errors.add(:source, :maximum_file_size_exceeded) if size_changed? && size > site.maximum_uploaded_file_size
52
+ end
46
53
  end
47
54
  end
@@ -15,6 +15,7 @@ module Locomotive
15
15
  include Concerns::ContentType::ClassHelpers
16
16
  include Concerns::ContentType::PublicSubmissionTitleTemplate
17
17
  include Concerns::ContentType::FilterFields
18
+ include Concerns::ContentType::Import
18
19
 
19
20
  ## fields ##
20
21
  field :name
@@ -76,7 +76,7 @@ module Locomotive
76
76
  properties: {
77
77
  id: { type: 'string' },
78
78
  label: { '$ref': '#/definitions/locale_string' },
79
- type: { enum: ['text', 'image_picker', 'checkbox', 'select', 'url', 'radio', 'content_type', 'hint', 'integer'] },
79
+ type: { enum: ['text', 'image_picker', 'asset_picker', 'checkbox', 'select', 'url', 'radio', 'content_type', 'content_entry', 'hint', 'integer'] },
80
80
  default: {}
81
81
  },
82
82
  required: [:id, :type]
@@ -129,6 +129,8 @@ module Locomotive
129
129
  presets: { type: 'array', items: { '$ref': '#/definitions/preset' } },
130
130
  blocks: { type: 'array', items: { '$ref': '#/definitions/blocks' } },
131
131
  max_blocks: { type: 'integer' },
132
+ blocks_display: { enum: ['list', 'tree'] },
133
+ max_blocks_depth: { type: 'integer' },
132
134
  default: {
133
135
  type: 'object',
134
136
  properties: {
@@ -18,6 +18,8 @@ module Locomotive
18
18
  ## fields ##
19
19
  field :name
20
20
  field :robots_txt
21
+ field :maximum_uploaded_file_size, type: ::Integer, default: Locomotive.config.default_maximum_uploaded_file_size
22
+ field :overwrite_same_content_assets, type: Boolean, default: false
21
23
 
22
24
  mount_uploader :picture, PictureUploader, validate_integrity: true
23
25
 
@@ -23,7 +23,6 @@ module Locomotive
23
23
 
24
24
  def destroy?
25
25
  site_staff? && !membership.visitor?
26
- end
27
-
26
+ end
28
27
  end
29
28
  end
@@ -25,5 +25,8 @@ module Locomotive
25
25
  site_admin_or_designer? || !@resource.hidden?
26
26
  end
27
27
 
28
+ def import?
29
+ @resource.import_enabled?
30
+ end
28
31
  end
29
32
  end
@@ -55,16 +55,18 @@ module Locomotive
55
55
 
56
56
  def permitted_attributes
57
57
  plain = [
58
- :name, :handle, :picture, :remove_picture, :seo_title, :meta_keywords, :meta_description, :robots_txt,
58
+ :name, :handle, :picture, :remove_picture, :seo_title, :meta_keywords, :meta_description, :robots_txt, :maximum_uploaded_file_size,
59
59
  :timezone_name, :timezone,
60
60
  :cache_enabled, :cache_control, :cache_vary,
61
61
  :asset_host, :redirect_to_first_domain, :redirect_to_https,
62
- :private_access, :password, :prefix_default_locale, :bypass_browser_locale
62
+ :private_access, :password, :prefix_default_locale, :bypass_browser_locale,
63
+ :overwrite_same_content_assets,
64
+ :permitted_params_from_policy
63
65
  ]
64
66
  hash = { domains: [], locales: [], url_redirections: [] }
65
67
 
66
68
  if persisted? && !update_advanced?
67
- plain -= [:timezone_name, :timezone, :robots_txt, :cache_enabled, :cache_control, :cache_vary, :prefix_default_locale, :bypass_browser_locale, :asset_host]
69
+ plain -= [:timezone_name, :timezone, :robots_txt, :cache_enabled, :cache_control, :cache_vary, :prefix_default_locale, :bypass_browser_locale, :asset_host, :maximum_uploaded_file_size]
68
70
  hash.delete(:locales)
69
71
  hash.delete(:url_redirections)
70
72
  end
@@ -13,13 +13,15 @@ module Locomotive
13
13
  .page(options[:page] || 1).per(options[:per_page])
14
14
  end
15
15
 
16
+ def create(params)
17
+ create_or_update(params)
18
+ end
19
+
16
20
  def bulk_create(list)
17
21
  list = list.values if list.is_a?(Hash)
18
22
 
19
- assets = list.map do |params|
20
- site.content_assets.create(params)
21
- end
22
-
23
+ assets = list.map { |params| create_or_update(params) }
24
+
23
25
  valid_assets = assets.map { |a| a.errors.empty? ? { name: a.source_filename, url: a.source.url, image: a.image?, id: a._id } : nil }.compact
24
26
  track_activity 'content_asset.created_bulk', parameters: { assets: valid_assets } unless valid_assets.empty?
25
27
 
@@ -32,5 +34,26 @@ module Locomotive
32
34
  end
33
35
  end
34
36
 
37
+ private
38
+
39
+ def create_or_update(params)
40
+ return site.content_assets.create(params) unless site.overwrite_same_content_assets?
41
+
42
+ asset = site.content_assets.build(params)
43
+ filename = asset.source.filename
44
+
45
+ existing_asset = site.content_assets.by_exact_filename(filename).first
46
+
47
+ return asset.tap { asset.save } unless existing_asset
48
+
49
+ existing_asset.tap do
50
+ # can't find out another way to replace the file when it has the same filename
51
+ existing_asset.destroy
52
+
53
+ # very important to keep the same asset id
54
+ asset._id = existing_asset._id
55
+ asset.save
56
+ end
57
+ end
35
58
  end
36
59
  end
@@ -0,0 +1,107 @@
1
+ require 'csv'
2
+
3
+ module Locomotive
4
+ class ContentEntryImportService < Struct.new(:content_type)
5
+
6
+ class WrongImportFileException < StandardError; end
7
+
8
+ def async_import(file, csv_options = nil)
9
+ csv_asset = content_assets.create(source: file)
10
+ Locomotive::ImportContentEntryJob.perform_later(
11
+ content_type.id.to_s,
12
+ csv_asset.id.to_s,
13
+ csv_options
14
+ )
15
+ end
16
+
17
+ def import(csv_asset_id, csv_options = nil)
18
+ begin
19
+ csv = load_csv(csv_asset_id, csv_options)
20
+ rescue WrongImportFileException => e
21
+ content_type.cancel_import(e.message)
22
+ return false
23
+ end
24
+
25
+ content_type.start_import(total: csv.count)
26
+ import_rows(csv)
27
+ content_type.finish_import
28
+
29
+ content_assets.destroy_all(id: csv_asset_id)
30
+ end
31
+
32
+ def cancel(reason)
33
+ content_type.cancel_import(reason)
34
+ end
35
+
36
+ private
37
+
38
+ def import_rows(csv)
39
+ csv.each_with_index do |row, index|
40
+ entry = content_type.entries.where(_slug: row['_slug']).first || content_type.entries.build
41
+ import_row(row, index, entry)
42
+ end
43
+ end
44
+
45
+ def import_row(row, index, entry)
46
+ is_new_entry = !entry.persisted?
47
+ entry.attributes = attributes_from_row(row)
48
+
49
+ if entry.save
50
+ content_type.on_imported_row(index, is_new_entry ? :created : :updated)
51
+ else
52
+ content_type.on_imported_row(index, :failed)
53
+ end
54
+ rescue Exception => e
55
+ Rails.logger.error e.message # don't hide the exception
56
+ Rails.logger.error e.backtrace.join("\n")
57
+ content_type.on_imported_row(index, :failed)
58
+ end
59
+
60
+ def attributes_from_row(row)
61
+ attributes = {}
62
+ content_type.entries_custom_fields.each do |field|
63
+ next if row[field.name].blank?
64
+ name, value = transform_attribute(field, row[field.name])
65
+ attributes[name] = value
66
+ end
67
+ attributes
68
+ end
69
+
70
+ def load_csv(csv_asset_id, csv_options = nil)
71
+ csv_options = { headers: true, col_sep: ';', quote_char: "\"" }.merge(csv_options || {})
72
+ asset = content_assets.where(id: csv_asset_id).first
73
+ raise 'The CSV file doesn\'t exist anymore' unless asset
74
+ CSV.parse(asset.source.read, csv_options)
75
+ rescue Exception => e
76
+ raise WrongImportFileException.new e.message
77
+ end
78
+
79
+ def transform_attribute(field, value)
80
+ case field.type
81
+ when 'string'
82
+ field.name =~ /_asset_url$/ ?
83
+ [field.name, content_assets.where(source_filename: value).order_by(:created_at.desc).first&.source&.url] :
84
+ [field.name, value]
85
+ when 'belongs_to'
86
+ [field.name, fetch_entry_ids(field.class_name, value).first]
87
+ when 'many_to_many'
88
+ ["#{field.name.singularize}_ids", fetch_entry_ids(field.class_name, value.split(','))]
89
+ else
90
+ [field.name, value]
91
+ end
92
+ end
93
+
94
+ def content_assets
95
+ content_type.site.content_assets
96
+ end
97
+
98
+ def fetch_entry_ids(class_name, ids_or_slugs)
99
+ return [] if ids_or_slugs.blank?
100
+
101
+ ids_or_slugs = [*ids_or_slugs]
102
+ klass = class_name.constantize
103
+
104
+ klass.by_ids_or_slugs(ids_or_slugs).pluck(:_id)
105
+ end
106
+ end
107
+ end
@@ -173,7 +173,7 @@ module Locomotive
173
173
  { :_id.in => self.content_type.public_submission_accounts || [] },
174
174
  { :email.in => emails || [] }
175
175
  ).each do |account|
176
- Locomotive::Notifications.new_content_entry(account, entry).deliver
176
+ Locomotive::Notifications.new_content_entry(site, account, entry).deliver
177
177
  end
178
178
  end
179
179
 
@@ -4,7 +4,7 @@ module Locomotive
4
4
  class PictureUploader < BaseUploader
5
5
 
6
6
  def extension_whitelist
7
- %w(jpg jpeg gif png)
7
+ %w(jpg jpeg gif png tiff)
8
8
  end
9
9
 
10
10
  def image?
@@ -10,7 +10,7 @@ module Locomotive
10
10
  end
11
11
 
12
12
  def extension_whitelist
13
- %w(jpg jpeg gif png css js swf flv mp4 eot svg svgz ttf ttc woff woff2 otf ico htc map html cur txt xml json ogv)
13
+ %w(jpg jpeg gif png css js swf flv mp4 eot svg svgz ttf ttc woff woff2 otf ico htc map html cur txt xml json ogv webm)
14
14
  end
15
15
 
16
16
  def apply_content_type_exception(value)
@@ -1,9 +1,13 @@
1
1
  .instructions
2
- i.fa.fa-cloud-upload
2
+ i.fas.fa-cloud-upload-alt
3
3
  br
4
4
  = t('.instructions').html_safe
5
5
  | &nbsp;
6
6
  = link_to t('.browse'), '#', class: 'upload'
7
+ br
8
+ br
9
+ i
10
+ = t('.maximum_file_size', maximum: number_to_human_size(current_site.maximum_uploaded_file_size)).html_safe
7
11
 
8
12
  = form_tag('', class: 'hide') do
9
13
  = file_field_tag :source, multiple: true
@@ -41,6 +41,14 @@
41
41
  | &nbsp;
42
42
  = t('.new')
43
43
 
44
+ - if @content_type.import_enabled?
45
+ | &nbsp;
46
+ | &nbsp;
47
+ = link_to new_content_entry_import_path(current_site, @content_type.slug), class: 'btn btn-primary btn-sm' do
48
+ i.fa.fa-file-import
49
+ | &nbsp;
50
+ = t('.import')
51
+
44
52
  - if @content_type.groupable?
45
53
  .row.list-groups
46
54
  .nav-container.nav-container--scroll
@@ -0,0 +1,23 @@
1
+ - title t('.title', type: @content_type.name.capitalize)
2
+
3
+ - content_for :actions do
4
+ .main-action
5
+ = link_to content_entry_import_path(current_site, @content_type.slug), class: 'btn btn-primary btn-sm' do
6
+ i.fas.fa-tachometer-alt
7
+ | &nbsp;
8
+ = t('.show')
9
+
10
+ | &nbsp;
11
+ | &nbsp;
12
+
13
+ = link_to t(:back, scope: 'locomotive.content_entries.shared').html_safe, content_entries_path(current_site, @content_type.slug), class: 'btn btn-sm btn-default'
14
+
15
+ = locomotive_form_for @import, url: content_entry_import_path(current_site, @content_type.slug) do |f|
16
+
17
+ = f.inputs :information do
18
+ = f.input :file, as: :file
19
+
20
+ = f.input :col_sep
21
+ = f.input :quote_char
22
+
23
+ = f.actions back_url: content_entries_path(current_site, @content_type.slug), use_stored_location: true
@@ -0,0 +1,54 @@
1
+ - title t('.title', type: @content_type.name.capitalize)
2
+
3
+ - content_for :actions do
4
+ .main-action
5
+ - if @content_type.importing?
6
+ = link_to content_entry_import_path(current_site, @content_type.slug), class: 'btn btn-primary btn-sm' do
7
+ i.fas.fa-sync
8
+ | &nbsp;
9
+ = t('.refresh')
10
+
11
+ | &nbsp;
12
+ | &nbsp;
13
+
14
+ = link_to content_entry_import_path(current_site, @content_type.slug), class: 'btn btn-danger btn-sm', method: :delete, data: { confirm: t('locomotive.messages.confirm') } do
15
+ i.far.fa-trash-alt
16
+ | &nbsp;
17
+ = t('.cancel')
18
+
19
+ | &nbsp;
20
+ | &nbsp;
21
+
22
+ = link_to t(:back, scope: 'locomotive.content_entries.shared').html_safe, content_entries_path(current_site, @content_type.slug), class: 'btn btn-sm btn-default'
23
+
24
+ br
25
+ br
26
+ br
27
+ br
28
+
29
+ .row
30
+ .col-md-3
31
+ .metric
32
+ h2= t('.report.status')
33
+ h3= t(".statuses.#{@content_type.import_status}")
34
+
35
+ .col-md-3
36
+ .metric
37
+ h2= t('.report.created_rows')
38
+ h3= @content_type.import_state.created_rows_count
39
+
40
+ .col-md-3
41
+ .metric
42
+ h2= t('.report.updated_rows')
43
+ h3= @content_type.import_state.updated_rows_count
44
+
45
+ .col-md-3
46
+ .metric
47
+ h2= t('.report.failed_rows')
48
+ h3= @content_type.import_state.failed_rows_count
49
+
50
+ br
51
+ br
52
+
53
+ pre
54
+ code= @content_type.raw_import_state
@@ -9,6 +9,8 @@
9
9
 
10
10
  = f.input :robots_txt, as: :code, wrapper_html: { class: 'small' }
11
11
 
12
+ = f.input :maximum_uploaded_file_size
13
+
12
14
  = f.inputs :cache_enabled do
13
15
  = f.input :cache_enabled, as: :toggle
14
16
  = f.input :cache_control, as: :string, wrapper_html: { class: "#{'hide' unless f.object.cache_enabled?}" }
@@ -11,6 +11,7 @@
11
11
 
12
12
  window.Locomotive.i18n = <%= h I18n.t('locomotive.editor').to_json.html_safe %>;
13
13
 
14
+ window.Locomotive.maximum_uploaded_file_size = <%= current_site.maximum_uploaded_file_size %>;
14
15
  </script>
15
16
 
16
17
  <%= javascript_include_tag 'locomotive/editor' %>
@@ -35,9 +35,19 @@ en:
35
35
  remove_button: "Remove"
36
36
  crop_button: "Crop"
37
37
 
38
+ asset_picker:
39
+ select_button: "Select"
40
+ change_button: "Change"
41
+ remove_button: "Remove"
42
+
38
43
  content_type:
39
44
  show: "Show"
40
45
 
46
+ content_entry:
47
+ select_button: "Select"
48
+ change_button: "Change"
49
+ remove_button: "Remove"
50
+
41
51
  views:
42
52
  action_bar:
43
53
  header:
@@ -54,6 +64,12 @@ en:
54
64
  seo: "SEO"
55
65
 
56
66
  pickers:
67
+ assets:
68
+ title: "Assets"
69
+ loading: "Loading the assets. Please wait!"
70
+ add: "+ add"
71
+ upload_in_progress: "Uploading..."
72
+ search_placeholder: "Name of your asset"
57
73
  images:
58
74
  title: "Images"
59
75
  loading: "Loading the images. Please wait!"
@@ -82,9 +98,15 @@ en:
82
98
  email:
83
99
  label: Email address
84
100
  placeholder: "name@example.com"
101
+ content_entry:
102
+ title: "Pick an entry"
103
+ input:
104
+ label: Instance
105
+ placeholder: Type the label of the instance
85
106
 
86
107
  preview:
87
108
  view: "View"
109
+ errorMessage: "The page couldn't be loaded. Please contact the developer of the site."
88
110
 
89
111
  sections:
90
112
  edit: