avo 4.0.0.beta.40 → 4.0.0.beta.41
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 +74 -53
- data/app/assets/builds/avo/application.js +99 -99
- data/app/assets/builds/avo/application.js.map +4 -4
- data/app/assets/stylesheets/application.css +1 -1
- data/app/assets/stylesheets/css/components/breadcrumbs.css +4 -4
- data/app/assets/stylesheets/css/components/color_scheme_switcher.css +3 -3
- data/app/assets/stylesheets/css/fields/stars.css +3 -3
- data/app/assets/stylesheets/css/fields/tags.css +5 -0
- data/app/assets/stylesheets/css/layout.css +14 -5
- data/app/assets/stylesheets/css/variables.css +37 -0
- data/app/components/avo/field_wrapper_component.html.erb +1 -1
- data/app/components/avo/fields/boolean_field/edit_component.html.erb +2 -2
- data/app/components/avo/fields/common/boolean_check_component.rb +3 -3
- data/app/components/avo/fields/common/files/view_type/grid_item_component.html.erb +3 -3
- data/app/components/avo/fields/common/files/view_type/grid_item_component.rb +2 -2
- data/app/components/avo/fields/common/status_viewer_component.html.erb +1 -1
- data/app/components/avo/fields/file_field/edit_component.html.erb +1 -1
- data/app/components/avo/fields/files_field/edit_component.html.erb +1 -1
- data/app/components/avo/fields/preview_field/index_component.rb +1 -1
- data/app/components/avo/fields/tags_field/tag_component.html.erb +1 -1
- data/app/components/avo/paginator_component.html.erb +13 -9
- data/app/controllers/avo/attachments_controller.rb +20 -4
- data/app/javascript/application.js +2 -13
- data/app/javascript/js/controllers/appearance_controller.js +2 -2
- data/app/javascript/js/controllers/appearance_preview_controller.js +34 -0
- data/app/javascript/js/controllers.js +2 -0
- data/app/javascript/js/global_hotkeys.js +2 -2
- data/app/views/avo/partials/_color_theme_override.html.erb +13 -0
- data/app/views/avo/private/appearance.html.erb +20 -55
- data/bin/setup +91 -0
- data/lib/avo/version.rb +1 -1
- data/lib/generators/avo/templates/locales/avo.ar.yml +7 -0
- data/lib/generators/avo/templates/locales/avo.de.yml +3 -0
- data/lib/generators/avo/templates/locales/avo.en.yml +3 -0
- data/lib/generators/avo/templates/locales/avo.es.yml +3 -0
- data/lib/generators/avo/templates/locales/avo.fr.yml +3 -0
- data/lib/generators/avo/templates/locales/avo.it.yml +3 -0
- data/lib/generators/avo/templates/locales/avo.ja.yml +3 -0
- data/lib/generators/avo/templates/locales/avo.nb.yml +3 -0
- data/lib/generators/avo/templates/locales/avo.nl.yml +3 -0
- data/lib/generators/avo/templates/locales/avo.nn.yml +3 -0
- data/lib/generators/avo/templates/locales/avo.pl.yml +5 -0
- data/lib/generators/avo/templates/locales/avo.pt-BR.yml +3 -0
- data/lib/generators/avo/templates/locales/avo.pt.yml +3 -0
- data/lib/generators/avo/templates/locales/avo.ro.yml +4 -0
- data/lib/generators/avo/templates/locales/avo.ru.yml +5 -0
- data/lib/generators/avo/templates/locales/avo.tr.yml +3 -0
- data/lib/generators/avo/templates/locales/avo.ua.yml +5 -0
- data/lib/generators/avo/templates/locales/avo.zh-TW.yml +3 -0
- data/lib/generators/avo/templates/locales/avo.zh.yml +3 -0
- metadata +3 -2
- data/bin/init +0 -52
|
@@ -97,6 +97,7 @@
|
|
|
97
97
|
|
|
98
98
|
@import "./css/components/ui/checkbox.css";
|
|
99
99
|
@import "./css/components/tooltip.css";
|
|
100
|
+
@import "./css/fields/tags.css";
|
|
100
101
|
|
|
101
102
|
/* variables.css uses explicit @layer theme { .dark } and @layer components
|
|
102
103
|
{ .neutral-theme-*, .accent-theme-* } blocks internally. Importing it OUTSIDE
|
|
@@ -125,7 +126,6 @@
|
|
|
125
126
|
@import "./css/fields/progress.css";
|
|
126
127
|
@import "./css/fields/key_value.css";
|
|
127
128
|
@import "./css/fields/trix.css";
|
|
128
|
-
@import "./css/fields/tags.css";
|
|
129
129
|
@import "./css/fields/tiptap.css";
|
|
130
130
|
@import "./css/fields/stars.css";
|
|
131
131
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
.breadcrumbs {
|
|
2
|
-
@apply sticky z-40 w-full bg-
|
|
2
|
+
@apply sticky z-40 w-full bg-(--color-main-content-background) mb-4;
|
|
3
3
|
/* top: calc(var(--top-navbar-height) + var(--spacing) * 4); */
|
|
4
4
|
top: calc(var(--top-navbar-height) + var(--spacing) * 2);
|
|
5
5
|
margin-top: --spacing(-2);
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
right: 0;
|
|
15
15
|
height: calc(0.5rem);
|
|
16
16
|
transform: translateY(-100%);
|
|
17
|
-
background: var(--color-
|
|
17
|
+
background: var(--color-main-content-background);
|
|
18
18
|
pointer-events: none;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
height: --spacing(4);
|
|
33
33
|
background: linear-gradient(
|
|
34
34
|
to bottom,
|
|
35
|
-
var(--color-
|
|
36
|
-
color-mix(in oklab, var(--color-
|
|
35
|
+
var(--color-main-content-background),
|
|
36
|
+
color-mix(in oklab, var(--color-main-content-background), transparent 100%)
|
|
37
37
|
);
|
|
38
38
|
pointer-events: none;
|
|
39
39
|
}
|
|
@@ -115,9 +115,9 @@
|
|
|
115
115
|
@apply bg-primary text-content shadow-sm;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
/* Shortcut badge buttons - active state mirrors
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
/* Shortcut badge buttons - active state mirrors :root.hotkeys-hide-badges. */
|
|
119
|
+
:root:not(.hotkeys-hide-badges) .color-scheme-switcher__scheme-button[data-key-badges="show"],
|
|
120
|
+
:root.hotkeys-hide-badges .color-scheme-switcher__scheme-button[data-key-badges="hide"] {
|
|
121
121
|
@apply bg-primary text-content shadow-sm;
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[data-component="avo/fields/common/stars_component"] {
|
|
2
2
|
.star {
|
|
3
|
-
@apply h-5 w-5 text-
|
|
3
|
+
@apply h-5 w-5 text-tertiary transition-colors;
|
|
4
4
|
}
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
.star.filled {
|
|
7
|
-
@apply text-
|
|
7
|
+
@apply text-warning-foreground;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
.rating-group {
|
|
@@ -8,6 +8,11 @@ tags.tagify {
|
|
|
8
8
|
--tags-focus-border-color: var(--color-primary);
|
|
9
9
|
--placeholder-color: var(--color-content-secondary);
|
|
10
10
|
--placeholder-color-focus: var(--color-content-secondary);
|
|
11
|
+
--tag-text-color: var(--color-content);
|
|
12
|
+
--tag-text-color--edit: var(--color-content);
|
|
13
|
+
--tag-remove-btn-color: var(--color-content);
|
|
14
|
+
--tag-bg: var(--color-secondary);
|
|
15
|
+
--tag-hover: var(--color-tertiary);
|
|
11
16
|
|
|
12
17
|
padding-block: var(--input-py);
|
|
13
18
|
align-items: center;
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
@theme {
|
|
2
2
|
--top-navbar-height: 3rem;
|
|
3
3
|
|
|
4
|
-
--navbar-
|
|
5
|
-
--navbar-notch-radius: 1rem;
|
|
6
|
-
--navbar-notch-color: var(--navbar-bg);
|
|
4
|
+
--navbar-notch-color: var(--color-navbar-background);
|
|
7
5
|
|
|
8
6
|
--border-color: var(--color-tertiary);
|
|
9
7
|
}
|
|
@@ -66,7 +64,7 @@
|
|
|
66
64
|
}
|
|
67
65
|
|
|
68
66
|
.main-content {
|
|
69
|
-
@apply w-(--content-width) flex flex-col bg-
|
|
67
|
+
@apply w-(--content-width) flex flex-col bg-(--color-main-content-background) py-2 lg:py-4 px-2 lg:px-4 border-s border-(--color-main-content-border) ms-1 rounded-se-(--navbar-notch-radius) rounded-ss-(--navbar-notch-radius);
|
|
70
68
|
|
|
71
69
|
transition: margin 0.1s ease-in-out, width 0.1s ease-in-out;
|
|
72
70
|
min-height: calc(100dvh - var(--top-navbar-height) - var(--spacing));
|
|
@@ -101,7 +99,7 @@
|
|
|
101
99
|
gap: 0.75rem;
|
|
102
100
|
padding-inline: 0.5rem;
|
|
103
101
|
|
|
104
|
-
background-color: var(--navbar-
|
|
102
|
+
background-color: var(--color-navbar-background);
|
|
105
103
|
|
|
106
104
|
> * {
|
|
107
105
|
@apply pointer-events-auto;
|
|
@@ -127,6 +125,17 @@
|
|
|
127
125
|
/* border: 1px solid red; */
|
|
128
126
|
}
|
|
129
127
|
|
|
128
|
+
/* Hide the arches when --navbar-notch-enabled is false. A style query lets the
|
|
129
|
+
variable read like a boolean (true/false) instead of exposing raw CSS
|
|
130
|
+
display keywords. The pseudo-elements query their originating element
|
|
131
|
+
(.top-navbar), which inherits --navbar-notch-enabled from :root. */
|
|
132
|
+
@container style(--navbar-notch-enabled: false) {
|
|
133
|
+
&::before,
|
|
134
|
+
&::after {
|
|
135
|
+
display: none;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
130
139
|
&::before {
|
|
131
140
|
left: 0;
|
|
132
141
|
background: radial-gradient(
|
|
@@ -73,7 +73,44 @@
|
|
|
73
73
|
--color-brand-accent: oklch(39.04% 0 89.88);
|
|
74
74
|
|
|
75
75
|
/* Semantic variables */
|
|
76
|
+
|
|
77
|
+
/* Sidebar background. Dedicated knob so installs can recolor the sidebar
|
|
78
|
+
independently of the neutral palette. Defaults to the page background in
|
|
79
|
+
light mode and a subtly tinted surface in dark (see the .dark override). */
|
|
76
80
|
--color-sidebar-background: var(--color-background);
|
|
81
|
+
|
|
82
|
+
/* Top navbar background. Dedicated knob so installs can recolor the navbar
|
|
83
|
+
without touching the neutral palette. Defaults to the page background so
|
|
84
|
+
the navbar blends with the canvas; resolves per-scheme automatically since
|
|
85
|
+
--color-background is itself redefined in .dark. Override it (here or in
|
|
86
|
+
`.dark`) to give the navbar its own color. */
|
|
87
|
+
--color-navbar-background: var(--color-avo-neutral-900);
|
|
88
|
+
|
|
89
|
+
/* Background of the main content panel. Dedicated knob so installs can recolor
|
|
90
|
+
the content surface independently of the primary surface token. Defaults to
|
|
91
|
+
--color-primary; resolves per-scheme since --color-primary is redefined in
|
|
92
|
+
.dark. */
|
|
93
|
+
--color-main-content-background: var(--color-primary);
|
|
94
|
+
|
|
95
|
+
/* Border between the sidebar and the main content panel. Dedicated knob so
|
|
96
|
+
installs can restyle just this seam without touching the shared
|
|
97
|
+
--border-color (which also draws the sidebar-status borders). Tracks
|
|
98
|
+
--border-color by default, so it resolves per-scheme automatically. */
|
|
99
|
+
--color-main-content-border: var(--border-color);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Navbar notch knobs live in a plain :root block, not in @theme:
|
|
103
|
+
--navbar-notch-enabled is read by a style query (Tailwind tree-shakes @theme
|
|
104
|
+
vars not referenced via var(), so it would be stripped), and we keep
|
|
105
|
+
--navbar-notch-radius alongside it so both notch knobs sit together. */
|
|
106
|
+
:root {
|
|
107
|
+
/* Set to `false` to hide the navbar's inverted corner arches (e.g. when the
|
|
108
|
+
navbar and content share a background). */
|
|
109
|
+
--navbar-notch-enabled: true;
|
|
110
|
+
|
|
111
|
+
/* Radius of the content panel's rounded top corners (and the matching navbar
|
|
112
|
+
arches that fill them). Set to `0` to flatten the corners entirely. */
|
|
113
|
+
--navbar-notch-radius: 1rem;
|
|
77
114
|
}
|
|
78
115
|
|
|
79
116
|
/* Dark mode defaults live in @layer theme — same layer as the @theme block
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<% else %>
|
|
13
13
|
<%= @field.name %>
|
|
14
14
|
<% end %>
|
|
15
|
-
<% if on_edit? && @field.is_required? %> <span class="text-
|
|
15
|
+
<% if on_edit? && @field.is_required? %> <span class="text-danger-content ms-1">*</span> <% end %>
|
|
16
16
|
<% if label_help.present? %>
|
|
17
17
|
<div class="field-wrapper__label-help"><%== label_help %></div>
|
|
18
18
|
<% end %>
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
) %>
|
|
22
22
|
|
|
23
23
|
<% if as_toggle? %>
|
|
24
|
-
<div class="block w-10 h-6 rounded-full bg-
|
|
25
|
-
<div class="dot absolute start-1 top-1 bg-
|
|
24
|
+
<div class="block w-10 h-6 rounded-full bg-tertiary peer-checked:bg-success"></div>
|
|
25
|
+
<div class="dot absolute start-1 top-1 bg-primary w-4 h-4 rounded-full transition-transform duration-200 transform peer-checked:translate-x-4"></div>
|
|
26
26
|
<% end %>
|
|
27
27
|
<% end %>
|
|
28
28
|
</div>
|
|
@@ -5,17 +5,17 @@ class Avo::Fields::Common::BooleanCheckComponent < Avo::BaseComponent
|
|
|
5
5
|
true => {
|
|
6
6
|
name: "checked",
|
|
7
7
|
icon: "tabler/outline/circle-check",
|
|
8
|
-
color: "text-
|
|
8
|
+
color: "text-success-content"
|
|
9
9
|
},
|
|
10
10
|
false => {
|
|
11
11
|
name: "unchecked",
|
|
12
12
|
icon: "tabler/outline/circle-x",
|
|
13
|
-
color: "text-
|
|
13
|
+
color: "text-danger-content"
|
|
14
14
|
},
|
|
15
15
|
nil => {
|
|
16
16
|
name: "indeterminate",
|
|
17
17
|
icon: "tabler/outline/circle-minus",
|
|
18
|
-
color: "text-
|
|
18
|
+
color: "text-content-secondary"
|
|
19
19
|
}
|
|
20
20
|
}.freeze
|
|
21
21
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div id="<%= dom_id file %>" class="relative min-h-full max-w-full flex-1 flex flex-col justify-between space-y-2">
|
|
2
2
|
<div class="flex flex-col h-full">
|
|
3
3
|
<% if file.representable? && is_image? %>
|
|
4
|
-
<%= image_tag helpers.main_app.url_for(file), class: "rounded-lg max-w-full self-start #{@extra_classes}", loading: :lazy, width: file.metadata["width"], height: file.metadata["height"] %>
|
|
4
|
+
<%= image_tag helpers.main_app.url_for(file), class: "rounded-lg max-w-full h-auto self-start object-cover #{@extra_classes}", loading: :lazy, width: file.metadata["width"], height: file.metadata["height"] %>
|
|
5
5
|
<% elsif is_audio? %>
|
|
6
6
|
<%= audio_tag(helpers.main_app.url_for(file), controls: true, preload: false, class: 'w-full') %>
|
|
7
7
|
<% elsif is_video? %>
|
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
<% else %>
|
|
10
10
|
<%= content_tag file.representable? ? :a : :div, **document_arguments do %>
|
|
11
11
|
<div class="flex flex-col justify-center items-center w-full">
|
|
12
|
-
<%= helpers.svg "tabler/outline/file-text", class: 'h-10 text-
|
|
12
|
+
<%= helpers.svg "tabler/outline/file-text", class: 'h-10 text-content-secondary mb-2' %>
|
|
13
13
|
</div>
|
|
14
14
|
<% end %>
|
|
15
15
|
<% end %>
|
|
16
16
|
<% if @field.display_filename %>
|
|
17
|
-
<span class="text-
|
|
17
|
+
<span class="text-content-secondary mt-1 text-sm truncate" title="<%= file.filename %>"><%= file.filename %></span>
|
|
18
18
|
<% end %>
|
|
19
19
|
</div>
|
|
20
20
|
<div class="flex space-x-2 rtl:space-x-reverse">
|
|
@@ -51,9 +51,9 @@ class Avo::Fields::Common::Files::ViewType::GridItemComponent < Avo::BaseCompone
|
|
|
51
51
|
def document_arguments
|
|
52
52
|
args = {
|
|
53
53
|
class: class_names(
|
|
54
|
-
"relative flex flex-col justify-evenly items-center px-2 rounded-lg border bg-
|
|
54
|
+
"relative flex flex-col justify-evenly items-center px-2 rounded-lg border bg-primary border-tertiary min-h-24",
|
|
55
55
|
{
|
|
56
|
-
"hover:bg-
|
|
56
|
+
"hover:bg-secondary transition": file.representable?
|
|
57
57
|
}
|
|
58
58
|
)
|
|
59
59
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<div class=" <%= class_names("flex shrink-0 items-center", "text-
|
|
1
|
+
<div class=" <%= class_names("flex shrink-0 items-center", "text-danger-content": @status == 'failed', "text-success-content": @status == 'success', "text-content-secondary": @status == 'neutral') %>">
|
|
2
2
|
<% if @status == 'success' %>
|
|
3
3
|
<%= helpers.svg 'tabler/filled/circle-check', class: 'h-4' %>
|
|
4
4
|
<% elsif @status == 'failed' %>
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
%>
|
|
32
32
|
<% end %>
|
|
33
33
|
<%= content_tag :button,
|
|
34
|
-
class: "self-center hidden font-semibold text-xs text-
|
|
34
|
+
class: "self-center hidden font-semibold text-xs text-danger-content p-1 cursor-pointer mt-2",
|
|
35
35
|
id: :reset,
|
|
36
36
|
type: :button,
|
|
37
37
|
data: {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
%>
|
|
27
27
|
<% end %>
|
|
28
28
|
<%= content_tag :button,
|
|
29
|
-
class: "self-center hidden font-semibold text-xs text-
|
|
29
|
+
class: "self-center hidden font-semibold text-xs text-danger-content p-1 cursor-pointer",
|
|
30
30
|
id: :reset,
|
|
31
31
|
type: :button,
|
|
32
32
|
data: {
|
|
@@ -5,7 +5,7 @@ class Avo::Fields::PreviewField::IndexComponent < Avo::Fields::IndexComponent
|
|
|
5
5
|
link_to resource_view_path, title: t("avo.view_item", item: @resource.name).humanize do
|
|
6
6
|
helpers.svg(
|
|
7
7
|
"tabler/outline/zoom-scan",
|
|
8
|
-
class: "block h-6 text-
|
|
8
|
+
class: "block h-6 text-content-secondary",
|
|
9
9
|
data: {
|
|
10
10
|
controller: "preview",
|
|
11
11
|
preview_url_value: helpers.preview_resource_path(resource: @resource, turbo_frame: :preview_modal),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= tag.div class: "flex px-2 py-0.5 rounded-sm bg-
|
|
1
|
+
<%= tag.div class: "flex px-2 py-0.5 rounded-sm bg-secondary text-sm text-content font-normal shrink-0",
|
|
2
2
|
data: {
|
|
3
3
|
target: "tag-component",
|
|
4
4
|
tippy: (@title.present? ? "tooltip" : nil),
|
|
@@ -32,16 +32,20 @@
|
|
|
32
32
|
<div class="pagination__controls">
|
|
33
33
|
<% if @resource.pagination_type.default? %>
|
|
34
34
|
<div class="pagination__info">
|
|
35
|
-
|
|
36
|
-
<%= "
|
|
37
|
-
</span>
|
|
38
|
-
of
|
|
39
|
-
<% if @pagy.count >= 10_000 %>
|
|
40
|
-
<span title="<%= formatted_count %>" data-tippy="tooltip">
|
|
41
|
-
<%= number_to_social(@pagy.count, start_at: 10_000) %>
|
|
42
|
-
</span>
|
|
35
|
+
<% if @pagy.pages <= 1 %>
|
|
36
|
+
<span><%= formatted_count %> <%= t("avo.record", count: @pagy.count) %></span>
|
|
43
37
|
<% else %>
|
|
44
|
-
<span
|
|
38
|
+
<span class="pagination__info-number">
|
|
39
|
+
<%= formatted_number(@pagy.from) %>-<%= formatted_number(@pagy.to) %>
|
|
40
|
+
</span>
|
|
41
|
+
of
|
|
42
|
+
<% if @pagy.count >= 10_000 %>
|
|
43
|
+
<span title="<%= formatted_count %>" data-tippy="tooltip">
|
|
44
|
+
<%= number_to_social(@pagy.count, start_at: 10_000) %>
|
|
45
|
+
</span>
|
|
46
|
+
<% else %>
|
|
47
|
+
<span><%= formatted_count %></span>
|
|
48
|
+
<% end %>
|
|
45
49
|
<% end %>
|
|
46
50
|
</div>
|
|
47
51
|
<% end %>
|
|
@@ -7,14 +7,18 @@ module Avo
|
|
|
7
7
|
before_action :set_record, only: [:destroy, :create]
|
|
8
8
|
|
|
9
9
|
def create
|
|
10
|
-
blob = ActiveStorage::Blob.create_and_upload! io: params[:file].to_io, filename: params[:filename]
|
|
11
10
|
association_name = BaseResource.valid_attachment_name(@record, params[:attachment_key])
|
|
12
11
|
|
|
13
|
-
# If association name is present attach the blob to it
|
|
14
12
|
if association_name
|
|
13
|
+
return render_upload_unauthorized unless authorized_to_upload(association_name)
|
|
14
|
+
|
|
15
|
+
blob = ActiveStorage::Blob.create_and_upload! io: params[:file].to_io, filename: params[:filename]
|
|
15
16
|
@record.send(association_name).attach blob
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
elsif params[:key].present?
|
|
18
|
+
return render_upload_unauthorized unless authorized_to_trix_upload?
|
|
19
|
+
|
|
20
|
+
blob = ActiveStorage::Blob.create_and_upload! io: params[:file].to_io, filename: params[:filename]
|
|
21
|
+
else
|
|
18
22
|
raise ActionController::BadRequest.new("Could not find the attachment association for #{params[:attachment_key]} (check the `attachment_key` for this Trix field)")
|
|
19
23
|
end
|
|
20
24
|
|
|
@@ -63,5 +67,17 @@ module Avo
|
|
|
63
67
|
def authorized_to(action)
|
|
64
68
|
@resource.authorization.authorize_action("#{action}_#{params[:attachment_name]}?", record: @record, raise_exception: false)
|
|
65
69
|
end
|
|
70
|
+
|
|
71
|
+
def authorized_to_upload(attachment_name)
|
|
72
|
+
@resource.authorization.authorize_action("upload_#{attachment_name}?", record: @record, raise_exception: false)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def authorized_to_trix_upload?
|
|
76
|
+
@resource.authorization.authorize_action("update?", record: @record, raise_exception: false)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def render_upload_unauthorized
|
|
80
|
+
render json: {error: "Not authorized"}, status: :forbidden
|
|
81
|
+
end
|
|
66
82
|
end
|
|
67
83
|
end
|
|
@@ -80,19 +80,8 @@ document.addEventListener('turbo:frame-render', (e) => {
|
|
|
80
80
|
})
|
|
81
81
|
|
|
82
82
|
document.addEventListener('turbo:load', () => {
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
if (localStorage.getItem('avo:hotkeys:hide_badges') === '1') {
|
|
87
|
-
document.body.classList.add('hotkeys-hide-badges')
|
|
88
|
-
} else {
|
|
89
|
-
document.body.classList.remove('hotkeys-hide-badges')
|
|
90
|
-
}
|
|
91
|
-
} catch (e) {
|
|
92
|
-
// localStorage unavailable
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
83
|
+
// Badge visibility preference is restored pre-paint on <html> in
|
|
84
|
+
// _color_theme_override.html.erb; the class persists across Turbo navigations.
|
|
96
85
|
if (window.Avo?.configuration?.hotkeys?.enabled !== false) installHotkeys()
|
|
97
86
|
initTippy()
|
|
98
87
|
|
|
@@ -327,14 +327,14 @@ export default class extends Controller {
|
|
|
327
327
|
}
|
|
328
328
|
|
|
329
329
|
// Mirrors the Shift+K hotkey in global_hotkeys.js. CSS handles the active
|
|
330
|
-
// state via [data-key-badges] selectors against
|
|
330
|
+
// state via [data-key-badges] selectors against `:root.hotkeys-hide-badges`.
|
|
331
331
|
setKeyBadges(event) {
|
|
332
332
|
event.preventDefault()
|
|
333
333
|
const { keyBadges } = event.currentTarget.dataset
|
|
334
334
|
if (keyBadges !== 'show' && keyBadges !== 'hide') return
|
|
335
335
|
|
|
336
336
|
const hide = keyBadges === 'hide'
|
|
337
|
-
document.
|
|
337
|
+
document.documentElement.classList.toggle('hotkeys-hide-badges', hide)
|
|
338
338
|
try {
|
|
339
339
|
if (hide) {
|
|
340
340
|
localStorage.setItem('avo:hotkeys:hide_badges', '1')
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
|
2
|
+
|
|
3
|
+
// Powers the internal color-token reference page (private#appearance): tab
|
|
4
|
+
// switching between token panels and click-to-copy on swatches, with a shared
|
|
5
|
+
// "Copied!" toast. Replaces the page's former inline <script> + onclick handlers
|
|
6
|
+
// so the page stays CSP-safe (no inline script execution).
|
|
7
|
+
export default class extends Controller {
|
|
8
|
+
static targets = ['indicator']
|
|
9
|
+
|
|
10
|
+
switchTab(event) {
|
|
11
|
+
event.preventDefault()
|
|
12
|
+
const button = event.currentTarget
|
|
13
|
+
const group = button.closest('.tabs')
|
|
14
|
+
const container = group.parentElement
|
|
15
|
+
|
|
16
|
+
container.querySelectorAll('.tab-content').forEach((tab) => tab.classList.remove('active'))
|
|
17
|
+
group.querySelectorAll('.tab-button').forEach((btn) => btn.classList.remove('active'))
|
|
18
|
+
|
|
19
|
+
document.getElementById(button.dataset.tab)?.classList.add('active')
|
|
20
|
+
button.classList.add('active')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
copy(event) {
|
|
24
|
+
const { copyText } = event.currentTarget.dataset
|
|
25
|
+
if (!copyText) return
|
|
26
|
+
|
|
27
|
+
navigator.clipboard.writeText(copyText).then(() => {
|
|
28
|
+
if (!this.hasIndicatorTarget) return
|
|
29
|
+
|
|
30
|
+
this.indicatorTarget.classList.add('show')
|
|
31
|
+
setTimeout(() => this.indicatorTarget.classList.remove('show'), 2000)
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -11,6 +11,7 @@ import CheckboxListFieldController from './controllers/fields/checkbox_list_fiel
|
|
|
11
11
|
import ClearInputController from './controllers/fields/clear_input_controller'
|
|
12
12
|
import CodeFieldController from './controllers/fields/code_field_controller'
|
|
13
13
|
import AppearanceController from './controllers/appearance_controller'
|
|
14
|
+
import AppearancePreviewController from './controllers/appearance_preview_controller'
|
|
14
15
|
import ConfirmDialogController from './controllers/confirm_dialog_controller'
|
|
15
16
|
import CopyToClipboardController from './controllers/copy_to_clipboard_controller'
|
|
16
17
|
import DashboardCardController from './controllers/dashboard_card_controller'
|
|
@@ -77,6 +78,7 @@ application.register('boolean-filter', BooleanFilterController)
|
|
|
77
78
|
application.register('card-filters', CardFiltersController)
|
|
78
79
|
application.register('clear-input', ClearInputController)
|
|
79
80
|
application.register('appearance', AppearanceController)
|
|
81
|
+
application.register('appearance-preview', AppearancePreviewController)
|
|
80
82
|
application.register('copy-to-clipboard', CopyToClipboardController)
|
|
81
83
|
application.register('dashboard-card', DashboardCardController)
|
|
82
84
|
application.register('date-time-filter', DateTimeFilterController)
|
|
@@ -97,8 +97,8 @@ const DIRECT_HOTKEYS = [
|
|
|
97
97
|
match: (e) => e.shiftKey && e.key === 'K'
|
|
98
98
|
&& window.Avo?.configuration?.hotkeys?.showKeyBadges !== false,
|
|
99
99
|
handle: () => {
|
|
100
|
-
document.
|
|
101
|
-
const hidden = document.
|
|
100
|
+
document.documentElement.classList.toggle('hotkeys-hide-badges')
|
|
101
|
+
const hidden = document.documentElement.classList.contains('hotkeys-hide-badges')
|
|
102
102
|
try {
|
|
103
103
|
if (hidden) {
|
|
104
104
|
localStorage.setItem('avo:hotkeys:hide_badges', '1')
|
|
@@ -6,6 +6,19 @@
|
|
|
6
6
|
var root = document.documentElement;
|
|
7
7
|
var appearance = window.Avo && window.Avo.configuration && window.Avo.configuration.appearance || {};
|
|
8
8
|
|
|
9
|
+
// Restore the kbd-badge visibility preference (Shift+K) before paint so a
|
|
10
|
+
// hidden set of badges never flashes in. It's a per-client preference, so it
|
|
11
|
+
// lives in localStorage rather than a cookie; the class sits on <html> next
|
|
12
|
+
// to the theme classes, which also lets it persist across Turbo navigations.
|
|
13
|
+
var hotkeys = window.Avo && window.Avo.configuration && window.Avo.configuration.hotkeys || {};
|
|
14
|
+
if (hotkeys.showKeyBadges !== false) {
|
|
15
|
+
try {
|
|
16
|
+
if (localStorage.getItem('avo:hotkeys:hide_badges') === '1') {
|
|
17
|
+
root.classList.add('hotkeys-hide-badges');
|
|
18
|
+
}
|
|
19
|
+
} catch (e) {}
|
|
20
|
+
}
|
|
21
|
+
|
|
9
22
|
if (appearance.persistence !== 'database') {
|
|
10
23
|
function getCookie(name) {
|
|
11
24
|
var value = '; ' + document.cookie;
|