plum-cms 0.1.2 → 0.2.1
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/CHANGELOG.md +51 -0
- data/README.md +81 -3
- data/app/assets/builds/tailwind.css +1 -1
- data/app/assets/stylesheets/plum/control_panel.css +1 -1
- data/app/assets/tailwind/application.css +42 -0
- data/app/controllers/plum/api/v1/entries_controller.rb +48 -0
- data/app/controllers/plum/cp/assets_controller.rb +4 -1
- data/app/controllers/plum/cp/content_types_controller.rb +25 -2
- data/app/controllers/plum/cp/entries_controller.rb +119 -9
- data/app/controllers/plum/cp/entry_revisions_controller.rb +27 -0
- data/app/controllers/plum/cp/fieldsets_controller.rb +37 -0
- data/app/controllers/plum/cp/site_settings_controller.rb +20 -1
- data/app/controllers/plum/pages_controller.rb +26 -1
- data/app/javascript/controllers/plum/asset_collection_controller.js +18 -0
- data/app/javascript/controllers/plum/blueprint_controller.js +202 -25
- data/app/javascript/controllers/plum/conditional_fields_controller.js +43 -0
- data/app/javascript/controllers/plum/focal_point_controller.js +16 -0
- data/app/javascript/controllers/plum/structured_field_controller.js +133 -0
- data/app/models/plum/asset.rb +5 -1
- data/app/models/plum/content_type.rb +63 -1
- data/app/models/plum/entry.rb +225 -1
- data/app/models/plum/entry_revision.rb +14 -0
- data/app/models/plum/fieldset.rb +17 -0
- data/app/models/plum/site.rb +15 -0
- data/app/models/plum/user.rb +1 -0
- data/app/services/plum/entry_serializer.rb +36 -0
- data/app/services/plum/field_expander.rb +12 -1
- data/app/services/plum/field_options.rb +21 -0
- data/app/services/plum/field_type_registry.rb +88 -0
- data/app/services/plum/liquid_context.rb +7 -1
- data/app/services/plum/site_archive.rb +367 -0
- data/app/views/layouts/plum/cp.html.erb +2 -0
- data/app/views/plum/cp/assets/_form.html.erb +13 -1
- data/app/views/plum/cp/content_types/_form.html.erb +81 -24
- data/app/views/plum/cp/custom_fields/_input.html.erb +5 -0
- data/app/views/plum/cp/entries/_form.html.erb +108 -31
- data/app/views/plum/cp/entries/edit.html.erb +16 -0
- data/app/views/plum/cp/entry_revisions/index.html.erb +25 -0
- data/app/views/plum/cp/fieldsets/index.html.erb +30 -0
- data/app/views/plum/cp/site_settings/edit.html.erb +12 -0
- data/config/plum_routes.rb +15 -0
- data/db/engine_migrate/20260807130000_create_plum_entry_revisions.rb +16 -0
- data/db/engine_migrate/20260807140000_create_plum_fieldsets.rb +13 -0
- data/db/engine_migrate/20260807150000_add_focal_point_to_plum_assets.rb +6 -0
- data/db/engine_migrate/20260807160000_add_localization_to_plum_entries.rb +9 -0
- data/docs/blueprint-fields.md +55 -0
- data/docs/extensions.md +32 -0
- data/docs/portability.md +40 -0
- data/docs/product-principles.md +73 -0
- data/docs/roadmap.md +100 -0
- data/docs/site/homepage.md +104 -0
- data/docs/site/information-architecture.md +176 -0
- data/docs/statamic-parity.md +28 -0
- data/docs/vision.md +82 -0
- data/lib/plum/engine.rb +3 -1
- data/lib/plum/version.rb +1 -1
- data/lib/plum.rb +4 -0
- data/lib/tasks/plum_portability.rake +37 -0
- data/lib/tasks/plum_styles.rake +12 -0
- metadata +36 -8
|
@@ -11,16 +11,16 @@ module Plum
|
|
|
11
11
|
].freeze
|
|
12
12
|
|
|
13
13
|
before_action :set_content_type
|
|
14
|
-
before_action :set_entry, only: [ :edit, :update, :destroy, :image_field ]
|
|
14
|
+
before_action :set_entry, only: [ :edit, :update, :destroy, :image_field, :translate ]
|
|
15
15
|
before_action :set_form_collections, only: [ :new, :create, :edit, :update ]
|
|
16
|
-
before_action :require_editor, only: [ :new, :create, :edit, :update, :destroy, :image_field ]
|
|
16
|
+
before_action :require_editor, only: [ :new, :create, :edit, :update, :destroy, :image_field, :translate ]
|
|
17
17
|
|
|
18
18
|
def index
|
|
19
19
|
@entries = @content_type.entries.order(updated_at: :desc)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def new
|
|
23
|
-
@entry = @content_type.entries.build(status: :draft, site: current_site)
|
|
23
|
+
@entry = @content_type.entries.build(status: :draft, site: current_site, locale: current_site.default_locale)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def create
|
|
@@ -73,6 +73,29 @@ module Plum
|
|
|
73
73
|
redirect_to cp_content_type_entries_path(@content_type), notice: "Entry deleted"
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
+
def translate
|
|
77
|
+
locale = params[:locale].to_s
|
|
78
|
+
unless current_site.locales.include?(locale)
|
|
79
|
+
return redirect_to edit_cp_content_type_entry_path(@content_type, @entry), alert: "Locale is not configured"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
source = @entry.origin || @entry
|
|
83
|
+
translation = source.translations.find_or_initialize_by(locale: locale)
|
|
84
|
+
if translation.new_record?
|
|
85
|
+
translation.assign_attributes(
|
|
86
|
+
site: current_site,
|
|
87
|
+
content_type: @content_type,
|
|
88
|
+
author: current_user.is_a?(Plum::User) ? current_user : nil,
|
|
89
|
+
title: @entry.title,
|
|
90
|
+
slug: @entry.slug,
|
|
91
|
+
status: :draft,
|
|
92
|
+
data: @entry.data.to_h.deep_dup
|
|
93
|
+
)
|
|
94
|
+
translation.save!
|
|
95
|
+
end
|
|
96
|
+
redirect_to edit_cp_content_type_entry_path(@content_type, translation), notice: "#{locale} translation ready"
|
|
97
|
+
end
|
|
98
|
+
|
|
76
99
|
private
|
|
77
100
|
|
|
78
101
|
def set_content_type
|
|
@@ -96,7 +119,7 @@ module Plum
|
|
|
96
119
|
end
|
|
97
120
|
|
|
98
121
|
def entry_params
|
|
99
|
-
permitted = params.require(:entry).permit(:title, :slug, :status, :published_at)
|
|
122
|
+
permitted = params.require(:entry).permit(:title, :slug, :status, :published_at, :locale)
|
|
100
123
|
if params[:entry][:data].is_a?(ActionController::Parameters)
|
|
101
124
|
permitted[:data] = params[:entry][:data].permit!.to_h
|
|
102
125
|
end
|
|
@@ -104,10 +127,17 @@ module Plum
|
|
|
104
127
|
end
|
|
105
128
|
|
|
106
129
|
def save_entry(entry)
|
|
107
|
-
|
|
108
|
-
|
|
130
|
+
saved = false
|
|
131
|
+
entry.transaction do
|
|
132
|
+
unless attach_uploaded_image_fields(entry) && entry.valid? && entry.save
|
|
133
|
+
raise ActiveRecord::Rollback
|
|
134
|
+
end
|
|
109
135
|
|
|
110
|
-
|
|
136
|
+
sync_entry_terms(entry)
|
|
137
|
+
entry.record_revision!(editor: current_user)
|
|
138
|
+
saved = true
|
|
139
|
+
end
|
|
140
|
+
saved
|
|
111
141
|
end
|
|
112
142
|
|
|
113
143
|
def sync_entry_terms(entry)
|
|
@@ -150,7 +180,7 @@ module Plum
|
|
|
150
180
|
|
|
151
181
|
@content_type.fields.each do |field|
|
|
152
182
|
handle = field["handle"].to_s
|
|
153
|
-
next if handle.blank?
|
|
183
|
+
next if handle.blank? || field["type"] == "section"
|
|
154
184
|
|
|
155
185
|
value = entry.data[handle]
|
|
156
186
|
entry.data[handle] = normalized_field_value(entry, field, value)
|
|
@@ -161,25 +191,105 @@ module Plum
|
|
|
161
191
|
end
|
|
162
192
|
|
|
163
193
|
def normalized_field_value(entry, field, value)
|
|
194
|
+
definition = FieldTypeRegistry.find(field["type"])
|
|
195
|
+
if definition&.normalizer
|
|
196
|
+
return definition.normalizer.call(value: value, field: field, entry: entry, controller: self)
|
|
197
|
+
end
|
|
198
|
+
|
|
164
199
|
case field["type"]
|
|
165
200
|
when "boolean"
|
|
166
201
|
ActiveModel::Type::Boolean.new.cast(value)
|
|
202
|
+
when "number"
|
|
203
|
+
normalized_number_value(value, field)
|
|
167
204
|
when "checkboxes"
|
|
168
205
|
Array(value).select(&:present?)
|
|
169
206
|
when "image"
|
|
170
|
-
|
|
207
|
+
normalized_image_value(entry, field, value)
|
|
208
|
+
when "images"
|
|
209
|
+
normalized_asset_ids(entry, field, value)
|
|
171
210
|
when "rich_text"
|
|
172
211
|
value
|
|
173
212
|
when "relationship"
|
|
174
213
|
normalized_relationship_value(entry, field, value)
|
|
175
214
|
when "blocks"
|
|
176
215
|
normalized_blocks_value(value)
|
|
216
|
+
when "list"
|
|
217
|
+
Array(parsed_structured_value(value, fallback: [])).map { |item| item.to_s.strip }.select(&:present?)
|
|
218
|
+
when "group"
|
|
219
|
+
normalized_structured_row(field, parsed_structured_value(value, fallback: {}))
|
|
220
|
+
when "repeater"
|
|
221
|
+
Array(parsed_structured_value(value, fallback: [])).filter_map do |row|
|
|
222
|
+
normalized = normalized_structured_row(field, row)
|
|
223
|
+
normalized if normalized.values.any?(&:present?)
|
|
224
|
+
end
|
|
177
225
|
else
|
|
178
226
|
value
|
|
179
227
|
end
|
|
180
228
|
end
|
|
181
229
|
|
|
230
|
+
def normalized_asset_ids(entry, field, value)
|
|
231
|
+
ids = Array(value).select(&:present?).map(&:to_i).uniq
|
|
232
|
+
valid_ids = current_site.assets.where(id: ids).pluck(:id)
|
|
233
|
+
if valid_ids.length != ids.length
|
|
234
|
+
entry.errors.add(:base, "#{field_label(field)} contains an invalid image")
|
|
235
|
+
return []
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
ids
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def normalized_image_value(entry, field, value)
|
|
242
|
+
return nil if value.blank?
|
|
243
|
+
|
|
244
|
+
asset = current_site.assets.find_by(id: value.to_i)
|
|
245
|
+
unless asset
|
|
246
|
+
entry.errors.add(:base, "#{field_label(field)} is not a valid image")
|
|
247
|
+
return nil
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
asset.update!(alt_text: default_asset_alt_text(entry, field)) if asset.alt_text.blank?
|
|
251
|
+
asset.id
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def normalized_number_value(value, field)
|
|
255
|
+
return nil if value.blank?
|
|
256
|
+
|
|
257
|
+
field["number_kind"] == "integer" ? Integer(value, 10) : Float(value)
|
|
258
|
+
rescue ArgumentError, TypeError
|
|
259
|
+
value
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def parsed_structured_value(value, fallback:)
|
|
263
|
+
return value unless value.is_a?(String)
|
|
264
|
+
|
|
265
|
+
JSON.parse(value)
|
|
266
|
+
rescue JSON::ParserError
|
|
267
|
+
fallback
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def normalized_structured_row(field, value)
|
|
271
|
+
row = value.respond_to?(:to_h) ? value.to_h : {}
|
|
272
|
+
Array(field["fields"]).each_with_object({}) do |nested_field, normalized|
|
|
273
|
+
handle = nested_field["handle"].to_s
|
|
274
|
+
next if handle.blank?
|
|
275
|
+
|
|
276
|
+
nested_value = row[handle]
|
|
277
|
+
nested_value = nested_field["default"] if nested_value.nil? && nested_field.key?("default")
|
|
278
|
+
normalized[handle] = nested_field["type"] == "boolean" ? ActiveModel::Type::Boolean.new.cast(nested_value) : nested_value
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
182
282
|
def normalized_relationship_value(entry, field, value)
|
|
283
|
+
if ActiveModel::Type::Boolean.new.cast(field["multiple"])
|
|
284
|
+
ids = Array(value).select(&:present?).map(&:to_i).uniq
|
|
285
|
+
valid_ids = relationship_entry_scope(entry, field).where(id: ids).pluck(:id)
|
|
286
|
+
if valid_ids.length != ids.length
|
|
287
|
+
entry.errors.add(:base, "#{field_label(field)} contains an invalid entry")
|
|
288
|
+
return []
|
|
289
|
+
end
|
|
290
|
+
return ids
|
|
291
|
+
end
|
|
292
|
+
|
|
183
293
|
return nil if value.blank?
|
|
184
294
|
|
|
185
295
|
related_entry = relationship_entry_scope(entry, field).find_by(id: value.to_i)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Plum
|
|
2
|
+
module Cp
|
|
3
|
+
class EntryRevisionsController < BaseController
|
|
4
|
+
before_action :require_editor
|
|
5
|
+
before_action :set_entry
|
|
6
|
+
|
|
7
|
+
def index
|
|
8
|
+
@revisions = @entry.revisions.order(created_at: :desc)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def restore
|
|
12
|
+
revision = @entry.revisions.find(params[:id])
|
|
13
|
+
@entry.restore_revision!(revision, editor: current_user)
|
|
14
|
+
redirect_to edit_cp_content_type_entry_path(@content_type, @entry), notice: "Revision restored"
|
|
15
|
+
rescue ActiveRecord::RecordInvalid => error
|
|
16
|
+
redirect_to cp_content_type_entry_revisions_path(@content_type, @entry), alert: "Could not restore revision: #{error.record.errors.full_messages.to_sentence}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def set_entry
|
|
22
|
+
@content_type = current_site.content_types.find(params[:content_type_id])
|
|
23
|
+
@entry = @content_type.entries.find(params[:entry_id])
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Plum
|
|
2
|
+
module Cp
|
|
3
|
+
class FieldsetsController < BaseController
|
|
4
|
+
before_action :require_admin
|
|
5
|
+
|
|
6
|
+
def index
|
|
7
|
+
@fieldsets = current_site.fieldsets.order(:name)
|
|
8
|
+
@content_types = current_site.content_types.order(:name)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def create
|
|
12
|
+
source = current_site.content_types.find(params.require(:fieldset)[:content_type_id])
|
|
13
|
+
fieldset = current_site.fieldsets.build(fieldset_params.except(:content_type_id))
|
|
14
|
+
fieldset.fields = source.fields.deep_dup
|
|
15
|
+
if fieldset.save
|
|
16
|
+
redirect_to cp_fieldsets_path, notice: "Fieldset created from #{source.name}"
|
|
17
|
+
else
|
|
18
|
+
@fieldsets = current_site.fieldsets.order(:name)
|
|
19
|
+
@content_types = current_site.content_types.order(:name)
|
|
20
|
+
@fieldset = fieldset
|
|
21
|
+
render :index, status: :unprocessable_entity
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def destroy
|
|
26
|
+
current_site.fieldsets.find(params[:id]).destroy!
|
|
27
|
+
redirect_to cp_fieldsets_path, notice: "Fieldset deleted"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def fieldset_params
|
|
33
|
+
params.require(:fieldset).permit(:name, :handle, :content_type_id)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -20,10 +20,19 @@ module Plum
|
|
|
20
20
|
def update
|
|
21
21
|
settings_attributes = site_settings_params
|
|
22
22
|
theme_settings_by_theme = settings_attributes.delete(:theme_settings_by_theme).to_h
|
|
23
|
+
locales = normalized_locales(settings_attributes.delete(:locales))
|
|
24
|
+
default_locale = settings_attributes.delete(:default_locale).to_s
|
|
23
25
|
@site_settings.assign_attributes(settings_attributes)
|
|
24
26
|
|
|
27
|
+
unless locales.any? && locales.include?(default_locale)
|
|
28
|
+
@site_settings.errors.add(:base, "Locales must be valid codes and include the default locale")
|
|
29
|
+
set_theme_options
|
|
30
|
+
return render :edit, status: :unprocessable_entity
|
|
31
|
+
end
|
|
32
|
+
|
|
25
33
|
if @site_settings.save
|
|
26
|
-
current_site.
|
|
34
|
+
site_settings = current_site.settings.to_h.merge("locales" => locales, "default_locale" => default_locale)
|
|
35
|
+
current_site.update!(theme_settings: theme_settings_for(@site_settings.theme_name, theme_settings_by_theme), settings: site_settings)
|
|
27
36
|
redirect_to edit_cp_site_settings_path, notice: "Site settings updated"
|
|
28
37
|
else
|
|
29
38
|
set_theme_options
|
|
@@ -92,9 +101,19 @@ module Plum
|
|
|
92
101
|
:primary_color,
|
|
93
102
|
:support_email,
|
|
94
103
|
:theme_name,
|
|
104
|
+
:locales,
|
|
105
|
+
:default_locale,
|
|
95
106
|
theme_settings_by_theme: {}
|
|
96
107
|
)
|
|
97
108
|
end
|
|
109
|
+
|
|
110
|
+
def normalized_locales(value)
|
|
111
|
+
value.to_s.split(/[\s,]+/).filter_map do |locale|
|
|
112
|
+
parts = locale.split("-", 2)
|
|
113
|
+
normalized = [ parts[0]&.downcase, parts[1]&.upcase ].compact.join("-")
|
|
114
|
+
normalized if normalized.match?(/\A[a-z]{2}(?:-[A-Z]{2})?\z/)
|
|
115
|
+
end.uniq
|
|
116
|
+
end
|
|
98
117
|
end
|
|
99
118
|
end
|
|
100
119
|
end
|
|
@@ -5,6 +5,7 @@ module Plum
|
|
|
5
5
|
def home
|
|
6
6
|
@site_settings = SiteSetting.instance(current_site)
|
|
7
7
|
@entry = Entry.for_site(current_site).live
|
|
8
|
+
.where(locale: requested_locale)
|
|
8
9
|
.includes(:content_type, terms: :taxonomy)
|
|
9
10
|
.find_by(slug: HOMEPAGE_SLUG)
|
|
10
11
|
|
|
@@ -13,10 +14,19 @@ module Plum
|
|
|
13
14
|
render html: html.html_safe, layout: false
|
|
14
15
|
end
|
|
15
16
|
|
|
17
|
+
def localized_home
|
|
18
|
+
raise ActiveRecord::RecordNotFound unless current_site.locales.include?(params[:locale])
|
|
19
|
+
|
|
20
|
+
home
|
|
21
|
+
rescue ActiveRecord::RecordNotFound
|
|
22
|
+
render file: Rails.public_path.join("404.html"), status: :not_found, layout: false
|
|
23
|
+
end
|
|
24
|
+
|
|
16
25
|
def search
|
|
17
26
|
query = params[:q].to_s.strip
|
|
18
27
|
context = build_context
|
|
19
28
|
results = Entry.for_site(current_site).live.search(query)
|
|
29
|
+
.where(locale: requested_locale)
|
|
20
30
|
.includes(:content_type)
|
|
21
31
|
.order(published_at: :desc)
|
|
22
32
|
.limit(50)
|
|
@@ -33,6 +43,8 @@ module Plum
|
|
|
33
43
|
end
|
|
34
44
|
|
|
35
45
|
def show
|
|
46
|
+
raise ActiveRecord::RecordNotFound unless current_site.locales.include?(requested_locale)
|
|
47
|
+
|
|
36
48
|
slug = params[:slug]
|
|
37
49
|
|
|
38
50
|
@content_type = ContentType.for_site(current_site).find_by(handle: slug)
|
|
@@ -47,6 +59,7 @@ module Plum
|
|
|
47
59
|
end
|
|
48
60
|
if content_type
|
|
49
61
|
@entry = Entry.for_site(current_site).live
|
|
62
|
+
.where(locale: requested_locale)
|
|
50
63
|
.includes(:content_type, terms: :taxonomy)
|
|
51
64
|
.find_by!(content_type: content_type, slug: parts[1])
|
|
52
65
|
html = Plum::LiquidRenderer.render_template("entries/#{content_type.handle}", build_context(@entry).to_h)
|
|
@@ -64,6 +77,7 @@ module Plum
|
|
|
64
77
|
end
|
|
65
78
|
|
|
66
79
|
@entry = Entry.for_site(current_site).live
|
|
80
|
+
.where(locale: requested_locale)
|
|
67
81
|
.includes(:content_type, terms: :taxonomy)
|
|
68
82
|
.find_by!(slug: slug)
|
|
69
83
|
template = "entries/#{@entry.content_type.handle}"
|
|
@@ -81,6 +95,7 @@ module Plum
|
|
|
81
95
|
context = build_context
|
|
82
96
|
page = [ params.fetch(:page, 1).to_i, 1 ].max
|
|
83
97
|
all_entries = Entry.for_site(current_site).live
|
|
98
|
+
.where(locale: requested_locale)
|
|
84
99
|
.where(content_type: content_type)
|
|
85
100
|
.order(published_at: :desc, created_at: :desc)
|
|
86
101
|
total = all_entries.count
|
|
@@ -112,6 +127,7 @@ module Plum
|
|
|
112
127
|
context = build_context
|
|
113
128
|
page = [ params.fetch(:page, 1).to_i, 1 ].max
|
|
114
129
|
all_entries = Entry.for_site(current_site).live
|
|
130
|
+
.where(locale: requested_locale)
|
|
115
131
|
.joins(:entry_terms)
|
|
116
132
|
.where(plum_entry_terms: { term_id: term.id })
|
|
117
133
|
.order(published_at: :desc)
|
|
@@ -146,7 +162,7 @@ module Plum
|
|
|
146
162
|
"name" => taxonomy.name,
|
|
147
163
|
"handle" => taxonomy.handle,
|
|
148
164
|
"slug" => taxonomy.slug,
|
|
149
|
-
"terms" => terms.map { |t| { "name" => t.name, "slug" => t.slug, "url" => "
|
|
165
|
+
"terms" => terms.map { |t| { "name" => t.name, "slug" => t.slug, "url" => localized_path("#{taxonomy.slug}/#{t.slug}"), "entries_count" => t.entries.live.where(locale: requested_locale).count } }
|
|
150
166
|
}
|
|
151
167
|
)
|
|
152
168
|
template = "taxonomies/#{taxonomy.handle}_index"
|
|
@@ -157,5 +173,14 @@ module Plum
|
|
|
157
173
|
def build_context(entry = nil)
|
|
158
174
|
LiquidContext.new(controller: self, site: current_site, entry: entry)
|
|
159
175
|
end
|
|
176
|
+
|
|
177
|
+
def requested_locale
|
|
178
|
+
params[:locale].presence || current_site.default_locale
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def localized_path(path)
|
|
182
|
+
prefix = requested_locale == current_site.default_locale ? nil : requested_locale
|
|
183
|
+
"/#{[ prefix, path ].compact.join('/')}"
|
|
184
|
+
end
|
|
160
185
|
end
|
|
161
186
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ["checkbox", "count"]
|
|
5
|
+
static values = { max: Number }
|
|
6
|
+
|
|
7
|
+
connect() {
|
|
8
|
+
this.refresh()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
refresh() {
|
|
12
|
+
const selected = this.checkboxTargets.filter(checkbox => checkbox.checked).length
|
|
13
|
+
if (this.hasCountTarget) this.countTarget.textContent = `${selected} selected`
|
|
14
|
+
this.checkboxTargets.forEach(checkbox => {
|
|
15
|
+
checkbox.disabled = this.hasMaxValue && this.maxValue > 0 && selected >= this.maxValue && !checkbox.checked
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
}
|