From 65ce3d482215151c544c46f08558d162f64a3487 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Mon, 22 Sep 2025 17:52:24 +0300 Subject: [PATCH 01/23] add use direct file links --- app/controllers/account_configs_controller.rb | 1 + app/views/accounts/show.html.erb | 16 ++++++++++++++++ config/locales/i18n.yml | 6 ++++++ 3 files changed, 23 insertions(+) diff --git a/app/controllers/account_configs_controller.rb b/app/controllers/account_configs_controller.rb index 66683e3f..cf128ef9 100644 --- a/app/controllers/account_configs_controller.rb +++ b/app/controllers/account_configs_controller.rb @@ -19,6 +19,7 @@ class AccountConfigsController < ApplicationController AccountConfig::FORCE_SSO_AUTH_KEY, AccountConfig::FLATTEN_RESULT_PDF_KEY, AccountConfig::ENFORCE_SIGNING_ORDER_KEY, + AccountConfig::WITH_FILE_LINKS_KEY, AccountConfig::WITH_SIGNATURE_ID, AccountConfig::COMBINE_PDF_RESULT_KEY, AccountConfig::REQUIRE_SIGNING_REASON_KEY, diff --git a/app/views/accounts/show.html.erb b/app/views/accounts/show.html.erb index 4152d1d0..5ca2d649 100644 --- a/app/views/accounts/show.html.erb +++ b/app/views/accounts/show.html.erb @@ -235,6 +235,22 @@ <% end %> <% end %> <% end %> + <% if can?(:manage, :personalization_advanced) %> + <% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::WITH_FILE_LINKS_KEY) %> + <% if can?(:manage, account_config) %> + <%= form_for account_config, url: account_configs_path, method: :post do |f| %> + <%= f.hidden_field :key %> +
+
+ <%= t('use_direct_file_attachment_links_in_the_documents') %> +
+ + <%= f.check_box :value, class: 'toggle', checked: account_config.value %> + +
+ <% end %> + <% end %> + <% end %> <%= render 'extra_preferences' %> <% if !Docuseal.multitenant? && SearchEntry.table_exists? && (!Docuseal.fulltext_search? || params[:reindex] == 'true') && can?(:manage, EncryptedConfig) %>
diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index eaf10d7f..84ddf10c 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -27,6 +27,7 @@ en: &en enabled: Enabled disabled: Disabled party: Party + use_direct_file_attachment_links_in_the_documents: Use direct file attachment links in the documents click_here_to_send_a_reset_password_email_html: ' to send a reset password email.' edit_order: Edit Order expirable_file_download_links: Expirable file download links @@ -932,6 +933,7 @@ en: &en range_without_total: "%{from}-%{to} events" es: &es + use_direct_file_attachment_links_in_the_documents: Usar enlaces directos de archivos adjuntos en los documentos enabled: Habilitado disabled: Deshabilitado expirable_file_download_links: Enlaces de descarga de archivos con vencimiento @@ -1843,6 +1845,7 @@ es: &es range_without_total: "%{from}-%{to} eventos" it: &it + use_direct_file_attachment_links_in_the_documents: Usa i link diretti per gli allegati nei documenti click_here_to_send_a_reset_password_email_html: ' per inviare una email per reimpostare la password.' enabled: Abilitato disabled: Disabilitato @@ -2755,6 +2758,7 @@ it: &it range_without_total: "%{from}-%{to} eventi" fr: &fr + use_direct_file_attachment_links_in_the_documents: Utiliser des liens directs pour les pièces jointes dans les documents click_here_to_send_a_reset_password_email_html: ' pour envoyer un e-mail de réinitialisation du mot de passe.' enabled: Activé disabled: Désactivé @@ -3670,6 +3674,7 @@ fr: &fr range_without_total: "%{from} à %{to} événements" pt: &pt + use_direct_file_attachment_links_in_the_documents: Usar links diretos de anexos de arquivos nos documentos click_here_to_send_a_reset_password_email_html: ' para enviar um e-mail de redefinição de senha.' enabled: Ativado disabled: Desativado @@ -4583,6 +4588,7 @@ pt: &pt range_without_total: "%{from}-%{to} eventos" de: &de + use_direct_file_attachment_links_in_the_documents: Verwenden Sie direkte Dateianhang-Links in den Dokumenten click_here_to_send_a_reset_password_email_html: ', um eine E-Mail zum Zurücksetzen des Passworts zu senden.' enabled: Aktiviert disabled: Deaktiviert From df8c44d2ce26e71c8a8fb03a55046ca6ea231576 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 23 Sep 2025 09:03:43 +0300 Subject: [PATCH 02/23] quantity formula --- app/javascript/template_builder/i18n.js | 6 ++++++ .../template_builder/payment_settings.vue | 19 +++++++++---------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/app/javascript/template_builder/i18n.js b/app/javascript/template_builder/i18n.js index 83807881..1fcbebc1 100644 --- a/app/javascript/template_builder/i18n.js +++ b/app/javascript/template_builder/i18n.js @@ -1,4 +1,5 @@ const en = { + quantity: 'Quantity', prefillable: 'Prefillable', signature_id: 'Signature ID', error_message: 'Error message', @@ -176,6 +177,7 @@ const en = { } const es = { + quantity: 'Cantidad', prefillable: 'Rellenable', signature_id: 'ID de Firma', error_message: 'Mensaje de error', @@ -353,6 +355,7 @@ const es = { } const it = { + quantity: 'Quantità', prefillable: 'Precompilabile', signature_id: 'ID firma', error_message: 'Messaggio di errore', @@ -530,6 +533,7 @@ const it = { } const pt = { + quantity: 'Quantidade', prefillable: 'Pré-preenchível', signature_id: 'ID da Assinatura', error_message: 'Mensagem de erro', @@ -707,6 +711,7 @@ const pt = { } const fr = { + quantity: 'Quantité', prefillable: 'Pré-remplissable', signature_id: 'ID de signature', error_message: 'Message d\'erreur', @@ -884,6 +889,7 @@ const fr = { } const de = { + quantity: 'Menge', prefillable: 'Vorausfüllbar', signature_id: 'Signatur-ID', error_message: 'Fehlermeldung', diff --git a/app/javascript/template_builder/payment_settings.vue b/app/javascript/template_builder/payment_settings.vue index d54ea09f..7bbb10e3 100644 --- a/app/javascript/template_builder/payment_settings.vue +++ b/app/javascript/template_builder/payment_settings.vue @@ -52,17 +52,17 @@ @click.stop > @@ -75,7 +75,7 @@ @blur="save" >
  • From f4426a8ee0b80536a80acacdcb89f3b5d8211422 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 23 Sep 2025 10:02:10 +0300 Subject: [PATCH 03/23] adjust payment title --- app/javascript/submission_form/payment_step.vue | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/javascript/submission_form/payment_step.vue b/app/javascript/submission_form/payment_step.vue index b345b246..6bbeb146 100644 --- a/app/javascript/submission_form/payment_step.vue +++ b/app/javascript/submission_form/payment_step.vue @@ -116,6 +116,10 @@ export default { return this.queryParams.get('stripe_session_id') }, defaultName () { + if (this.field.preferences?.price_id) { + return '' + } + const { price, currency } = this.field.preferences || {} const formatter = new Intl.NumberFormat([], { From b64a84a362cd58288dd8c5bd2fee1ac76d7b9c90 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 24 Sep 2025 12:07:54 +0300 Subject: [PATCH 04/23] add csp --- app/controllers/application_controller.rb | 22 ++ app/controllers/csp_controller.rb | 15 + app/javascript/application.js | 16 + app/javascript/application.scss | 4 +- app/javascript/elements/app_tour_start.js | 7 + app/javascript/elements/custom_validation.js | 14 + app/javascript/elements/remove_on_event.js | 15 + app/javascript/elements/review_form.js | 19 + app/javascript/elements/scroll_to.js | 10 + app/javascript/elements/search_input.js | 14 + app/javascript/elements/set_value.js | 11 + app/javascript/elements/show_on_value.js | 17 + app/javascript/elements/submit_form.js | 20 +- app/javascript/elements/toggle_classes.js | 11 + app/javascript/elements/toggle_visible.js | 12 +- app/javascript/form.js | 2 + app/javascript/form.scss | 4 +- app/javascript/submission_form/form.vue | 4 +- app/views/devise/shared/_links.html.erb | 4 +- app/views/esign_settings/show.html.erb | 14 +- .../notifications_settings/index.html.erb | 4 +- .../_form_toggle_options.html.erb | 4 +- app/views/scripts/_autosize_field.html.erb | 2 +- app/views/scripts/_server_selector.html.erb | 2 +- app/views/shared/_flash.html.erb | 2 +- app/views/shared/_navbar.html.erb | 4 +- app/views/shared/_search_input.html.erb | 18 +- app/views/shared/_settings_nav.html.erb | 4 +- app/views/shared/_test_mode_toggle.html.erb | 4 +- app/views/start_form/show.html.erb | 4 +- app/views/submissions/_detailed_form.html.erb | 24 +- app/views/submissions/_phone_form.html.erb | 12 +- app/views/submissions/_send_email.html.erb | 10 +- app/views/submissions/show.html.erb | 28 +- app/views/submitters/edit.html.erb | 8 +- app/views/templates/_upload_button.html.erb | 15 +- app/views/templates/new.html.erb | 16 +- app/views/templates_code_modal/show.html.erb | 4 +- app/views/templates_dashboard/index.html.erb | 10 +- .../templates_form_preview/show.html.erb | 14 +- .../_recipients.html.erb | 8 +- app/views/templates_preferences/show.html.erb | 46 ++- app/views/templates_share_link/show.html.erb | 8 +- .../webhook_events/_drawer_events.html.erb | 4 +- app/views/webhook_events/_event_row.html.erb | 4 +- app/views/webhook_settings/show.html.erb | 4 +- config/application.rb | 3 + config/routes.rb | 2 + package.json | 2 +- spec/system/template_spec.rb | 4 +- yarn.lock | 359 ++++++++++++++++-- 51 files changed, 723 insertions(+), 145 deletions(-) create mode 100644 app/controllers/csp_controller.rb create mode 100644 app/javascript/elements/app_tour_start.js create mode 100644 app/javascript/elements/custom_validation.js create mode 100644 app/javascript/elements/remove_on_event.js create mode 100644 app/javascript/elements/review_form.js create mode 100644 app/javascript/elements/scroll_to.js create mode 100644 app/javascript/elements/set_value.js create mode 100644 app/javascript/elements/show_on_value.js create mode 100644 app/javascript/elements/toggle_classes.js diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7500acdb..843ef605 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -13,6 +13,8 @@ class ApplicationController < ActionController::Base before_action :maybe_redirect_to_setup, unless: :signed_in? before_action :authenticate_user!, unless: :devise_controller? + before_action :set_csp, if: -> { request.get? && !turbo_frame_request? && !request.headers['HTTP_VND.PREFETCH'] } + helper_method :button_title, :current_account, :form_link_host, @@ -123,4 +125,24 @@ class ApplicationController < ActionController::Base redirect_to request.url.gsub('.co/', '.com/'), allow_other_host: true, status: :moved_permanently end + + def set_csp + request.content_security_policy_report_only = Rails.env.production? + + request.content_security_policy = current_content_security_policy.tap do |policy| + policy.default_src :self + policy.script_src :self + policy.style_src :self, :unsafe_inline + policy.img_src :self, :https, :http, :blob, :data + policy.font_src :self, :https, :http, :blob, :data + policy.manifest_src :self + policy.media_src :self + policy.frame_src :self + policy.worker_src :self, :blob + policy.connect_src :self + policy.report_uri '/csp' + + policy.directives['connect-src'] << 'ws:' if Rails.env.development? + end + end end diff --git a/app/controllers/csp_controller.rb b/app/controllers/csp_controller.rb new file mode 100644 index 00000000..587ee306 --- /dev/null +++ b/app/controllers/csp_controller.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class CspController < ActionController::API + FILTER_REPORT_REGEXP = /extension|sandbox/i + + SANITIZE_REGEXP = %r{(/[sdep]/)(\w{5})[^/"]+} + + def create + data = request.raw_post.gsub(SANITIZE_REGEXP, '\1\2') + + Rails.logger.warn(data) if Rails.env.development? + + Rollbar.warning('CSP', data:) if defined?(Rollbar) && !data.match?(FILTER_REPORT_REGEXP) + end +end diff --git a/app/javascript/application.js b/app/javascript/application.js index 7b51186d..b9467861 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -34,11 +34,19 @@ import MaskedInput from './elements/masked_input' import SetDateButton from './elements/set_date_button' import IndeterminateCheckbox from './elements/indeterminate_checkbox' import AppTour from './elements/app_tour' +import AppTourStart from './elements/app_tour_start' import DashboardDropzone from './elements/dashboard_dropzone' import RequiredCheckboxGroup from './elements/required_checkbox_group' import PageContainer from './elements/page_container' import EmailEditor from './elements/email_editor' import MountOnClick from './elements/mount_on_click' +import RemoveOnEvent from './elements/remove_on_event' +import ScrollTo from './elements/scroll_to' +import SetValue from './elements/set_value' +import ReviewForm from './elements/review_form' +import ShowOnValue from './elements/show_on_value' +import CustomValidation from './elements/custom_validation' +import ToggleClasses from './elements/toggle_classes' import * as TurboInstantClick from './lib/turbo_instant_click' @@ -107,12 +115,20 @@ safeRegisterElement('masked-input', MaskedInput) safeRegisterElement('set-date-button', SetDateButton) safeRegisterElement('indeterminate-checkbox', IndeterminateCheckbox) safeRegisterElement('app-tour', AppTour) +safeRegisterElement('app-tour-start', AppTourStart) safeRegisterElement('dashboard-dropzone', DashboardDropzone) safeRegisterElement('check-on-click', CheckOnClick) safeRegisterElement('required-checkbox-group', RequiredCheckboxGroup) safeRegisterElement('page-container', PageContainer) safeRegisterElement('email-editor', EmailEditor) safeRegisterElement('mount-on-click', MountOnClick) +safeRegisterElement('remove-on-event', RemoveOnEvent) +safeRegisterElement('scroll-to', ScrollTo) +safeRegisterElement('set-value', SetValue) +safeRegisterElement('review-form', ReviewForm) +safeRegisterElement('show-on-value', ShowOnValue) +safeRegisterElement('custom-validation', CustomValidation) +safeRegisterElement('toggle-classes', ToggleClasses) safeRegisterElement('template-builder', class extends HTMLElement { connectedCallback () { diff --git a/app/javascript/application.scss b/app/javascript/application.scss index 1ab92498..47eeb4f6 100644 --- a/app/javascript/application.scss +++ b/app/javascript/application.scss @@ -19,7 +19,7 @@ button .disabled { display: none; } -button[disabled] .disabled { +button[disabled] .disabled, button.btn-disabled .disabled { display: initial; } @@ -27,7 +27,7 @@ button .enabled { display: initial; } -button[disabled] .enabled { +button[disabled] .enabled, button.btn-disabled .enabled { display: none; } diff --git a/app/javascript/elements/app_tour_start.js b/app/javascript/elements/app_tour_start.js new file mode 100644 index 00000000..1b06becb --- /dev/null +++ b/app/javascript/elements/app_tour_start.js @@ -0,0 +1,7 @@ +export default class extends HTMLElement { + connectedCallback () { + this.querySelector('form').addEventListener('submit', () => { + window.app_tour.start() + }) + } +} diff --git a/app/javascript/elements/custom_validation.js b/app/javascript/elements/custom_validation.js new file mode 100644 index 00000000..ac738679 --- /dev/null +++ b/app/javascript/elements/custom_validation.js @@ -0,0 +1,14 @@ +export default class extends HTMLElement { + connectedCallback () { + const input = this.querySelector('input') + const invalidMessage = this.dataset.invalidMessage || '' + + input.addEventListener('invalid', () => { + input.setCustomValidity(input.value ? invalidMessage : '') + }) + + input.addEventListener('input', () => { + input.setCustomValidity('') + }) + } +} diff --git a/app/javascript/elements/remove_on_event.js b/app/javascript/elements/remove_on_event.js new file mode 100644 index 00000000..e912e4cd --- /dev/null +++ b/app/javascript/elements/remove_on_event.js @@ -0,0 +1,15 @@ +export default class extends HTMLElement { + connectedCallback () { + const eventType = this.dataset.on || 'click' + const selector = document.getElementById(this.dataset.selectorId) || this + const eventElement = eventType === 'submit' ? this.querySelector('form') : this + + eventElement.addEventListener(eventType, (event) => { + if (eventType === 'click') { + event.preventDefault() + } + + selector.remove() + }) + } +} diff --git a/app/javascript/elements/review_form.js b/app/javascript/elements/review_form.js new file mode 100644 index 00000000..537fe602 --- /dev/null +++ b/app/javascript/elements/review_form.js @@ -0,0 +1,19 @@ +export default class extends HTMLElement { + connectedCallback () { + this.querySelectorAll('input[type="radio"]').forEach(radio => { + radio.addEventListener('change', (event) => { + const rating = parseInt(event.target.value) + + if (rating === 10) { + window.review_comment.value = '' + window.review_comment.classList.add('hidden') + window.review_submit.classList.add('hidden') + event.target.form.submit() + } else { + window.review_comment.classList.remove('hidden') + window.review_submit.classList.remove('hidden') + } + }) + }) + } +} diff --git a/app/javascript/elements/scroll_to.js b/app/javascript/elements/scroll_to.js new file mode 100644 index 00000000..ded724b5 --- /dev/null +++ b/app/javascript/elements/scroll_to.js @@ -0,0 +1,10 @@ +export default class extends HTMLElement { + connectedCallback () { + this.selector = document.getElementById(this.dataset.selectorId) + + this.addEventListener('click', () => { + this.selector.scrollIntoView({ behavior: 'smooth', block: 'start' }) + history.replaceState(null, null, `#${this.dataset.selectorId}`) + }) + } +} diff --git a/app/javascript/elements/search_input.js b/app/javascript/elements/search_input.js index 9b536ab3..502e2b3a 100644 --- a/app/javascript/elements/search_input.js +++ b/app/javascript/elements/search_input.js @@ -13,6 +13,16 @@ export default class extends HTMLElement { this.input.classList.remove('w-60') } }) + + this.button.addEventListener('click', (event) => { + event.preventDefault() + + if (this.input.value || document.activeElement === this.input) { + return + } + + this.input.focus() + }) } get input () { @@ -22,4 +32,8 @@ export default class extends HTMLElement { get title () { return document.querySelector(this.dataset.title) } + + get button () { + return this.querySelector('button') + } } diff --git a/app/javascript/elements/set_value.js b/app/javascript/elements/set_value.js new file mode 100644 index 00000000..49e05c9c --- /dev/null +++ b/app/javascript/elements/set_value.js @@ -0,0 +1,11 @@ +export default class extends HTMLElement { + connectedCallback () { + const input = this.dataset.inputId ? document.getElementById(this.dataset.inputId) : this.querySelector('input') + + this.firstElementChild.addEventListener(this.dataset.on || 'click', () => { + if (this.dataset.emptyOnly !== 'true' || !input.value) { + input.value = this.dataset.value + } + }) + } +} diff --git a/app/javascript/elements/show_on_value.js b/app/javascript/elements/show_on_value.js new file mode 100644 index 00000000..c969d823 --- /dev/null +++ b/app/javascript/elements/show_on_value.js @@ -0,0 +1,17 @@ +export default class extends HTMLElement { + connectedCallback () { + this.addEventListener('change', (event) => { + const targetValue = this.dataset.value + const selectorId = this.dataset.selectorId + const targetElement = document.getElementById(selectorId) + + if (event.target.value === targetValue) { + targetElement.classList.remove('hidden') + } else { + targetElement.classList.add('hidden') + targetElement.value = '' + event.target.form.requestSubmit() + } + }) + } +} diff --git a/app/javascript/elements/submit_form.js b/app/javascript/elements/submit_form.js index 164a069e..e936c0bd 100644 --- a/app/javascript/elements/submit_form.js +++ b/app/javascript/elements/submit_form.js @@ -1,15 +1,27 @@ export default class extends HTMLElement { connectedCallback () { + const form = this.querySelector('form') || (this.querySelector('input, button, select') || this.lastElementChild).form + if (this.dataset.interval) { this.interval = setInterval(() => { - this.querySelector('form').requestSubmit() + form.requestSubmit() }, parseInt(this.dataset.interval)) } else if (this.dataset.on) { - this.lastElementChild.addEventListener(this.dataset.on, () => { - this.lastElementChild.form.requestSubmit() + this.lastElementChild.addEventListener(this.dataset.on, (event) => { + if (this.dataset.disable === 'true') { + form.querySelector('[type="submit"]')?.setAttribute('disabled', true) + } + + if (this.dataset.submitIfValue === 'true') { + if (event.target.value) { + form.requestSubmit() + } + } else { + form.requestSubmit() + } }) } else { - this.querySelector('form').requestSubmit() + form.requestSubmit() } } diff --git a/app/javascript/elements/toggle_classes.js b/app/javascript/elements/toggle_classes.js new file mode 100644 index 00000000..ab96f293 --- /dev/null +++ b/app/javascript/elements/toggle_classes.js @@ -0,0 +1,11 @@ +export default class extends HTMLElement { + connectedCallback () { + const button = this.querySelector('a, button') + + button.addEventListener('click', () => { + this.dataset.classes.split(' ').forEach((cls) => { + button.classList.toggle(cls) + }) + }) + } +} diff --git a/app/javascript/elements/toggle_visible.js b/app/javascript/elements/toggle_visible.js index a796b9c6..7fa968f2 100644 --- a/app/javascript/elements/toggle_visible.js +++ b/app/javascript/elements/toggle_visible.js @@ -4,9 +4,15 @@ export default actionable(class extends HTMLElement { trigger (event) { const elementIds = JSON.parse(this.dataset.elementIds) - elementIds.forEach((elementId) => { - document.getElementById(elementId).classList.toggle('hidden', (event.target.dataset.toggleId || event.target.value) !== elementId) - }) + if (event.target.type === 'checkbox') { + elementIds.forEach((elementId) => { + document.getElementById(elementId)?.classList.toggle('hidden') + }) + } else { + elementIds.forEach((elementId) => { + document.getElementById(elementId).classList.toggle('hidden', (event.target.dataset.toggleId || event.target.value) !== elementId) + }) + } if (this.dataset.focusId) { document.getElementById(this.dataset.focusId)?.focus() diff --git a/app/javascript/form.js b/app/javascript/form.js index ac49174d..b8253f86 100644 --- a/app/javascript/form.js +++ b/app/javascript/form.js @@ -6,6 +6,7 @@ import ToggleSubmit from './elements/toggle_submit' import FetchForm from './elements/fetch_form' import ScrollButtons from './elements/scroll_buttons' import PageContainer from './elements/page_container' +import SubmitForm from './elements/submit_form' const safeRegisterElement = (name, element, options = {}) => !window.customElements.get(name) && window.customElements.define(name, element, options) @@ -14,6 +15,7 @@ safeRegisterElement('toggle-submit', ToggleSubmit) safeRegisterElement('fetch-form', FetchForm) safeRegisterElement('scroll-buttons', ScrollButtons) safeRegisterElement('page-container', PageContainer) +safeRegisterElement('submit-form', SubmitForm) safeRegisterElement('submission-form', class extends HTMLElement { connectedCallback () { this.appElem = document.createElement('div') diff --git a/app/javascript/form.scss b/app/javascript/form.scss index f0d26baf..92fc6f6a 100644 --- a/app/javascript/form.scss +++ b/app/javascript/form.scss @@ -19,7 +19,7 @@ button .disabled { display: none; } -button[disabled] .disabled { +button[disabled] .disabled, button.btn-disabled .disabled { display: initial; } @@ -27,7 +27,7 @@ button .enabled { display: initial; } -button[disabled] .enabled { +button[disabled] .enabled, button.btn-disabled .enabled { display: none; } diff --git a/app/javascript/submission_form/form.vue b/app/javascript/submission_form/form.vue index d58c04d8..761ad0f9 100644 --- a/app/javascript/submission_form/form.vue +++ b/app/javascript/submission_form/form.vue @@ -349,10 +349,10 @@ :id="field.uuid" type="checkbox" class="base-checkbox !h-7 !w-7" - :oninvalid="`this.setCustomValidity('${t('please_check_the_box_to_continue')}')`" - :onchange="`this.setCustomValidity(validity.valueMissing ? '${t('please_check_the_box_to_continue')}' : '');`" :required="field.required" :checked="!!values[field.uuid]" + @invalid="$event.target.setCustomValidity(t('please_check_the_box_to_continue'))" + @change="$event.target.setCustomValidity($event.target.validity.valueMissing ? t('please_check_the_box_to_continue') : '')" @click="[scrollIntoField(field), values[field.uuid] = !values[field.uuid]]" > <%= hidden_field_tag :redir, params[:redir] %> <% end %> - <%= select_tag :lang, options_for_select((I18n.available_locales - %i[en pt-PT de-DE fr-FR it-IT es-ES]).map { |code| [t("language_#{code}"), code] }, I18n.locale), onchange: 'this.form.requestSubmit();', class: 'select select-sm border-base-content/30 text-base' %> + + <%= select_tag :lang, options_for_select((I18n.available_locales - %i[en pt-PT de-DE fr-FR it-IT es-ES]).map { |code| [t("language_#{code}"), code] }, I18n.locale), class: 'select select-sm border-base-content/30 text-base' %> + <% end %> diff --git a/app/views/esign_settings/show.html.erb b/app/views/esign_settings/show.html.erb index 6961c016..f22700b8 100644 --- a/app/views/esign_settings/show.html.erb +++ b/app/views/esign_settings/show.html.erb @@ -148,7 +148,9 @@ <%= t('apply_multiple_pdf_digital_signatures_in_the_document_per_each_signer') %> - <%= f.check_box :value, { class: 'toggle', checked: account_config.value == 'multiple', onchange: 'this.form.requestSubmit()' }, 'multiple', 'single' %> + + <%= f.check_box :value, { class: 'toggle', checked: account_config.value == 'multiple' }, 'multiple', 'single' %> + <% end %> <% end %> @@ -160,7 +162,9 @@ <%= t('remove_pdf_form_fillable_fields_from_the_signed_pdf_flatten_form') %> - <%= f.check_box :value, { class: 'toggle', checked: account_config.value != false, onchange: 'this.form.requestSubmit()' } %> + + <%= f.check_box :value, { class: 'toggle', checked: account_config.value != false } %> + <% end %> <% end %> @@ -172,9 +176,9 @@ <%= t('document_download_filename_format') %> -
    - <%= f.select :value, [["#{I18n.t('document_name')}.pdf", '{document.name}'], ["#{I18n.t('document_name')} - #{I18n.t(:signed)}.pdf", '{document.name} - {submission.status}'], ["#{I18n.t('document_name')} - name@domain.com.pdf", '{document.name} - {submission.submitters}'], ["#{I18n.t('document_name')} - name@domain.com - #{I18n.l(Time.current.beginning_of_year.in_time_zone(current_account.timezone), format: :short)}.pdf", '{document.name} - {submission.submitters} - {submission.completed_at}']], {}, class: 'base-select', onchange: 'this.form.requestSubmit()' %> -
    + + <%= f.select :value, [["#{I18n.t('document_name')}.pdf", '{document.name}'], ["#{I18n.t('document_name')} - #{I18n.t(:signed)}.pdf", '{document.name} - {submission.status}'], ["#{I18n.t('document_name')} - name@domain.com.pdf", '{document.name} - {submission.submitters}'], ["#{I18n.t('document_name')} - name@domain.com - #{I18n.l(Time.current.beginning_of_year.in_time_zone(current_account.timezone), format: :short)}.pdf", '{document.name} - {submission.submitters} - {submission.completed_at}']], {}, class: 'base-select' %> + <% end %> <% end %> diff --git a/app/views/notifications_settings/index.html.erb b/app/views/notifications_settings/index.html.erb index b3c0c253..b163e8e8 100644 --- a/app/views/notifications_settings/index.html.erb +++ b/app/views/notifications_settings/index.html.erb @@ -13,7 +13,9 @@ <%= t('receive_notification_emails_on_completed_submission') %> - <%= f.check_box :value, class: 'toggle', checked: user_config.value != false, onchange: 'this.form.requestSubmit()' %> + + <%= f.check_box :value, class: 'toggle', checked: user_config.value != false %> + <% end %> <% end %> diff --git a/app/views/personalization_settings/_form_toggle_options.html.erb b/app/views/personalization_settings/_form_toggle_options.html.erb index 78c45067..f57dbee8 100644 --- a/app/views/personalization_settings/_form_toggle_options.html.erb +++ b/app/views/personalization_settings/_form_toggle_options.html.erb @@ -7,7 +7,9 @@ <%= t('show_confetti_on_successful_completion') %> - <%= f.check_box :value, { class: 'toggle', checked: account_config.value != false, onchange: 'this.form.requestSubmit()' }, '1', '0' %> + + <%= f.check_box :value, { class: 'toggle', checked: account_config.value != false }, '1', '0' %> + <% end %> diff --git a/app/views/scripts/_autosize_field.html.erb b/app/views/scripts/_autosize_field.html.erb index 096a7f1c..0d34e314 100644 --- a/app/views/scripts/_autosize_field.html.erb +++ b/app/views/scripts/_autosize_field.html.erb @@ -1,4 +1,4 @@ - diff --git a/app/views/shared/_body_scripts.html.erb b/app/views/shared/_body_scripts.html.erb new file mode 100644 index 00000000..e69de29b diff --git a/app/views/submissions/show.html.erb b/app/views/submissions/show.html.erb index 0c1d897f..dbd75575 100644 --- a/app/views/submissions/show.html.erb +++ b/app/views/submissions/show.html.erb @@ -302,4 +302,6 @@ -<%= render 'scripts/autosize_field' %> +<% unless request.headers['HTTP_X_TURBO'] %> + <%= render 'scripts/autosize_field' %> +<% end %> From a9d249ccf0e0c5b11ea83cb493aaaff132562cb6 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 24 Sep 2025 19:51:37 +0300 Subject: [PATCH 06/23] fix variables schema --- lib/submissions/create_from_submitters.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/submissions/create_from_submitters.rb b/lib/submissions/create_from_submitters.rb index 29aa37e8..0ae3b09b 100644 --- a/lib/submissions/create_from_submitters.rb +++ b/lib/submissions/create_from_submitters.rb @@ -145,7 +145,7 @@ module Submissions submitters_attrs.any? { |e| e[:completed].present? } || !with_template || submission.variables.present? submission.template_fields = template_fields submission.template_schema = submission.template.schema if submission.template_schema.blank? - submission.variables_schema = submission.template.variables_schema if submission.template_id && + submission.variables_schema = submission.template.variables_schema if submission.template && submission.variables_schema.blank? end From a8196709be3b7ab595867f62857b3a4035760535 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 24 Sep 2025 23:23:42 +0300 Subject: [PATCH 07/23] remove csp meta tag --- app/views/layouts/application.html.erb | 1 - app/views/layouts/form.html.erb | 1 - app/views/layouts/plain.html.erb | 1 - app/views/submit_form_draw_signature/show.html.erb | 1 - 4 files changed, 4 deletions(-) diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 0dee5392..99c3a0ce 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -7,7 +7,6 @@ <% end %> <%= csrf_meta_tags %> - <%= csp_meta_tag %> <% if ENV['ROLLBAR_CLIENT_TOKEN'] %> <%= javascript_pack_tag 'rollbar', 'application', defer: true %> diff --git a/app/views/layouts/form.html.erb b/app/views/layouts/form.html.erb index 78163f73..756f8f83 100644 --- a/app/views/layouts/form.html.erb +++ b/app/views/layouts/form.html.erb @@ -4,7 +4,6 @@ <%= render 'layouts/head_tags' %> <%= csrf_meta_tags %> - <%= csp_meta_tag %> <% if ENV['ROLLBAR_CLIENT_TOKEN'] %> <%= javascript_pack_tag 'rollbar', 'form', defer: true %> diff --git a/app/views/layouts/plain.html.erb b/app/views/layouts/plain.html.erb index e4a1b6f9..88916977 100644 --- a/app/views/layouts/plain.html.erb +++ b/app/views/layouts/plain.html.erb @@ -4,7 +4,6 @@ <%= render 'layouts/head_tags' %> <%= csrf_meta_tags %> - <%= csp_meta_tag %> <% if ENV['ROLLBAR_CLIENT_TOKEN'] %> <%= javascript_pack_tag 'rollbar', 'application', defer: true %> diff --git a/app/views/submit_form_draw_signature/show.html.erb b/app/views/submit_form_draw_signature/show.html.erb index 1ab495ed..f1ffebf0 100644 --- a/app/views/submit_form_draw_signature/show.html.erb +++ b/app/views/submit_form_draw_signature/show.html.erb @@ -4,7 +4,6 @@ <%= render 'layouts/head_tags' %> <%= csrf_meta_tags %> - <%= csp_meta_tag %> <% if ENV['ROLLBAR_CLIENT_TOKEN'] %> <%= javascript_pack_tag 'rollbar', 'draw', defer: true %> From 70c7ef42472cf4f2c3bcbc5844d6f4a34bba4e30 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 25 Sep 2025 08:47:31 +0300 Subject: [PATCH 08/23] adjust reminders --- config/locales/i18n.yml | 6 ++++++ lib/account_configs.rb | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index 84ddf10c..0f385a38 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -593,6 +593,7 @@ en: &en four_days: 4 days eight_days: 8 days fifteen_days: 15 days + thirty_days: 30 days free: Free unlimited_documents_storage: Unlimited documents storage users_management: Users management @@ -1506,6 +1507,7 @@ es: &es four_days: 4 días eight_days: 8 días fifteen_days: 15 días + thirty_days: 30 días free: Gratis unlimited_documents_storage: Almacenamiento ilimitado de documentos users_management: Gestión de usuarios @@ -2417,6 +2419,7 @@ it: &it four_days: 4 giorni eight_days: 8 giorni fifteen_days: 15 giorni + thirty_days: 30 giorni free: Gratuito unlimited_documents_storage: Archiviazione illimitata di documenti users_management: Gestione utenti @@ -3333,6 +3336,7 @@ fr: &fr four_days: 4 jours eight_days: 8 jours fifteen_days: 15 jours + thirty_days: 30 jours free: Gratuit unlimited_documents_storage: Stockage illimité de documents users_management: Gestion des utilisateurs @@ -4248,6 +4252,7 @@ pt: &pt four_days: 4 dias eight_days: 8 dias fifteen_days: 15 dias + thirty_days: 30 dias free: Gratuito unlimited_documents_storage: Armazenamento ilimitado de documentos users_management: Gerenciamento de usuários @@ -5162,6 +5167,7 @@ de: &de four_days: 4 Tage eight_days: 8 Tage fifteen_days: 15 Tage + thirty_days: 30 Tage free: Kostenlos unlimited_documents_storage: Unbegrenzter Dokumentenspeicher users_management: Benutzerverwaltung diff --git a/lib/account_configs.rb b/lib/account_configs.rb index 111320f8..208f00a9 100644 --- a/lib/account_configs.rb +++ b/lib/account_configs.rb @@ -15,7 +15,8 @@ module AccountConfigs 'six_days' => '6 days', 'seven_days' => '7 days', 'eight_days' => '8 days', - 'fifteen_days' => '15 days' + 'fifteen_days' => '15 days', + 'thirty_days' => '30 days' }.freeze module_function From 18b89edc19449bd0e6a88e22eecaa6b3bb162b24 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 25 Sep 2025 09:01:04 +0300 Subject: [PATCH 09/23] fix search input --- app/javascript/elements/search_input.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/javascript/elements/search_input.js b/app/javascript/elements/search_input.js index 502e2b3a..585f7122 100644 --- a/app/javascript/elements/search_input.js +++ b/app/javascript/elements/search_input.js @@ -15,13 +15,11 @@ export default class extends HTMLElement { }) this.button.addEventListener('click', (event) => { - event.preventDefault() + if (!this.input.value && document.activeElement !== this.input) { + event.preventDefault() - if (this.input.value || document.activeElement === this.input) { - return + this.input.focus() } - - this.input.focus() }) } From 1b1cf36839e04a8a3ddefe040cec3d2795c4104c Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 25 Sep 2025 10:30:02 +0300 Subject: [PATCH 10/23] fix verification step --- .../submission_form/verification_step.vue | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/app/javascript/submission_form/verification_step.vue b/app/javascript/submission_form/verification_step.vue index 39da2419..1f711c5e 100644 --- a/app/javascript/submission_form/verification_step.vue +++ b/app/javascript/submission_form/verification_step.vue @@ -14,7 +14,7 @@
    {{ t('complete_all_required_fields_to_proceed_with_identity_verification') }} @@ -100,6 +100,9 @@ export default { } }, computed: { + isRequiredFieldEmpty () { + return this.emptyValueRequiredStep && this.emptyValueRequiredStep[0] !== this.field + }, countryCode () { const browserTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone const browserTz = browserTimeZone.split('/')[1] @@ -131,17 +134,19 @@ export default { } }, async mounted () { - this.isLoading = true + if (!this.isRequiredFieldEmpty) { + this.isLoading = true - if (new URLSearchParams(window.location.search).get('submit') === 'true') { - this.$emit('submit') - } else { - Promise.all([ - import('@eid-easy/eideasy-widget'), - this.start() - ]).finally(() => { - this.isLoading = false - }) + if (new URLSearchParams(window.location.search).get('submit') === 'true') { + this.$emit('submit') + } else { + Promise.all([ + import('@eid-easy/eideasy-widget'), + this.start() + ]).finally(() => { + this.isLoading = false + }) + } } }, methods: { @@ -170,7 +175,7 @@ export default { redirectUrl.searchParams.append('lang', this.locale) this.redirectUrl = redirectUrl.toString() - } else { + } else if (this.$refs.widgetContainer) { const eidEasyWidget = document.createElement('eideasy-widget') for (const key in this.widgetSettings) { From b898708306627720eb3c1feddebdeab1de6874b1 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 25 Sep 2025 13:12:36 +0300 Subject: [PATCH 11/23] fix formula condition --- app/javascript/submission_form/form.vue | 18 +++++++++-- .../submission_form/payment_step.vue | 32 +++++++++++++++++-- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/app/javascript/submission_form/form.vue b/app/javascript/submission_form/form.vue index 761ad0f9..7a825141 100644 --- a/app/javascript/submission_form/form.vue +++ b/app/javascript/submission_form/form.vue @@ -23,7 +23,7 @@ { - acc[f.uuid] = (this.values[f.uuid] || f.default_value) + acc[f.uuid] = isEmpty(this.values[f.uuid]) ? f.default_value : this.values[f.uuid] + + return acc + }, {}) + }, + readonlyFieldValues () { + return this.readonlyFields.reduce((acc, f) => { + acc[f.uuid] = isEmpty(this.values[f.uuid]) ? f.default_value : this.values[f.uuid] return acc }, {}) @@ -972,7 +981,10 @@ export default { return this.currentStepFields[0] }, readonlyConditionalFields () { - return this.fields.filter((f) => f.readonly && f.conditions?.length && this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f)) + return this.readonlyFields.filter((f) => f.conditions?.length) + }, + readonlyFields () { + return this.fields.filter((f) => f.readonly && this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f)) }, stepFields () { const verificationFields = [] diff --git a/app/javascript/submission_form/payment_step.vue b/app/javascript/submission_form/payment_step.vue index 6bbeb146..e4f2e232 100644 --- a/app/javascript/submission_form/payment_step.vue +++ b/app/javascript/submission_form/payment_step.vue @@ -92,10 +92,20 @@ export default { type: Object, required: true }, + readonlyValues: { + type: Object, + required: false, + default: () => ({}) + }, values: { type: Object, required: true }, + fields: { + type: Array, + required: false, + default: () => [] + }, submitterSlug: { type: String, required: true @@ -109,6 +119,13 @@ export default { } }, computed: { + fieldsUuidIndex () { + return this.fields.reduce((acc, field) => { + acc[field.uuid] = field + + return acc + }, {}) + }, queryParams () { return new URLSearchParams(window.location.search) }, @@ -182,12 +199,23 @@ export default { }, methods: { calculateFormula () { - const transformedFormula = this.field.preferences.formula.replace(/{{(.*?)}}/g, (match, uuid) => { - return this.values[uuid] || 0.0 + const transformedFormula = this.normalizeFormula(this.field.preferences.formula).replace(/{{(.*?)}}/g, (match, uuid) => { + return this.readonlyValues[uuid] || this.values[uuid] || 0.0 }) return this.math.evaluate(transformedFormula.toLowerCase()) }, + normalizeFormula (formula, depth = 0) { + if (depth > 10) return formula + + return formula.replace(/{{(.*?)}}/g, (match, uuid) => { + if (this.fieldsUuidIndex[uuid]) { + return `(${this.normalizeFormula(this.fieldsUuidIndex[uuid].preferences.formula, depth + 1)})` + } else { + return match + } + }) + }, async submit () { if (this.sessionId) { return fetch(this.baseUrl + '/api/stripe_payments/' + this.sessionId, { From 6b54072cb5e493ba680b233347e511562b81ae8c Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 25 Sep 2025 14:00:51 +0300 Subject: [PATCH 12/23] check formula field condition --- lib/submitters/submit_values.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/submitters/submit_values.rb b/lib/submitters/submit_values.rb index 63b1d839..063f7fca 100644 --- a/lib/submitters/submit_values.rb +++ b/lib/submitters/submit_values.rb @@ -189,8 +189,6 @@ module Submitters next if formula.blank? - formula = normalize_formula(formula, submitter.submission) - submission_values ||= if submitter.submission.template_submitters.size > 1 merge_submitters_values(submitter) @@ -198,20 +196,26 @@ module Submitters submitter.values end + formula = normalize_formula(formula, submitter.submission, submission_values:) + acc[field['uuid']] = calculate_formula_value(formula, submission_values.merge(acc.compact_blank)) end computed_values.compact_blank end - def normalize_formula(formula, submission, depth = 0) + def normalize_formula(formula, submission, depth: 0, submission_values: nil) raise ValidationError, 'Formula infinite loop' if depth > 10 formula.gsub(/{{(.*?)}}/) do |match| uuid = Regexp.last_match(1) if (nested_formula = submission.fields_uuid_index.dig(uuid, 'preferences', 'formula').presence) - "(#{normalize_formula(nested_formula, submission, depth + 1)})" + if check_field_conditions(submission_values, submission.fields_uuid_index[uuid], submission.fields_uuid_index) + "(#{normalize_formula(nested_formula, submission, depth: depth + 1, submission_values:)})" + else + '0' + end else match end From 7015d8dde8d5d6ab08362a4ac798299e23b60851 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 25 Sep 2025 18:30:07 +0300 Subject: [PATCH 13/23] fix csp --- app/views/scripts/_autosize_field.html.erb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/scripts/_autosize_field.html.erb b/app/views/scripts/_autosize_field.html.erb index 0d34e314..fc5b3b23 100644 --- a/app/views/scripts/_autosize_field.html.erb +++ b/app/views/scripts/_autosize_field.html.erb @@ -1,4 +1,4 @@ - From 8d9bea3b0f21d07cec744be44f475608783c7f60 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 25 Sep 2025 20:32:41 +0300 Subject: [PATCH 14/23] adjust date format --- lib/time_utils.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/time_utils.rb b/lib/time_utils.rb index 292b67bb..c1c0d07e 100644 --- a/lib/time_utils.rb +++ b/lib/time_utils.rb @@ -57,9 +57,9 @@ module TimeUtils format ||= locale.to_s.ends_with?('US') ? DEFAULT_DATE_FORMAT_US : DEFAULT_DATE_FORMAT - i18n_format = format.sub(/D+/, DAY_FORMATS[format[/D+/]]) - .sub(/M+/, MONTH_FORMATS[format[/M+/]]) - .sub(/Y+/, YEAR_FORMATS[format[/Y+/]]) + i18n_format = format.sub(/D+/) { DAY_FORMATS[format[/D+/]] } + .sub(/M+/) { MONTH_FORMATS[format[/M+/]] } + .sub(/Y+/) { YEAR_FORMATS[format[/Y+/]] } I18n.l(date, format: i18n_format, locale:) rescue Date::Error From 39eb67b1620f6a2437356684d073c1b143e27634 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 26 Sep 2025 08:20:03 +0300 Subject: [PATCH 15/23] refactor csp --- app/controllers/application_controller.rb | 3 --- app/controllers/csp_controller.rb | 15 --------------- config/routes.rb | 2 -- 3 files changed, 20 deletions(-) delete mode 100644 app/controllers/csp_controller.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 0ba4a117..cd8d228c 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -127,8 +127,6 @@ class ApplicationController < ActionController::Base end def set_csp - request.content_security_policy_report_only = Rails.env.production? - request.content_security_policy = current_content_security_policy.tap do |policy| policy.default_src :self policy.script_src :self @@ -140,7 +138,6 @@ class ApplicationController < ActionController::Base policy.frame_src :self policy.worker_src :self, :blob policy.connect_src :self - policy.report_uri '/csp' policy.directives['connect-src'] << 'ws:' if Rails.env.development? end diff --git a/app/controllers/csp_controller.rb b/app/controllers/csp_controller.rb deleted file mode 100644 index 587ee306..00000000 --- a/app/controllers/csp_controller.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -class CspController < ActionController::API - FILTER_REPORT_REGEXP = /extension|sandbox/i - - SANITIZE_REGEXP = %r{(/[sdep]/)(\w{5})[^/"]+} - - def create - data = request.raw_post.gsub(SANITIZE_REGEXP, '\1\2') - - Rails.logger.warn(data) if Rails.env.development? - - Rollbar.warning('CSP', data:) if defined?(Rollbar) && !data.match?(FILTER_REPORT_REGEXP) - end -end diff --git a/config/routes.rb b/config/routes.rb index 609e1fe3..e90bd2f2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -202,8 +202,6 @@ Rails.application.routes.draw do end end - resources :csp - get '/js/:filename', to: 'embed_scripts#show', as: :embed_script ActiveSupport.run_load_hooks(:routes, self) From e0469b10a79f92a10051bcd30e5f9f23b0fbf541 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 26 Sep 2025 09:35:32 +0300 Subject: [PATCH 16/23] hide mobile folder upload --- app/views/template_folders/show.html.erb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/template_folders/show.html.erb b/app/views/template_folders/show.html.erb index 2f6ac8ea..c51879d8 100644 --- a/app/views/template_folders/show.html.erb +++ b/app/views/template_folders/show.html.erb @@ -34,7 +34,9 @@ <%= render 'shared/search_input' %> <% end %> <% if can?(:create, ::Template) %> - <%= render 'templates/upload_button', folder_name: @template_folder.full_name %> + <%= link_to new_template_path(folder_name: @template_folder.full_name), class: 'white-button !border gap-2', data: { turbo_frame: :modal } do %> <%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %> From 9cc92b59b3f3ab4362cd0789874e124585477379 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 26 Sep 2025 20:52:44 +0300 Subject: [PATCH 17/23] add strikethrough --- app/javascript/submission_form/area.vue | 59 ++++++++++ app/javascript/submission_form/areas.vue | 53 +++++---- app/javascript/template_builder/area.vue | 101 ++++++++++++++++-- app/javascript/template_builder/builder.vue | 26 ++++- .../template_builder/conditions_modal.vue | 5 +- app/javascript/template_builder/field.vue | 12 ++- .../template_builder/field_settings.vue | 8 +- .../template_builder/field_type.vue | 9 +- app/javascript/template_builder/fields.vue | 5 +- app/javascript/template_builder/i18n.js | 12 +++ app/javascript/template_builder/page.vue | 4 + app/views/submissions/_value.html.erb | 13 +++ app/views/submissions/show.html.erb | 4 +- app/views/submit_form/show.html.erb | 2 +- lib/submissions/generate_audit_trail.rb | 2 +- .../generate_result_attachments.rb | 52 +++++++-- lib/submitters/submit_values.rb | 2 +- 17 files changed, 311 insertions(+), 58 deletions(-) diff --git a/app/javascript/submission_form/area.vue b/app/javascript/submission_form/area.vue index 784818b6..2a2fd20e 100644 --- a/app/javascript/submission_form/area.vue +++ b/app/javascript/submission_form/area.vue @@ -219,6 +219,48 @@ > {{ formatNumber(modelValue, field.preferences?.format) }} + + + + + + + + + - - - + + + + diff --git a/app/javascript/template_builder/area.vue b/app/javascript/template_builder/area.vue index c786061a..d88475b3 100644 --- a/app/javascript/template_builder/area.vue +++ b/app/javascript/template_builder/area.vue @@ -10,7 +10,7 @@
    + + + + + + + +
    +
    @@ -397,6 +439,16 @@ export default { required: false, default: false }, + pageWidth: { + type: Number, + required: false, + default: 0 + }, + pageHeight: { + type: Number, + required: false, + default: 0 + }, defaultSubmitters: { type: Array, required: false, @@ -436,8 +488,33 @@ export default { fieldNames: FieldType.computed.fieldNames, fieldLabels: FieldType.computed.fieldLabels, fieldIcons: FieldType.computed.fieldIcons, + bgClasses () { + if (this.field.type === 'heading') { + return 'bg-gray-50' + } else if (this.field.type === 'strikethrough') { + return 'bg-transparent' + } else { + return this.bgColors[this.submitterIndex % this.bgColors.length] + } + }, + activeBorderClasses () { + if (this.field.type === 'heading') { + return '' + } else if (this.field.type === 'strikethrough') { + return 'border-dashed border-gray-300' + } else { + return this.borderColors[this.submitterIndex % this.borderColors.length] + } + }, isWFullType () { - return ['cells', 'checkbox', 'radio', 'multiple', 'select'].includes(this.field.type) + return ['cells', 'checkbox', 'radio', 'multiple', 'select', 'strikethrough'].includes(this.field.type) + }, + strikethroughWidth () { + if (this.isInlineSize) { + return '0.6cqmin' + } else { + return 'clamp(0px, 0.5vw, 6px)' + } }, fontStyle () { let fontSize = '' @@ -471,8 +548,11 @@ export default { lineHeight () { return 1.3 }, + basePageWidth () { + return 1040.0 + }, fontScale () { - return 1040 / 612.0 + return this.basePageWidth / 612.0 }, isDefaultValuePresent () { return this.field?.default_value || this.field?.default_value === 0 @@ -742,8 +822,13 @@ export default { delete this.field.options } - if (['heading'].includes(this.field.type)) { + if (this.field.type === 'heading') { + this.field.readonly = true + } + + if (this.field.type === 'strikethrough') { this.field.readonly = true + this.field.default_value = true } if (['select', 'multiple', 'radio'].includes(this.field.type)) { diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue index a9f3709b..c679a37e 100644 --- a/app/javascript/template_builder/builder.vue +++ b/app/javascript/template_builder/builder.vue @@ -402,7 +402,10 @@ :style="{ backgroundColor }" >
    -

    +

    + {{ t('draw_strikethrough_the_document') }} +

    +

    {{ t('draw_field_on_the_document') }}

    @@ -413,7 +416,7 @@ {{ t('cancel') }} { - if (f !== this.item && (!f.conditions?.length || !f.conditions.find((c) => c.field_uuid === this.item.uuid))) { + if (f !== this.item && !this.excludeTypes.includes(f.type) && (!f.conditions?.length || !f.conditions.find((c) => c.field_uuid === this.item.uuid))) { acc.push(f) } diff --git a/app/javascript/template_builder/field.vue b/app/javascript/template_builder/field.vue index d8b6538e..f4a97212 100644 --- a/app/javascript/template_builder/field.vue +++ b/app/javascript/template_builder/field.vue @@ -14,7 +14,7 @@
    f.type === field.type).indexOf(field) - if (field.type === 'heading') { + if (field.type === 'heading' || field.type === 'strikethrough') { return `${this.fieldNames[field.type]} ${typeIndex + 1}` } else { return `${this.fieldLabels[field.type]} ${typeIndex + 1}` @@ -485,8 +484,13 @@ export default { this.field.options ||= [{ value: '', uuid: v4() }] } - if (['heading'].includes(this.field.type)) { + if (this.field.type === 'heading') { + this.field.readonly = true + } + + if (this.field.type === 'strikethrough') { this.field.readonly = true + this.field.default_value = true } (this.field.areas || []).forEach((area) => { diff --git a/app/javascript/template_builder/field_settings.vue b/app/javascript/template_builder/field_settings.vue index f446bca7..d3e2e842 100644 --- a/app/javascript/template_builder/field_settings.vue +++ b/app/javascript/template_builder/field_settings.vue @@ -380,7 +380,7 @@
  • +