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
         
     |