plutonium 0.51.0 → 0.53.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 +4 -4
- data/.claude/skills/plutonium-app/SKILL.md +2 -0
- data/.claude/skills/plutonium-auth/SKILL.md +6 -4
- data/.claude/skills/plutonium-behavior/SKILL.md +1 -1
- data/.claude/skills/plutonium-resource/SKILL.md +6 -4
- data/.claude/skills/plutonium-tenancy/SKILL.md +31 -7
- data/.claude/skills/plutonium-testing/SKILL.md +3 -1
- data/.claude/skills/plutonium-ui/SKILL.md +32 -8
- data/CHANGELOG.md +33 -0
- data/app/assets/plutonium.css +1 -1
- data/app/assets/plutonium.js +258 -11
- data/app/assets/plutonium.js.map +4 -4
- data/app/assets/plutonium.min.js +39 -39
- data/app/assets/plutonium.min.js.map +4 -4
- data/app/views/plutonium/_resource_header.html.erb +2 -1
- data/docs/.vitepress/config.ts +2 -2
- data/docs/.vitepress/theme/components/HomeAudienceSplit.vue +53 -0
- data/docs/.vitepress/theme/components/HomeCta.vue +108 -0
- data/docs/.vitepress/theme/components/HomeHero.vue +70 -0
- data/docs/.vitepress/theme/components/HomeInTheBox.vue +74 -0
- data/docs/.vitepress/theme/components/HomePillars.vue +42 -0
- data/docs/.vitepress/theme/components/HomeStopWriting.vue +49 -0
- data/docs/.vitepress/theme/components/HomeWalkthrough.vue +111 -0
- data/docs/.vitepress/theme/components/SectionLanding.vue +115 -0
- data/docs/.vitepress/theme/custom.css +144 -0
- data/docs/.vitepress/theme/index.ts +58 -1
- data/docs/getting-started/index.md +33 -50
- data/docs/getting-started/tutorial/02-first-resource.md +17 -8
- data/docs/getting-started/tutorial/03-authentication.md +31 -23
- data/docs/getting-started/tutorial/05-custom-actions.md +9 -4
- data/docs/getting-started/tutorial/06-nested-resources.md +7 -1
- data/docs/getting-started/tutorial/07-author-portal.md +8 -0
- data/docs/getting-started/tutorial/08-customizing-ui.md +4 -0
- data/docs/guides/authentication.md +11 -6
- data/docs/guides/authorization.md +3 -3
- data/docs/guides/creating-packages.md +8 -11
- data/docs/guides/custom-actions.md +8 -2
- data/docs/guides/customizing-ui.md +259 -0
- data/docs/guides/index.md +49 -32
- data/docs/guides/multi-tenancy.md +14 -6
- data/docs/guides/nested-resources.md +69 -0
- data/docs/guides/search-filtering.md +6 -0
- data/docs/guides/testing.md +5 -1
- data/docs/guides/theming.md +14 -1
- data/docs/guides/user-invites.md +10 -4
- data/docs/guides/user-profile.md +8 -0
- data/docs/index.md +10 -219
- data/docs/public/asciinema/home-scaffold.cast +305 -0
- data/docs/public/images/components/avatar.png +0 -0
- data/docs/public/images/guides/custom-actions-bulk.png +0 -0
- data/docs/public/images/guides/multi-tenancy-dashboard.png +0 -0
- data/docs/public/images/guides/multi-tenancy-welcome.png +0 -0
- data/docs/public/images/guides/nested-inputs.png +0 -0
- data/docs/public/images/guides/nested-resources-tab.png +0 -0
- data/docs/public/images/guides/search-filtering-index.png +0 -0
- data/docs/public/images/guides/search-filtering-panel.png +0 -0
- data/docs/public/images/guides/theming-after.png +0 -0
- data/docs/public/images/guides/theming-before.png +0 -0
- data/docs/public/images/guides/user-invites-landing.png +0 -0
- data/docs/public/images/guides/user-profile-edit.png +0 -0
- data/docs/public/images/guides/user-profile-show.png +0 -0
- data/docs/public/images/home-index.png +0 -0
- data/docs/public/images/home-new.png +0 -0
- data/docs/public/images/home-show.png +0 -0
- data/docs/public/images/tutorial/02-empty-index.png +0 -0
- data/docs/public/images/tutorial/02-index-with-posts.png +0 -0
- data/docs/public/images/tutorial/02-new-form-modal.png +0 -0
- data/docs/public/images/tutorial/02-new-form.png +0 -0
- data/docs/public/images/tutorial/03-create-account.png +0 -0
- data/docs/public/images/tutorial/03-login.png +0 -0
- data/docs/public/images/tutorial/04-admin-index.png +0 -0
- data/docs/public/images/tutorial/05-actions-menu.png +0 -0
- data/docs/public/images/tutorial/05-row-actions.png +0 -0
- data/docs/public/images/tutorial/06-comments-tab.png +0 -0
- data/docs/public/images/tutorial/06-post-with-comments.png +0 -0
- data/docs/public/images/tutorial/07-author-dashboard.png +0 -0
- data/docs/public/images/tutorial/07-author-portal.png +0 -0
- data/docs/public/images/tutorial/08-customized-index.png +0 -0
- data/docs/reference/app/generators.md +4 -4
- data/docs/reference/auth/accounts.md +7 -8
- data/docs/reference/auth/index.md +1 -1
- data/docs/reference/behavior/policies.md +2 -2
- data/docs/reference/configuration.md +61 -0
- data/docs/reference/index.md +67 -55
- data/docs/reference/resource/actions.md +2 -1
- data/docs/reference/resource/definition.md +5 -4
- data/docs/reference/tenancy/entity-scoping.md +14 -8
- data/docs/reference/tenancy/index.md +1 -1
- data/docs/reference/tenancy/invites.md +12 -5
- data/docs/reference/ui/components.md +53 -0
- data/docs/reference/ui/forms.md +1 -1
- data/docs/reference/ui/pages.md +6 -5
- data/docs/reference/ui/tables.md +8 -4
- data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md +1648 -0
- data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md.tasks.json +109 -0
- data/docs/superpowers/specs/2026-05-15-public-pages-overhaul-design.md +263 -0
- data/docs/superpowers/specs/2026-05-29-avatar-component-design.md +153 -0
- data/gemfiles/rails_7.gemfile.lock +1 -1
- data/gemfiles/rails_8.0.gemfile.lock +1 -1
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/generators/pu/core/assets/assets_generator.rb +10 -0
- data/lib/generators/pu/invites/install_generator.rb +44 -0
- data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/user_invitations_controller.rb.tt +1 -0
- data/lib/generators/pu/lite/solid_errors/solid_errors_generator.rb +7 -3
- data/lib/generators/pu/profile/conn_generator.rb +2 -2
- data/lib/generators/pu/res/conn/conn_generator.rb +33 -6
- data/lib/generators/pu/res/model/templates/model.rb.tt +4 -0
- data/lib/generators/pu/rodauth/account_generator.rb +2 -1
- data/lib/generators/pu/rodauth/admin_generator.rb +0 -2
- data/lib/generators/pu/rodauth/migration_generator.rb +0 -2
- data/lib/generators/pu/rodauth/views_generator.rb +0 -2
- data/lib/generators/pu/saas/membership/USAGE +4 -1
- data/lib/generators/pu/saas/setup_generator.rb +16 -4
- data/lib/generators/pu/saas/welcome/templates/app/controllers/welcome_controller.rb.tt +1 -1
- data/lib/plutonium/action/base.rb +43 -63
- data/lib/plutonium/configuration.rb +7 -0
- data/lib/plutonium/definition/actions.rb +10 -11
- data/lib/plutonium/definition/base.rb +29 -0
- data/lib/plutonium/helpers/assets_helper.rb +0 -30
- data/lib/plutonium/helpers/content_helper.rb +0 -44
- data/lib/plutonium/helpers/display_helper.rb +0 -62
- data/lib/plutonium/helpers/turbo_helper.rb +17 -2
- data/lib/plutonium/helpers.rb +0 -2
- data/lib/plutonium/resource/controllers/crud_actions.rb +4 -4
- data/lib/plutonium/resource/controllers/interactive_actions.rb +3 -3
- data/lib/plutonium/resource/definition.rb +0 -42
- data/lib/plutonium/ui/action_button.rb +4 -3
- data/lib/plutonium/ui/avatar.rb +182 -0
- data/lib/plutonium/ui/component/kit.rb +2 -0
- data/lib/plutonium/ui/component/methods.rb +1 -0
- data/lib/plutonium/ui/form/base.rb +32 -2
- data/lib/plutonium/ui/form/components/secure_association.rb +14 -8
- data/lib/plutonium/ui/form/interaction.rb +1 -1
- data/lib/plutonium/ui/form/resource.rb +58 -0
- data/lib/plutonium/ui/form/theme.rb +8 -4
- data/lib/plutonium/ui/grid/card.rb +10 -26
- data/lib/plutonium/ui/modal/base.rb +36 -1
- data/lib/plutonium/ui/modal/centered.rb +24 -6
- data/lib/plutonium/ui/modal/slideover.rb +26 -11
- data/lib/plutonium/ui/nav_user.rb +3 -23
- data/lib/plutonium/ui/page/edit.rb +7 -4
- data/lib/plutonium/ui/page/interactive_action.rb +5 -3
- data/lib/plutonium/ui/page/new.rb +7 -4
- data/lib/plutonium/ui/table/components/bulk_actions_toolbar.rb +1 -1
- data/lib/plutonium/ui/table/components/filter_form.rb +12 -4
- data/lib/plutonium/version.rb +1 -1
- data/package.json +4 -1
- data/src/css/components.css +38 -1
- data/src/css/slim_select.css +3 -2
- data/src/js/controllers/dirty_form_guard_controller.js +165 -0
- data/src/js/controllers/form_controller.js +5 -4
- data/src/js/controllers/register_controllers.js +2 -0
- data/src/js/controllers/remote_modal_controller.js +53 -19
- data/src/js/turbo/index.js +1 -0
- data/src/js/turbo/turbo_confirm.js +128 -0
- data/yarn.lock +108 -1
- metadata +52 -6
- data/lib/plutonium/helpers/attachment_helper.rb +0 -73
- data/lib/plutonium/helpers/table_helper.rb +0 -35
- /data/lib/generators/pu/rodauth/templates/app/views/rodauth_mailer/{password_changed.text.erb → change_password_notify.text.erb} +0 -0
|
@@ -32,6 +32,7 @@ import CaptureUrlController from "./capture_url_controller.js"
|
|
|
32
32
|
import RowClickController from "./row_click_controller.js"
|
|
33
33
|
import ViewSwitcherController from "./view_switcher_controller.js"
|
|
34
34
|
import AutosubmitController from "./autosubmit_controller.js"
|
|
35
|
+
import DirtyFormGuardController from "./dirty_form_guard_controller.js"
|
|
35
36
|
|
|
36
37
|
export default function (application) {
|
|
37
38
|
// Register controllers here
|
|
@@ -68,4 +69,5 @@ export default function (application) {
|
|
|
68
69
|
application.register("row-click", RowClickController)
|
|
69
70
|
application.register("view-switcher", ViewSwitcherController)
|
|
70
71
|
application.register("autosubmit", AutosubmitController)
|
|
72
|
+
application.register("dirty-form-guard", DirtyFormGuardController)
|
|
71
73
|
}
|
|
@@ -1,45 +1,79 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus";
|
|
2
2
|
|
|
3
3
|
// Connects to data-controller="remote-modal"
|
|
4
|
+
// Drives the open/close lifecycle of a turbo-fetched <dialog>.
|
|
5
|
+
//
|
|
6
|
+
// Entry is animated by deferring `data-open` to the frame after
|
|
7
|
+
// showModal() — the dialog renders one frame with its closed-state
|
|
8
|
+
// transform/opacity, then transitions into the open state. Exit
|
|
9
|
+
// reverses it: remove `data-open`, wait for the dialog's animations
|
|
10
|
+
// to settle, then call close(). This avoids the @starting-style /
|
|
11
|
+
// allow-discrete spec dance, which is unreliable across browsers.
|
|
4
12
|
export default class extends Controller {
|
|
5
13
|
connect() {
|
|
6
|
-
// Store original scroll position and body overflow
|
|
7
14
|
this.originalScrollPosition = window.scrollY;
|
|
8
15
|
this.originalOverflow = document.body.style.overflow;
|
|
9
16
|
this.bodyStateRestored = false;
|
|
10
|
-
|
|
11
|
-
// Lock body scroll
|
|
17
|
+
this._closing = false;
|
|
12
18
|
document.body.style.overflow = "hidden";
|
|
13
19
|
|
|
14
|
-
// Show the modal
|
|
15
20
|
this.element.showModal();
|
|
16
|
-
//
|
|
17
|
-
|
|
21
|
+
// Double rAF ensures the closed-state styles paint before we flip
|
|
22
|
+
// data-open, so the transition actually fires.
|
|
23
|
+
requestAnimationFrame(() => {
|
|
24
|
+
requestAnimationFrame(() => {
|
|
25
|
+
this.element.setAttribute("data-open", "");
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
this.onCancel = this.#onCancel.bind(this);
|
|
30
|
+
this.onClose = this.#onClose.bind(this);
|
|
31
|
+
this.onRequestClose = () => this.#animateClose();
|
|
32
|
+
|
|
33
|
+
this.element.addEventListener("cancel", this.onCancel);
|
|
34
|
+
this.element.addEventListener("close", this.onClose);
|
|
35
|
+
this.element.addEventListener("modal:request-close", this.onRequestClose);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
disconnect() {
|
|
39
|
+
this.element.removeEventListener("cancel", this.onCancel);
|
|
40
|
+
this.element.removeEventListener("close", this.onClose);
|
|
41
|
+
this.element.removeEventListener("modal:request-close", this.onRequestClose);
|
|
42
|
+
this.#restoreBodyState();
|
|
18
43
|
}
|
|
19
44
|
|
|
20
45
|
close() {
|
|
21
|
-
|
|
22
|
-
this.element.close();
|
|
23
|
-
this.restoreBodyState();
|
|
46
|
+
this.#animateClose();
|
|
24
47
|
}
|
|
25
48
|
|
|
26
|
-
|
|
27
|
-
//
|
|
28
|
-
this
|
|
29
|
-
|
|
49
|
+
#onCancel(event) {
|
|
50
|
+
// Another listener (typically dirty-form-guard) already handled
|
|
51
|
+
// this — don't double-process.
|
|
52
|
+
if (event.defaultPrevented) return;
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
this.#animateClose();
|
|
30
55
|
}
|
|
31
56
|
|
|
32
|
-
|
|
33
|
-
this
|
|
57
|
+
#onClose() {
|
|
58
|
+
this.#restoreBodyState();
|
|
34
59
|
}
|
|
35
60
|
|
|
36
|
-
|
|
61
|
+
async #animateClose() {
|
|
62
|
+
if (this._closing) return;
|
|
63
|
+
this._closing = true;
|
|
64
|
+
|
|
65
|
+
this.element.removeAttribute("data-open");
|
|
66
|
+
|
|
67
|
+
const animations = this.element.getAnimations({ subtree: true });
|
|
68
|
+
await Promise.allSettled(animations.map((a) => a.finished));
|
|
69
|
+
|
|
70
|
+
this.element.close();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
#restoreBodyState() {
|
|
37
74
|
if (this.bodyStateRestored) return;
|
|
38
75
|
this.bodyStateRestored = true;
|
|
39
|
-
|
|
40
|
-
// Restore body overflow
|
|
41
76
|
document.body.style.overflow = this.originalOverflow || "";
|
|
42
|
-
// Restore the original scroll position
|
|
43
77
|
window.scrollTo(0, this.originalScrollPosition);
|
|
44
78
|
}
|
|
45
79
|
}
|
data/src/js/turbo/index.js
CHANGED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Themed replacement for Turbo's default window.confirm. The dialog is
|
|
2
|
+
// built lazily and reused so per-call cost is just a textContent swap.
|
|
3
|
+
|
|
4
|
+
let dialog;
|
|
5
|
+
let messageEl;
|
|
6
|
+
let confirmButton;
|
|
7
|
+
let cancelButton;
|
|
8
|
+
|
|
9
|
+
function ensureDialog() {
|
|
10
|
+
// Turbo Drive replaces document.body on full-page navigation, which
|
|
11
|
+
// detaches the cached dialog. showModal() then throws InvalidStateError
|
|
12
|
+
// ("not in a Document"). Re-attach if detached; the node itself plus
|
|
13
|
+
// its listeners survive, so we don't have to rebuild.
|
|
14
|
+
if (dialog) {
|
|
15
|
+
if (!dialog.isConnected) document.body.appendChild(dialog);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
dialog = document.createElement("dialog");
|
|
20
|
+
// Surface (bg, border, radius, backdrop) comes from .pu-dialog; the
|
|
21
|
+
// remaining utilities are positioning, size, and the opacity/scale
|
|
22
|
+
// animation hooks driven by [data-open]. Matches Modal::Centered.
|
|
23
|
+
dialog.className = [
|
|
24
|
+
"pu-dialog",
|
|
25
|
+
"top-1/2",
|
|
26
|
+
"-translate-y-1/2",
|
|
27
|
+
"left-1/2",
|
|
28
|
+
"-translate-x-1/2",
|
|
29
|
+
"w-full",
|
|
30
|
+
"max-w-md",
|
|
31
|
+
"p-0",
|
|
32
|
+
"open:flex",
|
|
33
|
+
"flex-col",
|
|
34
|
+
"opacity-0",
|
|
35
|
+
"scale-95",
|
|
36
|
+
"data-[open]:opacity-100",
|
|
37
|
+
"data-[open]:scale-100",
|
|
38
|
+
"transition-[opacity,transform]",
|
|
39
|
+
"duration-200",
|
|
40
|
+
"ease-out",
|
|
41
|
+
].join(" ");
|
|
42
|
+
dialog.setAttribute("aria-labelledby", "pu-turbo-confirm-message");
|
|
43
|
+
|
|
44
|
+
const header = document.createElement("div");
|
|
45
|
+
header.className = "px-6 pt-5 pb-4 border-b border-[var(--pu-border)]";
|
|
46
|
+
|
|
47
|
+
messageEl = document.createElement("h2");
|
|
48
|
+
messageEl.id = "pu-turbo-confirm-message";
|
|
49
|
+
messageEl.className = "text-lg font-semibold text-[var(--pu-text)]";
|
|
50
|
+
header.appendChild(messageEl);
|
|
51
|
+
|
|
52
|
+
const footer = document.createElement("div");
|
|
53
|
+
footer.className = "flex items-center justify-end gap-2 px-6 py-4";
|
|
54
|
+
|
|
55
|
+
cancelButton = document.createElement("button");
|
|
56
|
+
cancelButton.type = "button";
|
|
57
|
+
cancelButton.className = "pu-btn pu-btn-md pu-btn-outline";
|
|
58
|
+
cancelButton.textContent = "Cancel";
|
|
59
|
+
|
|
60
|
+
confirmButton = document.createElement("button");
|
|
61
|
+
confirmButton.type = "button";
|
|
62
|
+
confirmButton.className = "pu-btn pu-btn-md pu-btn-primary";
|
|
63
|
+
confirmButton.textContent = "Confirm";
|
|
64
|
+
|
|
65
|
+
footer.appendChild(cancelButton);
|
|
66
|
+
footer.appendChild(confirmButton);
|
|
67
|
+
|
|
68
|
+
dialog.appendChild(header);
|
|
69
|
+
dialog.appendChild(footer);
|
|
70
|
+
document.body.appendChild(dialog);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function animateClose() {
|
|
74
|
+
dialog.removeAttribute("data-open");
|
|
75
|
+
const animations = dialog.getAnimations({ subtree: true });
|
|
76
|
+
await Promise.allSettled(animations.map((a) => a.finished));
|
|
77
|
+
if (dialog.open) dialog.close();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function themedConfirm(message) {
|
|
81
|
+
ensureDialog();
|
|
82
|
+
messageEl.textContent = message || "Are you sure?";
|
|
83
|
+
|
|
84
|
+
return new Promise((resolve) => {
|
|
85
|
+
let settled = false;
|
|
86
|
+
|
|
87
|
+
const settle = (value) => {
|
|
88
|
+
if (settled) return;
|
|
89
|
+
settled = true;
|
|
90
|
+
cleanup();
|
|
91
|
+
resolve(value);
|
|
92
|
+
animateClose();
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const onConfirm = () => settle(true);
|
|
96
|
+
const onCancel = () => settle(false);
|
|
97
|
+
const onClose = () => settle(false);
|
|
98
|
+
|
|
99
|
+
const cleanup = () => {
|
|
100
|
+
confirmButton.removeEventListener("click", onConfirm);
|
|
101
|
+
cancelButton.removeEventListener("click", onCancel);
|
|
102
|
+
dialog.removeEventListener("close", onClose);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
confirmButton.addEventListener("click", onConfirm);
|
|
106
|
+
cancelButton.addEventListener("click", onCancel);
|
|
107
|
+
// Esc / backdrop / programmatic close — all resolve as cancel.
|
|
108
|
+
dialog.addEventListener("close", onClose);
|
|
109
|
+
|
|
110
|
+
dialog.showModal();
|
|
111
|
+
// Double rAF so the closed-state styles paint before [data-open]
|
|
112
|
+
// flips — same rationale as remote_modal_controller.
|
|
113
|
+
requestAnimationFrame(() => {
|
|
114
|
+
requestAnimationFrame(() => dialog.setAttribute("data-open", ""));
|
|
115
|
+
});
|
|
116
|
+
confirmButton.focus();
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (typeof window !== "undefined" && window.Turbo) {
|
|
121
|
+
// Turbo 8 deprecated setConfirmMethod in favor of config.forms.confirm.
|
|
122
|
+
// Prefer the new path; fall back for older Turbo versions still in use.
|
|
123
|
+
if (window.Turbo.config?.forms) {
|
|
124
|
+
window.Turbo.config.forms.confirm = themedConfirm;
|
|
125
|
+
} else if (window.Turbo.setConfirmMethod) {
|
|
126
|
+
window.Turbo.setConfirmMethod(themedConfirm);
|
|
127
|
+
}
|
|
128
|
+
}
|
data/yarn.lock
CHANGED
|
@@ -876,6 +876,7 @@ __metadata:
|
|
|
876
876
|
"@hotwired/stimulus": "npm:^3.2.2"
|
|
877
877
|
"@hotwired/turbo": "npm:^8.0.4"
|
|
878
878
|
"@popperjs/core": "npm:^2.11.8"
|
|
879
|
+
"@tabler/icons-vue": "npm:^3.44.0"
|
|
879
880
|
"@tailwindcss/forms": "npm:^0.5.10"
|
|
880
881
|
"@tailwindcss/postcss": "npm:^4.3.0"
|
|
881
882
|
"@tailwindcss/typography": "npm:^0.5.16"
|
|
@@ -883,6 +884,7 @@ __metadata:
|
|
|
883
884
|
"@uppy/dashboard": "npm:^4.1.3"
|
|
884
885
|
"@uppy/image-editor": "npm:^3.2.1"
|
|
885
886
|
"@uppy/xhr-upload": "npm:^4.2.3"
|
|
887
|
+
asciinema-player: "npm:^3.15.1"
|
|
886
888
|
chokidar-cli: "npm:^3.0.0"
|
|
887
889
|
concurrently: "npm:^8.2.2"
|
|
888
890
|
cssnano: "npm:^7.0.2"
|
|
@@ -892,6 +894,7 @@ __metadata:
|
|
|
892
894
|
flowbite-typography: "npm:^1.0.5"
|
|
893
895
|
lodash.debounce: "npm:^4.0.8"
|
|
894
896
|
marked: "npm:^15.0.3"
|
|
897
|
+
medium-zoom: "npm:^1.1.0"
|
|
895
898
|
mermaid: "npm:^11.15.0"
|
|
896
899
|
postcss: "npm:^8.5.14"
|
|
897
900
|
postcss-cli: "npm:^11.0.1"
|
|
@@ -1158,6 +1161,53 @@ __metadata:
|
|
|
1158
1161
|
languageName: node
|
|
1159
1162
|
linkType: hard
|
|
1160
1163
|
|
|
1164
|
+
"@solid-primitives/refs@npm:^1.0.5":
|
|
1165
|
+
version: 1.1.3
|
|
1166
|
+
resolution: "@solid-primitives/refs@npm:1.1.3"
|
|
1167
|
+
dependencies:
|
|
1168
|
+
"@solid-primitives/utils": "npm:^6.4.0"
|
|
1169
|
+
peerDependencies:
|
|
1170
|
+
solid-js: ^1.6.12
|
|
1171
|
+
checksum: 10c0/af1e27b5b38f639e5a3125a982ff8f9c58fe2aea4609718c9383d88d1fc5a13e490fb40c262eb183c695b3efacd89b5328876a615814c0edb54981b58b3804fa
|
|
1172
|
+
languageName: node
|
|
1173
|
+
linkType: hard
|
|
1174
|
+
|
|
1175
|
+
"@solid-primitives/transition-group@npm:^1.0.2":
|
|
1176
|
+
version: 1.1.2
|
|
1177
|
+
resolution: "@solid-primitives/transition-group@npm:1.1.2"
|
|
1178
|
+
peerDependencies:
|
|
1179
|
+
solid-js: ^1.6.12
|
|
1180
|
+
checksum: 10c0/f676cefc38bab6aad7c1214ef2ea145d598ec2e873eca663a79a3a8e46b9ee8f6ea57f3e11c0acf5156ffa63ba727e9676c46c6fd0cf9130e034da79ead12402
|
|
1181
|
+
languageName: node
|
|
1182
|
+
linkType: hard
|
|
1183
|
+
|
|
1184
|
+
"@solid-primitives/utils@npm:^6.4.0":
|
|
1185
|
+
version: 6.4.0
|
|
1186
|
+
resolution: "@solid-primitives/utils@npm:6.4.0"
|
|
1187
|
+
peerDependencies:
|
|
1188
|
+
solid-js: ^1.6.12
|
|
1189
|
+
checksum: 10c0/fdac336c74be180251ac40df280571534d427c773b207e19a51aa01f013e16864f15c5c829f53a8e7d0033543bef07a6410c2dbaf364410dc29783966e14fcac
|
|
1190
|
+
languageName: node
|
|
1191
|
+
linkType: hard
|
|
1192
|
+
|
|
1193
|
+
"@tabler/icons-vue@npm:^3.44.0":
|
|
1194
|
+
version: 3.44.0
|
|
1195
|
+
resolution: "@tabler/icons-vue@npm:3.44.0"
|
|
1196
|
+
dependencies:
|
|
1197
|
+
"@tabler/icons": "npm:3.44.0"
|
|
1198
|
+
peerDependencies:
|
|
1199
|
+
vue: ">=3.0.1"
|
|
1200
|
+
checksum: 10c0/7730f3cd00056584ad322ab9d7b740d0f62632e5480a89dfd458f916af278010a940bfb62bcd1067b32177bf8ff5c101534aa898666501e47dc0ddaf3f0f9c9f
|
|
1201
|
+
languageName: node
|
|
1202
|
+
linkType: hard
|
|
1203
|
+
|
|
1204
|
+
"@tabler/icons@npm:3.44.0":
|
|
1205
|
+
version: 3.44.0
|
|
1206
|
+
resolution: "@tabler/icons@npm:3.44.0"
|
|
1207
|
+
checksum: 10c0/0d5c1f9d6e68aa04c4a661e96035190e0884e0bc0022d31922efad3cfba210a25b2f013c0b6c0ab6aeb3d5a402a9b85a168966bd11a30ba6087f614f8521ccf3
|
|
1208
|
+
languageName: node
|
|
1209
|
+
linkType: hard
|
|
1210
|
+
|
|
1161
1211
|
"@tailwindcss/forms@npm:^0.5.10":
|
|
1162
1212
|
version: 0.5.10
|
|
1163
1213
|
resolution: "@tailwindcss/forms@npm:0.5.10"
|
|
@@ -2184,6 +2234,17 @@ __metadata:
|
|
|
2184
2234
|
languageName: node
|
|
2185
2235
|
linkType: hard
|
|
2186
2236
|
|
|
2237
|
+
"asciinema-player@npm:^3.15.1":
|
|
2238
|
+
version: 3.15.1
|
|
2239
|
+
resolution: "asciinema-player@npm:3.15.1"
|
|
2240
|
+
dependencies:
|
|
2241
|
+
"@babel/runtime": "npm:^7.21.0"
|
|
2242
|
+
solid-js: "npm:^1.3.0"
|
|
2243
|
+
solid-transition-group: "npm:^0.2.3"
|
|
2244
|
+
checksum: 10c0/36638e9804a94866d6c6ae25cdbf867313873e68838b2630eed73229819170bc67667098865fe38b061012c3fa78e8235322725c5e1bda258ec82787d470f8a4
|
|
2245
|
+
languageName: node
|
|
2246
|
+
linkType: hard
|
|
2247
|
+
|
|
2187
2248
|
"balanced-match@npm:^4.0.2":
|
|
2188
2249
|
version: 4.0.4
|
|
2189
2250
|
resolution: "balanced-match@npm:4.0.4"
|
|
@@ -2647,7 +2708,7 @@ __metadata:
|
|
|
2647
2708
|
languageName: node
|
|
2648
2709
|
linkType: hard
|
|
2649
2710
|
|
|
2650
|
-
"csstype@npm:^3.2.3":
|
|
2711
|
+
"csstype@npm:^3.1.0, csstype@npm:^3.2.3":
|
|
2651
2712
|
version: 3.2.3
|
|
2652
2713
|
resolution: "csstype@npm:3.2.3"
|
|
2653
2714
|
checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce
|
|
@@ -4153,6 +4214,13 @@ __metadata:
|
|
|
4153
4214
|
languageName: node
|
|
4154
4215
|
linkType: hard
|
|
4155
4216
|
|
|
4217
|
+
"medium-zoom@npm:^1.1.0":
|
|
4218
|
+
version: 1.1.0
|
|
4219
|
+
resolution: "medium-zoom@npm:1.1.0"
|
|
4220
|
+
checksum: 10c0/7d1f05e8eab045c33d7c04d4ee7bf04f5246cf7a720d7b5f5a51c36ab23666e363bcbb6bffae50b5948d5eb19361914cb0e26a1fce5c1fff7a266bc0217893f3
|
|
4221
|
+
languageName: node
|
|
4222
|
+
linkType: hard
|
|
4223
|
+
|
|
4156
4224
|
"mermaid@npm:^11.15.0":
|
|
4157
4225
|
version: 11.15.0
|
|
4158
4226
|
resolution: "mermaid@npm:11.15.0"
|
|
@@ -5355,6 +5423,22 @@ __metadata:
|
|
|
5355
5423
|
languageName: node
|
|
5356
5424
|
linkType: hard
|
|
5357
5425
|
|
|
5426
|
+
"seroval-plugins@npm:~1.5.0":
|
|
5427
|
+
version: 1.5.4
|
|
5428
|
+
resolution: "seroval-plugins@npm:1.5.4"
|
|
5429
|
+
peerDependencies:
|
|
5430
|
+
seroval: ^1.0
|
|
5431
|
+
checksum: 10c0/f8843ff12c2fcbf0d1124d02addcffc1a727f55b500ac24218a528e95e540c42052e2e6f6b3dfaad2aa8105fd0985dff722c3ae774723b9899e0fafe7d4698be
|
|
5432
|
+
languageName: node
|
|
5433
|
+
linkType: hard
|
|
5434
|
+
|
|
5435
|
+
"seroval@npm:~1.5.0":
|
|
5436
|
+
version: 1.5.4
|
|
5437
|
+
resolution: "seroval@npm:1.5.4"
|
|
5438
|
+
checksum: 10c0/6191e27f21000f7693ab923fde69c47a3ce5fbb86e585e5a8fc072d70db52ebc3c4dab83c3b2ab67311ec646b2064df089a3a155c49b21846438aaf510d4b964
|
|
5439
|
+
languageName: node
|
|
5440
|
+
linkType: hard
|
|
5441
|
+
|
|
5358
5442
|
"set-blocking@npm:^2.0.0":
|
|
5359
5443
|
version: 2.0.0
|
|
5360
5444
|
resolution: "set-blocking@npm:2.0.0"
|
|
@@ -5434,6 +5518,29 @@ __metadata:
|
|
|
5434
5518
|
languageName: node
|
|
5435
5519
|
linkType: hard
|
|
5436
5520
|
|
|
5521
|
+
"solid-js@npm:^1.3.0":
|
|
5522
|
+
version: 1.9.13
|
|
5523
|
+
resolution: "solid-js@npm:1.9.13"
|
|
5524
|
+
dependencies:
|
|
5525
|
+
csstype: "npm:^3.1.0"
|
|
5526
|
+
seroval: "npm:~1.5.0"
|
|
5527
|
+
seroval-plugins: "npm:~1.5.0"
|
|
5528
|
+
checksum: 10c0/1c407da820435771ec6fd65e605fd804fc1faf74ee84af2d3dce2bc5c223563017a9e15746eb86d27237e6d0d6ac8660685c560eb1f1decdc6f3c7b913927928
|
|
5529
|
+
languageName: node
|
|
5530
|
+
linkType: hard
|
|
5531
|
+
|
|
5532
|
+
"solid-transition-group@npm:^0.2.3":
|
|
5533
|
+
version: 0.2.3
|
|
5534
|
+
resolution: "solid-transition-group@npm:0.2.3"
|
|
5535
|
+
dependencies:
|
|
5536
|
+
"@solid-primitives/refs": "npm:^1.0.5"
|
|
5537
|
+
"@solid-primitives/transition-group": "npm:^1.0.2"
|
|
5538
|
+
peerDependencies:
|
|
5539
|
+
solid-js: ^1.6.12
|
|
5540
|
+
checksum: 10c0/584656bedefb03fd91801d858c9abf0de5afc175e9a7bea2023000faa5ed3af4c6e4b8b99dd7ed1069595d362b002d4ae8ca08d030e422b65dc23742fb2ac681
|
|
5541
|
+
languageName: node
|
|
5542
|
+
linkType: hard
|
|
5543
|
+
|
|
5437
5544
|
"source-map-js@npm:^1.0.1, source-map-js@npm:^1.2.1":
|
|
5438
5545
|
version: 1.2.1
|
|
5439
5546
|
resolution: "source-map-js@npm:1.2.1"
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: plutonium
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.53.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stefan Froelich
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-05-
|
|
10
|
+
date: 2026-05-31 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: zeitwerk
|
|
@@ -532,6 +532,14 @@ files:
|
|
|
532
532
|
- config/initializers/rabl.rb
|
|
533
533
|
- config/initializers/sqlite_alias.rb
|
|
534
534
|
- docs/.vitepress/config.ts
|
|
535
|
+
- docs/.vitepress/theme/components/HomeAudienceSplit.vue
|
|
536
|
+
- docs/.vitepress/theme/components/HomeCta.vue
|
|
537
|
+
- docs/.vitepress/theme/components/HomeHero.vue
|
|
538
|
+
- docs/.vitepress/theme/components/HomeInTheBox.vue
|
|
539
|
+
- docs/.vitepress/theme/components/HomePillars.vue
|
|
540
|
+
- docs/.vitepress/theme/components/HomeStopWriting.vue
|
|
541
|
+
- docs/.vitepress/theme/components/HomeWalkthrough.vue
|
|
542
|
+
- docs/.vitepress/theme/components/SectionLanding.vue
|
|
535
543
|
- docs/.vitepress/theme/custom.css
|
|
536
544
|
- docs/.vitepress/theme/index.ts
|
|
537
545
|
- docs/getting-started/index.md
|
|
@@ -550,6 +558,7 @@ files:
|
|
|
550
558
|
- docs/guides/authorization.md
|
|
551
559
|
- docs/guides/creating-packages.md
|
|
552
560
|
- docs/guides/custom-actions.md
|
|
561
|
+
- docs/guides/customizing-ui.md
|
|
553
562
|
- docs/guides/index.md
|
|
554
563
|
- docs/guides/multi-tenancy.md
|
|
555
564
|
- docs/guides/nested-resources.md
|
|
@@ -564,9 +573,40 @@ files:
|
|
|
564
573
|
- docs/public/android-chrome-192x192.png
|
|
565
574
|
- docs/public/android-chrome-512x512.png
|
|
566
575
|
- docs/public/apple-touch-icon.png
|
|
576
|
+
- docs/public/asciinema/home-scaffold.cast
|
|
567
577
|
- docs/public/favicon-16x16.png
|
|
568
578
|
- docs/public/favicon-32x32.png
|
|
569
579
|
- docs/public/favicon.ico
|
|
580
|
+
- docs/public/images/components/avatar.png
|
|
581
|
+
- docs/public/images/guides/custom-actions-bulk.png
|
|
582
|
+
- docs/public/images/guides/multi-tenancy-dashboard.png
|
|
583
|
+
- docs/public/images/guides/multi-tenancy-welcome.png
|
|
584
|
+
- docs/public/images/guides/nested-inputs.png
|
|
585
|
+
- docs/public/images/guides/nested-resources-tab.png
|
|
586
|
+
- docs/public/images/guides/search-filtering-index.png
|
|
587
|
+
- docs/public/images/guides/search-filtering-panel.png
|
|
588
|
+
- docs/public/images/guides/theming-after.png
|
|
589
|
+
- docs/public/images/guides/theming-before.png
|
|
590
|
+
- docs/public/images/guides/user-invites-landing.png
|
|
591
|
+
- docs/public/images/guides/user-profile-edit.png
|
|
592
|
+
- docs/public/images/guides/user-profile-show.png
|
|
593
|
+
- docs/public/images/home-index.png
|
|
594
|
+
- docs/public/images/home-new.png
|
|
595
|
+
- docs/public/images/home-show.png
|
|
596
|
+
- docs/public/images/tutorial/02-empty-index.png
|
|
597
|
+
- docs/public/images/tutorial/02-index-with-posts.png
|
|
598
|
+
- docs/public/images/tutorial/02-new-form-modal.png
|
|
599
|
+
- docs/public/images/tutorial/02-new-form.png
|
|
600
|
+
- docs/public/images/tutorial/03-create-account.png
|
|
601
|
+
- docs/public/images/tutorial/03-login.png
|
|
602
|
+
- docs/public/images/tutorial/04-admin-index.png
|
|
603
|
+
- docs/public/images/tutorial/05-actions-menu.png
|
|
604
|
+
- docs/public/images/tutorial/05-row-actions.png
|
|
605
|
+
- docs/public/images/tutorial/06-comments-tab.png
|
|
606
|
+
- docs/public/images/tutorial/06-post-with-comments.png
|
|
607
|
+
- docs/public/images/tutorial/07-author-dashboard.png
|
|
608
|
+
- docs/public/images/tutorial/07-author-portal.png
|
|
609
|
+
- docs/public/images/tutorial/08-customized-index.png
|
|
570
610
|
- docs/public/og-image.png
|
|
571
611
|
- docs/public/plutonium.png
|
|
572
612
|
- docs/public/site.webmanifest
|
|
@@ -594,6 +634,7 @@ files:
|
|
|
594
634
|
- docs/reference/behavior/index.md
|
|
595
635
|
- docs/reference/behavior/interactions.md
|
|
596
636
|
- docs/reference/behavior/policies.md
|
|
637
|
+
- docs/reference/configuration.md
|
|
597
638
|
- docs/reference/index.md
|
|
598
639
|
- docs/reference/resource/actions.md
|
|
599
640
|
- docs/reference/resource/definition.md
|
|
@@ -620,12 +661,16 @@ files:
|
|
|
620
661
|
- docs/superpowers/plans/2026-05-06-multi-invite-model-support.md.tasks.json
|
|
621
662
|
- docs/superpowers/plans/2026-05-07-ui-layout-overhaul.md
|
|
622
663
|
- docs/superpowers/plans/2026-05-07-ui-layout-overhaul.md.tasks.json
|
|
664
|
+
- docs/superpowers/plans/2026-05-15-public-pages-overhaul.md
|
|
665
|
+
- docs/superpowers/plans/2026-05-15-public-pages-overhaul.md.tasks.json
|
|
623
666
|
- docs/superpowers/specs/2026-04-08-plutonium-skills-overhaul-design.md
|
|
624
667
|
- docs/superpowers/specs/2026-04-14-plutonium-testing-design.md
|
|
625
668
|
- docs/superpowers/specs/2026-05-07-ui-layout-overhaul-design.md
|
|
626
669
|
- docs/superpowers/specs/2026-05-09-typeahead-endpoint-design.md
|
|
627
670
|
- docs/superpowers/specs/2026-05-12-skill-compaction-design.md
|
|
628
671
|
- docs/superpowers/specs/2026-05-13-docs-restructure-design.md
|
|
672
|
+
- docs/superpowers/specs/2026-05-15-public-pages-overhaul-design.md
|
|
673
|
+
- docs/superpowers/specs/2026-05-29-avatar-component-design.md
|
|
629
674
|
- esbuild.config.js
|
|
630
675
|
- exe/pug
|
|
631
676
|
- gemfiles/rails_7.gemfile
|
|
@@ -837,13 +882,13 @@ files:
|
|
|
837
882
|
- lib/generators/pu/rodauth/templates/app/rodauth/rodauth_app.rb.tt
|
|
838
883
|
- lib/generators/pu/rodauth/templates/app/rodauth/rodauth_plugin.rb.tt
|
|
839
884
|
- lib/generators/pu/rodauth/templates/app/views/_login_form_footer.html.erb.tt
|
|
885
|
+
- lib/generators/pu/rodauth/templates/app/views/rodauth_mailer/change_password_notify.text.erb
|
|
840
886
|
- lib/generators/pu/rodauth/templates/app/views/rodauth_mailer/email_auth.text.erb
|
|
841
887
|
- lib/generators/pu/rodauth/templates/app/views/rodauth_mailer/otp_disabled.text.erb
|
|
842
888
|
- lib/generators/pu/rodauth/templates/app/views/rodauth_mailer/otp_locked_out.text.erb
|
|
843
889
|
- lib/generators/pu/rodauth/templates/app/views/rodauth_mailer/otp_setup.text.erb
|
|
844
890
|
- lib/generators/pu/rodauth/templates/app/views/rodauth_mailer/otp_unlock_failed.text.erb
|
|
845
891
|
- lib/generators/pu/rodauth/templates/app/views/rodauth_mailer/otp_unlocked.text.erb
|
|
846
|
-
- lib/generators/pu/rodauth/templates/app/views/rodauth_mailer/password_changed.text.erb
|
|
847
892
|
- lib/generators/pu/rodauth/templates/app/views/rodauth_mailer/reset_password.text.erb
|
|
848
893
|
- lib/generators/pu/rodauth/templates/app/views/rodauth_mailer/reset_password_notify.text.erb
|
|
849
894
|
- lib/generators/pu/rodauth/templates/app/views/rodauth_mailer/unlock_account.text.erb
|
|
@@ -933,10 +978,8 @@ files:
|
|
|
933
978
|
- lib/plutonium/helpers.rb
|
|
934
979
|
- lib/plutonium/helpers/application_helper.rb
|
|
935
980
|
- lib/plutonium/helpers/assets_helper.rb
|
|
936
|
-
- lib/plutonium/helpers/attachment_helper.rb
|
|
937
981
|
- lib/plutonium/helpers/content_helper.rb
|
|
938
982
|
- lib/plutonium/helpers/display_helper.rb
|
|
939
|
-
- lib/plutonium/helpers/table_helper.rb
|
|
940
983
|
- lib/plutonium/helpers/turbo_helper.rb
|
|
941
984
|
- lib/plutonium/helpers/turbo_stream_actions_helper.rb
|
|
942
985
|
- lib/plutonium/interaction/README.md
|
|
@@ -1021,6 +1064,7 @@ files:
|
|
|
1021
1064
|
- lib/plutonium/ui.rb
|
|
1022
1065
|
- lib/plutonium/ui/action_button.rb
|
|
1023
1066
|
- lib/plutonium/ui/actions_dropdown.rb
|
|
1067
|
+
- lib/plutonium/ui/avatar.rb
|
|
1024
1068
|
- lib/plutonium/ui/block.rb
|
|
1025
1069
|
- lib/plutonium/ui/breadcrumbs.rb
|
|
1026
1070
|
- lib/plutonium/ui/color_mode_selector.rb
|
|
@@ -1138,6 +1182,7 @@ files:
|
|
|
1138
1182
|
- src/js/controllers/capture_url_controller.js
|
|
1139
1183
|
- src/js/controllers/clipboard_controller.js
|
|
1140
1184
|
- src/js/controllers/color_mode_controller.js
|
|
1185
|
+
- src/js/controllers/dirty_form_guard_controller.js
|
|
1141
1186
|
- src/js/controllers/easymde_controller.js
|
|
1142
1187
|
- src/js/controllers/filter_panel_controller.js
|
|
1143
1188
|
- src/js/controllers/flatpickr_controller.js
|
|
@@ -1170,6 +1215,7 @@ files:
|
|
|
1170
1215
|
- src/js/support/mime_icon.js
|
|
1171
1216
|
- src/js/turbo/index.js
|
|
1172
1217
|
- src/js/turbo/turbo_actions.js
|
|
1218
|
+
- src/js/turbo/turbo_confirm.js
|
|
1173
1219
|
- src/js/turbo/turbo_debug.js
|
|
1174
1220
|
- src/js/turbo/turbo_frame_monkey_patch.js
|
|
1175
1221
|
- tailwind.config.js
|
|
@@ -1183,7 +1229,7 @@ metadata:
|
|
|
1183
1229
|
homepage_uri: https://radioactive-labs.github.io/plutonium-core/
|
|
1184
1230
|
source_code_uri: https://github.com/radioactive-labs/plutonium-core
|
|
1185
1231
|
post_install_message: |
|
|
1186
|
-
⚠️ Plutonium 0.
|
|
1232
|
+
⚠️ Plutonium 0.53.0 — breaking change
|
|
1187
1233
|
|
|
1188
1234
|
Entity-scoped URL helpers and path params have been renamed from
|
|
1189
1235
|
`<entity>_scope_*` to `<entity>_scoped_*`.
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
module Plutonium
|
|
2
|
-
module Helpers
|
|
3
|
-
module AttachmentHelper
|
|
4
|
-
def attachment_preview(attachments, **options)
|
|
5
|
-
clamp_content begin
|
|
6
|
-
tag.div class: [options[:identity_class], "attachment-preview-container d-flex flex-wrap gap-1 my-1"],
|
|
7
|
-
data: {controller: "attachment-preview-container"} do
|
|
8
|
-
Array(attachments).each do |attachment|
|
|
9
|
-
next unless attachment.url.present?
|
|
10
|
-
|
|
11
|
-
concat begin
|
|
12
|
-
tag.div class: [options[:identity_class], "attachment-preview d-inline-block text-center"],
|
|
13
|
-
title: attachment.filename,
|
|
14
|
-
data: {
|
|
15
|
-
controller: "attachment-preview",
|
|
16
|
-
attachment_preview_mime_type_value: attachment.content_type,
|
|
17
|
-
attachment_preview_thumbnail_url_value: _attachment_thumbnail_url(attachment)
|
|
18
|
-
} do
|
|
19
|
-
tag.figure class: "figure my-1", style: "width: 160px;" do
|
|
20
|
-
concat attachment_preview_thumnail(attachment)
|
|
21
|
-
concat begin
|
|
22
|
-
tag.figcaption class: "figure-caption text-truncate" do
|
|
23
|
-
if options[:caption]
|
|
24
|
-
caption = options[:caption].is_a?(String) ? options[:caption] : attachment.filename
|
|
25
|
-
concat link_to(caption, attachment.url, class: "text-decoration-none", target: :blank)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
if block_given?
|
|
29
|
-
elements = Array(yield attachment).compact
|
|
30
|
-
elements.each { |elem| concat elem }
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def attachment_preview_thumnail(attachment)
|
|
43
|
-
return unless attachment.url.present?
|
|
44
|
-
|
|
45
|
-
# Any changes made here must be reflected in attachment_input_controller#buildPreviewTemplate
|
|
46
|
-
|
|
47
|
-
tag.div class: "bg-white border border-gray-200 rounded-lg shadow hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700", data: {attachment_preview_target: "thumbnail"} do
|
|
48
|
-
thumbnail_url = _attachment_thumbnail_url(attachment)
|
|
49
|
-
link_body = if thumbnail_url
|
|
50
|
-
image_tag thumbnail_url, style: "width:100%; height:100%; object-fit: contain;"
|
|
51
|
-
else
|
|
52
|
-
_attachment_extension(attachment)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
link_to link_body, attachment.url, style: "width:150px; height:150px; line-height: 150px;",
|
|
56
|
-
class: "d-block text-decoration-none user-select-none fs-5 font-monospace text-body-secondary",
|
|
57
|
-
target: :blank,
|
|
58
|
-
data: {attachment_preview_target: "thumbnailLink"}
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
private
|
|
63
|
-
|
|
64
|
-
def _attachment_thumbnail_url(attachment)
|
|
65
|
-
attachment.url if attachment.representable?
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def _attachment_extension(attachment)
|
|
69
|
-
attachment.try(:extension) || File.extname(attachment.filename.to_s)
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|