formstrap 0.2.1 → 0.3.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/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
|