avo 4.2.9 → 4.2.10
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/Gemfile.lock +1 -1
- data/app/assets/builds/avo/application.css +137 -109
- data/app/assets/builds/avo/application.js +1 -1
- data/app/assets/builds/avo/application.js.map +3 -3
- data/app/assets/stylesheets/css/variables.css +141 -111
- data/app/components/avo/fields/trix_field/edit_component.rb +3 -1
- data/app/components/avo/index/resource_controls_component.rb +4 -2
- data/app/components/avo/resource_component.rb +2 -4
- data/app/components/avo/resource_sidebar_component.html.erb +1 -1
- data/app/components/avo/views/resource_index_component.rb +1 -2
- data/app/controllers/avo/array_controller.rb +8 -0
- data/app/controllers/avo/associations_controller.rb +7 -2
- data/app/javascript/js/controllers/fields/tags_field_controller.js +7 -0
- data/app/views/layouts/avo/application.html.erb +1 -1
- data/lib/avo/fields/concerns/has_html_attributes.rb +2 -1
- data/lib/avo/fields/tags_field.rb +2 -1
- data/lib/avo/resources/array_resource.rb +9 -0
- data/lib/avo/resources/base.rb +2 -0
- data/lib/avo/skills/avo-resources/SKILL.md +25 -0
- data/lib/avo/version.rb +1 -1
- metadata +1 -1
|
@@ -118,8 +118,7 @@
|
|
|
118
118
|
|
|
119
119
|
/* Background of the main content panel. Dedicated knob so installs can recolor
|
|
120
120
|
the content surface independently of the primary surface token. Defaults to
|
|
121
|
-
--color-primary
|
|
122
|
-
.dark. */
|
|
121
|
+
--color-primary. */
|
|
123
122
|
--color-main-content-background: var(--color-primary);
|
|
124
123
|
|
|
125
124
|
/* Border between the sidebar and the main content panel. Dedicated knob so
|
|
@@ -185,7 +184,11 @@
|
|
|
185
184
|
/* foundations - Dark Mode */
|
|
186
185
|
--color-background: var(--color-avo-neutral-900);
|
|
187
186
|
--color-foreground: var(--color-avo-neutral-50);
|
|
188
|
-
--color-primary:
|
|
187
|
+
--color-primary: color-mix(
|
|
188
|
+
in oklab,
|
|
189
|
+
var(--color-avo-neutral-950),
|
|
190
|
+
var(--color-background) 20%
|
|
191
|
+
);
|
|
189
192
|
--color-secondary: var(--color-avo-neutral-800);
|
|
190
193
|
--color-tertiary: var(--color-avo-neutral-700);
|
|
191
194
|
--color-quaternary: var(--color-avo-neutral-600);
|
|
@@ -219,130 +222,157 @@
|
|
|
219
222
|
`.accent-theme-orange` still wins over a configured brand color. */
|
|
220
223
|
@layer components {
|
|
221
224
|
.neutral-theme-slate {
|
|
222
|
-
--
|
|
223
|
-
--
|
|
224
|
-
--
|
|
225
|
-
--
|
|
226
|
-
--
|
|
227
|
-
--
|
|
228
|
-
--
|
|
229
|
-
--
|
|
230
|
-
--
|
|
231
|
-
--
|
|
232
|
-
--
|
|
233
|
-
--
|
|
225
|
+
--avo-neutral-source-25: var(--color-slate-50);
|
|
226
|
+
--avo-neutral-source-50: var(--color-slate-50);
|
|
227
|
+
--avo-neutral-source-100: var(--color-slate-100);
|
|
228
|
+
--avo-neutral-source-200: var(--color-slate-200);
|
|
229
|
+
--avo-neutral-source-300: var(--color-slate-300);
|
|
230
|
+
--avo-neutral-source-400: var(--color-slate-400);
|
|
231
|
+
--avo-neutral-source-500: var(--color-slate-500);
|
|
232
|
+
--avo-neutral-source-600: var(--color-slate-600);
|
|
233
|
+
--avo-neutral-source-700: var(--color-slate-700);
|
|
234
|
+
--avo-neutral-source-800: var(--color-slate-800);
|
|
235
|
+
--avo-neutral-source-900: var(--color-slate-900);
|
|
236
|
+
--avo-neutral-source-950: var(--color-slate-950);
|
|
234
237
|
}
|
|
235
238
|
.neutral-theme-stone {
|
|
236
|
-
--
|
|
237
|
-
--
|
|
238
|
-
--
|
|
239
|
-
--
|
|
240
|
-
--
|
|
241
|
-
--
|
|
242
|
-
--
|
|
243
|
-
--
|
|
244
|
-
--
|
|
245
|
-
--
|
|
246
|
-
--
|
|
247
|
-
--
|
|
239
|
+
--avo-neutral-source-25: var(--color-stone-50);
|
|
240
|
+
--avo-neutral-source-50: var(--color-stone-50);
|
|
241
|
+
--avo-neutral-source-100: var(--color-stone-100);
|
|
242
|
+
--avo-neutral-source-200: var(--color-stone-200);
|
|
243
|
+
--avo-neutral-source-300: var(--color-stone-300);
|
|
244
|
+
--avo-neutral-source-400: var(--color-stone-400);
|
|
245
|
+
--avo-neutral-source-500: var(--color-stone-500);
|
|
246
|
+
--avo-neutral-source-600: var(--color-stone-600);
|
|
247
|
+
--avo-neutral-source-700: var(--color-stone-700);
|
|
248
|
+
--avo-neutral-source-800: var(--color-stone-800);
|
|
249
|
+
--avo-neutral-source-900: var(--color-stone-900);
|
|
250
|
+
--avo-neutral-source-950: var(--color-stone-950);
|
|
248
251
|
}
|
|
249
252
|
.neutral-theme-taupe {
|
|
250
|
-
--
|
|
251
|
-
--
|
|
252
|
-
--
|
|
253
|
-
--
|
|
254
|
-
--
|
|
255
|
-
--
|
|
256
|
-
--
|
|
257
|
-
--
|
|
258
|
-
--
|
|
259
|
-
--
|
|
260
|
-
--
|
|
261
|
-
--
|
|
253
|
+
--avo-neutral-source-25: var(--color-taupe-50);
|
|
254
|
+
--avo-neutral-source-50: var(--color-taupe-50);
|
|
255
|
+
--avo-neutral-source-100: var(--color-taupe-100);
|
|
256
|
+
--avo-neutral-source-200: var(--color-taupe-200);
|
|
257
|
+
--avo-neutral-source-300: var(--color-taupe-300);
|
|
258
|
+
--avo-neutral-source-400: var(--color-taupe-400);
|
|
259
|
+
--avo-neutral-source-500: var(--color-taupe-500);
|
|
260
|
+
--avo-neutral-source-600: var(--color-taupe-600);
|
|
261
|
+
--avo-neutral-source-700: var(--color-taupe-700);
|
|
262
|
+
--avo-neutral-source-800: var(--color-taupe-800);
|
|
263
|
+
--avo-neutral-source-900: var(--color-taupe-900);
|
|
264
|
+
--avo-neutral-source-950: var(--color-taupe-950);
|
|
262
265
|
}
|
|
263
266
|
.neutral-theme-mauve {
|
|
264
|
-
--
|
|
265
|
-
--
|
|
266
|
-
--
|
|
267
|
-
--
|
|
268
|
-
--
|
|
269
|
-
--
|
|
270
|
-
--
|
|
271
|
-
--
|
|
272
|
-
--
|
|
273
|
-
--
|
|
274
|
-
--
|
|
275
|
-
--
|
|
267
|
+
--avo-neutral-source-25: var(--color-mauve-50);
|
|
268
|
+
--avo-neutral-source-50: var(--color-mauve-50);
|
|
269
|
+
--avo-neutral-source-100: var(--color-mauve-100);
|
|
270
|
+
--avo-neutral-source-200: var(--color-mauve-200);
|
|
271
|
+
--avo-neutral-source-300: var(--color-mauve-300);
|
|
272
|
+
--avo-neutral-source-400: var(--color-mauve-400);
|
|
273
|
+
--avo-neutral-source-500: var(--color-mauve-500);
|
|
274
|
+
--avo-neutral-source-600: var(--color-mauve-600);
|
|
275
|
+
--avo-neutral-source-700: var(--color-mauve-700);
|
|
276
|
+
--avo-neutral-source-800: var(--color-mauve-800);
|
|
277
|
+
--avo-neutral-source-900: var(--color-mauve-900);
|
|
278
|
+
--avo-neutral-source-950: var(--color-mauve-950);
|
|
276
279
|
}
|
|
277
280
|
.neutral-theme-mist {
|
|
278
|
-
--
|
|
279
|
-
--
|
|
280
|
-
--
|
|
281
|
-
--
|
|
282
|
-
--
|
|
283
|
-
--
|
|
284
|
-
--
|
|
285
|
-
--
|
|
286
|
-
--
|
|
287
|
-
--
|
|
288
|
-
--
|
|
289
|
-
--
|
|
281
|
+
--avo-neutral-source-25: var(--color-mist-50);
|
|
282
|
+
--avo-neutral-source-50: var(--color-mist-50);
|
|
283
|
+
--avo-neutral-source-100: var(--color-mist-100);
|
|
284
|
+
--avo-neutral-source-200: var(--color-mist-200);
|
|
285
|
+
--avo-neutral-source-300: var(--color-mist-300);
|
|
286
|
+
--avo-neutral-source-400: var(--color-mist-400);
|
|
287
|
+
--avo-neutral-source-500: var(--color-mist-500);
|
|
288
|
+
--avo-neutral-source-600: var(--color-mist-600);
|
|
289
|
+
--avo-neutral-source-700: var(--color-mist-700);
|
|
290
|
+
--avo-neutral-source-800: var(--color-mist-800);
|
|
291
|
+
--avo-neutral-source-900: var(--color-mist-900);
|
|
292
|
+
--avo-neutral-source-950: var(--color-mist-950);
|
|
290
293
|
}
|
|
291
294
|
.neutral-theme-gray {
|
|
292
|
-
--
|
|
293
|
-
--
|
|
294
|
-
--
|
|
295
|
-
--
|
|
296
|
-
--
|
|
297
|
-
--
|
|
298
|
-
--
|
|
299
|
-
--
|
|
300
|
-
--
|
|
301
|
-
--
|
|
302
|
-
--
|
|
303
|
-
--
|
|
295
|
+
--avo-neutral-source-25: var(--color-gray-50);
|
|
296
|
+
--avo-neutral-source-50: var(--color-gray-50);
|
|
297
|
+
--avo-neutral-source-100: var(--color-gray-100);
|
|
298
|
+
--avo-neutral-source-200: var(--color-gray-200);
|
|
299
|
+
--avo-neutral-source-300: var(--color-gray-300);
|
|
300
|
+
--avo-neutral-source-400: var(--color-gray-400);
|
|
301
|
+
--avo-neutral-source-500: var(--color-gray-500);
|
|
302
|
+
--avo-neutral-source-600: var(--color-gray-600);
|
|
303
|
+
--avo-neutral-source-700: var(--color-gray-700);
|
|
304
|
+
--avo-neutral-source-800: var(--color-gray-800);
|
|
305
|
+
--avo-neutral-source-900: var(--color-gray-900);
|
|
306
|
+
--avo-neutral-source-950: var(--color-gray-950);
|
|
304
307
|
}
|
|
305
308
|
.neutral-theme-zinc {
|
|
306
|
-
--
|
|
307
|
-
--
|
|
308
|
-
--
|
|
309
|
-
--
|
|
310
|
-
--
|
|
311
|
-
--
|
|
312
|
-
--
|
|
313
|
-
--
|
|
314
|
-
--
|
|
315
|
-
--
|
|
316
|
-
--
|
|
317
|
-
--
|
|
309
|
+
--avo-neutral-source-25: var(--color-zinc-50);
|
|
310
|
+
--avo-neutral-source-50: var(--color-zinc-50);
|
|
311
|
+
--avo-neutral-source-100: var(--color-zinc-100);
|
|
312
|
+
--avo-neutral-source-200: var(--color-zinc-200);
|
|
313
|
+
--avo-neutral-source-300: var(--color-zinc-300);
|
|
314
|
+
--avo-neutral-source-400: var(--color-zinc-400);
|
|
315
|
+
--avo-neutral-source-500: var(--color-zinc-500);
|
|
316
|
+
--avo-neutral-source-600: var(--color-zinc-600);
|
|
317
|
+
--avo-neutral-source-700: var(--color-zinc-700);
|
|
318
|
+
--avo-neutral-source-800: var(--color-zinc-800);
|
|
319
|
+
--avo-neutral-source-900: var(--color-zinc-900);
|
|
320
|
+
--avo-neutral-source-950: var(--color-zinc-950);
|
|
318
321
|
}
|
|
319
322
|
.neutral-theme-neutral {
|
|
320
|
-
--
|
|
321
|
-
--
|
|
322
|
-
--
|
|
323
|
-
--
|
|
324
|
-
--
|
|
325
|
-
--
|
|
326
|
-
--
|
|
327
|
-
--
|
|
328
|
-
--
|
|
329
|
-
--
|
|
330
|
-
--
|
|
331
|
-
--
|
|
323
|
+
--avo-neutral-source-25: var(--color-neutral-50);
|
|
324
|
+
--avo-neutral-source-50: var(--color-neutral-50);
|
|
325
|
+
--avo-neutral-source-100: var(--color-neutral-100);
|
|
326
|
+
--avo-neutral-source-200: var(--color-neutral-200);
|
|
327
|
+
--avo-neutral-source-300: var(--color-neutral-300);
|
|
328
|
+
--avo-neutral-source-400: var(--color-neutral-400);
|
|
329
|
+
--avo-neutral-source-500: var(--color-neutral-500);
|
|
330
|
+
--avo-neutral-source-600: var(--color-neutral-600);
|
|
331
|
+
--avo-neutral-source-700: var(--color-neutral-700);
|
|
332
|
+
--avo-neutral-source-800: var(--color-neutral-800);
|
|
333
|
+
--avo-neutral-source-900: var(--color-neutral-900);
|
|
334
|
+
--avo-neutral-source-950: var(--color-neutral-950);
|
|
332
335
|
}
|
|
333
336
|
.neutral-theme-olive {
|
|
334
|
-
--
|
|
335
|
-
--
|
|
336
|
-
--
|
|
337
|
-
--
|
|
338
|
-
--
|
|
339
|
-
--
|
|
340
|
-
--
|
|
341
|
-
--
|
|
342
|
-
--
|
|
343
|
-
--
|
|
344
|
-
--
|
|
345
|
-
--
|
|
337
|
+
--avo-neutral-source-25: var(--color-olive-50);
|
|
338
|
+
--avo-neutral-source-50: var(--color-olive-50);
|
|
339
|
+
--avo-neutral-source-100: var(--color-olive-100);
|
|
340
|
+
--avo-neutral-source-200: var(--color-olive-200);
|
|
341
|
+
--avo-neutral-source-300: var(--color-olive-300);
|
|
342
|
+
--avo-neutral-source-400: var(--color-olive-400);
|
|
343
|
+
--avo-neutral-source-500: var(--color-olive-500);
|
|
344
|
+
--avo-neutral-source-600: var(--color-olive-600);
|
|
345
|
+
--avo-neutral-source-700: var(--color-olive-700);
|
|
346
|
+
--avo-neutral-source-800: var(--color-olive-800);
|
|
347
|
+
--avo-neutral-source-900: var(--color-olive-900);
|
|
348
|
+
--avo-neutral-source-950: var(--color-olive-950);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/* Keep every selectable neutral theme on Brand's lightness curve while
|
|
352
|
+
preserving the source palette's chroma and hue at each shade. */
|
|
353
|
+
:is(
|
|
354
|
+
.neutral-theme-slate,
|
|
355
|
+
.neutral-theme-stone,
|
|
356
|
+
.neutral-theme-gray,
|
|
357
|
+
.neutral-theme-zinc,
|
|
358
|
+
.neutral-theme-neutral,
|
|
359
|
+
.neutral-theme-taupe,
|
|
360
|
+
.neutral-theme-mauve,
|
|
361
|
+
.neutral-theme-mist,
|
|
362
|
+
.neutral-theme-olive
|
|
363
|
+
) {
|
|
364
|
+
--color-avo-neutral-25: oklch(from var(--avo-neutral-source-25) 99.71% c h);
|
|
365
|
+
--color-avo-neutral-50: oklch(from var(--avo-neutral-source-50) 98.51% c h);
|
|
366
|
+
--color-avo-neutral-100: oklch(from var(--avo-neutral-source-100) 97.31% c h);
|
|
367
|
+
--color-avo-neutral-200: oklch(from var(--avo-neutral-source-200) 92.8% c h);
|
|
368
|
+
--color-avo-neutral-300: oklch(from var(--avo-neutral-source-300) 75.72% c h);
|
|
369
|
+
--color-avo-neutral-400: oklch(from var(--avo-neutral-source-400) 62.68% c h);
|
|
370
|
+
--color-avo-neutral-500: oklch(from var(--avo-neutral-source-500) 53.48% c h);
|
|
371
|
+
--color-avo-neutral-600: oklch(from var(--avo-neutral-source-600) 47.84% c h);
|
|
372
|
+
--color-avo-neutral-700: oklch(from var(--avo-neutral-source-700) 42.76% c h);
|
|
373
|
+
--color-avo-neutral-800: oklch(from var(--avo-neutral-source-800) 39.04% c h);
|
|
374
|
+
--color-avo-neutral-900: oklch(from var(--avo-neutral-source-900) 27.68% c h);
|
|
375
|
+
--color-avo-neutral-950: oklch(from var(--avo-neutral-source-950) 20.46% c h);
|
|
346
376
|
}
|
|
347
377
|
|
|
348
378
|
.accent-theme-red {
|
|
@@ -10,8 +10,10 @@ class Avo::Fields::TrixField::EditComponent < Avo::Fields::EditComponent
|
|
|
10
10
|
|
|
11
11
|
super
|
|
12
12
|
|
|
13
|
+
# A nested form (avo-nested) renders one editor per record of the same resource, so the form index
|
|
14
|
+
# keeps their ids apart. The new-record template's index is NEW_RECORD, replaced on each clone.
|
|
13
15
|
@input_id = if @resource_name.present?
|
|
14
|
-
|
|
16
|
+
[@field.type, @resource_name, form&.index, @field.id].compact.join("_")
|
|
15
17
|
elsif form.present?
|
|
16
18
|
"#{@field.type}_#{form.index}_#{@field.id}"
|
|
17
19
|
end
|
|
@@ -16,8 +16,7 @@ class Avo::Index::ResourceControlsComponent < Avo::ResourceComponent
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def can_edit?
|
|
19
|
-
|
|
20
|
-
return false if @resource.resource_type_array?
|
|
19
|
+
return false if @resource.read_only_array?
|
|
21
20
|
|
|
22
21
|
return authorize_association_for(:edit) if @reflection.present?
|
|
23
22
|
|
|
@@ -165,6 +164,9 @@ class Avo::Index::ResourceControlsComponent < Avo::ResourceComponent
|
|
|
165
164
|
if params[:turbo_frame]
|
|
166
165
|
hidden[:turbo_frame] = params[:turbo_frame]
|
|
167
166
|
hidden[:referrer] = referrer_path
|
|
167
|
+
else
|
|
168
|
+
# Keep the filters, sorting and page after a destroy redirects back here.
|
|
169
|
+
hidden[:referrer] = request.fullpath
|
|
168
170
|
end
|
|
169
171
|
|
|
170
172
|
hidden.compact
|
|
@@ -41,8 +41,7 @@ class Avo::ResourceComponent < Avo::BaseComponent
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def can_see_the_edit_button?
|
|
44
|
-
|
|
45
|
-
return false if @resource.resource_type_array?
|
|
44
|
+
return false if @resource.read_only_array?
|
|
46
45
|
|
|
47
46
|
return authorize_association_for(:edit) if @reflection.present?
|
|
48
47
|
|
|
@@ -50,8 +49,7 @@ class Avo::ResourceComponent < Avo::BaseComponent
|
|
|
50
49
|
end
|
|
51
50
|
|
|
52
51
|
def can_see_the_destroy_button?
|
|
53
|
-
|
|
54
|
-
return false if @resource.resource_type_array?
|
|
52
|
+
return false if @resource.read_only_array?
|
|
55
53
|
|
|
56
54
|
@resource.authorization.authorize_action(:destroy, raise_exception: false)
|
|
57
55
|
end
|
|
@@ -32,8 +32,7 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
|
|
|
32
32
|
# The Create button is dependent on the new? policy method.
|
|
33
33
|
# The create? should be called only when the user clicks the Save button so the developers gets access to the params from the form.
|
|
34
34
|
def can_see_the_create_button?
|
|
35
|
-
|
|
36
|
-
return false if @resource.resource_type_array?
|
|
35
|
+
return false if @resource.read_only_array?
|
|
37
36
|
|
|
38
37
|
return authorize_association_for(:create) if @reflection.present?
|
|
39
38
|
|
|
@@ -3,5 +3,13 @@ module Avo
|
|
|
3
3
|
def set_query
|
|
4
4
|
@query ||= @resource.fetch_records
|
|
5
5
|
end
|
|
6
|
+
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
# Fetching builds the model class with an accessor per attribute, so a new record can be filled.
|
|
10
|
+
def set_record_to_fill
|
|
11
|
+
@resource.fetch_records
|
|
12
|
+
super
|
|
13
|
+
end
|
|
6
14
|
end
|
|
7
15
|
end
|
|
@@ -355,8 +355,13 @@ module Avo
|
|
|
355
355
|
def reload_frame_turbo_streams
|
|
356
356
|
turbo_streams = super
|
|
357
357
|
|
|
358
|
-
#
|
|
359
|
-
|
|
358
|
+
# Close the modal if the user wants to add just one record, otherwise
|
|
359
|
+
# reload it so the options reflect the record that was just attached
|
|
360
|
+
turbo_streams << if params[:button] == "attach_another"
|
|
361
|
+
turbo_stream.turbo_frame_reload(Avo::MODAL_FRAME_ID)
|
|
362
|
+
else
|
|
363
|
+
turbo_stream.avo_close_modal
|
|
364
|
+
end
|
|
360
365
|
|
|
361
366
|
turbo_streams
|
|
362
367
|
end
|
|
@@ -26,6 +26,8 @@ export default class extends Controller {
|
|
|
26
26
|
|
|
27
27
|
searchDebounce = 500
|
|
28
28
|
|
|
29
|
+
searchCount = 0
|
|
30
|
+
|
|
29
31
|
debouncedFetch = debouncePromise(fetch, this.searchDebounce)
|
|
30
32
|
|
|
31
33
|
get suggestionsAreObjects() {
|
|
@@ -107,6 +109,8 @@ export default class extends Controller {
|
|
|
107
109
|
q: query,
|
|
108
110
|
})
|
|
109
111
|
|
|
112
|
+
const search = ++that.searchCount
|
|
113
|
+
|
|
110
114
|
// reset current whitelist
|
|
111
115
|
that.tagify.whitelist = null
|
|
112
116
|
// show the loader animation
|
|
@@ -115,6 +119,9 @@ export default class extends Controller {
|
|
|
115
119
|
// get new whitelist from a request
|
|
116
120
|
that.fetchResults(uri.toString())
|
|
117
121
|
.then((result) => {
|
|
122
|
+
// The user kept typing while this search was in flight; the newer search fills the dropdown
|
|
123
|
+
if (search !== that.searchCount) return
|
|
124
|
+
|
|
118
125
|
that.tagify.settings.whitelist = result // add already-existing tags to the new whitelist array
|
|
119
126
|
|
|
120
127
|
that.tagify
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<meta name="avo:resources-index-path" content="<%= resources_index_path %>">
|
|
12
12
|
<% end %>
|
|
13
13
|
<%# Same for "c" → new, on show only, and only when the create button would render on the index. %>
|
|
14
|
-
<% if action_name == "show" && !@resource.
|
|
14
|
+
<% if action_name == "show" && !@resource.read_only_array? && @resource.authorization.authorize_action(:new, raise_exception: false) %>
|
|
15
15
|
<% if (resources_new_path = (new_resource_path(resource: @resource) rescue nil)) %>
|
|
16
16
|
<meta name="avo:resources-new-path" content="<%= resources_new_path %>">
|
|
17
17
|
<% end %>
|
|
@@ -93,7 +93,8 @@ module Avo
|
|
|
93
93
|
:string
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
# A blank data hash still needs to be a Hash so the stimulus targets get merged in.
|
|
97
|
+
merge_values_as(as: values_type, values: values) || default_attribute_value(name)
|
|
97
98
|
end
|
|
98
99
|
|
|
99
100
|
def get_html_from_hash(name = nil, element:, hash:, view:)
|
|
@@ -79,7 +79,8 @@ module Avo
|
|
|
79
79
|
def whitelist_items
|
|
80
80
|
return suggestions.to_json if enforce_suggestions
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
# Tags and suggestions can be strings or {value:} objects; Tagify compares them by their stringified value
|
|
83
|
+
(suggestions + field_value).as_json.uniq { |item| (item.is_a?(Hash) ? item["value"] : item).to_s }.to_json
|
|
83
84
|
end
|
|
84
85
|
|
|
85
86
|
def suggestions
|
|
@@ -13,6 +13,10 @@ module Avo
|
|
|
13
13
|
type: :array
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
# Shows the create, edit and delete controls. The records have no save! or destroy!,
|
|
17
|
+
# so the resource's controller overrides save_record_action and destroy_record_action.
|
|
18
|
+
class_attribute :writable, default: false
|
|
19
|
+
|
|
16
20
|
class << self
|
|
17
21
|
# Each array resource keeps its own model class. It starts as a placeholder class and
|
|
18
22
|
# fetch_records replaces it with the class of the records it builds or finds.
|
|
@@ -131,6 +135,11 @@ module Avo
|
|
|
131
135
|
|
|
132
136
|
def resource_type_array? = true
|
|
133
137
|
|
|
138
|
+
def read_only_array? = !writable
|
|
139
|
+
|
|
140
|
+
# Base derives it from ActiveRecord's base_class.
|
|
141
|
+
def form_scope = class_name.underscore
|
|
142
|
+
|
|
134
143
|
def sort_by_param = nil
|
|
135
144
|
|
|
136
145
|
def sorting_supported? = false
|
data/lib/avo/resources/base.rb
CHANGED
|
@@ -206,6 +206,31 @@ class Avo::Resources::Movie < Avo::Resources::ArrayResource
|
|
|
206
206
|
end
|
|
207
207
|
```
|
|
208
208
|
|
|
209
|
+
Array resources are read-only by default. To add create, edit and delete, set `self.writable = true` and persist the changes in the resource's controller. Avo fills the record from the form, but array records have no `save!` or `destroy!`:
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
class Avo::Resources::Bookmark < Avo::Resources::ArrayResource
|
|
213
|
+
self.writable = true
|
|
214
|
+
|
|
215
|
+
def records = BookmarkStore.all # array of hashes with an :id
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# app/controllers/avo/bookmarks_controller.rb
|
|
219
|
+
class Avo::BookmarksController < Avo::ArrayController
|
|
220
|
+
private
|
|
221
|
+
|
|
222
|
+
def save_record_action
|
|
223
|
+
@record.id = BookmarkStore.save(id: @record.id, title: @record.title)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def destroy_record_action
|
|
227
|
+
BookmarkStore.destroy(@record.id)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The form's params are scoped under the resource name (`bookmark[title]`). The policy still decides who can create, edit and delete.
|
|
233
|
+
|
|
209
234
|
Array resources are **Beta**: sorting is not supported, and the array is rebuilt on every request (cache inside `records` if it gets heavy). For external-API-backed data, prefer an [HTTP Resource](https://docs.avohq.io/4.0/http-resource.md).
|
|
210
235
|
|
|
211
236
|
### 7. Sidebar, shortcuts, external links, and multiple resources per model
|
data/lib/avo/version.rb
CHANGED