formstrap 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -0
- data/app/assets/javascripts/formstrap/controllers/nested_preview_controller.js +167 -0
- data/app/assets/javascripts/formstrap/controllers/preview_controller.js +65 -0
- data/app/assets/javascripts/formstrap/controllers/repeater_controller.js +39 -7
- data/app/assets/javascripts/formstrap/index.js +4 -0
- data/app/assets/javascripts/formstrap.js +199 -7
- data/app/assets/stylesheets/formstrap/shared/nested_preview.scss +31 -0
- data/app/assets/stylesheets/formstrap/shared.scss +1 -0
- data/app/assets/stylesheets/formstrap.css +31 -0
- data/app/views/formstrap/_button.html.erb +0 -0
- data/app/views/formstrap/_repeater.html.erb +40 -22
- data/app/views/formstrap/repeater/_row.html.erb +20 -4
- data/app/views/formstrap/shared/_nested_preview.html.erb +35 -0
- data/config/locales/de.yml +5 -0
- data/config/locales/en.yml +1 -5
- data/config/locales/formstrap/de.yml +48 -0
- data/config/locales/formstrap/en.yml +48 -0
- data/config/locales/formstrap/fr.yml +48 -0
- data/config/locales/formstrap/nl.yml +48 -0
- data/config/locales/fr.yml +5 -0
- data/config/locales/nl.yml +1 -5
- data/lib/formstrap/form_builder.rb +15 -0
- data/lib/formstrap/version.rb +1 -1
- metadata +13 -8
- data/config/locales/formstrap/forms/en.yml +0 -25
- data/config/locales/formstrap/forms/nl.yml +0 -25
- data/config/locales/formstrap/media/en.yml +0 -16
- data/config/locales/formstrap/media/nl.yml +0 -16
- data/config/locales/formstrap/thumbnail/en.yml +0 -4
- data/config/locales/formstrap/thumbnail/nl.yml +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8243dd5f8e2fdae1537ffab42b56734ebdfe53e054feb92c68a323954c0a63e0
|
4
|
+
data.tar.gz: 0c5a4a73c1d4c518c2ee71cf36f3ccc882ee106a825d94aef212a639ee0bcb94
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6dbd8118d294ff8a863675299ce236cc3bd7f7a749b958b42cd22ac66465c3a5c751433edb93009a87b203d28995e2581b3f3600160d4f6a68e0b78110ab9fbe
|
7
|
+
data.tar.gz: 90ea13586df35f2db36e0b5f3139c6316ae5be21cf022355c665084a19e5d69663efe329f948bd6287b8deb8c4af76ac4b186a94b73aae5b73c8f6aded3659f9
|
data/README.md
CHANGED
@@ -72,6 +72,7 @@ An overview of all the Formstrap / Ruby on Rails form helpers:
|
|
72
72
|
| Textarea | textarea | textarea formstrap: false or text_area |
|
73
73
|
| URL | url | url formstrap: false or url_field |
|
74
74
|
| WYSIWYG * | wysiwyg | N/A |
|
75
|
+
| Repeater | repeater_for | Adds advanced features to fields_for |
|
75
76
|
|
76
77
|
\* Formstrap provides the implementation of these 3rd party libraries, however it is up to the user to provide the
|
77
78
|
correct assets.
|
@@ -0,0 +1,167 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static get targets () {
|
5
|
+
return ['fields', 'iframeWrapper', 'iframe', 'offcanvas', 'error', 'loader']
|
6
|
+
}
|
7
|
+
|
8
|
+
static get values () {
|
9
|
+
return {
|
10
|
+
url: String
|
11
|
+
}
|
12
|
+
}
|
13
|
+
|
14
|
+
connect () {
|
15
|
+
this.prepareIframe()
|
16
|
+
|
17
|
+
// Resize iFrame after content is loaded
|
18
|
+
this.iframeTarget.addEventListener('load', () => {
|
19
|
+
this.hideLoader()
|
20
|
+
this.resizeIframe()
|
21
|
+
})
|
22
|
+
|
23
|
+
// Offcanvas closes
|
24
|
+
this.offcanvasTarget.addEventListener('hide.bs.offcanvas', (event) => {
|
25
|
+
if (!this.update()) {
|
26
|
+
event.preventDefault()
|
27
|
+
}
|
28
|
+
})
|
29
|
+
}
|
30
|
+
|
31
|
+
showLoader () {
|
32
|
+
this.loaderTarget.classList.remove('d-none')
|
33
|
+
}
|
34
|
+
|
35
|
+
hideLoader () {
|
36
|
+
this.loaderTarget.classList.add('d-none')
|
37
|
+
}
|
38
|
+
|
39
|
+
showError () {
|
40
|
+
this.errorTarget.classList.remove('d-none')
|
41
|
+
}
|
42
|
+
|
43
|
+
hideError () {
|
44
|
+
this.errorTarget.classList.add('d-none')
|
45
|
+
}
|
46
|
+
|
47
|
+
update () {
|
48
|
+
// Validate fields
|
49
|
+
const isValid = this.validateFields()
|
50
|
+
|
51
|
+
if (isValid) {
|
52
|
+
this.requestPreview()
|
53
|
+
this.hideError()
|
54
|
+
return true
|
55
|
+
} else {
|
56
|
+
this.showError()
|
57
|
+
return false
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
requestPreview () {
|
62
|
+
// Create an AJAX request
|
63
|
+
// eslint-disable-next-line no-undef
|
64
|
+
const xhr = new XMLHttpRequest()
|
65
|
+
xhr.open('POST', this.urlValue, true)
|
66
|
+
|
67
|
+
// Submit the form data
|
68
|
+
const formData = this.buildFormData()
|
69
|
+
xhr.send(formData)
|
70
|
+
|
71
|
+
// Show loader
|
72
|
+
this.showLoader()
|
73
|
+
|
74
|
+
// Handle the request once it's done
|
75
|
+
xhr.onreadystatechange = () => {
|
76
|
+
if (xhr.readyState === XMLHttpRequest.DONE) {
|
77
|
+
this.hideLoader()
|
78
|
+
this.handleRequest(xhr)
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
handleRequest (request) {
|
84
|
+
// Handle the response
|
85
|
+
if (request.status === 200) {
|
86
|
+
this.updatePreview(request.responseText)
|
87
|
+
} else {
|
88
|
+
this.showError()
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
validateFields () {
|
93
|
+
let allValid = true
|
94
|
+
const fields = this.fieldsTarget
|
95
|
+
const formElements = fields.querySelectorAll('input[name], select[name], textarea[name]')
|
96
|
+
formElements.forEach(function (element) {
|
97
|
+
const isValid = element.reportValidity()
|
98
|
+
if (!isValid) {
|
99
|
+
allValid = false
|
100
|
+
}
|
101
|
+
})
|
102
|
+
return allValid
|
103
|
+
}
|
104
|
+
|
105
|
+
buildFormData () {
|
106
|
+
// Get fields
|
107
|
+
const fields = this.fieldsTarget
|
108
|
+
|
109
|
+
// Build FormData
|
110
|
+
const formData = new FormData()
|
111
|
+
|
112
|
+
// Replace all occurrences of "page[blocks_attributes][0]" with "block"
|
113
|
+
const regex = /\w+\[([^\]]+)s_attributes\]\[\d+\]/g
|
114
|
+
const formElements = fields.querySelectorAll('input[name]:not([name$="[id]"]), select[name]:not([name$="[id]"]), textarea[name]:not([name$="[id]"]), button[name]:not([name$="[id]"])')
|
115
|
+
formElements.forEach(function (element) {
|
116
|
+
const currentName = element.getAttribute('name')
|
117
|
+
const newName = currentName.replace(regex, '$1')
|
118
|
+
formData.append(newName, element.value)
|
119
|
+
})
|
120
|
+
|
121
|
+
// Add authenticity token
|
122
|
+
formData.append('authenticity_token', this.getAuthenticityToken())
|
123
|
+
|
124
|
+
return formData
|
125
|
+
}
|
126
|
+
|
127
|
+
// Prepare the iFrame for rendering
|
128
|
+
// Objective: render the iframe content at the scale of the browser window, but resize it to fit the preview container
|
129
|
+
prepareIframe () {
|
130
|
+
const scaleFactor = this.scaleFactor()
|
131
|
+
const style = `
|
132
|
+
transform: scale(${scaleFactor});
|
133
|
+
opacity: 0;
|
134
|
+
transform-origin: 0 0;
|
135
|
+
width: ${100 / scaleFactor}%;
|
136
|
+
`
|
137
|
+
this.iframeTarget.setAttribute('style', style)
|
138
|
+
}
|
139
|
+
|
140
|
+
// Relative size of the preview container compared to the browser window
|
141
|
+
scaleFactor () {
|
142
|
+
const width = this.iframeWrapperTarget.getBoundingClientRect().width
|
143
|
+
const viewportWidth = window.innerWidth
|
144
|
+
return (width / viewportWidth).toFixed(1)
|
145
|
+
}
|
146
|
+
|
147
|
+
// Replace the body of the iframe with the new content
|
148
|
+
updatePreview (html) {
|
149
|
+
this.iframeTarget.contentWindow.document.body.innerHTML = html
|
150
|
+
this.resizeIframe()
|
151
|
+
}
|
152
|
+
|
153
|
+
// Dynamically resize the iFrame to fit its content
|
154
|
+
resizeIframe () {
|
155
|
+
const scaleFactor = this.scaleFactor()
|
156
|
+
const iframeContentHeight = this.iframeTarget.contentWindow.document.body.scrollHeight
|
157
|
+
const iframeHeight = iframeContentHeight * scaleFactor
|
158
|
+
this.iframeTarget.style.height = iframeContentHeight + 'px'
|
159
|
+
this.iframeTarget.style.opacity = 1
|
160
|
+
this.iframeWrapperTarget.style.height = iframeHeight + 'px'
|
161
|
+
}
|
162
|
+
|
163
|
+
getAuthenticityToken () {
|
164
|
+
const tokenTag = document.querySelector('meta[name="csrf-token"]')
|
165
|
+
return tokenTag.getAttribute('content')
|
166
|
+
}
|
167
|
+
}
|
@@ -0,0 +1,65 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static values = {
|
5
|
+
url: String
|
6
|
+
}
|
7
|
+
|
8
|
+
connect () {
|
9
|
+
this.button = this.element
|
10
|
+
this.button.addEventListener('click', (event) => {
|
11
|
+
event.preventDefault()
|
12
|
+
this.requestPreview()
|
13
|
+
})
|
14
|
+
}
|
15
|
+
|
16
|
+
requestPreview () {
|
17
|
+
const form = this.buildFakeForm()
|
18
|
+
|
19
|
+
// Insert in DOM
|
20
|
+
document.body.appendChild(form)
|
21
|
+
|
22
|
+
// Submit form
|
23
|
+
form.submit()
|
24
|
+
|
25
|
+
// Remove from DOM
|
26
|
+
document.body.removeChild(form)
|
27
|
+
}
|
28
|
+
|
29
|
+
buildFakeForm () {
|
30
|
+
const form = this.form().cloneNode(true)
|
31
|
+
|
32
|
+
// Empty [id] fields
|
33
|
+
const idInputs = form.querySelectorAll('input[name$="[id]"], select[name$="[id]"], textarea[name$="[id]"], button[name$="[id]"]')
|
34
|
+
idInputs.forEach((input) => {
|
35
|
+
input.value = ''
|
36
|
+
})
|
37
|
+
|
38
|
+
// Set preview action
|
39
|
+
form.setAttribute('action', this.urlValue)
|
40
|
+
|
41
|
+
// Set target to blank
|
42
|
+
form.setAttribute('target', '_blank')
|
43
|
+
|
44
|
+
// Refresh authenticity token
|
45
|
+
const authenticityTokenInput = form.querySelector('input[name="authenticity_token"]')
|
46
|
+
authenticityTokenInput.value = this.getAuthenticityToken()
|
47
|
+
|
48
|
+
// Remove method input if present (to force POST)
|
49
|
+
form.querySelector('input[name="_method"]')?.remove()
|
50
|
+
|
51
|
+
// Ensure POST method
|
52
|
+
form.setAttribute('method', 'POST')
|
53
|
+
|
54
|
+
return form
|
55
|
+
}
|
56
|
+
|
57
|
+
getAuthenticityToken () {
|
58
|
+
const tokenTag = document.querySelector('meta[name="csrf-token"]')
|
59
|
+
return tokenTag.getAttribute('content')
|
60
|
+
}
|
61
|
+
|
62
|
+
form () {
|
63
|
+
return this.button.closest('form')
|
64
|
+
}
|
65
|
+
}
|
@@ -39,7 +39,7 @@ export default class extends Controller {
|
|
39
39
|
|
40
40
|
updatePopupButtonIndices (index) {
|
41
41
|
const popup = document.querySelector(`[data-popup-target="popup"][data-popup-id="repeater-buttons-${this.idValue}"]`)
|
42
|
-
const buttons = popup.querySelectorAll('
|
42
|
+
const buttons = popup.querySelectorAll('[data-popup-target="button"]')
|
43
43
|
buttons.forEach((button) => {
|
44
44
|
button.dataset.rowIndex = index
|
45
45
|
})
|
@@ -52,17 +52,17 @@ export default class extends Controller {
|
|
52
52
|
const rowIndex = button.dataset.rowIndex
|
53
53
|
|
54
54
|
// Prepare html from template
|
55
|
-
|
56
|
-
|
55
|
+
let template = this.getTemplate(templateName).content.cloneNode(true)
|
56
|
+
template = this.replaceIdsWithTimestamps(template)
|
57
57
|
|
58
58
|
// Fallback to last row if no index is set
|
59
59
|
if (rowIndex) {
|
60
60
|
// Insert new row after defined row
|
61
61
|
const row = this.rowTargets[rowIndex]
|
62
|
-
|
62
|
+
this.listTarget.insertBefore(template, row.nextSibling)
|
63
63
|
} else {
|
64
64
|
// Insert before footer
|
65
|
-
this.
|
65
|
+
this.listTarget.insertBefore(template, this.footerTarget)
|
66
66
|
}
|
67
67
|
|
68
68
|
this.resetIndices()
|
@@ -108,8 +108,40 @@ export default class extends Controller {
|
|
108
108
|
}
|
109
109
|
|
110
110
|
replaceIdsWithTimestamps (template) {
|
111
|
-
const
|
112
|
-
|
111
|
+
const pattern = 'rrrrrrrrr'
|
112
|
+
const replacement = new Date().getTime().toString()
|
113
|
+
|
114
|
+
// Replace ids
|
115
|
+
template.querySelectorAll(`input[id*="${pattern}"], select[id*="${pattern}"], textarea[id*="${pattern}"], button[id*="${pattern}"]`).forEach((node) => {
|
116
|
+
const idValue = node.getAttribute('id')
|
117
|
+
node.setAttribute('id', idValue.replace(pattern, replacement))
|
118
|
+
})
|
119
|
+
|
120
|
+
// Replace labels
|
121
|
+
template.querySelectorAll(`label[for*="${pattern}"]`).forEach((node) => {
|
122
|
+
const forValue = node.getAttribute('for')
|
123
|
+
node.setAttribute('for', forValue.replace(pattern, replacement))
|
124
|
+
})
|
125
|
+
|
126
|
+
// Replace names
|
127
|
+
template.querySelectorAll(`input[name*="${pattern}"], select[name*="${pattern}"], textarea[name*="${pattern}"], button[name*="${pattern}"]`).forEach((node) => {
|
128
|
+
const nameValue = node.getAttribute('name')
|
129
|
+
node.setAttribute('name', nameValue.replace(pattern, replacement))
|
130
|
+
})
|
131
|
+
|
132
|
+
// Replace offcanvas targets
|
133
|
+
template.querySelectorAll(`div[data-bs-target="#offcanvas-${pattern}"]`).forEach((node) => {
|
134
|
+
const targetValue = node.getAttribute('data-bs-target')
|
135
|
+
node.setAttribute('data-bs-target', targetValue.replace(pattern, replacement))
|
136
|
+
})
|
137
|
+
|
138
|
+
// Replace offcanvas ids
|
139
|
+
template.querySelectorAll(`.offcanvas[id="offcanvas-${pattern}"]`).forEach((node) => {
|
140
|
+
const idValue = node.getAttribute('id')
|
141
|
+
node.setAttribute('id', idValue.replace(pattern, replacement))
|
142
|
+
})
|
143
|
+
|
144
|
+
return template
|
113
145
|
}
|
114
146
|
|
115
147
|
visibleRowsCount () {
|
@@ -8,7 +8,9 @@ import FlatpickrController from './controllers/flatpickr_controller'
|
|
8
8
|
import InfiniteScrollerController from './controllers/infinite_scroller_controller'
|
9
9
|
import MediaController from './controllers/media_controller'
|
10
10
|
import MediaModalController from './controllers/media_modal_controller'
|
11
|
+
import NestedPreviewController from './controllers/nested_preview_controller'
|
11
12
|
import PopupController from './controllers/popup_controller'
|
13
|
+
import PreviewController from './controllers/preview_controller'
|
12
14
|
import RedactorxController from './controllers/redactorx_controller'
|
13
15
|
import RepeaterController from './controllers/repeater_controller'
|
14
16
|
import SelectController from './controllers/select_controller'
|
@@ -25,7 +27,9 @@ export class Formstrap {
|
|
25
27
|
Stimulus.register('infinite-scroller', InfiniteScrollerController)
|
26
28
|
Stimulus.register('media', MediaController)
|
27
29
|
Stimulus.register('media-modal', MediaModalController)
|
30
|
+
Stimulus.register('nested-preview', NestedPreviewController)
|
28
31
|
Stimulus.register('popup', PopupController)
|
32
|
+
Stimulus.register('preview', PreviewController)
|
29
33
|
Stimulus.register('redactorx', RedactorxController)
|
30
34
|
Stimulus.register('repeater', RepeaterController)
|
31
35
|
Stimulus.register('select', SelectController)
|
@@ -4,6 +4,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
5
5
|
var __getProtoOf = Object.getPrototypeOf;
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
7
8
|
var __commonJS = (cb, mod) => function __require() {
|
8
9
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
9
10
|
};
|
@@ -19,6 +20,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
19
20
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
20
21
|
mod
|
21
22
|
));
|
23
|
+
var __publicField = (obj, key, value) => {
|
24
|
+
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
25
|
+
return value;
|
26
|
+
};
|
22
27
|
|
23
28
|
// node_modules/tom-select/dist/js/tom-select.complete.js
|
24
29
|
var require_tom_select_complete = __commonJS({
|
@@ -11288,6 +11293,129 @@ var media_modal_controller_default = class extends Controller {
|
|
11288
11293
|
}
|
11289
11294
|
};
|
11290
11295
|
|
11296
|
+
// app/assets/javascripts/formstrap/controllers/nested_preview_controller.js
|
11297
|
+
var nested_preview_controller_default = class extends Controller {
|
11298
|
+
static get targets() {
|
11299
|
+
return ["fields", "iframeWrapper", "iframe", "offcanvas", "error", "loader"];
|
11300
|
+
}
|
11301
|
+
static get values() {
|
11302
|
+
return {
|
11303
|
+
url: String
|
11304
|
+
};
|
11305
|
+
}
|
11306
|
+
connect() {
|
11307
|
+
this.prepareIframe();
|
11308
|
+
this.iframeTarget.addEventListener("load", () => {
|
11309
|
+
this.hideLoader();
|
11310
|
+
this.resizeIframe();
|
11311
|
+
});
|
11312
|
+
this.offcanvasTarget.addEventListener("hide.bs.offcanvas", (event) => {
|
11313
|
+
if (!this.update()) {
|
11314
|
+
event.preventDefault();
|
11315
|
+
}
|
11316
|
+
});
|
11317
|
+
}
|
11318
|
+
showLoader() {
|
11319
|
+
this.loaderTarget.classList.remove("d-none");
|
11320
|
+
}
|
11321
|
+
hideLoader() {
|
11322
|
+
this.loaderTarget.classList.add("d-none");
|
11323
|
+
}
|
11324
|
+
showError() {
|
11325
|
+
this.errorTarget.classList.remove("d-none");
|
11326
|
+
}
|
11327
|
+
hideError() {
|
11328
|
+
this.errorTarget.classList.add("d-none");
|
11329
|
+
}
|
11330
|
+
update() {
|
11331
|
+
const isValid = this.validateFields();
|
11332
|
+
if (isValid) {
|
11333
|
+
this.requestPreview();
|
11334
|
+
this.hideError();
|
11335
|
+
return true;
|
11336
|
+
} else {
|
11337
|
+
this.showError();
|
11338
|
+
return false;
|
11339
|
+
}
|
11340
|
+
}
|
11341
|
+
requestPreview() {
|
11342
|
+
const xhr = new XMLHttpRequest();
|
11343
|
+
xhr.open("POST", this.urlValue, true);
|
11344
|
+
const formData = this.buildFormData();
|
11345
|
+
xhr.send(formData);
|
11346
|
+
this.showLoader();
|
11347
|
+
xhr.onreadystatechange = () => {
|
11348
|
+
if (xhr.readyState === XMLHttpRequest.DONE) {
|
11349
|
+
this.hideLoader();
|
11350
|
+
this.handleRequest(xhr);
|
11351
|
+
}
|
11352
|
+
};
|
11353
|
+
}
|
11354
|
+
handleRequest(request) {
|
11355
|
+
if (request.status === 200) {
|
11356
|
+
this.updatePreview(request.responseText);
|
11357
|
+
} else {
|
11358
|
+
this.showError();
|
11359
|
+
}
|
11360
|
+
}
|
11361
|
+
validateFields() {
|
11362
|
+
let allValid = true;
|
11363
|
+
const fields = this.fieldsTarget;
|
11364
|
+
const formElements = fields.querySelectorAll("input[name], select[name], textarea[name]");
|
11365
|
+
formElements.forEach(function(element) {
|
11366
|
+
const isValid = element.reportValidity();
|
11367
|
+
if (!isValid) {
|
11368
|
+
allValid = false;
|
11369
|
+
}
|
11370
|
+
});
|
11371
|
+
return allValid;
|
11372
|
+
}
|
11373
|
+
buildFormData() {
|
11374
|
+
const fields = this.fieldsTarget;
|
11375
|
+
const formData = new FormData();
|
11376
|
+
const regex = /\w+\[([^\]]+)s_attributes\]\[\d+\]/g;
|
11377
|
+
const formElements = fields.querySelectorAll('input[name]:not([name$="[id]"]), select[name]:not([name$="[id]"]), textarea[name]:not([name$="[id]"]), button[name]:not([name$="[id]"])');
|
11378
|
+
formElements.forEach(function(element) {
|
11379
|
+
const currentName = element.getAttribute("name");
|
11380
|
+
const newName = currentName.replace(regex, "$1");
|
11381
|
+
formData.append(newName, element.value);
|
11382
|
+
});
|
11383
|
+
formData.append("authenticity_token", this.getAuthenticityToken());
|
11384
|
+
return formData;
|
11385
|
+
}
|
11386
|
+
prepareIframe() {
|
11387
|
+
const scaleFactor = this.scaleFactor();
|
11388
|
+
const style = `
|
11389
|
+
transform: scale(${scaleFactor});
|
11390
|
+
opacity: 0;
|
11391
|
+
transform-origin: 0 0;
|
11392
|
+
width: ${100 / scaleFactor}%;
|
11393
|
+
`;
|
11394
|
+
this.iframeTarget.setAttribute("style", style);
|
11395
|
+
}
|
11396
|
+
scaleFactor() {
|
11397
|
+
const width = this.iframeWrapperTarget.getBoundingClientRect().width;
|
11398
|
+
const viewportWidth = window.innerWidth;
|
11399
|
+
return (width / viewportWidth).toFixed(1);
|
11400
|
+
}
|
11401
|
+
updatePreview(html) {
|
11402
|
+
this.iframeTarget.contentWindow.document.body.innerHTML = html;
|
11403
|
+
this.resizeIframe();
|
11404
|
+
}
|
11405
|
+
resizeIframe() {
|
11406
|
+
const scaleFactor = this.scaleFactor();
|
11407
|
+
const iframeContentHeight = this.iframeTarget.contentWindow.document.body.scrollHeight;
|
11408
|
+
const iframeHeight = iframeContentHeight * scaleFactor;
|
11409
|
+
this.iframeTarget.style.height = iframeContentHeight + "px";
|
11410
|
+
this.iframeTarget.style.opacity = 1;
|
11411
|
+
this.iframeWrapperTarget.style.height = iframeHeight + "px";
|
11412
|
+
}
|
11413
|
+
getAuthenticityToken() {
|
11414
|
+
const tokenTag = document.querySelector('meta[name="csrf-token"]');
|
11415
|
+
return tokenTag.getAttribute("content");
|
11416
|
+
}
|
11417
|
+
};
|
11418
|
+
|
11291
11419
|
// node_modules/@popperjs/core/lib/enums.js
|
11292
11420
|
var top = "top";
|
11293
11421
|
var bottom = "bottom";
|
@@ -12917,6 +13045,47 @@ var popup_controller_default = class extends Controller {
|
|
12917
13045
|
}
|
12918
13046
|
};
|
12919
13047
|
|
13048
|
+
// app/assets/javascripts/formstrap/controllers/preview_controller.js
|
13049
|
+
var preview_controller_default = class extends Controller {
|
13050
|
+
connect() {
|
13051
|
+
this.button = this.element;
|
13052
|
+
this.button.addEventListener("click", (event) => {
|
13053
|
+
event.preventDefault();
|
13054
|
+
this.requestPreview();
|
13055
|
+
});
|
13056
|
+
}
|
13057
|
+
requestPreview() {
|
13058
|
+
const form = this.buildFakeForm();
|
13059
|
+
document.body.appendChild(form);
|
13060
|
+
form.submit();
|
13061
|
+
document.body.removeChild(form);
|
13062
|
+
}
|
13063
|
+
buildFakeForm() {
|
13064
|
+
const form = this.form().cloneNode(true);
|
13065
|
+
const idInputs = form.querySelectorAll('input[name$="[id]"], select[name$="[id]"], textarea[name$="[id]"], button[name$="[id]"]');
|
13066
|
+
idInputs.forEach((input) => {
|
13067
|
+
input.value = "";
|
13068
|
+
});
|
13069
|
+
form.setAttribute("action", this.urlValue);
|
13070
|
+
form.setAttribute("target", "_blank");
|
13071
|
+
const authenticityTokenInput = form.querySelector('input[name="authenticity_token"]');
|
13072
|
+
authenticityTokenInput.value = this.getAuthenticityToken();
|
13073
|
+
form.querySelector('input[name="_method"]')?.remove();
|
13074
|
+
form.setAttribute("method", "POST");
|
13075
|
+
return form;
|
13076
|
+
}
|
13077
|
+
getAuthenticityToken() {
|
13078
|
+
const tokenTag = document.querySelector('meta[name="csrf-token"]');
|
13079
|
+
return tokenTag.getAttribute("content");
|
13080
|
+
}
|
13081
|
+
form() {
|
13082
|
+
return this.button.closest("form");
|
13083
|
+
}
|
13084
|
+
};
|
13085
|
+
__publicField(preview_controller_default, "values", {
|
13086
|
+
url: String
|
13087
|
+
});
|
13088
|
+
|
12920
13089
|
// app/assets/javascripts/formstrap/controllers/redactorx_controller.js
|
12921
13090
|
var redactorx_controller_default = class extends Controller {
|
12922
13091
|
connect() {
|
@@ -12980,7 +13149,7 @@ var repeater_controller_default = class extends Controller {
|
|
12980
13149
|
}
|
12981
13150
|
updatePopupButtonIndices(index2) {
|
12982
13151
|
const popup = document.querySelector(`[data-popup-target="popup"][data-popup-id="repeater-buttons-${this.idValue}"]`);
|
12983
|
-
const buttons = popup.querySelectorAll("
|
13152
|
+
const buttons = popup.querySelectorAll('[data-popup-target="button"]');
|
12984
13153
|
buttons.forEach((button) => {
|
12985
13154
|
button.dataset.rowIndex = index2;
|
12986
13155
|
});
|
@@ -12990,13 +13159,13 @@ var repeater_controller_default = class extends Controller {
|
|
12990
13159
|
const button = event.target;
|
12991
13160
|
const templateName = button.dataset.templateName;
|
12992
13161
|
const rowIndex = button.dataset.rowIndex;
|
12993
|
-
|
12994
|
-
|
13162
|
+
let template = this.getTemplate(templateName).content.cloneNode(true);
|
13163
|
+
template = this.replaceIdsWithTimestamps(template);
|
12995
13164
|
if (rowIndex) {
|
12996
13165
|
const row = this.rowTargets[rowIndex];
|
12997
|
-
|
13166
|
+
this.listTarget.insertBefore(template, row.nextSibling);
|
12998
13167
|
} else {
|
12999
|
-
this.
|
13168
|
+
this.listTarget.insertBefore(template, this.footerTarget);
|
13000
13169
|
}
|
13001
13170
|
this.resetIndices();
|
13002
13171
|
this.resetPositions();
|
@@ -13028,8 +13197,29 @@ var repeater_controller_default = class extends Controller {
|
|
13028
13197
|
})[0];
|
13029
13198
|
}
|
13030
13199
|
replaceIdsWithTimestamps(template) {
|
13031
|
-
const
|
13032
|
-
|
13200
|
+
const pattern = "rrrrrrrrr";
|
13201
|
+
const replacement = new Date().getTime().toString();
|
13202
|
+
template.querySelectorAll(`input[id*="${pattern}"], select[id*="${pattern}"], textarea[id*="${pattern}"], button[id*="${pattern}"]`).forEach((node) => {
|
13203
|
+
const idValue = node.getAttribute("id");
|
13204
|
+
node.setAttribute("id", idValue.replace(pattern, replacement));
|
13205
|
+
});
|
13206
|
+
template.querySelectorAll(`label[for*="${pattern}"]`).forEach((node) => {
|
13207
|
+
const forValue = node.getAttribute("for");
|
13208
|
+
node.setAttribute("for", forValue.replace(pattern, replacement));
|
13209
|
+
});
|
13210
|
+
template.querySelectorAll(`input[name*="${pattern}"], select[name*="${pattern}"], textarea[name*="${pattern}"], button[name*="${pattern}"]`).forEach((node) => {
|
13211
|
+
const nameValue = node.getAttribute("name");
|
13212
|
+
node.setAttribute("name", nameValue.replace(pattern, replacement));
|
13213
|
+
});
|
13214
|
+
template.querySelectorAll(`div[data-bs-target="#offcanvas-${pattern}"]`).forEach((node) => {
|
13215
|
+
const targetValue = node.getAttribute("data-bs-target");
|
13216
|
+
node.setAttribute("data-bs-target", targetValue.replace(pattern, replacement));
|
13217
|
+
});
|
13218
|
+
template.querySelectorAll(`.offcanvas[id="offcanvas-${pattern}"]`).forEach((node) => {
|
13219
|
+
const idValue = node.getAttribute("id");
|
13220
|
+
node.setAttribute("id", idValue.replace(pattern, replacement));
|
13221
|
+
});
|
13222
|
+
return template;
|
13033
13223
|
}
|
13034
13224
|
visibleRowsCount() {
|
13035
13225
|
return this.visibleRows().length;
|
@@ -13157,7 +13347,9 @@ var Formstrap = class {
|
|
13157
13347
|
Stimulus.register("infinite-scroller", infinite_scroller_controller_default);
|
13158
13348
|
Stimulus.register("media", media_controller_default);
|
13159
13349
|
Stimulus.register("media-modal", media_modal_controller_default);
|
13350
|
+
Stimulus.register("nested-preview", nested_preview_controller_default);
|
13160
13351
|
Stimulus.register("popup", popup_controller_default);
|
13352
|
+
Stimulus.register("preview", preview_controller_default);
|
13161
13353
|
Stimulus.register("redactorx", redactorx_controller_default);
|
13162
13354
|
Stimulus.register("repeater", repeater_controller_default);
|
13163
13355
|
Stimulus.register("select", select_controller_default);
|
@@ -0,0 +1,31 @@
|
|
1
|
+
.nested-preview-offcanvas, .nested-preview .hiding {
|
2
|
+
display: none !important;
|
3
|
+
|
4
|
+
&.show, &.showing {
|
5
|
+
display: flex !important;
|
6
|
+
}
|
7
|
+
}
|
8
|
+
|
9
|
+
.nested-preview-iframe-wrapper {
|
10
|
+
position: relative;
|
11
|
+
cursor: pointer;
|
12
|
+
|
13
|
+
iframe {
|
14
|
+
width: 100%;
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
.nested-preview-loader {
|
19
|
+
position: absolute;
|
20
|
+
width: 100%;
|
21
|
+
height: 100%;
|
22
|
+
z-index: 2;
|
23
|
+
top: 0;
|
24
|
+
left: 0;
|
25
|
+
padding: 40px;
|
26
|
+
display: flex;
|
27
|
+
align-items: center;
|
28
|
+
justify-content: center;
|
29
|
+
background: var(--bs-secondary-bg);
|
30
|
+
opacity: 0.5;
|
31
|
+
}
|
@@ -1400,6 +1400,37 @@ mark {
|
|
1400
1400
|
.formstrap-popup.closed {
|
1401
1401
|
display: none;
|
1402
1402
|
}
|
1403
|
+
.nested-preview-offcanvas,
|
1404
|
+
.nested-preview .hiding {
|
1405
|
+
display: none !important;
|
1406
|
+
}
|
1407
|
+
.nested-preview-offcanvas.show,
|
1408
|
+
.nested-preview-offcanvas.showing,
|
1409
|
+
.nested-preview .hiding.show,
|
1410
|
+
.nested-preview .hiding.showing {
|
1411
|
+
display: flex !important;
|
1412
|
+
}
|
1413
|
+
.nested-preview-iframe-wrapper {
|
1414
|
+
position: relative;
|
1415
|
+
cursor: pointer;
|
1416
|
+
}
|
1417
|
+
.nested-preview-iframe-wrapper iframe {
|
1418
|
+
width: 100%;
|
1419
|
+
}
|
1420
|
+
.nested-preview-loader {
|
1421
|
+
position: absolute;
|
1422
|
+
width: 100%;
|
1423
|
+
height: 100%;
|
1424
|
+
z-index: 2;
|
1425
|
+
top: 0;
|
1426
|
+
left: 0;
|
1427
|
+
padding: 40px;
|
1428
|
+
display: flex;
|
1429
|
+
align-items: center;
|
1430
|
+
justify-content: center;
|
1431
|
+
background: var(--bs-secondary-bg);
|
1432
|
+
opacity: 0.5;
|
1433
|
+
}
|
1403
1434
|
.formstrap-media-modal .formstrap-thumbnail {
|
1404
1435
|
cursor: pointer;
|
1405
1436
|
}
|
File without changes
|