diff --git a/.erb_lint.yml b/.erb_lint.yml index 1a76afa12..50576fd57 100644 --- a/.erb_lint.yml +++ b/.erb_lint.yml @@ -1,6 +1,11 @@ --- EnableDefaultLinters: true linters: + AllowedScriptType: + enabled: true + allowed_types: + - text/javascript + - application/json ErbSafety: enabled: false HardCodedString: diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 0b6113816..d2371aa55 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -20,7 +20,7 @@ class UsersController < ApplicationController respond_to do |format| format.html do - @pagy, @users = pagy(@users) + @pagy, @users = pagy(@users, limit: turbo_native_app? ? 1_000 : Pagy::OPTIONS[:limit]) end if current_ability.can?(:manage, current_account) diff --git a/app/javascript/application.js b/app/javascript/application.js index e95852523..7dee5edf9 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -281,7 +281,7 @@ safeRegisterElement('template-builder', class extends HTMLElement { } onSync = (e) => { - const { schema, documents, fields, submitters } = e.detail + const { schema, documents, fields, submitters, pendingFieldAttachmentUuids } = e.detail const selectedAreas = this.component.selectedAreasRef.value.map((area) => { const field = this.component.template.fields.find((f) => f.areas?.includes(area)) @@ -293,6 +293,7 @@ safeRegisterElement('template-builder', class extends HTMLElement { this.component.selectedAreasRef.value = selectedAreas.map((item) => item && fields.find((f) => f.uuid === item.uuid)?.areas?.[item.index]).filter(Boolean) this.component.selectedSubmitter = submitters.find((s) => s.uuid === selectedSubmitterUuid) || submitters[0] + this.component.pendingFieldAttachmentUuids = pendingFieldAttachmentUuids } onScrollTo = (e) => { diff --git a/app/javascript/elements/dashboard_dropzone.js b/app/javascript/elements/dashboard_dropzone.js index 2ee1d8fc6..8aa75c0f6 100644 --- a/app/javascript/elements/dashboard_dropzone.js +++ b/app/javascript/elements/dashboard_dropzone.js @@ -26,17 +26,41 @@ export default targetable(class extends HTMLElement { window.addEventListener('dragleave', this.onWindowDragleave) - this.fileDropzone?.addEventListener('drop', this.onDropFile) - this.folderDropzone?.addEventListener('drop', this.onDropNewFolder) + this.addEventListener('drop', this.onDrop) + this.addEventListener('dragstart', this.onDragstart) + this.addEventListener('dragover', this.onDragover) + this.addEventListener('dragleave', this.onDragleave) + } - this.folderCards.forEach((el) => el.addEventListener('drop', (e) => this.onDropFolder(e, el))) - this.templateCards.forEach((el) => el.addEventListener('drop', this.onDropTemplate)) - this.templateCards.forEach((el) => el.addEventListener('dragstart', this.onTemplateDragStart)) + dropTarget (e) { + return e.target.closest([ + '[data-target="dashboard-dropzone.fileDropzone"]', + '[data-target="dashboard-dropzone.folderDropzone"]', + '[data-targets="dashboard-dropzone.folderCards"]', + '[data-targets="dashboard-dropzone.templateCards"]' + ].join(', ')) + } - return [this.fileDropzone, this.folderDropzone, ...this.folderCards, ...this.templateCards].forEach((el) => { - el?.addEventListener('dragover', this.onDragover) - el?.addEventListener('dragleave', this.onDragleave) - }) + onDrop = (e) => { + const el = this.dropTarget(e) + + if (!el) return + + if (el === this.fileDropzone) { + this.onDropFile(e) + } else if (el === this.folderDropzone) { + this.onDropNewFolder(e) + } else if (el.dataset.targets === 'dashboard-dropzone.folderCards') { + this.onDropFolder(e, el) + } else { + this.onDropTemplate(e, el) + } + } + + onDragstart = (e) => { + if (e.target.closest('[data-targets="dashboard-dropzone.templateCards"]')) { + this.onTemplateDragStart(e) + } } disconnectedCallback () { @@ -128,7 +152,7 @@ export default targetable(class extends HTMLElement { } } - onDropTemplate = (e) => { + onDropTemplate = (e, el) => { e.preventDefault() if (e.dataTransfer.files.length) { @@ -136,10 +160,10 @@ export default targetable(class extends HTMLElement { loading.classList.add('bottom-5', 'left-0', 'flex', 'justify-center', 'w-full', 'absolute') loading.innerHTML = loadingIconHtml - e.target.appendChild(loading) - e.target.classList.add('opacity-50') + el.appendChild(loading) + el.classList.add('opacity-50') - const id = e.target.href.split('/').pop() + const id = el.href.split('/').pop() this.uploadFiles(e.dataTransfer.files, `/templates/${id}/clone_and_replace`) } @@ -173,16 +197,20 @@ export default targetable(class extends HTMLElement { } } - onDragover (e) { - if (e.dataTransfer?.types?.includes('Files') || this.dataset.targets !== 'dashboard-dropzone.templateCards') { - this.style.backgroundColor = '#F7F3F0' + onDragover = (e) => { + const el = this.dropTarget(e) - if (this.classList.contains('before:border-base-300')) { - this.classList.remove('before:border-base-300') - this.classList.add('before:border-base-content/30') - } else if (this.classList.contains('border-base-300')) { - this.classList.remove('border-base-300') - this.classList.add('border-base-content/30') + if (!el) return + + if (e.dataTransfer?.types?.includes('Files') || el.dataset.targets !== 'dashboard-dropzone.templateCards') { + el.style.backgroundColor = '#F7F3F0' + + if (el.classList.contains('before:border-base-300')) { + el.classList.remove('before:border-base-300') + el.classList.add('before:border-base-content/30') + } else if (el.classList.contains('border-base-300')) { + el.classList.remove('border-base-300') + el.classList.add('border-base-content/30') } } } @@ -205,15 +233,19 @@ export default targetable(class extends HTMLElement { a.remove() } - onDragleave () { - this.style.backgroundColor = null + onDragleave = (e) => { + const el = this.dropTarget(e) - if (this.classList.contains('before:border-base-content/30')) { - this.classList.remove('before:border-base-content/30') - this.classList.add('before:border-base-300') - } else if (this.classList.contains('border-base-content/30')) { - this.classList.remove('border-base-content/30') - this.classList.add('border-base-300') + if (!el) return + + el.style.backgroundColor = null + + if (el.classList.contains('before:border-base-content/30')) { + el.classList.remove('before:border-base-content/30') + el.classList.add('before:border-base-300') + } else if (el.classList.contains('border-base-content/30')) { + el.classList.remove('border-base-content/30') + el.classList.add('border-base-300') } } diff --git a/app/javascript/elements/native_modal.js b/app/javascript/elements/native_modal.js index dc8f35d8b..e412c5af4 100644 --- a/app/javascript/elements/native_modal.js +++ b/app/javascript/elements/native_modal.js @@ -8,7 +8,7 @@ export default class extends HTMLElement { document.removeEventListener('turbo:submit-end', this.onSubmit) } - onSubmit = (e) => { + onSubmit = async (e) => { if (!e.detail.success) return if (e.detail.fetchResponse?.redirected && e.detail.fetchResponse.location.href !== window.location.href) return if (e.detail.formSubmission?.formElement?.method === 'get') return @@ -16,7 +16,19 @@ export default class extends HTMLElement { if (e.detail.formSubmission?.formElement?.dataset?.closeOnSubmit === 'false') return if (e.detail.formSubmission?.formElement?.closest('native-event[data-on="submit"]')) return - window.webkit?.messageHandlers?.modal?.postMessage({ action: 'submitted' }) + const flash = await this.flashFrom(e.detail.fetchResponse) + + window.webkit?.messageHandlers?.modal?.postMessage({ action: 'submitted', ...flash }) + } + + async flashFrom (fetchResponse) { + const html = await fetchResponse?.responseHTML + const flash = html && new DOMParser().parseFromString(html, 'text/html').getElementById('native_flash') + + if (flash?.dataset.alert) return { alert: flash.dataset.alert } + if (flash?.dataset.notice) return { notice: flash.dataset.notice } + + return {} } onClick = () => { diff --git a/app/javascript/elements/open_modal_mobile.js b/app/javascript/elements/open_modal_mobile.js index a326af919..21ae835c6 100644 --- a/app/javascript/elements/open_modal_mobile.js +++ b/app/javascript/elements/open_modal_mobile.js @@ -1,13 +1,17 @@ export default class extends HTMLElement { connectedCallback () { + this.addEventListener('pointerdown', this.onPointerdown) + } + + onPointerdown = (event) => { if (this.dataset.disabled === 'true') return if (/Hotwire Native/.test(navigator.userAgent)) return if (!this.isMobile()) return if (window.innerWidth >= 768) return - this.querySelectorAll('a[data-turbo-frame]').forEach((link) => { - link.dataset.turboFrame = '_top' - }) + const link = event.target.closest('a[data-turbo-frame]') + + if (link) link.dataset.turboFrame = '_top' } isMobile () { diff --git a/app/javascript/elements/submit_form.js b/app/javascript/elements/submit_form.js index e936c0bdd..db83b5f35 100644 --- a/app/javascript/elements/submit_form.js +++ b/app/javascript/elements/submit_form.js @@ -1,5 +1,19 @@ export default class extends HTMLElement { connectedCallback () { + if (this.lastElementChild) { + this.init() + } else { + queueMicrotask(() => this.init()) + } + } + + disconnectedCallback () { + if (this.interval) { + clearInterval(this.interval) + } + } + + init () { const form = this.querySelector('form') || (this.querySelector('input, button, select') || this.lastElementChild).form if (this.dataset.interval) { @@ -24,10 +38,4 @@ export default class extends HTMLElement { form.requestSubmit() } } - - disconnectedCallback () { - if (this.interval) { - clearInterval(this.interval) - } - } } diff --git a/app/javascript/native.js b/app/javascript/native.js index 87afa5e32..7de1f76a1 100644 --- a/app/javascript/native.js +++ b/app/javascript/native.js @@ -50,8 +50,11 @@ export function nativeIcon (name) { } const handlers = window.webkit?.messageHandlers +const isNativeLoaded = window.isNativeLoaded + +if (handlers?.native && window === window.top && !isNativeLoaded) { + window.isNativeLoaded = true -if (handlers?.native && window === window.top) { handlers.native.postMessage({ type: 'reset' }) const reportPathConfiguration = () => { @@ -150,7 +153,7 @@ if (handlers?.native && window === window.top) { }) } -if (handlers?.native && window.Turbo) { +if (handlers?.native && window.Turbo && !isNativeLoaded) { const staleSnapshots = new Set() let isRestoring = false diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue index 688183013..d537a6297 100644 --- a/app/javascript/template_builder/builder.vue +++ b/app/javascript/template_builder/builder.vue @@ -7,7 +7,7 @@ @drop="isDragFile = false" > @@ -519,11 +519,11 @@ :class="($slots.buttons || withTitle) && !nativePlatform ? (isMobile ? 'max-h-[calc(100%_-_60px)]' : 'md:max-h-[calc(100%_-_60px)]') : (isMobile ? 'max-h-[100%]' : 'md:max-h-[100%]')" > @@ -824,7 +824,7 @@ /> 0 }, + withDocumentsDrawer () { + return this.withMobileDrawers && this.withDocumentsList && (this.isBreakpointLg || this.isMobile) + }, + withFieldsDrawer () { + return this.withMobileDrawers && this.withFieldsList && this.editable && this.isBreakpointMd + }, isMobile () { const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent) @@ -2402,8 +2409,11 @@ export default { }, onWindowResize (e) { const breakpointLg = 1024 + const breakpointMd = 768 + const width = this.$el.getRootNode().querySelector('div[data-v-app]').offsetWidth - this.isBreakpointLg = this.$el.getRootNode().querySelector('div[data-v-app]').offsetWidth < breakpointLg + this.isBreakpointLg = width < breakpointLg + this.isBreakpointMd = width < breakpointMd }, onPagesWheel (event) { if (!event.ctrlKey && !event.metaKey) return @@ -2497,7 +2507,7 @@ export default { }, syncNative () { const { schema, documents, fields, submitters } = this.template - const detail = JSON.stringify({ schema, documents, fields, submitters }) + const detail = JSON.stringify({ schema, documents, fields, submitters, pendingFieldAttachmentUuids: this.pendingFieldAttachmentUuids }) if (this.nativeView) { window.webkit?.messageHandlers?.modal?.postMessage({ action: 'dispatch', name: 'template-builder:sync', detail, dismiss: 'false' }) @@ -3439,6 +3449,10 @@ export default { }) } }) + + if (this.nativeView && this.pendingFieldAttachmentUuids.length) { + this.syncNative() + } }, updateName (value) { this.template.name = value diff --git a/app/javascript/template_builder/i18n.js b/app/javascript/template_builder/i18n.js index 1422f6e14..1e46b6c93 100644 --- a/app/javascript/template_builder/i18n.js +++ b/app/javascript/template_builder/i18n.js @@ -201,6 +201,7 @@ const en = { uploading_: 'Uploading...', add_document: 'Add Document', add_fields: 'Add Fields', + fields: 'Fields', none: 'None', ssn: 'SSN', ein: 'EIN', @@ -445,6 +446,7 @@ const es = { uploading_: 'Subiendo...', add_document: 'Subir', add_fields: 'Agregar campos', + fields: 'Campos', any: 'Cualquier', drawn: 'Dibujado', drawn_or_typed: 'Dibujado o Escrito', @@ -701,6 +703,7 @@ const it = { uploading_: 'Caricamento in corso...', add_document: 'Aggiungi', add_fields: 'Aggiungi campi', + fields: 'Campi', none: 'Nessuno', ssn: 'SSN', ein: 'EIN', @@ -945,6 +948,7 @@ const pt = { uploading_: 'Carregando...', add_document: 'Enviar', add_fields: 'Adicionar campos', + fields: 'Campos', any: 'Qualquer', drawn: 'Desenhado', drawn_or_typed: 'Desenhado ou Digitado', @@ -1201,6 +1205,7 @@ const fr = { uploading_: 'Téléversement...', add_document: 'Téléverser', add_fields: 'Ajouter des champs', + fields: 'Champs', none: 'Aucun', ssn: 'SSN', ein: 'EIN', @@ -1451,6 +1456,7 @@ const de = { uploading_: 'Hochladen...', add_document: 'Hochladen', add_fields: 'Felder hinzufügen', + fields: 'Felder', none: 'Keine', ssn: 'SSN', ein: 'EIN', @@ -1701,6 +1707,7 @@ const nl = { uploading_: 'Bezig met uploaden...', add_document: 'Doc. toevoegen', add_fields: 'Velden toevoegen', + fields: 'Velden', none: 'Geen', ssn: 'SSN', ein: 'EIN', diff --git a/app/views/accounts/show.html.erb b/app/views/accounts/show.html.erb index b9ef9192c..96653e662 100644 --- a/app/views/accounts/show.html.erb +++ b/app/views/accounts/show.html.erb @@ -303,6 +303,6 @@ <% end %> - + <%= render 'shared/app_tour', type: 'account' %> diff --git a/app/views/devise/passwords/new.html.erb b/app/views/devise/passwords/new.html.erb index c4269b9cd..b8e4f3bdd 100644 --- a/app/views/devise/passwords/new.html.erb +++ b/app/views/devise/passwords/new.html.erb @@ -1,7 +1,10 @@ - - - <%= t('forgot_your_password_') %> - +<% content_for(:native_title, t('forgot_your_password_')) if turbo_native_app? %> + + <% unless turbo_native_app? %> + + <%= t('forgot_your_password_') %> + + <% end %> <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'space-y-6' }) do |f| %> <%= render 'devise/shared/error_messages', resource: %> diff --git a/app/views/email_smtp_settings/index.html.erb b/app/views/email_smtp_settings/index.html.erb index 102ac75bd..39199116f 100644 --- a/app/views/email_smtp_settings/index.html.erb +++ b/app/views/email_smtp_settings/index.html.erb @@ -64,5 +64,5 @@ <% end %> - + diff --git a/app/views/layouts/_head_tags.html.erb b/app/views/layouts/_head_tags.html.erb index 9c8757efb..cfb72a186 100644 --- a/app/views/layouts/_head_tags.html.erb +++ b/app/views/layouts/_head_tags.html.erb @@ -1,5 +1,5 @@ - <%= content_for(:native_title) || content_for(:html_title) || (signed_in? ? 'DocuSeal' : 'DocuSeal | Open Source Document Signing') %> + <%= turbo_native_app? ? content_for(:native_title) : (content_for(:html_title) || (signed_in? ? 'DocuSeal' : 'DocuSeal | Open Source Document Signing')) %> <%= render 'shared/meta' %> <% if turbo_native_app? %> diff --git a/app/views/layouts/_native_path_configuration.json.erb b/app/views/layouts/_native_path_configuration.json.erb index f3517034a..f6fb39a36 100644 --- a/app/views/layouts/_native_path_configuration.json.erb +++ b/app/views/layouts/_native_path_configuration.json.erb @@ -8,6 +8,8 @@ "fields": <%== t('fields').to_json %>, "more": <%== t('more').to_json %>, "close": <%== t('close').to_json %>, + "settings": <%== t('settings').to_json %>, + "sign_out": <%== t('sign_out').to_json %>, "cancel": <%== t('cancel').to_json %>, "ok": <%== t('ok').to_json %>, "failed_to_download_files": <%== t('failed_to_download_files').to_json %>, diff --git a/app/views/notifications_settings/index.html.erb b/app/views/notifications_settings/index.html.erb index 41bc2cdf6..e1e3c536c 100644 --- a/app/views/notifications_settings/index.html.erb +++ b/app/views/notifications_settings/index.html.erb @@ -32,5 +32,5 @@ <%= render 'reminder_form', config: @reminder_config %> <% end %> - + diff --git a/app/views/personalization_settings/show.html.erb b/app/views/personalization_settings/show.html.erb index f039d4503..90566562f 100644 --- a/app/views/personalization_settings/show.html.erb +++ b/app/views/personalization_settings/show.html.erb @@ -26,5 +26,5 @@ <%= render 'form_customization_settings' %> - + diff --git a/app/views/profile/index.html.erb b/app/views/profile/index.html.erb index eef076f67..5a5229d0f 100644 --- a/app/views/profile/index.html.erb +++ b/app/views/profile/index.html.erb @@ -107,5 +107,5 @@ <% end %> - + diff --git a/app/views/shared/_native_navbar.html.erb b/app/views/shared/_native_navbar.html.erb index fd97d7a5e..997f6492f 100644 --- a/app/views/shared/_native_navbar.html.erb +++ b/app/views/shared/_native_navbar.html.erb @@ -27,5 +27,12 @@ <%= link_to t('create'), new_template_path(folder_name: folder_name), data: { turbo_frame: :modal, native_modal: :medium } %> + <% unless controller_name.in?(%w[templates_archived submissions_archived]) %> + "> + + + <%= link_to t('create'), new_template_path(folder_name: folder_name), data: { turbo_frame: :modal, native_modal: :medium } %> + + <% end %> <% end %> <% end %> diff --git a/app/views/shared/_settings_nav.html.erb b/app/views/shared/_settings_nav.html.erb index d3f458396..ae8e9d29d 100644 --- a/app/views/shared/_settings_nav.html.erb +++ b/app/views/shared/_settings_nav.html.erb @@ -1,5 +1,6 @@ <% expanded = local_assigns[:expanded] %> - +<% nav_link_data = turbo_native_app? && !expanded ? { turbo_action: 'replace' } : {} %> + <% unless expanded || turbo_native_app? %> @@ -26,60 +27,60 @@ <% end %> - <%= link_to t('profile'), settings_profile_index_path, class: 'text-base hover:bg-base-300' %> + <%= link_to t('profile'), settings_profile_index_path, class: 'text-base hover:bg-base-300', data: nav_link_data %> - <%= link_to t('account'), settings_account_path, class: 'text-base hover:bg-base-300' %> + <%= link_to t('account'), settings_account_path, class: 'text-base hover:bg-base-300', data: nav_link_data %> <% if (!Docuseal.multitenant? || EncryptedConfig.exists?(key: EncryptedConfig::EMAIL_SMTP_KEY, account: current_account)) && can?(:read, EncryptedConfig.new(key: EncryptedConfig::EMAIL_SMTP_KEY, account: current_account)) && ENV['SMTP_ADDRESS'].blank? && true_user == current_user %> - <%= link_to t('email'), settings_email_index_path, class: 'text-base hover:bg-base-300' %> + <%= link_to t('email'), settings_email_index_path, class: 'text-base hover:bg-base-300', data: nav_link_data %> <% end %> <% unless Docuseal.multitenant? %> <% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::FILES_STORAGE_KEY, account: current_account)) && true_user == current_user && ENV['S3_ATTACHMENTS_BUCKET'].blank? && ENV['GCS_BUCKET'].blank? && ENV['AZURE_CONTAINER'].blank? %> - <%= link_to t('storage'), settings_storage_index_path, class: 'text-base hover:bg-base-300' %> + <%= link_to t('storage'), settings_storage_index_path, class: 'text-base hover:bg-base-300', data: nav_link_data %> <% end %> <% if can?(:read, EncryptedConfig.new(key: 'submitter_invitation_sms', account: current_account)) && true_user == current_user && (!turbo_native_ios? || can?(:manage, :sms_verification)) %> - <%= link_to 'SMS', settings_sms_path, class: 'text-base hover:bg-base-300' %> + <%= link_to 'SMS', settings_sms_path, class: 'text-base hover:bg-base-300', data: nav_link_data %> <% end %> <% end %> <% if can?(:read, AccountConfig) %> - <%= link_to t('notifications'), settings_notifications_path, class: 'text-base hover:bg-base-300' %> + <%= link_to t('notifications'), settings_notifications_path, class: 'text-base hover:bg-base-300', data: nav_link_data %> <% end %> <% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::ESIGN_CERTS_KEY, account: current_account)) %> - <%= link_to t('e_signature'), settings_esign_path, class: 'text-base hover:bg-base-300' %> + <%= link_to t('e_signature'), settings_esign_path, class: 'text-base hover:bg-base-300', data: nav_link_data %> <% end %> <% if can?(:read, AccountConfig) %> - <%= link_to t('personalization'), settings_personalization_path, class: 'text-base hover:bg-base-300' %> + <%= link_to t('personalization'), settings_personalization_path, class: 'text-base hover:bg-base-300', data: nav_link_data %> <% end %> <% if can?(:read, User) %> - <%= link_to t('users'), settings_users_path, class: 'text-base hover:bg-base-300' %> + <%= link_to t('users'), settings_users_path, class: 'text-base hover:bg-base-300', data: nav_link_data %> <% end %> - <%= render 'shared/settings_nav_extra' %> + <%= render 'shared/settings_nav_extra', nav_link_data: %> <% if Docuseal.demo? || !Docuseal.multitenant? %> <% if can?(:read, AccessToken) %> - <%= link_to 'API', settings_api_index_path, class: 'text-base hover:bg-base-300' %> + <%= link_to 'API', settings_api_index_path, class: 'text-base hover:bg-base-300', data: nav_link_data %> <% end %> <% end %> <% if Docuseal.demo? || !Docuseal.multitenant? || (current_user != true_user && !current_account.testing?) %> <% if can?(:read, WebhookUrl) %> - <%= link_to 'Webhooks', settings_webhooks_path, class: 'text-base hover:bg-base-300' %> + <%= link_to 'Webhooks', settings_webhooks_path, class: 'text-base hover:bg-base-300', data: nav_link_data %> <% end %> <% end %> @@ -108,15 +109,15 @@ <% end %> <% if (!Docuseal.multitenant? || can?(:manage, :saml_sso)) && can?(:read, EncryptedConfig.new(key: 'saml_configs', account: current_account)) && true_user == current_user && (!turbo_native_ios? || can?(:manage, :saml_sso)) %> - <%= link_to 'SSO', settings_sso_index_path, class: 'text-base hover:bg-base-300' %> + <%= link_to 'SSO', settings_sso_index_path, class: 'text-base hover:bg-base-300', data: nav_link_data %> <% end %> <% if !Docuseal.multitenant? && can?(:read, McpToken) && can?(:manage, :mcp) %> - <%= link_to 'MCP', settings_mcp_index_path, class: 'text-base hover:bg-base-300' %> + <%= link_to 'MCP', settings_mcp_index_path, class: 'text-base hover:bg-base-300', data: nav_link_data %> <% end %> - <%= render 'shared/settings_nav_extra2' %> + <%= render 'shared/settings_nav_extra2', nav_link_data: %> <% if (can?(:manage, EncryptedConfig) && current_user == true_user) || (current_user != true_user && current_account.testing?) %> <%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :post, html: { class: 'w-full' } do |f| %> diff --git a/app/views/shared/_turbo_drawer.html.erb b/app/views/shared/_turbo_drawer.html.erb index c8928aeee..eaa9418c6 100644 --- a/app/views/shared/_turbo_drawer.html.erb +++ b/app/views/shared/_turbo_drawer.html.erb @@ -1,5 +1,5 @@ <% if turbo_native_app? && !turbo_frame_request? %> - <% content_for(:html_title, local_assigns[:title]) if local_assigns[:title] %> + <% content_for(:native_title, local_assigns[:title]) if local_assigns[:title] %> <%= yield %> diff --git a/app/views/shared/_turbo_modal.html.erb b/app/views/shared/_turbo_modal.html.erb index 8edb991d2..f7af42efa 100644 --- a/app/views/shared/_turbo_modal.html.erb +++ b/app/views/shared/_turbo_modal.html.erb @@ -1,6 +1,6 @@ <% if turbo_native_ios? || (turbo_native_app? && !turbo_frame_request?) %> <% content_for(:native_modal, 'true') %> - <% content_for(:html_title, local_assigns[:title]) if local_assigns[:title] %> + <% content_for(:native_title, local_assigns[:title]) if local_assigns[:title] %> <%= yield %> diff --git a/app/views/shared/_turbo_modal_large.html.erb b/app/views/shared/_turbo_modal_large.html.erb index 96e4530ca..1ae76d786 100644 --- a/app/views/shared/_turbo_modal_large.html.erb +++ b/app/views/shared/_turbo_modal_large.html.erb @@ -18,7 +18,7 @@ <% elsif turbo_native_app? %> - <% content_for(:html_title, local_assigns[:title]) if local_assigns[:title] %> + <% content_for(:native_title, local_assigns[:title]) if local_assigns[:title] %> <%= yield %> diff --git a/app/views/sms_settings/index.html.erb b/app/views/sms_settings/index.html.erb index b865f4e16..6e7caa26b 100644 --- a/app/views/sms_settings/index.html.erb +++ b/app/views/sms_settings/index.html.erb @@ -5,5 +5,5 @@ SMS <%= render 'placeholder' %> - + diff --git a/app/views/sso_settings/index.html.erb b/app/views/sso_settings/index.html.erb index fb13a57fd..5b911da70 100644 --- a/app/views/sso_settings/index.html.erb +++ b/app/views/sso_settings/index.html.erb @@ -5,5 +5,5 @@ SAML SSO <%= render 'placeholder' %> - + diff --git a/app/views/storage_settings/index.html.erb b/app/views/storage_settings/index.html.erb index b459f1f7f..6d4e8f346 100644 --- a/app/views/storage_settings/index.html.erb +++ b/app/views/storage_settings/index.html.erb @@ -7,7 +7,7 @@ <% value = @encrypted_config.value || { 'service' => 'disk' } %> <% configs = value['configs'] || {} %> - <%= form_for @encrypted_config, url: settings_storage_index_path, method: :post, html: { autocomplete: 'off', class: 'w-full' } do |f| %> + <%= form_for @encrypted_config, url: settings_storage_index_path, method: :post, html: { autocomplete: 'off', class: 'w-full' }, data: { turbo_action: 'advance' } do |f| %> <% options = [%w[Disk disk], %w[AWS aws_s3], %w[GCP google], %w[Azure azure]] %> @@ -36,5 +36,5 @@ <% end %> - + diff --git a/app/views/submissions/show.html.erb b/app/views/submissions/show.html.erb index f1c861178..f4b249a9f 100644 --- a/app/views/submissions/show.html.erb +++ b/app/views/submissions/show.html.erb @@ -111,20 +111,21 @@ <% end %> <% elsif @submission.submitters.to_a.size == 1 && !@submission.expired? && !@submission.submitters.to_a.first.declined_at? && !@submission.archived_at? && !@submission.template&.archived_at? %> - + <%= render 'shared/clipboard_copy', text: submit_form_url(slug: @submission.submitters.to_a.first.slug, host: form_link_host), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy_share_link'), copied_title: t('copied_to_clipboard') %> <% end %> <% end %> - + <% values = @submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %> <% service_url_time = ActiveStorage::Attachment.service_url_time %> <% attachments_index = ActiveStorage::Attachment.where(record: @submission.submitters, name: :attachments).preload(:blob).index_by(&:uuid) %> <% unless native_tab == 'recipients' %> + <% schema = Submissions.filtered_conditions_schema(@submission, values:) %> + <% unless turbo_native_app? %> - <% schema = Submissions.filtered_conditions_schema(@submission, values:) %> <% schema.each do |item| %> <% document = @submission.schema_documents.find { |a| item['attachment_uuid'] == a.uuid } %> <% if document.preview_images.first %> @@ -137,6 +138,7 @@ <% end %> <% end %> + <% end %> <% fields_index = Templates.build_field_areas_index(@submission.template_fields || @submission.template.fields) %> @@ -193,7 +195,7 @@ <% end %> <% unless native_tab == 'document' %> - + <% colors = %w[bg-red-500 bg-sky-500 bg-emerald-500 bg-yellow-300 bg-purple-600 bg-pink-500 bg-cyan-500 bg-orange-500 bg-lime-500 bg-indigo-500] %> <% submitter_fields_index = (@submission.template_fields || @submission.template.fields).group_by { |f| f['submitter_uuid'] } %> <% submitter_field_counters = Hash.new { 0 } %> diff --git a/app/views/submissions_archived/index.html.erb b/app/views/submissions_archived/index.html.erb index 96651a2a6..6e8529b3a 100644 --- a/app/views/submissions_archived/index.html.erb +++ b/app/views/submissions_archived/index.html.erb @@ -11,7 +11,7 @@ <% end %> <% end %> - + <% unless turbo_native_app? %> diff --git a/app/views/submissions_dashboard/index.html.erb b/app/views/submissions_dashboard/index.html.erb index b28ac3b0d..bf5fbe842 100644 --- a/app/views/submissions_dashboard/index.html.erb +++ b/app/views/submissions_dashboard/index.html.erb @@ -43,7 +43,7 @@ <% end %> <% if is_show_tabs %> - + <%= svg_icon('list', class: 'w-5 h-5') %> diff --git a/app/views/submissions_filters/_applied_filters.html.erb b/app/views/submissions_filters/_applied_filters.html.erb index 3d9caf689..32175f020 100644 --- a/app/views/submissions_filters/_applied_filters.html.erb +++ b/app/views/submissions_filters/_applied_filters.html.erb @@ -6,7 +6,7 @@ <% status_icons = { 'all' => 'list', 'pending' => 'clock', 'completed' => 'circle_check' } %> <% current_status = status_icons.key?(params[:status].to_s) ? params[:status].to_s : 'all' %> <% status_icon = { 'declined' => 'x_circle', 'expired' => 'clock_cancel', 'partially_completed' => 'clock_edit', 'sent' => 'send', 'opened' => 'mail_opened' }[params[:status]] %> -<% default_status_icon = status_icons[params[:status]] if status_icon.blank? && local_assigns[:with_default_status] && current_status != 'all' %> +<% default_status_icon = status_icons[params[:status]] if status_icon.blank? && local_assigns[:with_default_status] && current_status != 'all' && !turbo_native_app? %> <% with_status_button = local_assigns[:with_status] && status_icon.blank? %> <% status_chip_html = capture do %> <% if status_icon || default_status_icon %> @@ -105,7 +105,7 @@ <% end %> <% with_status_dropdown = with_status_button && chips_html.blank? %> <% if with_status_dropdown %> - + <%= svg_icon(status_icons[current_status], class: 'w-5 h-5 shrink-0') %> <%= t(current_status) %> @@ -157,7 +157,7 @@ <% if chips_html.present? || status_chip_html.present? %> <% if with_status_button && !with_status_dropdown %> - + <%= svg_icon(status_icons[current_status], class: 'w-5 h-5 shrink-0') %> <%= t(current_status) %> <%= svg_icon('chevron_down', class: 'w-4 h-4 shrink-0') %> diff --git a/app/views/template_folders/show.html.erb b/app/views/template_folders/show.html.erb index abd43b214..592f58985 100644 --- a/app/views/template_folders/show.html.erb +++ b/app/views/template_folders/show.html.erb @@ -2,6 +2,16 @@ <% if turbo_native_app? %> <% end %> +<% if turbo_native_app? && can?(:update, @template_folder) && @template_folder.full_name != TemplateFolder::DEFAULT_NAME %> + + + <%= form_for @template_folder, url: folder_path(@template_folder), html: { class: 'hidden', id: nil } do |f| %> + <%= f.label :name, t('folder_name'), for: name_input_id = SecureRandom.uuid %> + <%= f.hidden_field :name, id: name_input_id %> + <% end %> + + +<% end %> <% if turbo_native_app? && (can?(:manage, :countless) || current_account.templates.where.not(archived_at: nil).exists?) %> <%= t('view_archived') %> diff --git a/app/views/templates/_submission.html.erb b/app/views/templates/_submission.html.erb index fa59054b9..095afd902 100644 --- a/app/views/templates/_submission.html.erb +++ b/app/views/templates/_submission.html.erb @@ -1,7 +1,7 @@ - + <% if local_assigns[:with_template] %> <% template = submission.template %> - + <%= svg_icon('file_text', class: 'w-4 h-4 mt-0.5 flex-shrink-0') %> @@ -11,7 +11,7 @@ - + <%= svg_icon('user', class: 'w-4 h-4 flex-shrink-0') %> diff --git a/app/views/templates/_title.html.erb b/app/views/templates/_title.html.erb index b8e328c5e..81cd33d95 100644 --- a/app/views/templates/_title.html.erb +++ b/app/views/templates/_title.html.erb @@ -7,9 +7,9 @@ <% if turbo_native_app? && !template.archived_at? && can?(:update, template) %> - <%= form_for template, url: template_path(template), method: :put, html: { class: 'hidden' } do |f| %> - <%= f.label :name, t('name') %> - <%= f.hidden_field :name %> + <%= form_for template, url: template_path(template), method: :put, html: { class: 'hidden', id: nil } do |f| %> + <%= f.label :name, t('name'), for: name_input_id = SecureRandom.uuid %> + <%= f.hidden_field :name, id: name_input_id %> <% end %> <%= name_html %> @@ -38,7 +38,7 @@ <% if can?(:update, template) %> - + <%= svg_icon('pencil_share', class: 'w-5 h-5') %> @@ -61,7 +61,7 @@ <% if !template.archived_at? %> - <% if can?(:update, template) %> + <% if can?(:update, template) && !turbo_native_app? %> <%= link_to template_preferences_path(template), class: 'btn border border-base-200 bg-base-200 hover:bg-base-300 hover:border-base-300 btn-sm flex-1 hidden md:flex', data: { turbo_frame: :modal } do %> diff --git a/app/views/templates/show.html.erb b/app/views/templates/show.html.erb index 13e209950..7ee8567cb 100644 --- a/app/views/templates/show.html.erb +++ b/app/views/templates/show.html.erb @@ -42,7 +42,7 @@ <% end %> <% if is_show_tabs %> - + <%= svg_icon('list', class: 'w-5 h-5') %> diff --git a/app/views/templates_archived_submissions/index.html.erb b/app/views/templates_archived_submissions/index.html.erb index 2f5456b0f..4afa4f796 100644 --- a/app/views/templates_archived_submissions/index.html.erb +++ b/app/views/templates_archived_submissions/index.html.erb @@ -1,5 +1,5 @@ <% content_for(:native_title, t('archived')) if turbo_native_app? %> -<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %> +<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS - %w[template_id]).compact_blank %> <% if turbo_native_app? %> <% end %> @@ -13,7 +13,7 @@ <% end %> <% end %> - + <% unless turbo_native_app? %> @@ -26,7 +26,7 @@ <% end %> - + <%= render 'submissions_filters/applied_filters', filter_params:, with_status: true, with_default_status: true %> <% unless turbo_native_app? %> diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index 769a273ac..224379fcc 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -40,7 +40,7 @@ <% end %> - + <% @users.each do |user| %> <% can_edit = params[:status].blank? && can?(:update, user) && user.archived_at.blank? %> <% can_remove = params[:status] != 'archived' && can?(:destroy, user) && user != current_user %> @@ -104,71 +104,73 @@ <% end %> - - - - - - <%= t('name') %> - - - <%= t('email') %> - - - <%= t('role') %> - - - <%= t('last_session') %> - - - - - - - <% @users.each do |user| %> - - - <%= user.full_name %> - - - <% if user.try(:pending_reconfirmation?) %> - <%= user.unconfirmed_email %> - - (<%= t('unconfirmed') %>) - <% else %> - <%= user.email %> - <% end %> - - - - <%= t(user.role) %> - - - - <%= user.current_sign_in_at ? l(user.current_sign_in_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) : '-' %> - - - <% if params[:status].blank? && can?(:update, user) && user.archived_at.blank? %> - <%= link_to edit_user_path(user), class: 'btn btn-outline btn-xs', title: t('edit'), data: { turbo_frame: 'modal' } do %> - <%= t('edit') %> - <% end %> - <% end %> - <% if params[:status] != 'archived' && can?(:destroy, user) && user != current_user %> - <%= button_to user_path(user), method: :delete, class: 'btn btn-outline btn-error btn-xs', title: t('remove'), data: { turbo_confirm: t('are_you_sure_') } do %> - <%= t('remove') %> - <% end %> - <% end %> - <% if params[:status] == 'archived' && can?(:create, user) && user != current_user && user.archived_at? %> - <%= button_to user_path(user), method: :put, params: { user: { archived_at: nil } }, class: 'btn btn-outline btn-xs', title: t('unarchive'), data: { turbo_confirm: t('are_you_sure_') } do %> - <%= t('unarchive') %> - <% end %> - <% end %> - + <% unless turbo_native_app? %> + + + + + + <%= t('name') %> + + + <%= t('email') %> + + + <%= t('role') %> + + + <%= t('last_session') %> + + + - <% end %> - - - + + + <% @users.each do |user| %> + + + <%= user.full_name %> + + + <% if user.try(:pending_reconfirmation?) %> + <%= user.unconfirmed_email %> + + (<%= t('unconfirmed') %>) + <% else %> + <%= user.email %> + <% end %> + + + + <%= t(user.role) %> + + + + <%= user.current_sign_in_at ? l(user.current_sign_in_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) : '-' %> + + + <% if params[:status].blank? && can?(:update, user) && user.archived_at.blank? %> + <%= link_to edit_user_path(user), class: 'btn btn-outline btn-xs', title: t('edit'), data: { turbo_frame: 'modal' } do %> + <%= t('edit') %> + <% end %> + <% end %> + <% if params[:status] != 'archived' && can?(:destroy, user) && user != current_user %> + <%= button_to user_path(user), method: :delete, class: 'btn btn-outline btn-error btn-xs', title: t('remove'), data: { turbo_confirm: t('are_you_sure_') } do %> + <%= t('remove') %> + <% end %> + <% end %> + <% if params[:status] == 'archived' && can?(:create, user) && user != current_user && user.archived_at? %> + <%= button_to user_path(user), method: :put, params: { user: { archived_at: nil } }, class: 'btn btn-outline btn-xs', title: t('unarchive'), data: { turbo_confirm: t('are_you_sure_') } do %> + <%= t('unarchive') %> + <% end %> + <% end %> + + + <% end %> + + + + <% end %> <% if @pagy.pages > 1 %> <%= render 'shared/pagination', pagy: @pagy, items_name: 'users', left_additional_html: render('bottom_links') %> <% else %> diff --git a/app/views/webhook_events/show.html.erb b/app/views/webhook_events/show.html.erb index 733af9e58..c01beefa3 100644 --- a/app/views/webhook_events/show.html.erb +++ b/app/views/webhook_events/show.html.erb @@ -1,5 +1,5 @@ <%= render 'shared/turbo_drawer', title: @webhook_event.event_type, close_after_submit: false do %> - + <%= render 'drawer_events', webhook_url: @webhook_url, webhook_event: @webhook_event %> <% if @data %> diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index 3b2efb870..7ac1fdb94 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -2840,7 +2840,7 @@ it: &it send: Invia send_to_recipients: Invia ai destinatari sign_it_yourself: Firmalo tu stesso - sign_yourself: Firma te stesso + sign_yourself: Firma tu stesso revisions: Revisioni no_revisions_yet: Nessuna revisione ancora api_and_embedding: API e Incorporazione diff --git a/tailwind.config.js b/tailwind.config.js index e3d5576fa..d6c5b9bf9 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,7 +1,4 @@ module.exports = { - future: { - hoverOnlyWhenSupported: true - }, plugins: [ require('daisyui') ], diff --git a/tailwind.dynamic.config.js b/tailwind.dynamic.config.js index 2058c30f0..eb7851f46 100644 --- a/tailwind.dynamic.config.js +++ b/tailwind.dynamic.config.js @@ -1,9 +1,6 @@ const path = require('path') module.exports = { - future: { - hoverOnlyWhenSupported: true - }, content: [ path.resolve(__dirname, 'app/javascript/template_builder/dynamic_area.vue'), path.resolve(__dirname, 'app/javascript/template_builder/dynamic_section.vue')