swal_rails 0.3.1.beta1
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 +7 -0
- data/Appraisals +43 -0
- data/CHANGELOG.md +73 -0
- data/LICENSE.txt +21 -0
- data/README.md +973 -0
- data/Rakefile +12 -0
- data/app/assets/javascripts/swal_rails/chain.js +38 -0
- data/app/assets/javascripts/swal_rails/confirm.js +93 -0
- data/app/assets/javascripts/swal_rails/controllers/swal_controller.js +54 -0
- data/app/assets/javascripts/swal_rails/flash.js +24 -0
- data/app/assets/javascripts/swal_rails/index.js +62 -0
- data/app/assets/stylesheets/swal_rails/index.css +5 -0
- data/config/importmap.rb +9 -0
- data/config/locales/swal_rails.en.yml +19 -0
- data/config/locales/swal_rails.fr.yml +19 -0
- data/gemfiles/rails_7_2.gemfile +25 -0
- data/gemfiles/rails_8_0.gemfile +25 -0
- data/gemfiles/rails_8_1.gemfile +25 -0
- data/gemfiles/rails_8_1_sprockets.gemfile +25 -0
- data/lib/generators/swal_rails/install/install_generator.rb +138 -0
- data/lib/generators/swal_rails/install/templates/initializer.rb +33 -0
- data/lib/generators/swal_rails/locales/locales_generator.rb +19 -0
- data/lib/swal_rails/configuration.rb +88 -0
- data/lib/swal_rails/engine.rb +55 -0
- data/lib/swal_rails/helpers.rb +96 -0
- data/lib/swal_rails/version.rb +6 -0
- data/lib/swal_rails.rb +25 -0
- data/vendor/javascript/sweetalert2/LICENSE +22 -0
- data/vendor/javascript/sweetalert2/sweetalert2.all.js +4814 -0
- data/vendor/javascript/sweetalert2/sweetalert2.all.min.js +6 -0
- data/vendor/javascript/sweetalert2/sweetalert2.esm.all.js +4805 -0
- data/vendor/javascript/sweetalert2/sweetalert2.esm.all.min.js +6 -0
- data/vendor/javascript/sweetalert2/sweetalert2.esm.js +4804 -0
- data/vendor/javascript/sweetalert2/sweetalert2.esm.min.js +5 -0
- data/vendor/javascript/sweetalert2/sweetalert2.js +4813 -0
- data/vendor/javascript/sweetalert2/sweetalert2.min.js +5 -0
- data/vendor/stylesheets/sweetalert2/LICENSE +22 -0
- data/vendor/stylesheets/sweetalert2/sweetalert2.css +1233 -0
- data/vendor/stylesheets/sweetalert2/sweetalert2.min.css +1 -0
- data/vendor/stylesheets/sweetalert2/themes/bootstrap-4.css +167 -0
- data/vendor/stylesheets/sweetalert2/themes/bootstrap-5.css +173 -0
- data/vendor/stylesheets/sweetalert2/themes/borderless.css +46 -0
- data/vendor/stylesheets/sweetalert2/themes/bulma.css +94 -0
- data/vendor/stylesheets/sweetalert2/themes/material-ui.css +183 -0
- data/vendor/stylesheets/sweetalert2/themes/minimal.css +40 -0
- metadata +124 -0
data/Rakefile
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Runs a sequence of SweetAlert2 modals, advancing only on confirm.
|
|
2
|
+
//
|
|
3
|
+
// Semantics (per step):
|
|
4
|
+
// - isDismissed → abort the chain, return false
|
|
5
|
+
// - isConfirmed → run onConfirmed sub-chain if present, else continue
|
|
6
|
+
// - isDenied → run onDenied sub-chain if present, else abort
|
|
7
|
+
//
|
|
8
|
+
// A chain resolves to `true` iff it ran to completion along a path without
|
|
9
|
+
// abort. That boolean is the contract expected by Turbo.setConfirmMethod
|
|
10
|
+
// and by the data-attribute re-dispatch logic in confirm.js.
|
|
11
|
+
export const CHAIN_DEFAULTS = {
|
|
12
|
+
showCancelButton: true,
|
|
13
|
+
focusCancel: true,
|
|
14
|
+
icon: "warning"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const chainDialogs = async (Swal, steps) => {
|
|
18
|
+
if (!Array.isArray(steps) || steps.length === 0) return true
|
|
19
|
+
|
|
20
|
+
for (const step of steps) {
|
|
21
|
+
// Strip our own control keys — SA2 ignores unknown options, but leaking
|
|
22
|
+
// `onConfirmed`/`onDenied` into the popup options keeps the serialized
|
|
23
|
+
// payload noisy and invites confusion.
|
|
24
|
+
const { onConfirmed, onDenied, ...sa2Options } = step || {}
|
|
25
|
+
const result = await Swal.fire({ ...CHAIN_DEFAULTS, ...sa2Options })
|
|
26
|
+
|
|
27
|
+
if (result.isDismissed) return false
|
|
28
|
+
if (result.isConfirmed) {
|
|
29
|
+
if (Array.isArray(onConfirmed)) return chainDialogs(Swal, onConfirmed)
|
|
30
|
+
continue
|
|
31
|
+
}
|
|
32
|
+
if (result.isDenied) {
|
|
33
|
+
if (Array.isArray(onDenied)) return chainDialogs(Swal, onDenied)
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return true
|
|
38
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { chainDialogs } from "swal_rails/chain"
|
|
2
|
+
|
|
3
|
+
const parseJSON = (value) => {
|
|
4
|
+
if (!value) return null
|
|
5
|
+
try { return JSON.parse(value) } catch { return null }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// When Rails serializes `data: { turbo_confirm: { icon: "error" } }`, the
|
|
9
|
+
// attribute value is a JSON string. Detect that, and accept both Object
|
|
10
|
+
// (single-step options) and Array (multi-step chain) shapes.
|
|
11
|
+
const messagePayload = (message) => {
|
|
12
|
+
if (typeof message !== "string") return null
|
|
13
|
+
const trimmed = message.trim()
|
|
14
|
+
if (trimmed[0] !== "{" && trimmed[0] !== "[") return null
|
|
15
|
+
const parsed = parseJSON(trimmed)
|
|
16
|
+
if (Array.isArray(parsed)) return parsed
|
|
17
|
+
if (parsed && typeof parsed === "object") return parsed
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const confirmDialog = (Swal, message, element) => {
|
|
22
|
+
const dataset = element?.dataset || {}
|
|
23
|
+
const payload = messagePayload(message)
|
|
24
|
+
const fromMessage = payload && !Array.isArray(payload) ? payload : null
|
|
25
|
+
const text = fromMessage ? undefined : message
|
|
26
|
+
|
|
27
|
+
const options = {
|
|
28
|
+
title: dataset.swalTitle || text || "Are you sure?",
|
|
29
|
+
text: dataset.swalText || (dataset.swalTitle ? text : undefined),
|
|
30
|
+
icon: dataset.swalIcon || "warning",
|
|
31
|
+
showCancelButton: true,
|
|
32
|
+
focusCancel: true
|
|
33
|
+
}
|
|
34
|
+
if (dataset.swalConfirmText) options.confirmButtonText = dataset.swalConfirmText
|
|
35
|
+
if (dataset.swalCancelText) options.cancelButtonText = dataset.swalCancelText
|
|
36
|
+
|
|
37
|
+
// Merge order (later wins): defaults → data-swal-* shortcuts → JSON message
|
|
38
|
+
// (turbo_confirm: {}) → data-swal-options (most specific).
|
|
39
|
+
const extras = parseJSON(dataset.swalOptions) || {}
|
|
40
|
+
return Swal.fire({ ...options, ...(fromMessage || {}), ...extras }).then((result) => result.isConfirmed)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Dispatches to either a multi-step chain or a single-step confirm. Called
|
|
44
|
+
// from both the Turbo override and the data-attribute listener so both
|
|
45
|
+
// paths behave identically.
|
|
46
|
+
const confirmFlow = (Swal, message, element) => {
|
|
47
|
+
const fromDataset = parseJSON(element?.dataset?.swalSteps)
|
|
48
|
+
if (Array.isArray(fromDataset) && fromDataset.length) return chainDialogs(Swal, fromDataset)
|
|
49
|
+
|
|
50
|
+
const payload = messagePayload(message)
|
|
51
|
+
if (Array.isArray(payload) && payload.length) return chainDialogs(Swal, payload)
|
|
52
|
+
|
|
53
|
+
return confirmDialog(Swal, message, element)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const installTurboOverride = (Swal) => {
|
|
57
|
+
if (typeof window.Turbo === "undefined" || !window.Turbo.setConfirmMethod) return false
|
|
58
|
+
|
|
59
|
+
window.Turbo.setConfirmMethod((message, element) => confirmFlow(Swal, message, element))
|
|
60
|
+
return true
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const installDataAttribute = (Swal) => {
|
|
64
|
+
const handler = (event) => {
|
|
65
|
+
const el = event.target.closest("[data-swal-confirm], [data-swal-steps]")
|
|
66
|
+
if (!el) return
|
|
67
|
+
const message = el.getAttribute("data-swal-confirm")
|
|
68
|
+
event.preventDefault()
|
|
69
|
+
event.stopPropagation()
|
|
70
|
+
confirmFlow(Swal, message, el).then((confirmed) => {
|
|
71
|
+
if (!confirmed) return
|
|
72
|
+
el.removeAttribute("data-swal-confirm")
|
|
73
|
+
el.removeAttribute("data-swal-steps")
|
|
74
|
+
if (typeof el.click === "function" && event.type === "click") {
|
|
75
|
+
el.click()
|
|
76
|
+
} else if (el.tagName === "FORM") {
|
|
77
|
+
// requestSubmit() fires the 'submit' event, so Turbo and any UJS
|
|
78
|
+
// handlers stay in the loop — unlike the raw .submit() which skips them.
|
|
79
|
+
if (typeof el.requestSubmit === "function") el.requestSubmit()
|
|
80
|
+
else el.submit()
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
document.addEventListener("click", handler, true)
|
|
85
|
+
document.addEventListener("submit", handler, true)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const installConfirm = (Swal, config) => {
|
|
89
|
+
const mode = config.confirmMode || "data_attribute"
|
|
90
|
+
if (mode === "off") return
|
|
91
|
+
if (mode === "turbo_override" || mode === "both") installTurboOverride(Swal)
|
|
92
|
+
if (mode === "data_attribute" || mode === "both") installDataAttribute(Swal)
|
|
93
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import Swal from "sweetalert2"
|
|
3
|
+
import { chainDialogs } from "swal_rails/chain"
|
|
4
|
+
|
|
5
|
+
// Fire a Swal modal/toast or a multi-step chain from markup.
|
|
6
|
+
//
|
|
7
|
+
// <button data-controller="swal"
|
|
8
|
+
// data-action="click->swal#fire"
|
|
9
|
+
// data-swal-options-value='{"title":"Hi","icon":"info"}'>
|
|
10
|
+
// Ping
|
|
11
|
+
// </button>
|
|
12
|
+
//
|
|
13
|
+
// <button data-controller="swal"
|
|
14
|
+
// data-action="click->swal#chain"
|
|
15
|
+
// data-swal-steps-value='[{"title":"Sure?"},{"title":"Really?"}]'>
|
|
16
|
+
// Ping
|
|
17
|
+
// </button>
|
|
18
|
+
export default class extends Controller {
|
|
19
|
+
static values = {
|
|
20
|
+
options: { type: Object, default: {} },
|
|
21
|
+
steps: { type: Array, default: [] }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
fire(event) {
|
|
25
|
+
if (this.element.tagName === "A" || this.element.tagName === "BUTTON") {
|
|
26
|
+
event?.preventDefault?.()
|
|
27
|
+
}
|
|
28
|
+
return Swal.fire(this.optionsValue)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
confirm(event) {
|
|
32
|
+
event?.preventDefault?.()
|
|
33
|
+
const form = event?.target?.closest?.("form") || this.element
|
|
34
|
+
Swal.fire({
|
|
35
|
+
showCancelButton: true,
|
|
36
|
+
focusCancel: true,
|
|
37
|
+
...this.optionsValue
|
|
38
|
+
}).then((result) => {
|
|
39
|
+
if (result.isConfirmed && form?.tagName === "FORM") {
|
|
40
|
+
typeof form.requestSubmit === "function" ? form.requestSubmit() : form.submit()
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async chain(event) {
|
|
46
|
+
event?.preventDefault?.()
|
|
47
|
+
const form = event?.target?.closest?.("form") || this.element
|
|
48
|
+
const ok = await chainDialogs(window.Swal || Swal, this.stepsValue)
|
|
49
|
+
if (ok && form?.tagName === "FORM") {
|
|
50
|
+
typeof form.requestSubmit === "function" ? form.requestSubmit() : form.submit()
|
|
51
|
+
}
|
|
52
|
+
return ok
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const readFlash = () => {
|
|
2
|
+
const el = document.querySelector('meta[name="swal-flash"]')
|
|
3
|
+
if (!el) return []
|
|
4
|
+
try { return JSON.parse(el.getAttribute("content")) || [] } catch { return [] }
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const installFlash = (Swal, config) => {
|
|
8
|
+
const flashes = readFlash()
|
|
9
|
+
if (!flashes.length) return
|
|
10
|
+
|
|
11
|
+
const map = config.flashMap || {}
|
|
12
|
+
const queue = flashes.map((flash) => {
|
|
13
|
+
const spec = map[flash.key] || map[flash.key.toLowerCase()] || { icon: "info", toast: true, position: "top-end", timer: 3000 }
|
|
14
|
+
// Per-request options win over the per-key defaults from flash_map.
|
|
15
|
+
return { ...spec, ...(flash.options || {}) }
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const fireNext = () => {
|
|
19
|
+
const opts = queue.shift()
|
|
20
|
+
if (!opts) return
|
|
21
|
+
Swal.fire(opts).then(fireNext)
|
|
22
|
+
}
|
|
23
|
+
fireNext()
|
|
24
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import Swal from "sweetalert2"
|
|
2
|
+
import { installConfirm } from "swal_rails/confirm"
|
|
3
|
+
import { installFlash } from "swal_rails/flash"
|
|
4
|
+
|
|
5
|
+
const readMeta = (name) => {
|
|
6
|
+
const el = document.querySelector(`meta[name="${name}"]`)
|
|
7
|
+
if (!el) return null
|
|
8
|
+
try { return JSON.parse(el.getAttribute("content")) } catch { return null }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const prefersReducedMotion = () =>
|
|
12
|
+
window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
13
|
+
|
|
14
|
+
const buildMixin = (config) => {
|
|
15
|
+
const base = { ...(config.defaultOptions || {}) }
|
|
16
|
+
if (config.respectReducedMotion && prefersReducedMotion()) {
|
|
17
|
+
base.showClass = { popup: "" }
|
|
18
|
+
base.hideClass = { popup: "" }
|
|
19
|
+
}
|
|
20
|
+
if (config.i18n?.confirm_button_text) base.confirmButtonText = config.i18n.confirm_button_text
|
|
21
|
+
if (config.i18n?.cancel_button_text) base.cancelButtonText = config.i18n.cancel_button_text
|
|
22
|
+
if (config.i18n?.deny_button_text) base.denyButtonText = config.i18n.deny_button_text
|
|
23
|
+
if (config.i18n?.close_button_aria_label) base.closeButtonAriaLabel = config.i18n.close_button_aria_label
|
|
24
|
+
return base
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Module-scoped so repeated calls to boot() (DOMContentLoaded + every
|
|
28
|
+
// turbo:load) don't stack a new click/submit listener per navigation.
|
|
29
|
+
let booted = null
|
|
30
|
+
|
|
31
|
+
const boot = () => {
|
|
32
|
+
if (!booted) {
|
|
33
|
+
const config = readMeta("swal-config") || {}
|
|
34
|
+
const Mixin = Swal.mixin(buildMixin(config))
|
|
35
|
+
|
|
36
|
+
if (config.exposeWindowSwal !== false) {
|
|
37
|
+
window.Swal = Mixin
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
installConfirm(Mixin, config)
|
|
41
|
+
booted = { Swal: Mixin, config }
|
|
42
|
+
document.dispatchEvent(new CustomEvent("swal-rails:ready", { detail: booted }))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Flash meta is re-rendered per request, so read and fire on every page.
|
|
46
|
+
installFlash(booted.Swal, booted.config)
|
|
47
|
+
return booted.Swal
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const ready = (fn) => {
|
|
51
|
+
if (document.readyState === "loading") {
|
|
52
|
+
document.addEventListener("DOMContentLoaded", fn, { once: true })
|
|
53
|
+
} else {
|
|
54
|
+
fn()
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
ready(boot)
|
|
59
|
+
document.addEventListener("turbo:load", boot)
|
|
60
|
+
|
|
61
|
+
export { Swal }
|
|
62
|
+
export default Swal
|
data/config/importmap.rb
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
pin "sweetalert2", to: "sweetalert2.esm.all.js"
|
|
4
|
+
pin "swal_rails", to: "swal_rails/index.js"
|
|
5
|
+
pin "swal_rails/confirm", to: "swal_rails/confirm.js"
|
|
6
|
+
pin "swal_rails/flash", to: "swal_rails/flash.js"
|
|
7
|
+
pin "swal_rails/chain", to: "swal_rails/chain.js"
|
|
8
|
+
pin_all_from SwalRails::Engine.root.join("app/assets/javascripts/swal_rails/controllers"),
|
|
9
|
+
under: "controllers", to: "swal_rails/controllers"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
en:
|
|
2
|
+
swal_rails:
|
|
3
|
+
confirm_button_text: "OK"
|
|
4
|
+
cancel_button_text: "Cancel"
|
|
5
|
+
deny_button_text: "No"
|
|
6
|
+
close_button_aria_label: "Close this dialog"
|
|
7
|
+
confirm:
|
|
8
|
+
title: "Are you sure?"
|
|
9
|
+
confirm_button_text: "Yes"
|
|
10
|
+
cancel_button_text: "Cancel"
|
|
11
|
+
flash:
|
|
12
|
+
success:
|
|
13
|
+
title: "Success"
|
|
14
|
+
error:
|
|
15
|
+
title: "Error"
|
|
16
|
+
warning:
|
|
17
|
+
title: "Warning"
|
|
18
|
+
info:
|
|
19
|
+
title: "Info"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
fr:
|
|
2
|
+
swal_rails:
|
|
3
|
+
confirm_button_text: "OK"
|
|
4
|
+
cancel_button_text: "Annuler"
|
|
5
|
+
deny_button_text: "Non"
|
|
6
|
+
close_button_aria_label: "Fermer cette fenêtre"
|
|
7
|
+
confirm:
|
|
8
|
+
title: "Êtes-vous sûr ?"
|
|
9
|
+
confirm_button_text: "Oui"
|
|
10
|
+
cancel_button_text: "Annuler"
|
|
11
|
+
flash:
|
|
12
|
+
success:
|
|
13
|
+
title: "Succès"
|
|
14
|
+
error:
|
|
15
|
+
title: "Erreur"
|
|
16
|
+
warning:
|
|
17
|
+
title: "Attention"
|
|
18
|
+
info:
|
|
19
|
+
title: "Info"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# This file was generated by Appraisal
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
gem "irb"
|
|
6
|
+
gem "rake", "~> 13.0"
|
|
7
|
+
gem "importmap-rails", "~> 2.0"
|
|
8
|
+
gem "propshaft", "~> 0.9"
|
|
9
|
+
gem "rails", "~> 7.2.2"
|
|
10
|
+
gem "stimulus-rails", "~> 1.3"
|
|
11
|
+
gem "turbo-rails", "~> 2.0"
|
|
12
|
+
|
|
13
|
+
group :development, :test do
|
|
14
|
+
gem "appraisal", "~> 2.5"
|
|
15
|
+
gem "capybara", "~> 3.40"
|
|
16
|
+
gem "cuprite", "~> 0.15"
|
|
17
|
+
gem "puma", "~> 6.4"
|
|
18
|
+
gem "rspec", "~> 3.12"
|
|
19
|
+
gem "rspec-rails", "~> 6.1"
|
|
20
|
+
gem "rubocop", "~> 1.60", require: false
|
|
21
|
+
gem "rubocop-rspec", require: false
|
|
22
|
+
gem "sqlite3", "~> 1.7"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
gemspec path: "../"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# This file was generated by Appraisal
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
gem "irb"
|
|
6
|
+
gem "rake", "~> 13.0"
|
|
7
|
+
gem "importmap-rails", "~> 2.0"
|
|
8
|
+
gem "propshaft", "~> 1.0"
|
|
9
|
+
gem "rails", "~> 8.0.0"
|
|
10
|
+
gem "stimulus-rails", "~> 1.3"
|
|
11
|
+
gem "turbo-rails", "~> 2.0"
|
|
12
|
+
|
|
13
|
+
group :development, :test do
|
|
14
|
+
gem "appraisal", "~> 2.5"
|
|
15
|
+
gem "capybara", "~> 3.40"
|
|
16
|
+
gem "cuprite", "~> 0.15"
|
|
17
|
+
gem "puma", "~> 6.4"
|
|
18
|
+
gem "rspec", "~> 3.12"
|
|
19
|
+
gem "rspec-rails", "~> 6.1"
|
|
20
|
+
gem "rubocop", "~> 1.60", require: false
|
|
21
|
+
gem "rubocop-rspec", require: false
|
|
22
|
+
gem "sqlite3", "~> 1.7"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
gemspec path: "../"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# This file was generated by Appraisal
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
gem "irb"
|
|
6
|
+
gem "rake", "~> 13.0"
|
|
7
|
+
gem "importmap-rails", "~> 2.0"
|
|
8
|
+
gem "propshaft", "~> 1.0"
|
|
9
|
+
gem "rails", "~> 8.1.3"
|
|
10
|
+
gem "stimulus-rails", "~> 1.3"
|
|
11
|
+
gem "turbo-rails", "~> 2.0"
|
|
12
|
+
|
|
13
|
+
group :development, :test do
|
|
14
|
+
gem "appraisal", "~> 2.5"
|
|
15
|
+
gem "capybara", "~> 3.40"
|
|
16
|
+
gem "cuprite", "~> 0.15"
|
|
17
|
+
gem "puma", "~> 6.4"
|
|
18
|
+
gem "rspec", "~> 3.12"
|
|
19
|
+
gem "rspec-rails", "~> 6.1"
|
|
20
|
+
gem "rubocop", "~> 1.60", require: false
|
|
21
|
+
gem "rubocop-rspec", require: false
|
|
22
|
+
gem "sqlite3", "~> 1.7"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
gemspec path: "../"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# This file was generated by Appraisal
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
gem "irb"
|
|
6
|
+
gem "rake", "~> 13.0"
|
|
7
|
+
gem "importmap-rails", "~> 2.0"
|
|
8
|
+
gem "rails", "~> 8.1.3"
|
|
9
|
+
gem "stimulus-rails", "~> 1.3"
|
|
10
|
+
gem "turbo-rails", "~> 2.0"
|
|
11
|
+
gem "sprockets-rails", "~> 3.5"
|
|
12
|
+
|
|
13
|
+
group :development, :test do
|
|
14
|
+
gem "appraisal", "~> 2.5"
|
|
15
|
+
gem "capybara", "~> 3.40"
|
|
16
|
+
gem "cuprite", "~> 0.15"
|
|
17
|
+
gem "puma", "~> 6.4"
|
|
18
|
+
gem "rspec", "~> 3.12"
|
|
19
|
+
gem "rspec-rails", "~> 6.1"
|
|
20
|
+
gem "rubocop", "~> 1.60", require: false
|
|
21
|
+
gem "rubocop-rspec", require: false
|
|
22
|
+
gem "sqlite3", "~> 1.7"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
gemspec path: "../"
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/base"
|
|
5
|
+
|
|
6
|
+
module SwalRails
|
|
7
|
+
module Generators
|
|
8
|
+
class InstallGenerator < Rails::Generators::Base
|
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
|
10
|
+
|
|
11
|
+
ASSETS_MODES = %w[importmap jsbundling sprockets auto].freeze
|
|
12
|
+
|
|
13
|
+
# `--mode` is used instead of `--assets` because Rails::Generators::Base
|
|
14
|
+
# reserves `:assets` as a boolean option (legacy `rails new --skip-assets`).
|
|
15
|
+
class_option :mode, type: :string, default: "auto", desc: "Asset mode: importmap, jsbundling, sprockets, auto"
|
|
16
|
+
class_option :confirm_mode, type: :string, default: "data_attribute", desc: "Confirm mode: off, data_attribute, turbo_override, both"
|
|
17
|
+
class_option :skip_layout, type: :boolean, default: false, desc: "Skip layout injection"
|
|
18
|
+
|
|
19
|
+
def validate_options!
|
|
20
|
+
mode = (options[:mode] || "auto").to_s
|
|
21
|
+
return if ASSETS_MODES.include?(mode)
|
|
22
|
+
|
|
23
|
+
raise Thor::Error, "--mode must be one of #{ASSETS_MODES.join(", ")}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def copy_initializer
|
|
27
|
+
template "initializer.rb", "config/initializers/swal_rails.rb"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def configure_assets
|
|
31
|
+
case resolved_assets_mode
|
|
32
|
+
when "importmap" then install_importmap
|
|
33
|
+
when "jsbundling" then install_jsbundling
|
|
34
|
+
when "sprockets" then install_sprockets
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def inject_meta_tags
|
|
39
|
+
return if options[:skip_layout]
|
|
40
|
+
|
|
41
|
+
layout = "app/views/layouts/application.html.erb"
|
|
42
|
+
return say_status(:skip, "#{layout} not found", :yellow) unless file_exists?(layout)
|
|
43
|
+
|
|
44
|
+
inject_into_file layout, before: %r{</head>} do
|
|
45
|
+
" <%= swal_rails_meta_tags %>\n "
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def show_readme
|
|
50
|
+
readme_text = <<~TXT
|
|
51
|
+
|
|
52
|
+
swal_rails installed.
|
|
53
|
+
|
|
54
|
+
Next steps:
|
|
55
|
+
1. Edit config/initializers/swal_rails.rb to customize flash_map / confirm_mode.
|
|
56
|
+
2. Ensure <%= swal_rails_meta_tags %> is rendered in your <head>.
|
|
57
|
+
3. Import the runtime in your JS entrypoint:
|
|
58
|
+
import "swal_rails"
|
|
59
|
+
|
|
60
|
+
Confirm mode: #{options[:confirm_mode]}
|
|
61
|
+
Assets mode: #{resolved_assets_mode}
|
|
62
|
+
TXT
|
|
63
|
+
say readme_text, :green
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def resolved_assets_mode
|
|
69
|
+
@resolved_assets_mode ||= detect_assets_mode
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def detect_assets_mode
|
|
73
|
+
mode = (options[:mode] || "auto").to_s
|
|
74
|
+
return mode unless mode == "auto"
|
|
75
|
+
return "importmap" if file_exists?("config/importmap.rb")
|
|
76
|
+
return "jsbundling" if file_exists?("package.json")
|
|
77
|
+
|
|
78
|
+
"sprockets"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def install_importmap
|
|
82
|
+
pin_line = 'pin "swal_rails", to: "swal_rails/index.js"'
|
|
83
|
+
pin_sa2 = 'pin "sweetalert2", to: "sweetalert2.esm.all.js"'
|
|
84
|
+
|
|
85
|
+
if file_exists?("config/importmap.rb")
|
|
86
|
+
append_unique "config/importmap.rb", pin_sa2
|
|
87
|
+
append_unique "config/importmap.rb", pin_line
|
|
88
|
+
else
|
|
89
|
+
say_status(:warn, "config/importmap.rb not found, skipping pins", :yellow)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
app_js = "app/javascript/application.js"
|
|
93
|
+
if file_exists?(app_js)
|
|
94
|
+
append_unique app_js, 'import "swal_rails"'
|
|
95
|
+
else
|
|
96
|
+
say_status(:warn, "#{app_js} not found, add `import \"swal_rails\"` to your JS entrypoint", :yellow)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def install_jsbundling
|
|
101
|
+
if file_exists?("package.json")
|
|
102
|
+
run "yarn add sweetalert2@#{SwalRails::SWEETALERT2_VERSION}" if file_exists?("yarn.lock")
|
|
103
|
+
run "npm install sweetalert2@#{SwalRails::SWEETALERT2_VERSION}" if file_exists?("package-lock.json") && !file_exists?("yarn.lock")
|
|
104
|
+
else
|
|
105
|
+
say_status(:warn, "package.json not found", :yellow)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
app_js = "app/javascript/application.js"
|
|
109
|
+
if file_exists?(app_js)
|
|
110
|
+
append_unique app_js, 'import "swal_rails"'
|
|
111
|
+
else
|
|
112
|
+
say_status(:warn, "#{app_js} not found", :yellow)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def install_sprockets
|
|
117
|
+
manifest = "app/assets/config/manifest.js"
|
|
118
|
+
if file_exists?(manifest)
|
|
119
|
+
append_unique manifest, "//= link sweetalert2.js"
|
|
120
|
+
append_unique manifest, "//= link sweetalert2.css"
|
|
121
|
+
else
|
|
122
|
+
say_status(:warn, "#{manifest} not found; add `//= require sweetalert2` to your bundle", :yellow)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def append_unique(path, line)
|
|
127
|
+
content = File.read(File.join(destination_root, path))
|
|
128
|
+
return if content.include?(line)
|
|
129
|
+
|
|
130
|
+
append_to_file path, "\n#{line}\n"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def file_exists?(path)
|
|
134
|
+
File.exist?(File.join(destination_root, path))
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
SwalRails.configure do |config|
|
|
4
|
+
# How confirmation modals are wired.
|
|
5
|
+
# :off — do nothing, use Swal manually
|
|
6
|
+
# :data_attribute — intercept clicks/submits on [data-swal-confirm] (default, non-intrusive)
|
|
7
|
+
# :turbo_override — replace Turbo.setConfirmMethod globally
|
|
8
|
+
# :both — both mechanisms at once
|
|
9
|
+
config.confirm_mode = :<%= options[:confirm_mode] %>
|
|
10
|
+
|
|
11
|
+
# Whether to expose `window.Swal` globally (useful for console / inline scripts).
|
|
12
|
+
config.expose_window_swal = true
|
|
13
|
+
|
|
14
|
+
# Whether to honor the user's OS prefers-reduced-motion setting.
|
|
15
|
+
config.respect_reduced_motion = true
|
|
16
|
+
|
|
17
|
+
# Default options merged into every Swal.fire call.
|
|
18
|
+
config.default_options = {
|
|
19
|
+
buttonsStyling: true,
|
|
20
|
+
reverseButtons: false,
|
|
21
|
+
focusConfirm: true,
|
|
22
|
+
returnFocus: true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Map Rails flash keys to SweetAlert2 options.
|
|
26
|
+
# Set a key to nil to silence it. Customize icon/toast/position/timer per key.
|
|
27
|
+
config.flash_map[:notice] = { icon: "success", toast: true, position: "top-end", timer: 3000, timerProgressBar: true, showConfirmButton: false }
|
|
28
|
+
config.flash_map[:success] = { icon: "success", toast: true, position: "top-end", timer: 3000, timerProgressBar: true, showConfirmButton: false }
|
|
29
|
+
config.flash_map[:alert] = { icon: "error", toast: false }
|
|
30
|
+
config.flash_map[:error] = { icon: "error", toast: false }
|
|
31
|
+
config.flash_map[:warning] = { icon: "warning", toast: true, position: "top-end", timer: 4000, timerProgressBar: true, showConfirmButton: false }
|
|
32
|
+
config.flash_map[:info] = { icon: "info", toast: true, position: "top-end", timer: 3000, timerProgressBar: true, showConfirmButton: false }
|
|
33
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/base"
|
|
4
|
+
|
|
5
|
+
module SwalRails
|
|
6
|
+
module Generators
|
|
7
|
+
class LocalesGenerator < Rails::Generators::Base
|
|
8
|
+
source_root File.expand_path("../../../../config/locales", __dir__)
|
|
9
|
+
|
|
10
|
+
desc "Copies swal_rails locale files (en, fr) into config/locales/"
|
|
11
|
+
|
|
12
|
+
def copy_locales
|
|
13
|
+
Dir["#{self.class.source_root}/*.yml"].each do |file|
|
|
14
|
+
copy_file file, "config/locales/#{File.basename(file)}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|