Compare commits

...
13 Commits
Author SHA1 Message Date
Alex Turchyn 3c65b0e4ba Merge from docusealco/wip 2026-10-12 15:29:17 +03:00
Pete Matsyburka ced5ab67ab adjust generate previews 2026-10-12 09:33:42 +03:00
Pete Matsyburka 28c4177128 adjsut detailed send button 2026-10-11 17:52:54 +03:00
Pete Matsyburka 5d86a08b5f update send modal 2026-10-10 15:57:08 +03:00
Pete Matsyburka a69fb81429 fix multiselect 2026-10-09 12:57:12 +03:00
Pete Matsyburka e6ac3380d4 dynamic editor image drag 2026-10-09 12:25:21 +03:00
Pete Matsyburka 3a535e7112 adjust date timezone 2026-10-08 16:06:41 +03:00
Pete Matsyburka 7464f9cfad download event log 2026-10-08 10:04:12 +03:00
Pete Matsyburka 58e24ef758 update submitter slug 2026-10-08 10:04:01 +03:00
Pete Matsyburka c1d809f989 update demo 2026-10-07 09:00:58 +03:00
Pete Matsyburka 80b24f0e2a show signature when completed 2026-10-06 12:12:17 +03:00
Pete Matsyburka 2e253904a3 invalid email error 2026-10-06 11:01:04 +03:00
Pete Matsyburka a7f381caf0 update gem 2026-10-05 12:31:08 +03:00
39 changed files with 431 additions and 140 deletions

No files matched your search

+5 -5
View File
@@ -524,11 +524,11 @@ GEM
simplecov-html (0.13.2) simplecov-html (0.13.2)
simplecov_json_formatter (0.1.4) simplecov_json_formatter (0.1.4)
smart_properties (1.17.0) smart_properties (1.17.0)
sqlite3 (2.9.5-aarch64-linux-gnu) sqlite3 (2.9.6-aarch64-linux-gnu)
sqlite3 (2.9.5-aarch64-linux-musl) sqlite3 (2.9.6-aarch64-linux-musl)
sqlite3 (2.9.5-arm64-darwin) sqlite3 (2.9.6-arm64-darwin)
sqlite3 (2.9.5-x86_64-linux-gnu) sqlite3 (2.9.6-x86_64-linux-gnu)
sqlite3 (2.9.5-x86_64-linux-musl) sqlite3 (2.9.6-x86_64-linux-musl)
strip_attributes (2.0.1) strip_attributes (2.0.1)
activemodel (>= 3.0, < 9.0) activemodel (>= 3.0, < 9.0)
strscan (3.1.8) strscan (3.1.8)
+6 -1
View File
@@ -118,7 +118,12 @@ class ApplicationController < ActionController::Base
end end
def sign_in_for_demo def sign_in_for_demo
sign_in(User.active.order('random()').take) unless signed_in? return if signed_in?
account = Account.create!(name: "Demo #{SecureRandom.hex(3)}")
sign_in(account.users.create!(first_name: 'Demo', last_name: 'User',
email: "#{SecureRandom.hex}@docuseal.com", password: SecureRandom.hex))
end end
def current_account def current_account
@@ -66,6 +66,8 @@ class SubmissionsController < ApplicationController
create_submissions(@template, submissions_params, params) create_submissions(@template, submissions_params, params)
end end
raise Submissions::CreateFromSubmitters::BaseError, 'Invalid email address' if submissions.blank?
WebhookUrls.enqueue_events(submissions, 'submission.created') WebhookUrls.enqueue_events(submissions, 'submission.created')
Submissions.send_signature_requests(submissions) Submissions.send_signature_requests(submissions)
+17
View File
@@ -81,9 +81,26 @@ class SubmittersController < ApplicationController
submitter.name = attrs[:name] if attrs.key?(:name) submitter.name = attrs[:name] if attrs.key?(:name)
if submitter_changed?(submitter)
submitter.submitter_versions.new(slug: submitter.slug, email: submitter.email_was,
name: submitter.name_was, phone: submitter.phone_was)
submitter.slug = SecureRandom.base58(14)
submitter.values = Submitters.fetch_values_for_delegate(submitter)
end
submitter submitter
end end
def submitter_changed?(submitter)
if submitter.email_was.blank? && submitter.phone_was.blank?
submitter.name_was.present? && submitter.name_changed?
else
(submitter.email_was.present? && submitter.email_changed?) ||
(submitter.phone_was.present? && submitter.phone_changed?)
end
end
def submitter_params def submitter_params
params.require(:submitter).permit(:email, :name, :phone).transform_values(&:strip) params.require(:submitter).permit(:email, :name, :phone).transform_values(&:strip)
end end
+6
View File
@@ -35,6 +35,9 @@ import PromptPassword from './elements/prompt_password'
import EmailsTextarea from './elements/emails_textarea' import EmailsTextarea from './elements/emails_textarea'
import ToggleSubmit from './elements/toggle_submit' import ToggleSubmit from './elements/toggle_submit'
import ToggleOnSubmit from './elements/toggle_on_submit' import ToggleOnSubmit from './elements/toggle_on_submit'
import ToggleOnChecked from './elements/toggle_on_checked'
import ToggleSendTitle from './elements/toggle_send_title'
import AppendTemplate from './elements/append_template'
import CheckOnClick from './elements/check_on_click' import CheckOnClick from './elements/check_on_click'
import PasswordInput from './elements/password_input' import PasswordInput from './elements/password_input'
import SearchInput from './elements/search_input' import SearchInput from './elements/search_input'
@@ -159,6 +162,9 @@ safeRegisterElement('emails-textarea', EmailsTextarea)
safeRegisterElement('toggle-cookies', ToggleCookies) safeRegisterElement('toggle-cookies', ToggleCookies)
safeRegisterElement('toggle-submit', ToggleSubmit) safeRegisterElement('toggle-submit', ToggleSubmit)
safeRegisterElement('toggle-on-submit', ToggleOnSubmit) safeRegisterElement('toggle-on-submit', ToggleOnSubmit)
safeRegisterElement('toggle-on-checked', ToggleOnChecked)
safeRegisterElement('toggle-send-title', ToggleSendTitle)
safeRegisterElement('append-template', AppendTemplate)
safeRegisterElement('password-input', PasswordInput) safeRegisterElement('password-input', PasswordInput)
safeRegisterElement('search-input', SearchInput) safeRegisterElement('search-input', SearchInput)
safeRegisterElement('toggle-attribute', ToggleAttribute) safeRegisterElement('toggle-attribute', ToggleAttribute)
@@ -0,0 +1,29 @@
export default class extends HTMLElement {
connectedCallback () {
this.addEventListener('click', this.onClick)
}
disconnectedCallback () {
this.removeEventListener('click', this.onClick)
}
onClick = (e) => {
e.preventDefault()
const target = document.getElementById(this.dataset.targetId)
const item = this.querySelector('template').content.firstElementChild.cloneNode(true)
item.querySelector('a').addEventListener('click', (e) => {
e.preventDefault()
item.remove()
target.classList.toggle('hidden', !target.childElementCount)
})
target.append(item)
target.classList.remove('hidden')
item.querySelector('input').focus()
}
}
+18 -1
View File
@@ -2,7 +2,7 @@ import { actionable } from '@github/catalyst/lib/actionable'
import { targets, targetable } from '@github/catalyst/lib/targetable' import { targets, targetable } from '@github/catalyst/lib/targetable'
export default actionable(targetable(class extends HTMLElement { export default actionable(targetable(class extends HTMLElement {
static [targets.static] = ['items'] static [targets.static] = ['items', 'groups']
addItem (e) { addItem (e) {
e.preventDefault() e.preventDefault()
@@ -24,11 +24,28 @@ export default actionable(targetable(class extends HTMLElement {
duplicateItem.querySelectorAll('a.hidden').forEach((button) => button.classList.toggle('hidden')) duplicateItem.querySelectorAll('a.hidden').forEach((button) => button.classList.toggle('hidden'))
originalItem.parentNode.append(duplicateItem) originalItem.parentNode.append(duplicateItem)
this.toggleMultipleClasses()
duplicateItem.querySelector("input:not([type='hidden'])")?.focus()
} }
removeItem (e) { removeItem (e) {
e.preventDefault() e.preventDefault()
this.items.find((item) => item.contains(e.target))?.remove() this.items.find((item) => item.contains(e.target))?.remove()
this.toggleMultipleClasses()
}
toggleMultipleClasses () {
const isMultiple = this.items.length > 1
const elements = [...this.items, ...this.groups]
elements.forEach((element) => {
const classes = element.dataset.multipleClasses || this.dataset.multipleClasses
classes?.split(' ').forEach((cls) => element.classList.toggle(cls, isMultiple))
})
} }
})) }))
@@ -0,0 +1,23 @@
export default class extends HTMLElement {
connectedCallback () {
this.form.addEventListener('change', this.toggle)
this.toggle()
}
disconnectedCallback () {
this.form.removeEventListener('change', this.toggle)
}
toggle = () => {
const selector = this.dataset.names.split(' ').map((name) => `[name="${name}"]:checked:enabled`).join(', ')
const isChecked = !!this.form.querySelector(selector)
this.firstElementChild.classList.toggle('hidden', !isChecked)
this.lastElementChild.classList.toggle('hidden', isChecked)
}
get form () {
return this.closest('form')
}
}
@@ -0,0 +1,43 @@
export default class extends HTMLElement {
connectedCallback () {
this.events.forEach((event) => this.form.addEventListener(event, this.toggle))
this.toggle()
}
disconnectedCallback () {
this.events.forEach((event) => this.form.removeEventListener(event, this.toggle))
}
toggle = (event) => {
if (event?.type === 'input' && event.target.matches(this.fieldSelector('name'))) return
const isNameOnly = (field) => this.isFilled('name') && !this.isFilled(field)
const isSend = (this.isChecked('send_email') && !isNameOnly('email')) ||
(this.isChecked('send_sms') && !isNameOnly('phone'))
this.firstElementChild.classList.toggle('hidden', !isSend)
this.lastElementChild.classList.toggle('hidden', isSend)
}
isChecked (name) {
return !!this.form.querySelector(`[name="${name}"]:checked:enabled`)
}
isFilled (field) {
return [...this.form.querySelectorAll(this.fieldSelector(field))].some((input) => input.value.trim())
}
fieldSelector (field) {
return `[name$="[][${field}]"]`
}
get events () {
return ['change', 'input', 'linked-input.update']
}
get form () {
return this.closest('form')
}
}
+3
View File
@@ -24,6 +24,9 @@ const ICONS = {
user: 'person', user: 'person',
users: 'person.2', users: 'person.2',
users_plus: 'person.badge.plus', users_plus: 'person.badge.plus',
user_plus: 'person.badge.plus',
eye_plus: 'eye',
mail_plus: 'envelope',
photo: 'photo', photo: 'photo',
settings: 'gearshape', settings: 'gearshape',
logout: 'rectangle.portrait.and.arrow.right', logout: 'rectangle.portrait.and.arrow.right',
+1 -3
View File
@@ -319,7 +319,7 @@ export default {
default: false default: false
} }
}, },
emits: ['start-resize', 'stop-resize', 'start-drag', 'stop-drag', 'remove', 'scroll-to', 'add-custom-field', 'click-title', 'multi-select'], emits: ['start-resize', 'stop-resize', 'start-drag', 'stop-drag', 'remove', 'scroll-to', 'add-custom-field', 'click-title'],
data () { data () {
return { return {
isContenteditable: false, isContenteditable: false,
@@ -798,8 +798,6 @@ export default {
this.selectedAreasRef.value.splice(this.selectedAreasRef.value.indexOf(this.area), 1) this.selectedAreasRef.value.splice(this.selectedAreasRef.value.indexOf(this.area), 1)
} }
this.$emit('multi-select', e)
return return
} }
+16 -3
View File
@@ -3,8 +3,10 @@
ref="dragContainer" ref="dragContainer"
style="max-width: 1600px" style="max-width: 1600px"
:class="nativeView ? (isSheetView ? 'bg-base-100' : 'px-4') : 'mx-auto pl-3 md:pl-4 h-full'" :class="nativeView ? (isSheetView ? 'bg-base-100' : 'px-4') : 'mx-auto pl-3 md:pl-4 h-full'"
@dragstart="isInternalDrag = true"
@dragend="isInternalDrag = false"
@dragover="onDragover" @dragover="onDragover"
@drop="isDragFile = false" @drop="[isDragFile = false, isInternalDrag = false]"
> >
<HoverDropzone <HoverDropzone
v-if="sortedDocuments.length && withUploadButton && editable && !isSheetView" v-if="sortedDocuments.length && withUploadButton && editable && !isSheetView"
@@ -1390,6 +1392,7 @@ export default {
drawOption: null, drawOption: null,
dragField: null, dragField: null,
isDragFile: false, isDragFile: false,
isInternalDrag: false,
isMathLoaded: false, isMathLoaded: false,
isRevisionsModalOpen: false, isRevisionsModalOpen: false,
isDocumentsDrawerOpen: false, isDocumentsDrawerOpen: false,
@@ -2039,8 +2042,18 @@ export default {
ref.x = e.clientX - ref.offsetX ref.x = e.clientX - ref.offsetX
ref.y = e.clientY - ref.offsetY ref.y = e.clientY - ref.offsetY
} else if (e.dataTransfer?.types?.includes('Files') && !this.editModalDocumentUuid) { } else if (e.dataTransfer?.types?.includes('Files') && !this.editModalDocumentUuid && !this.isInternalDrag) {
this.isDragFile = true const items = Array.from(e.dataTransfer.items)
if (this.dynamicDocuments.length && items.length && items.every((item) => item.type.startsWith('image/'))) {
if (!e.defaultPrevented) {
e.preventDefault()
e.dataTransfer.dropEffect = 'none'
}
} else {
this.isDragFile = true
}
} }
}, },
onWindowDragLeave (event) { onWindowDragLeave (event) {
@@ -639,6 +639,20 @@ export default {
if (!file) return if (!file) return
const image = await this.uploadImage(file)
if (image) {
this.editor.chain().focus().insertContent(image).run()
}
},
async insertImageFiles (files, pos) {
const images = (await Promise.all(files.map(this.uploadImage))).filter(Boolean)
if (images.length) {
this.editor.chain().focus().insertContentAt(pos, images).run()
}
},
async uploadImage (file) {
const formData = new FormData() const formData = new FormData()
formData.append('file', file) formData.append('file', file)
@@ -656,9 +670,7 @@ export default {
await this.$nextTick() await this.$nextTick()
this.editor.chain().focus() return { type: 'image', attrs: { htmlAttrs: { src: `blob:${data.uuid}`, style: 'max-width: 100%' } } }
.insertContent({ type: 'image', attrs: { htmlAttrs: { src: `blob:${data.uuid}`, style: 'max-width: 100%' } } })
.run()
}, },
setDynamicMenuCoords (editor) { setDynamicMenuCoords (editor) {
const { from, to } = editor.state.selection const { from, to } = editor.state.selection
@@ -975,7 +987,7 @@ export default {
onSelectedAreaChange () { onSelectedAreaChange () {
this.save() this.save()
}, },
onFieldDrop (view, event, _slice, moved) { onFieldDrop (view, event, slice, moved) {
this.isAreaDrag = false this.isAreaDrag = false
if (moved) { if (moved) {
@@ -983,8 +995,9 @@ export default {
} }
const draggedField = this.fieldsDragFieldRef?.value || this.customDragFieldRef?.value || this.dragField const draggedField = this.fieldsDragFieldRef?.value || this.customDragFieldRef?.value || this.dragField
const imageFiles = slice.size ? [] : Array.from(event.dataTransfer.files).filter((file) => file.type.startsWith('image/'))
if (!draggedField) return false if (!draggedField && !imageFiles.length) return false
event.preventDefault() event.preventDefault()
@@ -992,14 +1005,18 @@ export default {
if (!pos) return false if (!pos) return false
this.insertFieldAtRange({ if (draggedField) {
sourceField: draggedField, this.insertFieldAtRange({
existingField: this.fieldsDragFieldRef?.value, sourceField: draggedField,
from: pos.pos existingField: this.fieldsDragFieldRef?.value,
}) from: pos.pos
})
this.fieldsDragFieldRef.value = null this.fieldsDragFieldRef.value = null
this.customDragFieldRef.value = null this.customDragFieldRef.value = null
} else {
this.insertImageFiles(imageFiles, pos.pos)
}
return true return true
}, },
-8
View File
@@ -57,7 +57,6 @@
@scroll-to="$emit('scroll-to', $event)" @scroll-to="$emit('scroll-to', $event)"
@add-custom-field="$emit('add-custom-field', $event)" @add-custom-field="$emit('add-custom-field', $event)"
@contextmenu="openAreaContextMenu($event, item.area, item.field)" @contextmenu="openAreaContextMenu($event, item.area, item.field)"
@multi-select="openMultiSelectContextMenu"
@click-title="closeContextMenu" @click-title="closeContextMenu"
/> />
<FieldArea <FieldArea
@@ -450,13 +449,6 @@ export default {
areas: this.selectedAreasRef.value areas: this.selectedAreasRef.value
} }
}, },
openMultiSelectContextMenu (event) {
if (this.selectedAreasRef.value.length >= 2) {
this.openSelectionContextMenu(event)
} else {
this.closeContextMenu()
}
},
handleSelectionCopy () { handleSelectionCopy () {
this.$emit('copy-selected-areas') this.$emit('copy-selected-areas')
+1 -1
View File
@@ -196,7 +196,7 @@
<% end %> <% end %>
<% end %> <% end %>
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::DOWNLOAD_LINKS_EXPIRE_KEY) %> <% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::DOWNLOAD_LINKS_EXPIRE_KEY) %>
<% if can?(:manage, account_config) %> <% if !Docuseal.demo? && can?(:manage, account_config) %>
<%= form_for account_config, url: account_configs_path, method: :post do |f| %> <%= form_for account_config, url: account_configs_path, method: :post do |f| %>
<%= f.hidden_field :key %> <%= f.hidden_field :key %>
<div class="flex items-center justify-between gap-4 py-2.5"> <div class="flex items-center justify-between gap-4 py-2.5">
+7
View File
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M12 18c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" />
<path d="M16 19h6" />
<path d="M19 16v6" />
</svg>

After

Width:  |  Height:  |  Size: 460 B

+7
View File
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 19h-7a2 2 0 0 1 -2 -2v-10a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v5.5" />
<path d="M16 19h6" />
<path d="M19 16v6" />
<path d="M3 7l9 6l9 -6" />
</svg>

After

Width:  |  Height:  |  Size: 441 B

+2 -3
View File
@@ -1,8 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M5 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0" /> <path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0" />
<path d="M3 21v-2a4 4 0 0 1 4 -4h4c.96 0 1.84 .338 2.53 .901" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
<path d="M16 19h6" /> <path d="M16 19h6" />
<path d="M19 16v6" /> <path d="M19 16v6" />
<path d="M6 21v-2a4 4 0 0 1 4 -4h4" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 488 B

After

Width:  |  Height:  |  Size: 421 B

+1 -1
View File
@@ -3,7 +3,7 @@
<div class="w-full text-center"> <div class="w-full text-center">
<span class="font-bold"><%= t('demo_environment') %></span> <span class="font-bold"><%= t('demo_environment') %></span>
<br> <br>
<%= t('create_a_new_template_document_form_or_submit_the_existing_one_html', new_template_link: new_template_path, start_form_link: start_form_url(slug: ::Template.first&.slug)) %> 😊 <%= t('create_a_new_template_document_form_html', new_template_link: new_template_path) %>
</div> </div>
</div> </div>
</div> </div>
+1 -1
View File
@@ -12,7 +12,7 @@
<div> <div>
<p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.template&.name %></p> <p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.template&.name %></p>
<p dir="auto" class="text-sm"> <p dir="auto" class="text-sm">
<%= t(@submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(@submitter.completed_at.to_date, format: :long)) %> <%= t(@submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(@submitter.completed_at.in_time_zone(@submitter.account.timezone).to_date, format: :long)) %>
</p> </p>
</div> </div>
</div> </div>
+70
View File
@@ -0,0 +1,70 @@
<% fields_id = "add_new_fields_#{SecureRandom.hex(3)}" %>
<div id="<%= fields_id %>" class="space-y-3 mt-4 hidden" data-targets="dynamic-list.groups" data-multiple-classes="px-3 pt-3 pb-4 rounded-xl border border-dashed border-base-300"></div>
<native-menu class="<%= turbo_native_app? ? 'block' : 'dropdown block max-sm:hidden' %> mt-4 -mb-2">
<label tabindex="0" class="btn btn-ghost btn-sm normal-case font-normal w-full flex items-center justify-center border-dashed border-base-300 hover:border-base-content/20 !bg-transparent">
<%= svg_icon('users_plus', class: 'w-4 h-4 stroke-2') %>
<span><%= t('add_new') %></span>
</label>
<ul tabindex="0" class="<%= turbo_native_app? ? 'hidden' : 'z-10 dropdown-content p-2 mt-1 shadow menu bg-base-100 rounded-box' %>" style="left: 50%; --tw-translate-x: -50%">
<li>
<native-menu-action data-icon="user_plus" data-label="<%= t('recipients') %>" class="grid-cols-1 p-0">
<a href="#" class="flex items-center gap-2 px-4 py-2" data-action="click:dynamic-list#addItem">
<%= svg_icon('user_plus', class: 'w-4 h-4 stroke-2') %>
<span class="whitespace-nowrap"><%= t('recipients') %></span>
</a>
</native-menu-action>
</li>
<append-template class="contents" data-target-id="<%= fields_id %>">
<li>
<native-menu-action data-icon="eye_plus" data-label="<%= t('cc_view_only') %>" class="grid-cols-1 p-0">
<a href="#" class="flex items-center gap-2 px-4 py-2">
<%= svg_icon('eye_plus', class: 'w-4 h-4 stroke-2') %>
<span class="whitespace-nowrap"><%= t('cc_view_only') %></span>
</a>
</native-menu-action>
</li>
<template>
<submitter-item class="form-control">
<label class="label pt-0 pb-1">
<span class="label-text"><%= t('cc_view_only') %></span>
</label>
<div class="relative">
<submitters-autocomplete data-field="email">
<input type="email" name="cc_recipients[]" autocomplete="off" class="base-input !h-10 w-full pr-10" placeholder="<%= t('email') %>">
</submitters-autocomplete>
<a href="#" class="absolute right-2 top-2 block w-6 h-6 text-neutral-700 p-1 hover:text-base-content">
<%= svg_icon('trash', class: 'w-4 h-4') %>
</a>
</div>
</submitter-item>
</template>
</append-template>
<append-template class="contents" data-target-id="<%= fields_id %>">
<li>
<native-menu-action data-icon="mail_plus" data-label="<%= t('bcc_completed') %>" class="grid-cols-1 p-0">
<a href="#" class="flex items-center gap-2 px-4 py-2">
<%= svg_icon('mail_plus', class: 'w-4 h-4 stroke-2') %>
<span class="whitespace-nowrap"><%= t('bcc_completed') %></span>
</a>
</native-menu-action>
</li>
<template>
<div class="form-control">
<label class="label pt-0 pb-1">
<span class="label-text"><%= t('bcc_completed') %></span>
</label>
<div class="relative">
<input type="email" name="bcc_completed[]" autocomplete="off" class="base-input !h-10 w-full pr-10" placeholder="<%= t('email') %>">
<a href="#" class="absolute right-2 top-2 block w-6 h-6 text-neutral-700 p-1 hover:text-base-content">
<%= svg_icon('trash', class: 'w-4 h-4') %>
</a>
</div>
<% default_bcc_completed = template.preferences['bcc_completed'].presence || current_account.account_configs.find_by(key: AccountConfig::BCC_EMAILS)&.value %>
<% if default_bcc_completed.present? %>
<input type="hidden" name="bcc_completed[]" value="<%= default_bcc_completed %>">
<% end %>
</div>
</template>
</append-template>
</ul>
</native-menu>
+14 -12
View File
@@ -1,12 +1,12 @@
<% has_phone_field = false %> <% has_phone_field = false %>
<%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %> <%= form_for '', url: template_submissions_path(template), html: { autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %> <% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %>
<dynamic-list class="space-y-4"> <dynamic-list class="block" data-multiple-classes="px-3 <%= submitters.size > 1 ? 'pt-3 pb-4' : 'py-4' %> rounded-xl border border-dashed border-base-300">
<div class="space-y-4"> <div class="space-y-4">
<div class="relative <%= 'p-4 rounded-xl bg-base-300/40 max-sm:p-0 max-sm:bg-transparent' unless local_assigns[:variables_form] %>" data-targets="dynamic-list.items"> <div class="relative" data-targets="dynamic-list.items">
<div> <div>
<div class="absolute right-4 top-5"> <div class="absolute top-5" style="right: 12px">
<a href="#" data-action="click:dynamic-list#removeItem" class="<%= submitters.size == 1 ? 'right-2 top-1' : '-top-3' %> relative block w-6 h-6 rounded-lg text-neutral-700 text-center bg-base-300 p-1 hidden hover:bg-neutral hover:text-white"> <a href="#" data-action="click:dynamic-list#removeItem" class="<%= submitters.size == 1 ? 'right-2 top-1' : '-top-3' %> relative block w-6 h-6 text-neutral-700 p-1 hidden hover:text-base-content">
<%= svg_icon('trash', class: 'w-4 h-4') %> <%= svg_icon('trash', class: 'w-4 h-4') %>
</a> </a>
</div> </div>
@@ -86,14 +86,11 @@
</div> </div>
</div> </div>
<% if params[:selfsign].blank? && local_assigns[:prefillable_fields].blank? && local_assigns[:recipient_form_fields].blank? && local_assigns[:variables_form].blank? %> <% if params[:selfsign].blank? && local_assigns[:prefillable_fields].blank? && local_assigns[:recipient_form_fields].blank? && local_assigns[:variables_form].blank? %>
<a href="#" class="btn btn-primary btn-sm w-full flex items-center justify-center max-sm:hidden" data-action="click:dynamic-list#addItem"> <%= render 'add_new', template: %>
<%= svg_icon('user_plus', class: 'w-4 h-4 stroke-2') %>
<span><%= t('add_new') %></span>
</a>
<% end %> <% end %>
</dynamic-list> </dynamic-list>
<%= local_assigns[:variables_form] %> <%= local_assigns[:variables_form] %>
<div> <div class="mt-4">
<%= render('submitters_order', f:, template:) if can_send_emails %> <%= render('submitters_order', f:, template:) if can_send_emails %>
<%= render 'send_email', f:, template:, can_send_emails:, viewer_submitter_uuids: local_assigns[:viewer_submitter_uuids] %> <%= render 'send_email', f:, template:, can_send_emails:, viewer_submitter_uuids: local_assigns[:viewer_submitter_uuids] %>
<% if has_phone_field %> <% if has_phone_field %>
@@ -101,7 +98,12 @@
<% end %> <% end %>
<%= render 'extra_fields', f: %> <%= render 'extra_fields', f: %>
</div> </div>
<div class="form-control"> <div class="form-control mt-4">
<%= f.button button_title(title: t('add_recipients')), class: 'base-button' %> <%= f.button class: 'base-button' do %>
<toggle-send-title>
<span><%= button_title(title: t('send'), disabled_with: t('sending')) %></span>
<span class="hidden"><%= button_title(title: t('add_recipients')) %></span>
</toggle-send-title>
<% end %>
</div> </div>
<% end %> <% end %>
+15 -13
View File
@@ -1,4 +1,4 @@
<%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %> <%= form_for '', url: template_submissions_path(template), html: { autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %> <% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %>
<% if submitters.size == 1 && local_assigns[:variables_form] %> <% if submitters.size == 1 && local_assigns[:variables_form] %>
<% item = submitters.first %> <% item = submitters.first %>
@@ -26,18 +26,18 @@
<emails-textarea data-bulk-enabled="<%= Docuseal.demo? || !Docuseal.multitenant? || can?(:manage, :bulk_send) %>" data-limit="<%= Docuseal.multitenant? ? (can?(:manage, :bulk_send) ? 40 : 1) : nil %>"> <emails-textarea data-bulk-enabled="<%= Docuseal.demo? || !Docuseal.multitenant? || can?(:manage, :bulk_send) %>" data-limit="<%= Docuseal.multitenant? ? (can?(:manage, :bulk_send) ? 40 : 1) : nil %>">
<submitters-autocomplete data-field="email" class="block relative"> <submitters-autocomplete data-field="email" class="block relative">
<autoresize-textarea> <autoresize-textarea>
<%= f.text_area :emails, required: true, class: 'base-textarea w-full !text-lg', placeholder: "#{t('type_emails_here')}...", rows: 2 %> <%= f.text_area :emails, required: true, class: 'base-textarea block w-full !text-lg', placeholder: "#{t('type_emails_here')}...", rows: 2 %>
</autoresize-textarea> </autoresize-textarea>
</submitters-autocomplete> </submitters-autocomplete>
</emails-textarea> </emails-textarea>
</submitter-item> </submitter-item>
<% else %> <% else %>
<dynamic-list class="space-y-4"> <dynamic-list class="block" data-multiple-classes="px-3 pt-3 pb-4 rounded-xl border border-dashed border-base-300">
<div class="space-y-4"> <div class="space-y-4">
<div class="relative <%= 'p-4 rounded-xl bg-base-300/40 max-sm:p-0 max-sm:bg-transparent' unless local_assigns[:variables_form] %>" data-targets="dynamic-list.items"> <div class="relative" data-targets="dynamic-list.items">
<div> <div>
<div class="absolute right-4 top-5"> <div class="absolute top-5" style="right: 12px">
<a href="#" data-action="click:dynamic-list#removeItem" class="-top-3 relative block w-6 h-6 rounded-lg text-neutral-700 text-center bg-base-300 p-1 hidden hover:bg-neutral hover:text-white"> <a href="#" data-action="click:dynamic-list#removeItem" class="-top-3 relative block w-6 h-6 text-neutral-700 p-1 hidden hover:text-base-content">
<%= svg_icon('trash', class: 'w-4 h-4') %> <%= svg_icon('trash', class: 'w-4 h-4') %>
</a> </a>
</div> </div>
@@ -60,22 +60,24 @@
</div> </div>
</div> </div>
<% if params[:selfsign].blank? && local_assigns[:variables_form].blank? %> <% if params[:selfsign].blank? && local_assigns[:variables_form].blank? %>
<a href="#" class="btn btn-primary btn-sm w-full flex items-center justify-center max-sm:hidden" data-action="click:dynamic-list#addItem"> <%= render 'add_new', template: %>
<%= svg_icon('user_plus', class: 'w-4 h-4 stroke-2') %>
<span><%= t('add_new') %></span>
</a>
<% end %> <% end %>
</dynamic-list> </dynamic-list>
<% end %> <% end %>
<%= local_assigns[:variables_form] %> <%= local_assigns[:variables_form] %>
<div> <div class="mt-4">
<%= render('submitters_order', f:, template:) if can_send_emails %> <%= render('submitters_order', f:, template:) if can_send_emails %>
<%= render 'send_email', f:, template:, can_send_emails:, viewer_submitter_uuids: local_assigns[:viewer_submitter_uuids] %> <%= render 'send_email', f:, template:, can_send_emails:, viewer_submitter_uuids: local_assigns[:viewer_submitter_uuids] %>
<%= render 'extra_fields', f: %> <%= render 'extra_fields', f: %>
</div> </div>
<div class="form-control"> <div class="form-control mt-4">
<%= content_for(:submit_button) || capture do %> <%= content_for(:submit_button) || capture do %>
<%= f.button button_title(title: t('add_recipients')), class: 'base-button' %> <%= f.button class: 'base-button' do %>
<toggle-on-checked data-names="send_email send_sms">
<span><%= button_title(title: t('send'), disabled_with: t('sending')) %></span>
<span class="hidden"><%= button_title(title: t('add_recipients')) %></span>
</toggle-on-checked>
<% end %>
<% end %> <% end %>
</div> </div>
<% end %> <% end %>
+14 -12
View File
@@ -1,11 +1,11 @@
<%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %> <%= form_for '', url: template_submissions_path(template), html: { autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %> <% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %>
<dynamic-list class="space-y-4"> <dynamic-list class="block" data-multiple-classes="px-3 <%= submitters.size > 1 ? 'pt-3 pb-4' : 'py-4' %> rounded-xl border border-dashed border-base-300">
<div class="space-y-4"> <div class="space-y-4">
<div class="relative <%= 'p-4 rounded-xl bg-base-300/40 max-sm:p-0 max-sm:bg-transparent' unless local_assigns[:variables_form] %>" data-targets="dynamic-list.items"> <div class="relative" data-targets="dynamic-list.items">
<div> <div>
<div class="absolute right-4 top-5"> <div class="absolute top-5" style="right: 12px">
<a href="#" data-action="click:dynamic-list#removeItem" class="<%= submitters.size == 1 ? 'right-2 top-1' : '-top-3' %> relative block w-6 h-6 rounded-lg text-neutral-700 text-center bg-base-300 p-1 hidden hover:bg-neutral hover:text-white"> <a href="#" data-action="click:dynamic-list#removeItem" class="<%= submitters.size == 1 ? 'right-2 top-1' : '-top-3' %> relative block w-6 h-6 text-neutral-700 p-1 hidden hover:text-base-content">
<%= svg_icon('trash', class: 'w-4 h-4') %> <%= svg_icon('trash', class: 'w-4 h-4') %>
</a> </a>
</div> </div>
@@ -48,19 +48,21 @@
</div> </div>
</div> </div>
<% if params[:selfsign].blank? && local_assigns[:variables_form].blank? %> <% if params[:selfsign].blank? && local_assigns[:variables_form].blank? %>
<a href="#" class="btn btn-primary btn-sm w-full flex items-center justify-center max-sm:hidden" data-action="click:dynamic-list#addItem"> <%= render 'add_new', template: %>
<%= svg_icon('user_plus', class: 'w-4 h-4 stroke-2') %>
<span><%= t('add_new') %></span>
</a>
<% end %> <% end %>
</dynamic-list> </dynamic-list>
<%= local_assigns[:variables_form] %> <%= local_assigns[:variables_form] %>
<div> <div class="mt-4">
<%= render 'submitters_order', f:, template: %> <%= render 'submitters_order', f:, template: %>
<%= render 'send_sms', f:, checked: true %> <%= render 'send_sms', f:, checked: true %>
<%= render 'extra_phone_fields', f: %> <%= render 'extra_phone_fields', f: %>
</div> </div>
<div class="form-control"> <div class="form-control mt-4">
<%= f.button button_title(title: t('add_recipients')), class: 'base-button' %> <%= f.button class: 'base-button' do %>
<toggle-on-checked data-names="send_email send_sms">
<span><%= button_title(title: t('send'), disabled_with: t('sending')) %></span>
<span class="hidden"><%= button_title(title: t('add_recipients')) %></span>
</toggle-on-checked>
<% end %>
</div> </div>
<% end %> <% end %>
@@ -14,7 +14,7 @@
<toggle-visible data-element-ids="<%= [message_field_id].to_json %>" class="flex"> <toggle-visible data-element-ids="<%= [message_field_id].to_json %>" class="flex">
<label> <label>
<%= f.check_box :is_custom_message, checked: false, class: 'hidden peer', data: { action: 'change:toggle-visible#trigger', type: 'checkbox' } %> <%= f.check_box :is_custom_message, checked: false, class: 'hidden peer', data: { action: 'change:toggle-visible#trigger', type: 'checkbox' } %>
<span class="link peer-checked:hidden"><%= t('edit_message') %></span> <span class="link link-hover peer-checked:hidden"><%= t('edit_message') %></span>
</label> </label>
</toggle-visible> </toggle-visible>
<% end %> <% end %>
+5 -3
View File
@@ -1,5 +1,7 @@
<% unless turbo_native_ios? %> <% unless turbo_native_ios? %>
<div class="form-control"> <toggle-on-checked data-names="is_custom_message" class="block">
<%= render 'sms_settings/placeholder' %> <div class="form-control">
</div> <%= render 'sms_settings/placeholder' %>
</div>
</toggle-on-checked>
<% end %> <% end %>
+1 -1
View File
@@ -26,7 +26,7 @@
</div> </div>
</toggle-visible> </toggle-visible>
<% end %> <% end %>
<div class="px-5 mb-5 mt-4"> <div class="px-5 mb-5 mt-5">
<% unless only_detailed %> <% unless only_detailed %>
<div id="email" class="<%= 'hidden' if default_tab != 'email' %>"> <div id="email" class="<%= 'hidden' if default_tab != 'email' %>">
<%= render 'email_form', template: @template, variables_form:, can_send_emails:, viewer_submitter_uuids: %> <%= render 'email_form', template: @template, variables_form:, can_send_emails:, viewer_submitter_uuids: %>
+7 -1
View File
@@ -54,7 +54,8 @@
<% if @submission.submitters.to_a.any?(&:completed_at?) %> <% if @submission.submitters.to_a.any?(&:completed_at?) %>
<% show_combined_download = @submission.completed_at? && !is_combined_enabled %> <% show_combined_download = @submission.completed_at? && !is_combined_enabled %>
<% show_unarchive = signed_in? && can?(:destroy, @submission) && @submission.archived_at? && @submission.completed_at? %> <% show_unarchive = signed_in? && can?(:destroy, @submission) && @submission.archived_at? && @submission.completed_at? %>
<% show_download_dropdown = show_combined_download || show_unarchive %> <% show_event_log_download = signed_in? && can?(:manage, :event_log_download) && can?(:read, @submission) %>
<% show_download_dropdown = show_combined_download || show_event_log_download || show_unarchive %>
<% if @submission.completed_at? || !is_combined_enabled %> <% if @submission.completed_at? || !is_combined_enabled %>
<div class="join relative"> <div class="join relative">
<native-action data-label="<%= t('download') %>" data-icon="download"> <native-action data-label="<%= t('download') %>" data-icon="download">
@@ -95,6 +96,9 @@
</li> </li>
</native-action> </native-action>
<% end %> <% end %>
<% if show_event_log_download %>
<%= render 'submissions/download_event_log' %>
<% end %>
<% if show_unarchive %> <% if show_unarchive %>
<native-action data-label="<%= t('unarchive') %>" data-icon="rotate" data-section="archive"> <native-action data-label="<%= t('unarchive') %>" data-icon="rotate" data-section="archive">
<li> <li>
@@ -163,6 +167,7 @@
<% value = values[field['uuid']].presence || (field['default_value'] != '{{date}}' && field['readonly'] == true && field['conditions'].blank? && field['default_value'].present? ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %> <% value = values[field['uuid']].presence || (field['default_value'] != '{{date}}' && field['readonly'] == true && field['conditions'].blank? && field['default_value'].present? ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %>
<% value ||= field['default_value'] if field['type'] == 'heading' %> <% value ||= field['default_value'] if field['type'] == 'heading' %>
<% submitter = submitters_index[field['submitter_uuid']] %> <% submitter = submitters_index[field['submitter_uuid']] %>
<% value = nil if field['type'] == 'signature' && submitter && !submitter.completed_at? %>
<% if value.present? %> <% if value.present? %>
<% if (mask = field.dig('preferences', 'mask').presence) && signed_in? && can?(:read, @submission) %> <% if (mask = field.dig('preferences', 'mask').presence) && signed_in? && can?(:read, @submission) %>
<span class="group"> <span class="group">
@@ -320,6 +325,7 @@
<% value = values[field['uuid']].presence || (field['default_value'] != '{{date}}' && field['readonly'] == true && field['default_value'].present? ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %> <% value = values[field['uuid']].presence || (field['default_value'] != '{{date}}' && field['readonly'] == true && field['default_value'].present? ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %>
<% next if value.blank? %> <% next if value.blank? %>
<% next if field['type'] == 'heading' || field['type'] == 'strikethrough' %> <% next if field['type'] == 'heading' || field['type'] == 'strikethrough' %>
<% next if field['type'] == 'signature' && submitter && !submitter.completed_at? %>
<div class="pt-2.5 border-b border-base-300"> <div class="pt-2.5 border-b border-base-300">
<div class="text-xs font-medium uppercase mb-0.5" dir="auto"> <div class="text-xs font-medium uppercase mb-0.5" dir="auto">
<%= field['name'].presence || "#{t("#{field['type']}_field")} #{submitter_field_counters[field['type']]}" %> <%= field['name'].presence || "#{t("#{field['type']}_field")} #{submitter_field_counters[field['type']]}" %>
@@ -15,7 +15,7 @@
<% last_submitter = @submission.submitters.completed.order(:completed_at).last %> <% last_submitter = @submission.submitters.completed.order(:completed_at).last %>
<% if last_submitter %> <% if last_submitter %>
<p dir="auto" class="text-sm"> <p dir="auto" class="text-sm">
<%= t(last_submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(last_submitter.completed_at.to_date, format: :long)) %> <%= t(last_submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(last_submitter.completed_at.in_time_zone(@submission.account.timezone).to_date, format: :long)) %>
</p> </p>
<% end %> <% end %>
</div> </div>
+1 -1
View File
@@ -13,7 +13,7 @@
<div> <div>
<p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p> <p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p>
<p dir="auto" class="text-sm"> <p dir="auto" class="text-sm">
<%= t(@submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(completed_at.to_date, format: :long)) %> <%= t(@submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(completed_at.in_time_zone(@submitter.account.timezone).to_date, format: :long)) %>
</p> </p>
</div> </div>
</div> </div>
+1 -1
View File
@@ -11,7 +11,7 @@
</div> </div>
<div dir="auto"> <div dir="auto">
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p> <p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p>
<p class="text-sm"><%= t('form_has_been_declined_on_html', time: l(@submitter.declined_at, format: :long)) %></p> <p class="text-sm"><%= t('form_has_been_declined_on_html', time: l(@submitter.declined_at.in_time_zone(@submitter.account.timezone).to_date, format: :long)) %></p>
</div> </div>
</div> </div>
</div> </div>
+1 -1
View File
@@ -11,7 +11,7 @@
</div> </div>
<div dir="auto"> <div dir="auto">
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p> <p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p>
<p class="text-sm"><%= t('document_has_been_delegated_on_html', time: l(@submitter.submission_events.order(event_timestamp: :desc).find_by!(event_type: :delegate_form).event_timestamp, format: :long)) %></p> <p class="text-sm"><%= t('document_has_been_delegated_on_html', time: l(@submitter.submission_events.order(event_timestamp: :desc).find_by!(event_type: :delegate_form).event_timestamp.in_time_zone(@submitter.account.timezone).to_date, format: :long)) %></p>
</div> </div>
</div> </div>
</div> </div>
+1 -1
View File
@@ -19,7 +19,7 @@
<% last_submitter = @submitter.submission.submitters.completed.order(:completed_at).last %> <% last_submitter = @submitter.submission.submitters.completed.order(:completed_at).last %>
<% if last_submitter %> <% if last_submitter %>
<p dir="auto" class="text-sm"> <p dir="auto" class="text-sm">
<%= t(last_submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(last_submitter.completed_at.to_date, format: :long)) %> <%= t(last_submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(last_submitter.completed_at.in_time_zone(@submitter.account.timezone).to_date, format: :long)) %>
</p> </p>
<% end %> <% end %>
</div> </div>
+1 -1
View File
@@ -11,7 +11,7 @@
</div> </div>
<div dir="auto"> <div dir="auto">
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p> <p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p>
<p class="text-sm"><%= t('form_expired_at_html', time: l(@submitter.submission.expire_at, format: :long)) %></p> <p class="text-sm"><%= t('form_expired_at_html', time: l(@submitter.submission.expire_at.in_time_zone(@submitter.account.timezone).to_date, format: :long)) %></p>
</div> </div>
</div> </div>
</div> </div>
+33 -12
View File
@@ -704,6 +704,7 @@ en: &en
developer_sandbox: Developer Sandbox. developer_sandbox: Developer Sandbox.
upgrade_to_start_using_in_production_html: '<a href="%{url}" target="_blank" class="link font-semibold">Upgrade</a> to start using in Production.' upgrade_to_start_using_in_production_html: '<a href="%{url}" target="_blank" class="link font-semibold">Upgrade</a> to start using in Production.'
download_combined_pdf: Download combined PDF download_combined_pdf: Download combined PDF
download_event_log: Download Event Log
audit_log: Audit Log audit_log: Audit Log
signers: Signers signers: Signers
not_invited_yet: Not invited yet not_invited_yet: Not invited yet
@@ -715,7 +716,7 @@ en: &en
declined_on_time: 'Declined on %{time}' declined_on_time: 'Declined on %{time}'
expire_on_time: 'Expire on %{time}' expire_on_time: 'Expire on %{time}'
sign_in_person: Sign In-person sign_in_person: Sign In-person
create_a_new_template_document_form_or_submit_the_existing_one_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Create a new template</a> document form or <a href="%{start_form_link}" target="_blank" class="inline underline font-medium">submit the existing one</a> create_a_new_template_document_form_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Create a new template</a> document form
send_email_copy_with_completed_documents_to_a_specified_bcc_address: Send email copy with completed documents to a specified BCC address. send_email_copy_with_completed_documents_to_a_specified_bcc_address: Send email copy with completed documents to a specified BCC address.
re_send_email: Re-send Email re_send_email: Re-send Email
re_send_emails: Re-send Emails re_send_emails: Re-send Emails
@@ -1027,6 +1028,8 @@ en: &en
failed_to_scan_the_document: Failed to scan the document failed_to_scan_the_document: Failed to scan the document
document_scanning_is_not_available_on_this_device: Document scanning is not available on this device document_scanning_is_not_available_on_this_device: Document scanning is not available on this device
click_to_upload: Click to upload click_to_upload: Click to upload
cc_view_only: CC View-only
bcc_completed: BCC Completed
devise: devise:
confirmations: confirmations:
confirmed: Your email address has been successfully confirmed. confirmed: Your email address has been successfully confirmed.
@@ -1862,6 +1865,7 @@ es: &es
developer_sandbox: Sandbox para desarrolladores. developer_sandbox: Sandbox para desarrolladores.
upgrade_to_start_using_in_production_html: '<a href="%{url}" target="_blank" class="link font-semibold">Actualiza</a> para empezar a usar en Producción.' upgrade_to_start_using_in_production_html: '<a href="%{url}" target="_blank" class="link font-semibold">Actualiza</a> para empezar a usar en Producción.'
download_combined_pdf: Descargar PDF combinado download_combined_pdf: Descargar PDF combinado
download_event_log: Descargar registro de eventos
audit_log: Registro de Auditoría audit_log: Registro de Auditoría
signers: Firmantes signers: Firmantes
not_invited_yet: Aún no invitado not_invited_yet: Aún no invitado
@@ -1873,7 +1877,7 @@ es: &es
declined_on_time: 'Rechazado el %{time}' declined_on_time: 'Rechazado el %{time}'
expire_on_time: 'Expira el %{time}' expire_on_time: 'Expira el %{time}'
sign_in_person: Firma en persona sign_in_person: Firma en persona
create_a_new_template_document_form_or_submit_the_existing_one_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Crear una nueva plantilla</a> de documento o <a href="%{start_form_link}" target="_blank" class="inline underline font-medium">enviar el existente</a> create_a_new_template_document_form_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Crear una nueva plantilla</a> de documento
send_email_copy_with_completed_documents_to_a_specified_bcc_address: Enviar una copia del correo electrónico con los documentos completados a una dirección BCC especificada. send_email_copy_with_completed_documents_to_a_specified_bcc_address: Enviar una copia del correo electrónico con los documentos completados a una dirección BCC especificada.
re_send_email: Reenviar email re_send_email: Reenviar email
re_send_emails: Reenviar emails re_send_emails: Reenviar emails
@@ -2174,6 +2178,8 @@ es: &es
failed_to_save_the_scan: No se pudo guardar el escaneo failed_to_save_the_scan: No se pudo guardar el escaneo
failed_to_scan_the_document: No se pudo escanear el documento failed_to_scan_the_document: No se pudo escanear el documento
document_scanning_is_not_available_on_this_device: El escaneo de documentos no está disponible en este dispositivo document_scanning_is_not_available_on_this_device: El escaneo de documentos no está disponible en este dispositivo
cc_view_only: CC Solo lectura
bcc_completed: CCO Completado
devise: devise:
confirmations: confirmations:
confirmed: Tu dirección de correo electrónico ha sido confirmada correctamente. confirmed: Tu dirección de correo electrónico ha sido confirmada correctamente.
@@ -3023,6 +3029,7 @@ it: &it
developer_sandbox: Sandbox per sviluppatori developer_sandbox: Sandbox per sviluppatori
upgrade_to_start_using_in_production_html: '<a href="%{url}" target="_blank" class="link font-semibold">Aggiorna</a> per iniziare a usare in produzione.' upgrade_to_start_using_in_production_html: '<a href="%{url}" target="_blank" class="link font-semibold">Aggiorna</a> per iniziare a usare in produzione.'
download_combined_pdf: Scarica PDF combinato download_combined_pdf: Scarica PDF combinato
download_event_log: Scarica registro degli eventi
audit_log: Registro di audit audit_log: Registro di audit
signers: Firmatari signers: Firmatari
not_invited_yet: Non ancora invitato not_invited_yet: Non ancora invitato
@@ -3034,7 +3041,7 @@ it: &it
declined_on_time: 'Rifiutato il %{time}' declined_on_time: 'Rifiutato il %{time}'
expire_on_time: 'Scade il %{time}' expire_on_time: 'Scade il %{time}'
sign_in_person: Firma di persona sign_in_person: Firma di persona
create_a_new_template_document_form_or_submit_the_existing_one_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Crea un nuovo modello</a> di documento o <a href="%{start_form_link}" target="_blank" class="inline underline font-medium">invia quello esistente</a> create_a_new_template_document_form_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Crea un nuovo modello</a> di documento
send_email_copy_with_completed_documents_to_a_specified_bcc_address: Invia una copia dell'email con i documenti completati a un indirizzo BCC specificato. send_email_copy_with_completed_documents_to_a_specified_bcc_address: Invia una copia dell'email con i documenti completati a un indirizzo BCC specificato.
re_send_email: Reinvia email re_send_email: Reinvia email
re_send_emails: Reinvia le email re_send_emails: Reinvia le email
@@ -3335,6 +3342,8 @@ it: &it
failed_to_save_the_scan: Impossibile salvare la scansione failed_to_save_the_scan: Impossibile salvare la scansione
failed_to_scan_the_document: Impossibile scansionare il documento failed_to_scan_the_document: Impossibile scansionare il documento
document_scanning_is_not_available_on_this_device: La scansione dei documenti non è disponibile su questo dispositivo document_scanning_is_not_available_on_this_device: La scansione dei documenti non è disponibile su questo dispositivo
cc_view_only: CC Sola lettura
bcc_completed: BCC Completato
devise: devise:
confirmations: confirmations:
confirmed: Il tuo indirizzo email è stato confermato con successo. confirmed: Il tuo indirizzo email è stato confermato con successo.
@@ -4170,6 +4179,7 @@ fr: &fr
developer_sandbox: Bac à sable développeur. developer_sandbox: Bac à sable développeur.
upgrade_to_start_using_in_production_html: <a href="%{url}" target="_blank" class="link font-semibold">Mettez à niveau</a> pour commencer à utiliser en production. upgrade_to_start_using_in_production_html: <a href="%{url}" target="_blank" class="link font-semibold">Mettez à niveau</a> pour commencer à utiliser en production.
download_combined_pdf: Télécharger le PDF combiné download_combined_pdf: Télécharger le PDF combiné
download_event_log: Télécharger le journal des événements
audit_log: Journal d’audit audit_log: Journal d’audit
signers: Signataires signers: Signataires
not_invited_yet: Pas encore invité not_invited_yet: Pas encore invité
@@ -4181,7 +4191,7 @@ fr: &fr
declined_on_time: Refusé le %{time} declined_on_time: Refusé le %{time}
expire_on_time: Expire le %{time} expire_on_time: Expire le %{time}
sign_in_person: Signer en personne sign_in_person: Signer en personne
create_a_new_template_document_form_or_submit_the_existing_one_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Créez un nouveau modèle</a> de document ou <a href="%{start_form_link}" target="_blank" class="inline underline font-medium">soumettez l’existant</a> create_a_new_template_document_form_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Créez un nouveau modèle</a> de document
send_email_copy_with_completed_documents_to_a_specified_bcc_address: Envoyer une copie e‑mail avec les documents complétés à une adresse Cci spécifiée. send_email_copy_with_completed_documents_to_a_specified_bcc_address: Envoyer une copie e‑mail avec les documents complétés à une adresse Cci spécifiée.
re_send_email: Renvoyer l’e‑mail re_send_email: Renvoyer l’e‑mail
re_send_emails: Renvoyer e‑mails re_send_emails: Renvoyer e‑mails
@@ -4479,6 +4489,8 @@ fr: &fr
failed_to_save_the_scan: Impossible d’enregistrer la numérisation failed_to_save_the_scan: Impossible d’enregistrer la numérisation
failed_to_scan_the_document: Impossible de numériser le document failed_to_scan_the_document: Impossible de numériser le document
document_scanning_is_not_available_on_this_device: La numérisation de documents n’est pas disponible sur cet appareil document_scanning_is_not_available_on_this_device: La numérisation de documents n’est pas disponible sur cet appareil
cc_view_only: Cc Lecture seule
bcc_completed: Cci Terminé
devise: devise:
confirmations: confirmations:
confirmed: Votre adresse e-mail a été confirmée avec succès. confirmed: Votre adresse e-mail a été confirmée avec succès.
@@ -5327,6 +5339,7 @@ pt: &pt
developer_sandbox: Sandbox para desenvolvedores developer_sandbox: Sandbox para desenvolvedores
upgrade_to_start_using_in_production_html: '<a href="%{url}" target="_blank" class="link font-semibold">Atualizar</a> para começar a usar em produção.' upgrade_to_start_using_in_production_html: '<a href="%{url}" target="_blank" class="link font-semibold">Atualizar</a> para começar a usar em produção.'
download_combined_pdf: Baixar PDF combinado download_combined_pdf: Baixar PDF combinado
download_event_log: Baixar log de eventos
audit_log: Log de auditoria audit_log: Log de auditoria
signers: Signatários signers: Signatários
not_invited_yet: Ainda não convidado not_invited_yet: Ainda não convidado
@@ -5338,7 +5351,7 @@ pt: &pt
declined_on_time: 'Recusado em %{time}' declined_on_time: 'Recusado em %{time}'
expire_on_time: 'Expira em %{time}' expire_on_time: 'Expira em %{time}'
sign_in_person: Assinar pessoalmente sign_in_person: Assinar pessoalmente
create_a_new_template_document_form_or_submit_the_existing_one_html: '<a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Criar um novo modelo</a> de documento ou <a href="%{start_form_link}" target="_blank" class="inline underline font-medium">submeter o existente</a>' create_a_new_template_document_form_html: '<a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Criar um novo modelo</a> de documento'
send_email_copy_with_completed_documents_to_a_specified_bcc_address: Envie uma cópia do e-mail com documentos concluídos para um endereço BCC especificado. send_email_copy_with_completed_documents_to_a_specified_bcc_address: Envie uma cópia do e-mail com documentos concluídos para um endereço BCC especificado.
re_send_email: Reenviar e-mail re_send_email: Reenviar e-mail
re_send_emails: Reenviar e-mails re_send_emails: Reenviar e-mails
@@ -5639,6 +5652,8 @@ pt: &pt
failed_to_save_the_scan: Não foi possível salvar a digitalização failed_to_save_the_scan: Não foi possível salvar a digitalização
failed_to_scan_the_document: Não foi possível digitalizar o documento failed_to_scan_the_document: Não foi possível digitalizar o documento
document_scanning_is_not_available_on_this_device: A digitalização de documentos não está disponível neste dispositivo document_scanning_is_not_available_on_this_device: A digitalização de documentos não está disponível neste dispositivo
cc_view_only: CC Somente leitura
bcc_completed: BCC Concluído
devise: devise:
confirmations: confirmations:
confirmed: Seu endereço de e-mail foi confirmado com sucesso. confirmed: Seu endereço de e-mail foi confirmado com sucesso.
@@ -6487,6 +6502,7 @@ de: &de
developer_sandbox: Entwickler-Sandbox. developer_sandbox: Entwickler-Sandbox.
upgrade_to_start_using_in_production_html: '<a href="%{url}" target="_blank" class="link font-semibold">Upgrade</a> durchführen, um die Produktionsnutzung zu starten.' upgrade_to_start_using_in_production_html: '<a href="%{url}" target="_blank" class="link font-semibold">Upgrade</a> durchführen, um die Produktionsnutzung zu starten.'
download_combined_pdf: Kombiniertes PDF herunterladen download_combined_pdf: Kombiniertes PDF herunterladen
download_event_log: Ereignisprotokoll herunterladen
audit_log: Prüfprotokoll audit_log: Prüfprotokoll
signers: Unterzeichner signers: Unterzeichner
not_invited_yet: Noch nicht eingeladen not_invited_yet: Noch nicht eingeladen
@@ -6498,7 +6514,7 @@ de: &de
declined_on_time: 'Abgelehnt am %{time}' declined_on_time: 'Abgelehnt am %{time}'
expire_on_time: 'Läuft ab am %{time}' expire_on_time: 'Läuft ab am %{time}'
sign_in_person: Vor Ort unterschreiben sign_in_person: Vor Ort unterschreiben
create_a_new_template_document_form_or_submit_the_existing_one_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Neue Vorlage erstellen</a> oder <a href="%{start_form_link}" target="_blank" class="inline underline font-medium">bestehende einreichen</a> create_a_new_template_document_form_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Neue Vorlage erstellen</a>
send_email_copy_with_completed_documents_to_a_specified_bcc_address: Senden Sie eine E-Mail-Kopie mit abgeschlossenen Dokumenten an eine angegebene BCC-Adresse. send_email_copy_with_completed_documents_to_a_specified_bcc_address: Senden Sie eine E-Mail-Kopie mit abgeschlossenen Dokumenten an eine angegebene BCC-Adresse.
re_send_email: E-Mail erneut senden re_send_email: E-Mail erneut senden
re_send_emails: E-Mails senden re_send_emails: E-Mails senden
@@ -6799,6 +6815,8 @@ de: &de
failed_to_save_the_scan: Scan konnte nicht gespeichert werden failed_to_save_the_scan: Scan konnte nicht gespeichert werden
failed_to_scan_the_document: Dokument konnte nicht gescannt werden failed_to_scan_the_document: Dokument konnte nicht gescannt werden
document_scanning_is_not_available_on_this_device: Das Scannen von Dokumenten ist auf diesem Gerät nicht verfügbar document_scanning_is_not_available_on_this_device: Das Scannen von Dokumenten ist auf diesem Gerät nicht verfügbar
cc_view_only: CC Nur Ansicht
bcc_completed: BCC Abgeschlossen
devise: devise:
confirmations: confirmations:
confirmed: Ihre E-Mail-Adresse wurde erfolgreich bestätigt. confirmed: Ihre E-Mail-Adresse wurde erfolgreich bestätigt.
@@ -6981,14 +6999,14 @@ pl:
decline_reason: Powód odrzucenia decline_reason: Powód odrzucenia
provide_a_reason: Podaj powód provide_a_reason: Podaj powód
notify_the_sender_with_the_reason_you_declined: Powiadom nadawcę o powodzie, dla którego odrzuciłeś notify_the_sender_with_the_reason_you_declined: Powiadom nadawcę o powodzie, dla którego odrzuciłeś
form_has_been_declined_on_html: 'Formularz został odrzucony o <span class="font-semibold">%{time}</span>' form_has_been_declined_on_html: 'Formularz został odrzucony <span class="font-semibold">%{time}</span>'
document_has_been_delegated_on_html: 'Dokument został oddelegowany o <span class="font-semibold">%{time}</span>' document_has_been_delegated_on_html: 'Dokument został oddelegowany <span class="font-semibold">%{time}</span>'
name_declined_by_submitter: '"%{name}" odrzucono przez %{submitter}' name_declined_by_submitter: '"%{name}" odrzucono przez %{submitter}'
name_declined_by_submitter_with_the_following_reason: '"%{name}" został odrzucony przez %{submitter} z następującym powodem:' name_declined_by_submitter_with_the_following_reason: '"%{name}" został odrzucony przez %{submitter} z następującym powodem:'
email: E-mail email: E-mail
verification_code_code: 'Kod weryfikacyjny: %{code}' verification_code_code: 'Kod weryfikacyjny: %{code}'
digitally_signed_by: Podpis cyfrowy przez digitally_signed_by: Podpis cyfrowy przez
form_expired_at_html: 'Formularz wygasł o <span class="font-semibold">%{time}</span>' form_expired_at_html: 'Formularz wygasł <span class="font-semibold">%{time}</span>'
role: Rola role: Rola
provide_your_email_to_start: Podaj swój e-mail, aby rozpocząć provide_your_email_to_start: Podaj swój e-mail, aby rozpocząć
provide_your_email: Podaj swój e-mail provide_your_email: Podaj swój e-mail
@@ -7123,8 +7141,8 @@ uk:
decline_reason: Причина відхилення decline_reason: Причина відхилення
provide_a_reason: Надайте причину provide_a_reason: Надайте причину
notify_the_sender_with_the_reason_you_declined: Повідомте відправника про причину свого відхилення notify_the_sender_with_the_reason_you_declined: Повідомте відправника про причину свого відхилення
form_has_been_declined_on_html: 'Форму було відхилено о <span class="font-semibold">%{time}</span>' form_has_been_declined_on_html: 'Форму було відхилено <span class="font-semibold">%{time}</span>'
document_has_been_delegated_on_html: 'Документ було делеговано о <span class="font-semibold">%{time}</span>' document_has_been_delegated_on_html: 'Документ було делеговано <span class="font-semibold">%{time}</span>'
name_declined_by_submitter: '"%{name}" відхилено %{submitter}' name_declined_by_submitter: '"%{name}" відхилено %{submitter}'
name_declined_by_submitter_with_the_following_reason: '"%{name}" було відхилено %{submitter} з такою причиною:' name_declined_by_submitter_with_the_following_reason: '"%{name}" було відхилено %{submitter} з такою причиною:'
email: E-mail email: E-mail
@@ -8175,6 +8193,7 @@ nl: &nl
developer_sandbox: Ontwikkelaars-sandbox. developer_sandbox: Ontwikkelaars-sandbox.
upgrade_to_start_using_in_production_html: <a href="%{url}" target="_blank" class="link font-semibold">Upgrade</a> om in productie te gaan gebruiken. upgrade_to_start_using_in_production_html: <a href="%{url}" target="_blank" class="link font-semibold">Upgrade</a> om in productie te gaan gebruiken.
download_combined_pdf: Gecombineerde PDF downloaden download_combined_pdf: Gecombineerde PDF downloaden
download_event_log: Gebeurtenislogboek downloaden
audit_log: Auditlogboek audit_log: Auditlogboek
signers: Ondertekenaars signers: Ondertekenaars
not_invited_yet: Nog niet uitgenodigd not_invited_yet: Nog niet uitgenodigd
@@ -8186,7 +8205,7 @@ nl: &nl
declined_on_time: Geweigerd op %{time} declined_on_time: Geweigerd op %{time}
expire_on_time: Verloopt op %{time} expire_on_time: Verloopt op %{time}
sign_in_person: In persoon ondertekenen sign_in_person: In persoon ondertekenen
create_a_new_template_document_form_or_submit_the_existing_one_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Maak een nieuw sjabloon</a> voor een documentformulier of <a href="%{start_form_link}" target="_blank" class="inline underline font-medium">dien het bestaande in</a> create_a_new_template_document_form_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Maak een nieuw sjabloon</a> voor een documentformulier
send_email_copy_with_completed_documents_to_a_specified_bcc_address: Stuur een e-mailkopie met voltooide documenten naar een opgegeven BCC-adres. send_email_copy_with_completed_documents_to_a_specified_bcc_address: Stuur een e-mailkopie met voltooide documenten naar een opgegeven BCC-adres.
re_send_email: E-mail opnieuw verzenden re_send_email: E-mail opnieuw verzenden
re_send_emails: E-mails verzenden re_send_emails: E-mails verzenden
@@ -8483,6 +8502,8 @@ nl: &nl
failed_to_save_the_scan: Scan kon niet worden opgeslagen failed_to_save_the_scan: Scan kon niet worden opgeslagen
failed_to_scan_the_document: Document kon niet worden gescand failed_to_scan_the_document: Document kon niet worden gescand
document_scanning_is_not_available_on_this_device: Documenten scannen is niet beschikbaar op dit apparaat document_scanning_is_not_available_on_this_device: Documenten scannen is niet beschikbaar op dit apparaat
cc_view_only: CC Alleen bekijken
bcc_completed: BCC Voltooid
devise: devise:
confirmations: confirmations:
confirmed: Uw e-mailadres is succesvol bevestigd. confirmed: Uw e-mailadres is succesvol bevestigd.
-28
View File
@@ -5,34 +5,6 @@ module Accounts
module_function module_function
def create_duplicate(account)
new_account = account.dup
new_user = account.users.first.dup
new_user.uuid = SecureRandom.uuid
new_user.account = new_account
new_user.encrypted_password = SecureRandom.hex
new_user.email = "#{SecureRandom.hex}@docuseal.com"
account.templates.each do |template|
new_template = template.dup
new_template.account = new_account
new_template.slug = SecureRandom.base58(14)
new_template.archived_at = nil
new_template.save!
Templates::CloneAttachments.call(template: new_template, original_template: template)
end
new_user.save!(validate: false)
new_account.templates.update_all(folder_id: new_account.default_template_folder.id)
new_account
end
def users_count(account) def users_count(account)
rel = User.where(account_id: account.id).or( rel = User.where(account_id: account.id).or(
User.where(account_id: account.account_linked_accounts User.where(account_id: account.account_linked_accounts
+23
View File
@@ -90,6 +90,10 @@ module Submissions
maybe_add_invite_submitters(submission, template, attrs[:submitters]) maybe_add_invite_submitters(submission, template, attrs[:submitters])
maybe_add_cc_submitters(submission, params[:cc_recipients],
submitters_order:, user:, params:,
preferences: preferences.merge(submission_preferences))
assign_submitters_is_viewer(submission) assign_submitters_is_viewer(submission)
submission.template = nil unless with_template submission.template = nil unless with_template
@@ -211,6 +215,25 @@ module Submissions
end end
end end
def maybe_add_cc_submitters(submission, recipients, submitters_order:, user:, params:, preferences:)
order = submission.template_submitters.filter_map { |s| s['order'] }.max
Array.wrap(recipients).each do |recipient|
attrs = recipient.is_a?(String) ? { email: recipient } : recipient.slice(:name, :email, :phone)
next if attrs.compact_blank.blank?
uuid = SecureRandom.uuid
submission.template_submitters <<
{ 'name' => 'CC', 'uuid' => uuid, 'is_viewer' => true, 'order' => order }.compact
build_submitter(submission:, attrs:, uuid:, user:, params:,
is_order_sent: submitters_order == 'random',
preferences: preferences.except('email_message_uuid'))
end
end
def submitter_message_preferences(uuid, params) def submitter_message_preferences(uuid, params)
return {} if params[:request_email_per_submitter] != '1' return {} if params[:request_email_per_submitter] != '1'
return {} if params[:is_custom_message] != '1' return {} if params[:is_custom_message] != '1'
+9 -1
View File
@@ -152,7 +152,7 @@ module Submitters
preferences['send_sms'] = params['send_sms'].in?(TRUE_VALUES) if params.key?('send_sms') preferences['send_sms'] = params['send_sms'].in?(TRUE_VALUES) if params.key?('send_sms')
preferences['require_phone_2fa'] = params['require_phone_2fa'].in?(TRUE_VALUES) if params.key?('require_phone_2fa') preferences['require_phone_2fa'] = params['require_phone_2fa'].in?(TRUE_VALUES) if params.key?('require_phone_2fa')
preferences['require_email_2fa'] = params['require_email_2fa'].in?(TRUE_VALUES) if params.key?('require_email_2fa') preferences['require_email_2fa'] = params['require_email_2fa'].in?(TRUE_VALUES) if params.key?('require_email_2fa')
preferences['bcc_completed'] = params['bcc_completed'] if params.key?('bcc_completed') preferences['bcc_completed'] = normalize_bcc_completed(params['bcc_completed']) if params.key?('bcc_completed')
preferences['reply_to'] = params['reply_to'] if params.key?('reply_to') preferences['reply_to'] = params['reply_to'] if params.key?('reply_to')
preferences['go_to_last'] = params['go_to_last'] if params.key?('go_to_last') preferences['go_to_last'] = params['go_to_last'] if params.key?('go_to_last')
preferences['completed_redirect_url'] = params['completed_redirect_url'] if params.key?('completed_redirect_url') preferences['completed_redirect_url'] = params['completed_redirect_url'] if params.key?('completed_redirect_url')
@@ -160,6 +160,14 @@ module Submitters
preferences preferences
end end
def normalize_bcc_completed(value)
if value.is_a?(Array)
value.compact_blank.uniq.join(', ')
else
value
end
end
def fetch_values_for_delegate(submitter) def fetch_values_for_delegate(submitter)
fields = submitter.submission.template_fields || submitter.template.fields fields = submitter.submission.template_fields || submitter.template.fields
default_values = submitter.preferences['default_values'] || {} default_values = submitter.preferences['default_values'] || {}
+16 -11
View File
@@ -90,23 +90,28 @@ module Templates
pool = Concurrent::FixedThreadPool.new(concurrency) pool = Concurrent::FixedThreadPool.new(concurrency)
promises = blobs =
range.map do |page_number| range.each_slice(concurrency).flat_map do |page_numbers|
doc_page = doc.get_page(page_number) promises =
page_numbers.map do |page_number|
doc_page = doc.get_page(page_number)
hide_placeholder_widgets(doc_page, hide_empty: !flatten_pages) hide_placeholder_widgets(doc_page, hide_empty: !flatten_pages)
doc_page.flatten if flatten_pages doc_page.flatten if flatten_pages
bytes, width, height = doc_page.render_to_bitmap(width: MAX_WIDTH) bytes, width, height = doc_page.render_to_bitmap(width: MAX_WIDTH)
image = Vips::Image.new_from_memory_copy(bytes, width, height, 4, :uchar) image = Vips::Image.new_from_memory_copy(bytes, width, height, 4, :uchar)
Concurrent::Promise.execute(executor: pool) { build_and_upload_blob(image, page_number) } Concurrent::Promise.execute(executor: pool) { build_and_upload_blob(image, page_number) }
ensure ensure
doc_page&.close doc_page&.close
end
Concurrent::Promise.zip(*promises).value!
end end
Concurrent::Promise.zip(*promises).value!.each do |blob| blobs.each do |blob|
next unless blob next unless blob
ApplicationRecord.no_touching do ApplicationRecord.no_touching do