mirror of
https://github.com/docusealco/docuseal.git
synced 2026-08-07 23:32:07 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f94858901 | |||
| d0ac4ad222 | |||
| 10e4476f4a | |||
| 74411d883a | |||
| 18ee2d5316 | |||
| b4f80422eb | |||
| 7fe9ab7601 | |||
| 13ad614e6b | |||
| 7b1b4bcf2c | |||
| a6715d4034 | |||
| 254995c6c6 | |||
| 0e33e23d1f | |||
| 57503eefd2 | |||
| cd477c880c | |||
| bf2f5b24dc | |||
| 7eba4865bc | |||
| 64cd24a24d | |||
| de5acd0949 | |||
| 6ebbc3edac | |||
| 2020f6c136 | |||
| ee4acd5a82 | |||
| 2e487ac936 | |||
| dee5fc7c07 | |||
| e130584fec | |||
| ade5c4832f | |||
| 33b14d5278 | |||
| 48e73c2c81 | |||
| 894d98e92b |
+1
-1
@@ -66,7 +66,7 @@ RSpec/ExampleLength:
|
||||
Max: 50
|
||||
|
||||
RSpec/MultipleMemoizedHelpers:
|
||||
Max: 9
|
||||
Max: 15
|
||||
|
||||
Metrics/BlockNesting:
|
||||
Max: 5
|
||||
|
||||
@@ -10,6 +10,12 @@ module Api
|
||||
def create
|
||||
submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
||||
|
||||
if params[:type].in?(%w[initials signature]) && ImageUtils.blank?(Vips::Image.new_from_file(params[:file].path))
|
||||
Rollbar.error("Empty signature: #{submitter.id}") if defined?(Rollbar)
|
||||
|
||||
return render json: { error: "#{params[:type]} is empty" }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
attachment = Submitters.create_attachment!(submitter, params)
|
||||
|
||||
if params[:remember_signature] == 'true' && submitter.email.present?
|
||||
|
||||
@@ -187,11 +187,12 @@ module Api
|
||||
def submissions_params
|
||||
permitted_attrs = [
|
||||
:send_email, :send_sms, :bcc_completed, :completed_redirect_url, :reply_to, :go_to_last,
|
||||
:expire_at, :name,
|
||||
:require_phone_2fa, :expire_at, :name,
|
||||
{
|
||||
message: %i[subject body],
|
||||
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
|
||||
:completed, :phone, :application_key, :external_id, :reply_to, :go_to_last,
|
||||
:require_phone_2fa,
|
||||
{ metadata: {}, values: {}, roles: [], readonly_fields: [], message: %i[subject body],
|
||||
fields: [:name, :uuid, :default_value, :value, :title, :description,
|
||||
:readonly, :required, :validation_pattern, :invalid_message,
|
||||
|
||||
@@ -82,7 +82,7 @@ module Api
|
||||
|
||||
submitter_params.permit(
|
||||
:send_email, :send_sms, :reply_to, :completed_redirect_url, :uuid, :name, :email, :role,
|
||||
:completed, :phone, :application_key, :external_id, :go_to_last,
|
||||
:completed, :phone, :application_key, :external_id, :go_to_last, :require_phone_2fa,
|
||||
{ metadata: {}, values: {}, readonly_fields: [], message: %i[subject body],
|
||||
fields: [[:name, :uuid, :default_value, :value, :required,
|
||||
:readonly, :validation_pattern, :invalid_message,
|
||||
@@ -193,6 +193,10 @@ module Api
|
||||
|
||||
submitter.preferences['send_sms'] = submitter_preferences['send_sms'] if submitter_preferences.key?('send_sms')
|
||||
submitter.preferences['reply_to'] = submitter_preferences['reply_to'] if submitter_preferences.key?('reply_to')
|
||||
if submitter_preferences.key?('require_phone_2fa')
|
||||
submitter.preferences['require_phone_2fa'] = submitter_preferences['require_phone_2fa']
|
||||
end
|
||||
|
||||
if submitter_preferences.key?('go_to_last')
|
||||
submitter.preferences['go_to_last'] = submitter_preferences['go_to_last']
|
||||
end
|
||||
|
||||
@@ -33,12 +33,12 @@ class StartFormController < ApplicationController
|
||||
@submitter = find_or_initialize_submitter(@template, submitter_params)
|
||||
|
||||
if @submitter.completed_at?
|
||||
redirect_to start_form_completed_path(@template.slug, email: submitter_params[:email])
|
||||
redirect_to start_form_completed_path(@template.slug, submitter_params.compact_blank)
|
||||
else
|
||||
if filter_undefined_submitters(@template).size > 1 && @submitter.new_record?
|
||||
@error_message = multiple_submitters_error_message
|
||||
|
||||
return render :show
|
||||
return render :show, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
if (is_new_record = @submitter.new_record?)
|
||||
@@ -49,7 +49,7 @@ class StartFormController < ApplicationController
|
||||
@submitter.assign_attributes(ip: request.remote_ip, ua: request.user_agent)
|
||||
end
|
||||
|
||||
if @submitter.save
|
||||
if @submitter.errors.blank? && @submitter.save
|
||||
if is_new_record
|
||||
enqueue_submission_create_webhooks(@submitter)
|
||||
|
||||
@@ -63,7 +63,7 @@ class StartFormController < ApplicationController
|
||||
|
||||
redirect_to submit_form_path(@submitter.slug)
|
||||
else
|
||||
render :show
|
||||
render :show, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -71,9 +71,20 @@ class StartFormController < ApplicationController
|
||||
def completed
|
||||
return redirect_to start_form_path(@template.slug) if !@template.shared_link? || @template.archived_at?
|
||||
|
||||
submitter_params = params.permit(:name, :email, :phone).tap do |attrs|
|
||||
attrs[:email] = Submissions.normalize_email(attrs[:email])
|
||||
end
|
||||
|
||||
required_fields = @template.preferences.fetch('link_form_fields', ['email'])
|
||||
|
||||
required_params = required_fields.index_with { |key| submitter_params[key] }
|
||||
|
||||
raise ActionController::RoutingError, I18n.t('not_found') if required_params.any? { |_, v| v.blank? } ||
|
||||
required_params.except('name').compact_blank.blank?
|
||||
|
||||
@submitter = Submitter.where(submission: @template.submissions)
|
||||
.where.not(completed_at: nil)
|
||||
.find_by!(email: params[:email])
|
||||
.find_by!(required_params)
|
||||
end
|
||||
|
||||
private
|
||||
@@ -104,7 +115,16 @@ class StartFormController < ApplicationController
|
||||
end
|
||||
|
||||
def find_or_initialize_submitter(template, submitter_params)
|
||||
Submitter
|
||||
required_fields = template.preferences.fetch('link_form_fields', ['email'])
|
||||
|
||||
required_params = required_fields.index_with { |key| submitter_params[key] }
|
||||
|
||||
find_params = required_params.except('name')
|
||||
|
||||
submitter = Submitter.new if find_params.compact_blank.blank?
|
||||
|
||||
submitter ||=
|
||||
Submitter
|
||||
.where(submission: template.submissions.where(expire_at: Time.current..)
|
||||
.or(template.submissions.where(expire_at: nil)).where(archived_at: nil))
|
||||
.order(id: :desc)
|
||||
@@ -112,7 +132,17 @@ class StartFormController < ApplicationController
|
||||
.where(external_id: nil)
|
||||
.where(ip: [nil, request.remote_ip])
|
||||
.then { |rel| params[:resubmit].present? || params[:selfsign].present? ? rel.where(completed_at: nil) : rel }
|
||||
.find_or_initialize_by(email: submitter_params[:email], **submitter_params.compact_blank)
|
||||
.find_or_initialize_by(find_params)
|
||||
|
||||
submitter.name = required_params['name'] if submitter.new_record?
|
||||
|
||||
unless @resubmit_submitter
|
||||
required_params.each do |key, value|
|
||||
submitter.errors.add(key.to_sym, :blank) if value.blank?
|
||||
end
|
||||
end
|
||||
|
||||
submitter
|
||||
end
|
||||
|
||||
def assign_submission_attributes(submitter, template)
|
||||
@@ -125,6 +155,8 @@ class StartFormController < ApplicationController
|
||||
metadata: @resubmit_submitter&.metadata.presence || {}
|
||||
)
|
||||
|
||||
submitter.assign_attributes(@resubmit_submitter.slice(:name, :email, :phone)) if @resubmit_submitter
|
||||
|
||||
if submitter.values.present?
|
||||
@resubmit_submitter.attachments.each do |attachment|
|
||||
submitter.attachments << attachment.dup if submitter.values.value?(attachment.uuid)
|
||||
|
||||
@@ -12,7 +12,7 @@ class SubmitFormDrawSignatureController < ApplicationController
|
||||
|
||||
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
|
||||
|
||||
if @submitter.submission.template.archived_at? || @submitter.submission.archived_at?
|
||||
if @submitter.submission.template&.archived_at? || @submitter.submission.archived_at?
|
||||
return redirect_to submit_form_path(@submitter.slug)
|
||||
end
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class SubmitFormInviteController < ApplicationController
|
||||
!submitter.completed_at? &&
|
||||
!submitter.submission.archived_at? &&
|
||||
!submitter.submission.expired? &&
|
||||
!submitter.submission.template.archived_at?
|
||||
!submitter.submission.template&.archived_at?
|
||||
end
|
||||
|
||||
def filter_invite_submitters(submitter, key = 'invite_by_uuid')
|
||||
|
||||
@@ -8,7 +8,7 @@ class SubmitFormValuesController < ApplicationController
|
||||
submitter = Submitter.find_by!(slug: params[:submit_form_slug])
|
||||
|
||||
return render json: {} if submitter.completed_at? || submitter.declined_at?
|
||||
return render json: {} if submitter.submission.template.archived_at? ||
|
||||
return render json: {} if submitter.submission.template&.archived_at? ||
|
||||
submitter.submission.archived_at? ||
|
||||
submitter.submission.expired?
|
||||
|
||||
|
||||
@@ -55,10 +55,8 @@ class TemplatesDashboardController < ApplicationController
|
||||
|
||||
rel = Template.where(
|
||||
Template.arel_table[:id].in(
|
||||
Arel::Nodes::Union.new(
|
||||
rel.where(folder_id: current_account.default_template_folder.id).select(:id).arel,
|
||||
shared_template_ids.arel
|
||||
)
|
||||
rel.where(folder_id: current_account.default_template_folder.id).select(:id).arel
|
||||
.union(shared_template_ids.arel)
|
||||
)
|
||||
)
|
||||
else
|
||||
|
||||
@@ -31,7 +31,7 @@ class TemplatesPreferencesController < ApplicationController
|
||||
completed_notification_email_subject completed_notification_email_body
|
||||
completed_notification_email_enabled completed_notification_email_attach_audit] +
|
||||
[completed_message: %i[title body],
|
||||
submitters: [%i[uuid request_email_subject request_email_body]]]
|
||||
submitters: [%i[uuid request_email_subject request_email_body]], link_form_fields: []]
|
||||
).tap do |attrs|
|
||||
attrs[:preferences].delete(:submitters) if params[:request_email_per_submitter] != '1'
|
||||
|
||||
|
||||
@@ -10,7 +10,11 @@ class TemplatesShareLinkController < ApplicationController
|
||||
|
||||
@template.update!(template_params)
|
||||
|
||||
head :ok
|
||||
if params[:redir].present?
|
||||
redirect_to params[:redir]
|
||||
else
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -35,6 +35,7 @@ import SetDateButton from './elements/set_date_button'
|
||||
import IndeterminateCheckbox from './elements/indeterminate_checkbox'
|
||||
import AppTour from './elements/app_tour'
|
||||
import DashboardDropzone from './elements/dashboard_dropzone'
|
||||
import RequiredCheckboxGroup from './elements/required_checkbox_group'
|
||||
|
||||
import * as TurboInstantClick from './lib/turbo_instant_click'
|
||||
|
||||
@@ -105,6 +106,7 @@ safeRegisterElement('indeterminate-checkbox', IndeterminateCheckbox)
|
||||
safeRegisterElement('app-tour', AppTour)
|
||||
safeRegisterElement('dashboard-dropzone', DashboardDropzone)
|
||||
safeRegisterElement('check-on-click', CheckOnClick)
|
||||
safeRegisterElement('required-checkbox-group', RequiredCheckboxGroup)
|
||||
|
||||
safeRegisterElement('template-builder', class extends HTMLElement {
|
||||
connectedCallback () {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export default class extends HTMLElement {
|
||||
connectedCallback () {
|
||||
this.addEventListener('click', () => {
|
||||
if (!this.element.checked) {
|
||||
if (this.element && !this.element.disabled && !this.element.checked) {
|
||||
this.element.checked = true
|
||||
this.element.dispatchEvent(new Event('change', { bubbles: true }))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
export default class extends HTMLElement {
|
||||
connectedCallback () {
|
||||
this.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
|
||||
checkbox.addEventListener('change', this.handleChange)
|
||||
})
|
||||
}
|
||||
|
||||
handleChange = () => {
|
||||
if (this.checkedCount !== 0) {
|
||||
this.closest('form')?.requestSubmit()
|
||||
}
|
||||
}
|
||||
|
||||
get checkedCount () {
|
||||
return this.querySelectorAll('input[type="checkbox"]:checked').length
|
||||
}
|
||||
}
|
||||
@@ -203,7 +203,7 @@ export default {
|
||||
emits: ['attached', 'update:model-value', 'start', 'minimize', 'focus'],
|
||||
data () {
|
||||
return {
|
||||
isInitialsStarted: !!this.previousValue,
|
||||
isInitialsStarted: false,
|
||||
isUsePreviousValue: true,
|
||||
isDrawInitials: false,
|
||||
uploadImageInputKey: Math.random().toString()
|
||||
@@ -218,6 +218,9 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.isInitialsStarted = !!this.computedPreviousValue
|
||||
},
|
||||
async mounted () {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.canvas) {
|
||||
@@ -362,11 +365,20 @@ export default {
|
||||
formData.append('file', file)
|
||||
formData.append('submitter_slug', this.submitterSlug)
|
||||
formData.append('name', 'attachments')
|
||||
formData.append('type', 'initials')
|
||||
|
||||
return fetch(this.baseUrl + '/api/attachments', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then((resp) => resp.json()).then((attachment) => {
|
||||
}).then(async (resp) => {
|
||||
if (resp.status === 422 || resp.status === 500) {
|
||||
const data = await resp.json()
|
||||
|
||||
return Promise.reject(new Error(data.error))
|
||||
}
|
||||
|
||||
const attachment = await resp.json()
|
||||
|
||||
this.$emit('attached', attachment)
|
||||
this.$emit('update:model-value', attachment.uuid)
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
v-if="isTextSignature"
|
||||
v-if="isTextSignature && !modelValue && !computedPreviousValue"
|
||||
id="signature_text_input"
|
||||
ref="textInput"
|
||||
class="base-input !text-2xl w-full mt-6"
|
||||
@@ -391,7 +391,7 @@ export default {
|
||||
emits: ['attached', 'update:model-value', 'start', 'minimize', 'update:reason'],
|
||||
data () {
|
||||
return {
|
||||
isSignatureStarted: !!this.previousValue,
|
||||
isSignatureStarted: false,
|
||||
isShowQr: false,
|
||||
isOtherReason: false,
|
||||
isUsePreviousValue: true,
|
||||
@@ -422,6 +422,8 @@ export default {
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.isSignatureStarted = !!this.computedPreviousValue
|
||||
|
||||
if (this.requireSigningReason) {
|
||||
this.field.preferences ||= {}
|
||||
this.field.preferences.reason_field_uuid ||= v4()
|
||||
@@ -716,11 +718,20 @@ export default {
|
||||
formData.append('submitter_slug', this.submitterSlug)
|
||||
formData.append('name', 'attachments')
|
||||
formData.append('remember_signature', this.rememberSignature)
|
||||
formData.append('type', 'signature')
|
||||
|
||||
return fetch(this.baseUrl + '/api/attachments', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then((resp) => resp.json()).then((attachment) => {
|
||||
}).then(async (resp) => {
|
||||
if (resp.status === 422 || resp.status === 500) {
|
||||
const data = await resp.json()
|
||||
|
||||
return Promise.reject(new Error(data.error))
|
||||
}
|
||||
|
||||
const attachment = await resp.json()
|
||||
|
||||
this.$emit('attached', attachment)
|
||||
this.$emit('update:model-value', attachment.uuid)
|
||||
|
||||
|
||||
@@ -406,7 +406,7 @@ export default {
|
||||
this.mappings.every((m) => m.column_index !== index)
|
||||
})
|
||||
|
||||
if (columnIndex !== -1) {
|
||||
if (columnIndex !== -1 && this.rows.some((row) => row[columnIndex])) {
|
||||
this.mappings.push({ uuid: v4(), field_name: field.name, column_index: columnIndex, submitter_uuid: submitter.uuid })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -40,7 +40,7 @@ class Submission < ApplicationRecord
|
||||
belongs_to :account
|
||||
belongs_to :created_by_user, class_name: 'User', optional: true
|
||||
|
||||
has_one :search_entry, as: :record, inverse_of: :record, dependent: :destroy
|
||||
has_one :search_entry, as: :record, inverse_of: :record, dependent: :destroy if SearchEntry.table_exists?
|
||||
|
||||
has_many :submitters, dependent: :destroy
|
||||
has_many :submission_events, dependent: :destroy
|
||||
|
||||
@@ -43,7 +43,7 @@ class Submitter < ApplicationRecord
|
||||
belongs_to :submission
|
||||
belongs_to :account
|
||||
has_one :template, through: :submission
|
||||
has_one :search_entry, as: :record, inverse_of: :record, dependent: :destroy
|
||||
has_one :search_entry, as: :record, inverse_of: :record, dependent: :destroy if SearchEntry.table_exists?
|
||||
|
||||
attribute :values, :string, default: -> { {} }
|
||||
attribute :preferences, :string, default: -> { {} }
|
||||
|
||||
@@ -44,7 +44,7 @@ class Template < ApplicationRecord
|
||||
belongs_to :account
|
||||
belongs_to :folder, class_name: 'TemplateFolder'
|
||||
|
||||
has_one :search_entry, as: :record, inverse_of: :record, dependent: :destroy
|
||||
has_one :search_entry, as: :record, inverse_of: :record, dependent: :destroy if SearchEntry.table_exists?
|
||||
|
||||
before_validation :maybe_set_default_folder, on: :create
|
||||
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
<div class="form-control">
|
||||
<%= ff.label :timezone, t('time_zone'), class: 'label' %>
|
||||
<%= ff.select :timezone, nil, {}, class: 'base-select' do %>
|
||||
<%= time_zone_options_for_select(current_account.timezone) %>
|
||||
<% tzinfo = TZInfo::Timezone.get(ActiveSupport::TimeZone::MAPPING[current_account.timezone] || current_account.timezone) %>
|
||||
<% items = ActiveSupport::TimeZone.all.map { |z| [z.to_s, z.name] } %>
|
||||
<% items.unshift([tzinfo.to_s, current_account.timezone]) unless ActiveSupport::TimeZone.all.find { |e| e.tzinfo == tzinfo } %>
|
||||
<%= options_for_select(items, current_account.timezone) %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<% end %>
|
||||
<% if Templates.filter_undefined_submitters(@template.submitters).size == 1 && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false && @template.shared_link? %>
|
||||
<toggle-submit class="block">
|
||||
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@template.slug), params: { submitter: { email: params[:email] }, resubmit: true }, method: :put, class: 'white-button w-full' %>
|
||||
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@template.slug), params: { submitter: params.permit(:name, :email, :phone).compact_blank, resubmit: true }, method: :put, class: 'white-button w-full' %>
|
||||
</toggle-submit>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -1,28 +1,31 @@
|
||||
<% content_for(:html_title, "#{@template.name} | DocuSeal") %>
|
||||
<% content_for(:html_description, t('share_link_is_currently_disabled')) %>
|
||||
<div class="max-w-md mx-auto px-2 mt-12 mb-4">
|
||||
<div class="space-y-6 mx-auto">
|
||||
<div class="space-y-6">
|
||||
<div class="text-center w-full space-y-6">
|
||||
<%= render 'banner' %>
|
||||
<p class="text-xl font-semibold text-center">
|
||||
<%= t('share_link_is_currently_disabled') %>
|
||||
</p>
|
||||
<% I18n.with_locale(@template.account.locale) do %>
|
||||
<% content_for(:html_description, t('share_link_is_currently_disabled')) %>
|
||||
<% end %>
|
||||
<div class="max-w-md space-y-6 mx-auto px-2 mt-12 mb-4">
|
||||
<div class="text-center w-full space-y-6">
|
||||
<%= render 'banner' %>
|
||||
<p class="text-xl font-semibold text-center">
|
||||
<%= t('share_link_is_currently_disabled') %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center bg-base-200 rounded-xl p-4 mb-4">
|
||||
<div class="flex items-center">
|
||||
<div class="mr-3">
|
||||
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||
</div>
|
||||
<div class="flex items-center bg-base-200 rounded-xl p-4 mb-4">
|
||||
<div class="flex items-center">
|
||||
<div class="mr-3">
|
||||
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-lg font-bold mb-1"><%= @template.name %></p>
|
||||
<% if @template.archived_at? %>
|
||||
<p dir="auto" class="text-sm"><%= t('form_has_been_deleted_by_html', name: @template.account.name) %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-lg font-bold mb-1"><%= @template.name %></p>
|
||||
<% if @template.archived_at? %>
|
||||
<p dir="auto" class="text-sm"><%= t('form_has_been_deleted_by_html', name: @template.account.name) %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% if can?(:update, @template) %>
|
||||
<toggle-submit class="block">
|
||||
<%= button_to button_title(title: t('enable_shared_link'), icon: svg_icon('lock_open', class: 'w-6 h-6')), template_share_link_path(@template), params: { template: { shared_link: true }, redir: start_form_path(slug: @template.slug) }, method: :post, class: 'white-button w-full' %>
|
||||
</toggle-submit>
|
||||
<% end %>
|
||||
</div>
|
||||
<%= render 'shared/attribution', link_path: '/start', account: @template.account %>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<% content_for(:html_title, "#{@template.name} | DocuSeal") %>
|
||||
<% content_for(:html_description, "#{@template.account.name} has invited you to fill and sign documents online effortlessly with a secure, fast, and user-friendly digital document signing solution.") %>
|
||||
<% I18n.with_locale(@template.account.locale) do %>
|
||||
<% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @template.account.name)) %>
|
||||
<% end %>
|
||||
<div class="max-w-md mx-auto px-2 mt-12 mb-4">
|
||||
<div class="space-y-6 mx-auto">
|
||||
<div class="space-y-6">
|
||||
@@ -27,13 +29,32 @@
|
||||
</div>
|
||||
<% if !@template.archived_at? && !@template.account.archived_at? %>
|
||||
<%= form_for @submitter, url: start_form_path(@template.slug), data: { turbo_frame: :_top }, method: :put, html: { class: 'space-y-4' } do |f| %>
|
||||
<div dir="auto" class="form-control !mt-0">
|
||||
<%= f.label :email, t('email'), class: 'label' %>
|
||||
<%= f.email_field :email, value: current_user&.email || params[:email] || @submitter.email, required: true, class: 'base-input', placeholder: t('provide_your_email_to_start') %>
|
||||
<% if @error_message %>
|
||||
<span class="label-text-alt text-red-400 mt-1"><%= @error_message %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if @error_message %>
|
||||
<div class="alert items-start bg-base-100 border border-red-400 text-red-400">
|
||||
<%= svg_icon('info_circle', class: 'stroke-current shrink-0 h-6 w-6 mt-1') %>
|
||||
<div class="text-sm"><%= @error_message %></div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% link_form_fields = @template.preferences.fetch('link_form_fields', ['email']) %>
|
||||
<% multiple_fields = link_form_fields.size > 1 %>
|
||||
<% if link_form_fields.include?('name') %>
|
||||
<div dir="auto" class="form-control !mt-0">
|
||||
<%= f.label :name, t('name'), class: 'label' %>
|
||||
<%= f.text_field :name, value: current_user&.full_name || params[:name] || @submitter.name, required: true, class: 'base-input', placeholder: t(multiple_fields ? 'provide_your_name' : 'provide_your_name_to_start') %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if link_form_fields.include?('email') %>
|
||||
<div dir="auto" class="form-control !mt-0">
|
||||
<%= f.label :email, t('email'), class: 'label' %>
|
||||
<%= f.email_field :email, value: current_user&.email || params[:email] || @submitter.email, required: true, class: 'base-input', placeholder: t(multiple_fields ? 'provide_your_email' : 'provide_your_email_to_start') %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if link_form_fields.include?('phone') %>
|
||||
<div dir="auto" class="form-control !mt-0">
|
||||
<%= f.label :phone, t('phone'), class: 'label' %>
|
||||
<%= f.telephone_field :phone, value: params[:phone] || @submitter.phone, pattern: '^\+[0-9\s\-]+$', oninvalid: "this.value ? this.setCustomValidity('#{t('use_international_format_1xxx_')}') : ''", oninput: "this.setCustomValidity('')", required: true, class: 'base-input', placeholder: t(multiple_fields ? 'provide_your_phone_in_international_format' : 'provide_your_phone_in_international_format_to_start') %>
|
||||
</div>
|
||||
<% end %>
|
||||
<toggle-submit dir="auto" class="form-control">
|
||||
<%= f.button button_title(title: t('start'), disabled_with: t('starting')), class: 'base-button' %>
|
||||
</toggle-submit>
|
||||
|
||||
@@ -28,9 +28,9 @@
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% elsif field['type'].in?(['image', 'initials', 'stamp']) %>
|
||||
<% elsif field['type'].in?(['image', 'initials', 'stamp']) && attachments_index[value].image? %>
|
||||
<img class="object-contain mx-auto" src="<%= attachments_index[value].url %>" loading="lazy">
|
||||
<% elsif field['type'].in?(['file', 'payment']) %>
|
||||
<% elsif field['type'].in?(['file', 'payment', 'image']) %>
|
||||
<autosize-field></autosize-field>
|
||||
<div class="px-0.5 flex flex-col justify-center">
|
||||
<% Array.wrap(value).each do |val| %>
|
||||
|
||||
@@ -74,12 +74,14 @@
|
||||
<% schema = Submissions.filtered_conditions_schema(@submission, values:) %>
|
||||
<% schema.each do |item| %>
|
||||
<% document = @submission.schema_documents.find { |a| item['attachment_uuid'] == a.uuid } %>
|
||||
<a href="#<%= "page-#{document.uuid}-0" %>" onclick="[event.preventDefault(), window[event.target.closest('a').href.split('#')[1]].scrollIntoView({ behavior: 'smooth', block: 'start' })]" class="block cursor-pointer">
|
||||
<img src="<%= Docuseal::URL_CACHE.fetch([document.id, document.uuid, 0].join(':'), expires_in: 10.minutes) { document.preview_images.first.url } %>" width="<%= document.preview_images.first.metadata['width'] %>" height="<%= document.preview_images.first.metadata['height'] %>" class="rounded border" loading="lazy">
|
||||
<div class="pb-2 pt-1.5 text-center" dir="auto">
|
||||
<%= item['name'].presence || document.filename.base %>
|
||||
</div>
|
||||
</a>
|
||||
<% if document.preview_images.first %>
|
||||
<a href="#<%= "page-#{document.uuid}-0" %>" onclick="[event.preventDefault(), window[event.target.closest('a').href.split('#')[1]].scrollIntoView({ behavior: 'smooth', block: 'start' })]" class="block cursor-pointer">
|
||||
<img src="<%= Docuseal::URL_CACHE.fetch([document.id, document.uuid, 0].join(':'), expires_in: 10.minutes) { document.preview_images.first.url } %>" width="<%= document.preview_images.first.metadata['width'] %>" height="<%= document.preview_images.first.metadata['height'] %>" class="rounded border" loading="lazy">
|
||||
<div class="pb-2 pt-1.5 text-center" dir="auto">
|
||||
<%= item['name'].presence || document.filename.base %>
|
||||
</div>
|
||||
</a>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div id="document_view" class="w-full overflow-y-auto overflow-x-hidden mt-0.5 pt-0.5">
|
||||
@@ -92,7 +94,7 @@
|
||||
<% document = @submission.schema_documents.find { |e| e.uuid == item['attachment_uuid'] } %>
|
||||
<% document_annots_index = document.metadata.dig('pdf', 'annotations')&.group_by { |e| e['page'] } || {} %>
|
||||
<% preview_images_index = document.preview_images.loaded? ? document.preview_images.index_by { |e| e.filename.base.to_i } : {} %>
|
||||
<% lazyload_metadata = document.preview_images.first.metadata %>
|
||||
<% lazyload_metadata = document.preview_images.first&.metadata || {} %>
|
||||
<% (document.metadata.dig('pdf', 'number_of_pages') || (document.preview_images.loaded? ? preview_images_index.size : document.preview_images.size)).times do |index| %>
|
||||
<% page = preview_images_index[index] || page_blob_struct.new(metadata: lazyload_metadata, url: preview_document_page_path(document.signed_uuid, "#{index}.jpg")) %>
|
||||
<div id="<%= "page-#{document.uuid}-#{index}" %>" class="relative">
|
||||
@@ -242,9 +244,9 @@
|
||||
<div class="w-full bg-base-300 py-1">
|
||||
<img class="object-contain mx-auto" style="max-height: <%= field['type'] == 'signature' ? 100 : 50 %>px" height="<%= attachments_index[value].metadata['height'] %>" width="<%= attachments_index[value].metadata['width'] %>" src="<%= attachments_index[value].url %>" loading="lazy">
|
||||
</div>
|
||||
<% elsif field['type'].in?(['image', 'stamp']) %>
|
||||
<% elsif field['type'].in?(['image', 'stamp']) && attachments_index[value].image? %>
|
||||
<img class="object-contain mx-auto max-h-28" style="max-height: 200px" height="<%= attachments_index[value].metadata['height'] %>" width="<%= attachments_index[value].metadata['width'] %>" src="<%= attachments_index[value].url %>" loading="lazy">
|
||||
<% elsif field['type'] == 'file' || field['type'] == 'payment' %>
|
||||
<% elsif field['type'].in?(['file', 'payment', 'image']) %>
|
||||
<div class="flex flex-col justify-center">
|
||||
<% Array.wrap(value).each do |val| %>
|
||||
<a target="_blank" class="flex items-center space-x-1" href="<%= attachments_index[val].url %>">
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<% content_for(:html_title, "#{@submitter.submission.name || @submitter.submission.template.name} | DocuSeal") %>
|
||||
<% content_for(:html_description, "#{@submitter.account.name} has invited you to fill and sign documents online effortlessly with a secure, fast, and user-friendly digital document signing solution.") %>
|
||||
<% I18n.with_locale(@submitter.account.locale) do %>
|
||||
<% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @submitter.account.name)) %>
|
||||
<% end %>
|
||||
<% fields_index = Templates.build_field_areas_index(@submitter.submission.template_fields || @submitter.submission.template.fields) %>
|
||||
<% values = @submitter.submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
|
||||
<% submitters_index = @submitter.submission.submitters.index_by(&:uuid) %>
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<% close_on_submit = local_assigns.fetch(:close_on_submit, true) %>
|
||||
<%= form_for template, url: template_recipients_path(template), method: :post, html: { autocomplete: 'off', class: 'mt-1', id: :submitters_form }, data: { close_on_submit: } do |f| %>
|
||||
<% unless close_on_submit %>
|
||||
<toggle-on-submit data-element-id="form_saved_alert"></toggle-on-submit>
|
||||
<% end %>
|
||||
<div class="space-y-3 divide-y">
|
||||
<% template.submitters.each_with_index do |submitter, index| %>
|
||||
<div class="<%= 'pt-3' if index.positive? %>">
|
||||
<%= f.fields_for :submitters, item = Struct.new(:name, :uuid, :is_requester, :email, :invite_by_uuid, :optional_invite_by_uuid, :linked_to_uuid, :option).new(*submitter.values_at('name', 'uuid', 'is_requester', 'email', 'invite_by_uuid', 'optional_invite_by_uuid', 'linked_to_uuid')), index: do |ff| %>
|
||||
<% item.option = item.is_requester.present? ? 'is_requester' : (item.email.present? ? 'email' : (item.linked_to_uuid.present? ? "linked_to_#{item.linked_to_uuid}" : (item.invite_by_uuid.present? ? "invite_by_#{item.invite_by_uuid}" : (item.optional_invite_by_uuid.present? ? "optional_invite_by_#{item.optional_invite_by_uuid}" : '')))) %>
|
||||
<%= ff.hidden_field :uuid %>
|
||||
<div class="form-control">
|
||||
<%= ff.text_field :name, class: 'w-full outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1 peer mb-2', autocomplete: 'off', placeholder: "#{index + 1}#{(index + 1).ordinal} Party", required: true %>
|
||||
<% if template.submitters.size == 2 %>
|
||||
<%= tag.input name: ff.field_name(:email), value: ff.object.email, type: :email, class: 'base-input', multiple: true, autocomplete: 'off', placeholder: t('default_email'), disabled: ff.object.is_requester || ff.object.invite_by_uuid.present? || ff.object.optional_invite_by_uuid.present?, id: field_uuid = SecureRandom.uuid %>
|
||||
<% else %>
|
||||
<toggle-attribute data-target-id="<%= email_field_uuid = SecureRandom.uuid %>" data-class-name="hidden" data-value="email">
|
||||
<%= ff.select :option, [[t('not_specified'), 'not_set'], (local_assigns[:with_submission_requester] != false ? [t('submission_requester'), 'is_requester'] : nil), [t('specified_email'), 'email'], *(template.submitters - [submitter]).flat_map { |e| [[t('invite_by_name', name: e['name']), "invite_by_#{e['uuid']}"], [t('invite_by_name', name: e['name']) + " (#{t(:optional).capitalize})", "optional_invite_by_#{e['uuid']}"]] }, *(template.submitters - [submitter]).map { |e| [t('same_as_name', name: e['name']), "linked_to_#{e['uuid']}"] }].compact, {}, class: 'base-select mb-3' %>
|
||||
</toggle-attribute>
|
||||
<%= tag.input name: ff.field_name(:email), type: :email, value: ff.object.email, multiple: true, class: "base-input #{'hidden' if item.option != 'email'}", autocomplete: 'off', placeholder: t('default_email'), id: email_field_uuid %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if template.submitters.size == 2 %>
|
||||
<checkbox-group class="mt-3 flex items-center space-x-4">
|
||||
<% if local_assigns[:with_submission_requester] != false %>
|
||||
<label class="flex items-center space-x-2 cursor-pointer">
|
||||
<toggle-attribute data-target-id="<%= field_uuid %>" class="flex" data-attribute="disabled">
|
||||
<%= ff.check_box :is_requester, class: 'base-checkbox' %>
|
||||
</toggle-attribute>
|
||||
<span class="select-none">
|
||||
<%= t('submission_requester') %>
|
||||
</span>
|
||||
</label>
|
||||
<% end %>
|
||||
<% if index == 1 %>
|
||||
<label class="flex items-center space-x-2 cursor-pointer">
|
||||
<toggle-attribute data-target-id="<%= field_uuid %>" class="flex" data-attribute="disabled">
|
||||
<indeterminate-checkbox data-indeterminate="<%= ff.object.optional_invite_by_uuid.present? %>" data-show-indeterminate-id="invite_optional" data-name="<%= ff.field_name(:invite_by_uuid) %>" data-indeterminate-name="<%= ff.field_name(:optional_invite_by_uuid) %>" class="flex">
|
||||
<%= ff.check_box ff.object.optional_invite_by_uuid.present? ? :optional_invite_by_uuid : :invite_by_uuid, { class: 'base-checkbox' }, template.submitters.first['uuid'], '' %>
|
||||
</indeterminate-checkbox>
|
||||
</toggle-attribute>
|
||||
<span class="select-none">
|
||||
<%= t('invite_by_name', name: template.submitters.first['name']) %> <span id="invite_optional" class="<%= 'hidden' if ff.object.optional_invite_by_uuid.blank? %>">(<%= t(:optional).capitalize %>)</span>
|
||||
</span>
|
||||
</label>
|
||||
<% end %>
|
||||
</checkbox-group>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if local_assigns[:with_toggles] != false %>
|
||||
<% unless current_account.account_configs.exists?(key: AccountConfig::ENFORCE_SIGNING_ORDER_KEY, value: true) %>
|
||||
<%= form_for template, url: template_preferences_path(template), method: :post, html: { autocomplete: 'off', class: 'mt-2' }, data: { close_on_submit: false } do |f| %>
|
||||
<div class="flex items-center pt-4 mt-4 justify-between border-t w-full">
|
||||
<span>
|
||||
<%= t('enforce_recipients_order') %>
|
||||
</span>
|
||||
<%= f.fields_for :preferences, Struct.new(:submitters_order).new(template.preferences['submitters_order']) do |ff| %>
|
||||
<%= ff.check_box :submitters_order, { class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'preserved', '' %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if can?(:manage, :personalization_advanced) %>
|
||||
<%= form_for template, url: template_preferences_path(template), method: :post, html: { autocomplete: 'off', class: 'mt-2' }, data: { close_on_submit: false } do |f| %>
|
||||
<div class="flex items-center mt-4 justify-between w-full">
|
||||
<span>
|
||||
<%= t('ensure_unique_recipients') %>
|
||||
</span>
|
||||
<%= f.fields_for :preferences, Struct.new(:validate_unique_submitters).new(template.preferences['validate_unique_submitters']) do |ff| %>
|
||||
<%= ff.check_box :validate_unique_submitters, { class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'true', '' %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<div class="form-control mt-5">
|
||||
<%= button_tag button_title(title: t('save'), disabled_with: t('updating')), class: 'base-button', form: :submitters_form %>
|
||||
<% unless close_on_submit %>
|
||||
<div class="flex justify-center">
|
||||
<span id="form_saved_alert" class="text-sm invisible font-normal mt-0.5"><%= t('changes_have_been_saved') %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -287,91 +287,8 @@
|
||||
<%= render 'templates_code_modal/preferences', class: 'pt-2' %>
|
||||
</div>
|
||||
<% if show_recipients %>
|
||||
<div id="recipients" class="hidden mt-2 mb-4 px-5">
|
||||
<%= form_for @template, url: template_recipients_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1', id: :submitters_form } do |f| %>
|
||||
<div class="space-y-3 divide-y">
|
||||
<% @template.submitters.each_with_index do |submitter, index| %>
|
||||
<div class="pt-3">
|
||||
<%= f.fields_for :submitters, item = Struct.new(:name, :uuid, :is_requester, :email, :invite_by_uuid, :optional_invite_by_uuid, :linked_to_uuid, :option).new(*submitter.values_at('name', 'uuid', 'is_requester', 'email', 'invite_by_uuid', 'optional_invite_by_uuid', 'linked_to_uuid')), index: do |ff| %>
|
||||
<% item.option = item.is_requester.present? ? 'is_requester' : (item.email.present? ? 'email' : (item.linked_to_uuid.present? ? "linked_to_#{item.linked_to_uuid}" : (item.invite_by_uuid.present? ? "invite_by_#{item.invite_by_uuid}" : (item.optional_invite_by_uuid.present? ? "optional_invite_by_#{item.optional_invite_by_uuid}" : '')))) %>
|
||||
<%= ff.hidden_field :uuid %>
|
||||
<div class="form-control">
|
||||
<%= ff.text_field :name, class: 'w-full outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1 peer mb-2', autocomplete: 'off', placeholder: "#{index + 1}#{(index + 1).ordinal} Party", required: true %>
|
||||
<% if @template.submitters.size == 2 %>
|
||||
<%= tag.input name: ff.field_name(:email), value: ff.object.email, type: :email, class: 'base-input', multiple: true, autocomplete: 'off', placeholder: t('default_email'), disabled: ff.object.is_requester || ff.object.invite_by_uuid.present? || ff.object.optional_invite_by_uuid.present?, id: field_uuid = SecureRandom.uuid %>
|
||||
<% else %>
|
||||
<toggle-attribute data-target-id="<%= email_field_uuid = SecureRandom.uuid %>" data-class-name="hidden" data-value="email">
|
||||
<%= ff.select :option, [[t('not_specified'), 'not_set'], [t('submission_requester'), 'is_requester'], [t('specified_email'), 'email'], *(@template.submitters - [submitter]).flat_map { |e| [[t('invite_by_name', name: e['name']), "invite_by_#{e['uuid']}"], [t('invite_by_name', name: e['name']) + " (#{t(:optional).capitalize})", "optional_invite_by_#{e['uuid']}"]] }, *(@template.submitters - [submitter]).map { |e| [t('same_as_name', name: e['name']), "linked_to_#{e['uuid']}"] }], {}, class: 'base-select mb-3' %>
|
||||
</toggle-attribute>
|
||||
<%= tag.input name: ff.field_name(:email), type: :email, value: ff.object.email, multiple: true, class: "base-input #{'hidden' if item.option != 'email'}", autocomplete: 'off', placeholder: t('default_email'), id: email_field_uuid %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if @template.submitters.size == 2 %>
|
||||
<checkbox-group class="mt-3 flex items-center space-x-4">
|
||||
<label class="flex items-center space-x-2 cursor-pointer">
|
||||
<toggle-attribute data-target-id="<%= field_uuid %>" class="flex" data-attribute="disabled">
|
||||
<%= ff.check_box :is_requester, class: 'base-checkbox' %>
|
||||
</toggle-attribute>
|
||||
<span class="select-none">
|
||||
<%= t('submission_requester') %>
|
||||
</span>
|
||||
</label>
|
||||
<% if index == 1 %>
|
||||
<label class="flex items-center space-x-2 cursor-pointer">
|
||||
<toggle-attribute data-target-id="<%= field_uuid %>" class="flex" data-attribute="disabled">
|
||||
<indeterminate-checkbox data-indeterminate="<%= ff.object.optional_invite_by_uuid.present? %>" data-show-indeterminate-id="invite_optional" data-name="<%= ff.field_name(:invite_by_uuid) %>" data-indeterminate-name="<%= ff.field_name(:optional_invite_by_uuid) %>" class="flex">
|
||||
<%= ff.check_box ff.object.optional_invite_by_uuid.present? ? :optional_invite_by_uuid : :invite_by_uuid, { class: 'base-checkbox' }, @template.submitters.first['uuid'], '' %>
|
||||
</indeterminate-checkbox>
|
||||
</toggle-attribute>
|
||||
<span class="select-none">
|
||||
<%= t('invite_by_name', name: @template.submitters.first['name']) %> <span id="invite_optional" class="<%= 'hidden' if ff.object.optional_invite_by_uuid.blank? %>">(<%= t(:optional).capitalize %>)</span>
|
||||
</span>
|
||||
</label>
|
||||
<% end %>
|
||||
</checkbox-group>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% unless current_account.account_configs.exists?(key: AccountConfig::ENFORCE_SIGNING_ORDER_KEY, value: true) %>
|
||||
<%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-2' }, data: { close_on_submit: false } do |f| %>
|
||||
<div class="flex items-center pt-4 mt-4 justify-between border-t w-full">
|
||||
<span>
|
||||
<%= t('enforce_recipients_order') %>
|
||||
</span>
|
||||
<%= f.fields_for :preferences, Struct.new(:submitters_order).new(@template.preferences['submitters_order']) do |ff| %>
|
||||
<%= ff.check_box :submitters_order, { class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'preserved', '' %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if can?(:manage, :personalization_advanced) %>
|
||||
<%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-2' }, data: { close_on_submit: false } do |f| %>
|
||||
<div class="flex items-center mt-4 justify-between w-full">
|
||||
<span>
|
||||
<%= t('ensure_unique_recipients') %>
|
||||
</span>
|
||||
<%= f.fields_for :preferences, Struct.new(:validate_unique_submitters).new(@template.preferences['validate_unique_submitters']) do |ff| %>
|
||||
<%= ff.check_box :validate_unique_submitters, { class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'true', '' %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-2' }, data: { close_on_submit: false } do |f| %>
|
||||
<div class="flex items-center mt-4 justify-between w-full">
|
||||
<span>
|
||||
<%= t('require_all_recipients') %>
|
||||
</span>
|
||||
<%= f.fields_for :preferences, Struct.new(:require_all_submitters).new(@template.preferences['require_all_submitters']) do |ff| %>
|
||||
<%= ff.check_box :require_all_submitters, { class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'true', '' %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<div class="form-control mt-5 pb-2">
|
||||
<%= button_tag button_title(title: t('save'), disabled_with: t('updating')), class: 'base-button', form: :submitters_form %>
|
||||
</div>
|
||||
<div id="recipients" class="hidden mt-2 mb-4 px-5 pt-1 pb-2">
|
||||
<%= render 'recipients', template: @template %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if show_api %>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<%= render 'shared/turbo_modal_large', title: t('share_link') do %>
|
||||
<div class="mt-2 mb-4 px-5">
|
||||
<%= form_for @template, url: template_share_link_path(@template), method: :post, html: { id: 'shared_link_form', autocomplete: 'off', class: 'mt-3' }, data: { close_on_submit: false } do |f| %>
|
||||
<div class="flex items-center justify-between gap-1 px-1">
|
||||
<label for="template_shared_link" class="flex items-center my-4 justify-between gap-1 alert bg-base-100 border-base-300">
|
||||
<span><%= t('enable_shared_link') %></span>
|
||||
<%= f.check_box :shared_link, { disabled: !can?(:update, @template), class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'true', 'false' %>
|
||||
</div>
|
||||
</label>
|
||||
<div class="flex gap-2 mt-3">
|
||||
<input id="embedding_url" type="text" value="<%= start_form_url(slug: @template.slug) %>" class="base-input w-full" autocomplete="off" readonly>
|
||||
<check-on-click data-element-id="template_shared_link">
|
||||
@@ -12,5 +12,34 @@
|
||||
</check-on-click>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-4 mb-5' }, data: { close_on_submit: false } do |f| %>
|
||||
<% link_form_fields = @template.preferences.fetch('link_form_fields', ['email']) %>
|
||||
<label class="pl-1"><%= t('link_form_fields') %></label>
|
||||
<required-checkbox-group class="flex flex-col md:flex-row items-center gap-2 w-full mt-2">
|
||||
<% %w[name email phone].each do |field| %>
|
||||
<%= label_tag "link_form_fields_#{field}", t(field), class: 'relative flex w-full md:w-1/3 items-center h-12 border-base-300 py-3.5 border rounded-xl' do %>
|
||||
<%= check_box_tag 'template[preferences][link_form_fields][]', field, link_form_fields.include?(field), class: 'absolute !animate-none checkbox left-3', id: "link_form_fields_#{field}" %>
|
||||
<span class="font-medium w-full text-center"><%= t(field) %></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</required-checkbox-group>
|
||||
<% end %>
|
||||
<% if Templates.filter_undefined_submitters(@template.submitters).size > 1 %>
|
||||
<div class="alert items-start bg-base-100 border-base-300 mt-4">
|
||||
<%= svg_icon('info_circle', class: 'stroke-current shrink-0 h-6 w-6 mt-1') %>
|
||||
<div><%= t('this_template_has_multiple_parties_which_prevents_the_use_of_a_sharing_link') %></div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @template.submitters.to_a.length > 1 %>
|
||||
<div class="collapse collapse-arrow join-item border border-base-300 mt-4 mb-5">
|
||||
<input type="checkbox" name="accordion">
|
||||
<div class="collapse-title text-xl font-medium">
|
||||
<%= t('default_parties') %>
|
||||
</div>
|
||||
<div class="collapse-content !pb-0">
|
||||
<%= render 'templates_preferences/recipients', template: @template, close_on_submit: false, with_toggles: false, with_submission_requester: false %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
+124
-10
@@ -22,6 +22,7 @@ en: &en
|
||||
hi_there: Hi there
|
||||
thanks: Thanks
|
||||
private: Private
|
||||
default_parties: Default parties
|
||||
authenticate_embedded_form_preview_with_token: Authenticate embedded form preview with token
|
||||
stripe_integration: Stripe Integration
|
||||
require_all_recipients: Require all recipients
|
||||
@@ -115,6 +116,11 @@ en: &en
|
||||
role: Role
|
||||
reason: Reason
|
||||
provide_your_email_to_start: Provide your email to start
|
||||
provide_your_email: Provide your email
|
||||
provide_your_name_to_start: Provide your name to start
|
||||
provide_your_name: Provide your name
|
||||
provide_your_phone_in_international_format_to_start: Provide your phone in international format to start
|
||||
provide_your_phone_in_international_format: Provide your phone in international format
|
||||
start: Start
|
||||
enforce_recipients_order: Enforce recipients order
|
||||
starting: Starting
|
||||
@@ -752,6 +758,11 @@ en: &en
|
||||
enable_shared_link: Enable shared link
|
||||
share_link_is_currently_disabled: Share link is currently disabled
|
||||
select_data_residency: Select data residency
|
||||
account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: '%{account_name} has invited you to fill and sign documents online effortlessly with a secure, fast, and user-friendly digital document signing solution.'
|
||||
review_or_download_completed_documents_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: Review or download completed documents. Fill and sign documents online effortlessly with a secure, fast, and user-friendly digital document signing solution.
|
||||
link_form_fields: Link form fields
|
||||
at_least_one_field_must_be_displayed_in_the_form: At least one field must be displayed in the form.
|
||||
this_template_has_multiple_parties_which_prevents_the_use_of_a_sharing_link: This template has multiple parties, which prevents the use of a shared link as it's unclear which party is responsible for specific fields. To resolve this, define the default party details.
|
||||
submission_sources:
|
||||
api: API
|
||||
bulk: Bulk Send
|
||||
@@ -854,6 +865,7 @@ en: &en
|
||||
range_without_total: "%{from}-%{to} items"
|
||||
|
||||
es: &es
|
||||
default_parties: Partes predeterminadas
|
||||
authenticate_embedded_form_preview_with_token: Autenticar vista previa del formulario incrustado con token
|
||||
require_all_recipients: Requerir a todos los destinatarios
|
||||
stripe_integration: Integración con Stripe
|
||||
@@ -951,6 +963,11 @@ es: &es
|
||||
role: Rol
|
||||
reason: Razón
|
||||
provide_your_email_to_start: Proporciona tu correo electrónico para comenzar
|
||||
provide_your_email: Proporciona tu correo electrónico
|
||||
provide_your_name_to_start: Proporciona tu nombre para comenzar
|
||||
provide_your_name: Proporciona tu nombre
|
||||
provide_your_phone_in_international_format_to_start: Proporciona tu teléfono en formato internacional para comenzar
|
||||
provide_your_phone_in_international_format: Proporciona tu teléfono en formato internacional
|
||||
start: Comenzar
|
||||
starting: Comenzando
|
||||
form_has_been_deleted_by_html: 'El formulario ha sido eliminado por <span class="font-semibold">%{name}</span>.'
|
||||
@@ -1587,6 +1604,11 @@ es: &es
|
||||
enable_shared_link: Habilitar enlace compartido
|
||||
share_link_is_currently_disabled: El enlace compartido está deshabilitado actualmente
|
||||
select_data_residency: Seleccionar ubicación de datos
|
||||
account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: '%{account_name} te ha invitado a completar y firmar documentos en línea fácilmente con una solución de firma digital segura, rápida y fácil de usar.'
|
||||
review_or_download_completed_documents_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: Revisa o descarga los documentos completados. Completa y firma documentos en línea fácilmente con una solución de firma digital segura, rápida y fácil de usar.
|
||||
link_form_fields: Vincular campos del formulario
|
||||
at_least_one_field_must_be_displayed_in_the_form: Al menos un campo debe mostrarse en el formulario.
|
||||
this_template_has_multiple_parties_which_prevents_the_use_of_a_sharing_link: Esta plantilla tiene varias partes, lo que impide el uso de un enlace compartido porque no está claro qué parte es responsable de campos específicos. Para resolverlo, define los detalles predeterminados de la parte.
|
||||
submission_sources:
|
||||
api: API
|
||||
bulk: Envío masivo
|
||||
@@ -1689,6 +1711,7 @@ es: &es
|
||||
range_without_total: "%{from}-%{to} elementos"
|
||||
|
||||
it: &it
|
||||
default_parties: Parti predefiniti
|
||||
authenticate_embedded_form_preview_with_token: "Autentica l'anteprima del modulo incorporato con il token"
|
||||
require_all_recipients: Richiedi tutti i destinatari
|
||||
stripe_integration: Integrazione Stripe
|
||||
@@ -1785,6 +1808,11 @@ it: &it
|
||||
role: Ruolo
|
||||
reason: Ragione
|
||||
provide_your_email_to_start: Fornisci la tua email per iniziare
|
||||
provide_your_email: Fornisci la tua email
|
||||
provide_your_name_to_start: Fornisci il tuo nome per iniziare
|
||||
provide_your_name: Fornisci il tuo nome
|
||||
provide_your_phone_in_international_format_to_start: Fornisci il tuo telefono in formato internazionale per iniziare
|
||||
provide_your_phone_in_international_format: Fornisci il tuo telefono in formato internazionale
|
||||
start: Inizia
|
||||
starting: Iniziando
|
||||
form_has_been_deleted_by_html: 'Il modulo è stato eliminato da <span class="font-semibold">%{name}</span>.'
|
||||
@@ -2420,6 +2448,11 @@ it: &it
|
||||
enable_shared_link: Abilita link condiviso
|
||||
share_link_is_currently_disabled: Il link condiviso è attualmente disabilitato
|
||||
select_data_residency: Seleziona la residenza dei dati
|
||||
account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: '%{account_name} ti ha invitato a compilare e firmare documenti online con facilità utilizzando una soluzione di firma digitale sicura, veloce e facile da usare.'
|
||||
review_or_download_completed_documents_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: Rivedi o scarica i documenti completati. Compila e firma documenti online facilmente con una soluzione di firma digitale sicura, veloce e facile da usare.
|
||||
link_form_fields: Collega i campi del modulo
|
||||
at_least_one_field_must_be_displayed_in_the_form: Almeno un campo deve essere visualizzato nel modulo.
|
||||
this_template_has_multiple_parties_which_prevents_the_use_of_a_sharing_link: Questo modello ha più parti, il che impedisce l’uso di un link di condivisione perché non è chiaro quale parte sia responsabile di campi specifici. Per risolvere, definisci i dettagli predefiniti della parte.
|
||||
submission_sources:
|
||||
api: API
|
||||
bulk: Invio massivo
|
||||
@@ -2522,6 +2555,7 @@ it: &it
|
||||
range_without_total: "%{from}-%{to} elementi"
|
||||
|
||||
fr: &fr
|
||||
default_parties: Parties par défaut
|
||||
authenticate_embedded_form_preview_with_token: Authentifier l’aperçu du formulaire intégré avec un jeton
|
||||
require_all_recipients: Exiger tous les destinataires
|
||||
stripe_integration: Intégration Stripe
|
||||
@@ -2618,7 +2652,12 @@ fr: &fr
|
||||
digitally_signed_by: Signé numériquement par
|
||||
role: Rôle
|
||||
reason: Raison
|
||||
provide_your_email_to_start: Entrez votre adresse email pour commencer
|
||||
provide_your_email_to_start: Fournissez votre e-mail pour commencer
|
||||
provide_your_email: Fournissez votre e-mail
|
||||
provide_your_name_to_start: Fournissez votre nom pour commencer
|
||||
provide_your_name: Fournissez votre nom
|
||||
provide_your_phone_in_international_format_to_start: Fournissez votre téléphone au format international pour commencer
|
||||
provide_your_phone_in_international_format: Fournissez votre téléphone au format international
|
||||
start: Démarrer
|
||||
starting: Démarrage
|
||||
form_has_been_deleted_by_html: 'Le formulaire a été supprimé par <span class="font-semibold">%{name}</span>.'
|
||||
@@ -3256,6 +3295,11 @@ fr: &fr
|
||||
enable_shared_link: Activer le lien de partage
|
||||
share_link_is_currently_disabled: Le lien de partage est actuellement désactivé
|
||||
select_data_residency: Sélectionner la résidence des données
|
||||
account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: '%{account_name} vous a invité à remplir et signer des documents en ligne facilement avec une solution de signature numérique sécurisée, rapide et conviviale.'
|
||||
review_or_download_completed_documents_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: Consultez ou téléchargez les documents complétés. Remplissez et signez des documents en ligne facilement avec une solution de signature numérique sécurisée, rapide et conviviale.
|
||||
link_form_fields: Lier les champs du formulaire
|
||||
at_least_one_field_must_be_displayed_in_the_form: Au moins un champ doit être affiché dans le formulaire.
|
||||
this_template_has_multiple_parties_which_prevents_the_use_of_a_sharing_link: Ce modèle contient plusieurs parties, ce qui empêche l’utilisation d’un lien de partage car il n’est pas clair quelle partie est responsable de certains champs. Pour résoudre cela, définissez les détails de la partie par défaut.
|
||||
submission_sources:
|
||||
api: API
|
||||
bulk: Envoi en masse
|
||||
@@ -3358,6 +3402,7 @@ fr: &fr
|
||||
range_without_total: "%{from} à %{to} éléments"
|
||||
|
||||
pt: &pt
|
||||
default_parties: Partes padrão
|
||||
authenticate_embedded_form_preview_with_token: Autenticar visualização incorporada do formulário com token
|
||||
require_all_recipients: Exigir todos os destinatários
|
||||
stripe_integration: Integração com Stripe
|
||||
@@ -3454,7 +3499,12 @@ pt: &pt
|
||||
digitally_signed_by: Assinado digitalmente por
|
||||
role: Função
|
||||
reason: Motivo
|
||||
provide_your_email_to_start: Forneça o seu email para começar
|
||||
provide_your_email_to_start: Informe seu e-mail para começar
|
||||
provide_your_email: Informe seu e-mail
|
||||
provide_your_name_to_start: Informe seu nome para começar
|
||||
provide_your_name: Informe seu nome
|
||||
provide_your_phone_in_international_format_to_start: Informe seu telefone no formato internacional para começar
|
||||
provide_your_phone_in_international_format: Informe seu telefone no formato internacional
|
||||
start: Iniciar
|
||||
starting: Iniciando
|
||||
form_has_been_deleted_by_html: 'O formulário foi eliminado por <span class="font-semibold">%{name}</span>.'
|
||||
@@ -4091,6 +4141,11 @@ pt: &pt
|
||||
enable_shared_link: Ativar link compartilhado
|
||||
share_link_is_currently_disabled: O link compartilhado está desativado no momento
|
||||
select_data_residency: Selecionar local dos dados
|
||||
account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: '%{account_name} convidou você para preencher e assinar documentos online com facilidade usando uma solução de assinatura digital segura, rápida e fácil de usar.'
|
||||
review_or_download_completed_documents_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: Reveja ou baixe os documentos concluídos. Preencha e assine documentos online com facilidade usando uma solução de assinatura digital segura, rápida e fácil de usar.
|
||||
link_form_fields: Vincular campos do formulário
|
||||
at_least_one_field_must_be_displayed_in_the_form: Pelo menos um campo deve ser exibido no formulário.
|
||||
this_template_has_multiple_parties_which_prevents_the_use_of_a_sharing_link: Este modelo tem várias partes, o que impede o uso de um link de compartilhamento, pois não está claro qual parte é responsável por campos específicos. Para resolver isso, defina os detalhes padrão da parte.
|
||||
submission_sources:
|
||||
api: API
|
||||
bulk: Envio em massa
|
||||
@@ -4194,6 +4249,7 @@ pt: &pt
|
||||
range_without_total: "%{from}-%{to} itens"
|
||||
|
||||
de: &de
|
||||
default_parties: Standardparteien
|
||||
authenticate_embedded_form_preview_with_token: Authentifizieren Sie die eingebettete Formularvorschau mit Token
|
||||
require_all_recipients: Alle Empfänger erforderlich
|
||||
stripe_integration: Stripe-Integration
|
||||
@@ -4290,7 +4346,12 @@ de: &de
|
||||
digitally_signed_by: Digital signiert von
|
||||
role: Rolle
|
||||
reason: Grund
|
||||
provide_your_email_to_start: Gib deine E-Mail-Adresse ein, um zu starten
|
||||
provide_your_email_to_start: Geben Sie Ihre E-Mail-Adresse ein, um zu starten
|
||||
provide_your_email: Geben Sie Ihre E-Mail-Adresse ein
|
||||
provide_your_name_to_start: Geben Sie Ihren Namen ein, um zu starten
|
||||
provide_your_name: Geben Sie Ihren Namen ein
|
||||
provide_your_phone_in_international_format_to_start: Geben Sie Ihre Telefonnummer im internationalen Format ein, um zu starten
|
||||
provide_your_phone_in_international_format: Geben Sie Ihre Telefonnummer im internationalen Format ein
|
||||
start: Starten
|
||||
starting: Starten
|
||||
form_has_been_deleted_by_html: 'Das Formular wurde von <span class="font-semibold">%{name}</span> gelöscht.'
|
||||
@@ -4927,6 +4988,11 @@ de: &de
|
||||
enable_shared_link: 'Freigabelink aktivieren'
|
||||
share_link_is_currently_disabled: 'Freigabelink ist derzeit deaktiviert'
|
||||
select_data_residency: Datenstandort auswählen
|
||||
account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: '%{account_name} hat Sie eingeladen, Dokumente mühelos online mit einer sicheren, schnellen und benutzerfreundlichen digitalen Signaturlösung auszufüllen und zu unterschreiben.'
|
||||
review_or_download_completed_documents_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: Überprüfen oder laden Sie abgeschlossene Dokumente herunter. Füllen und unterschreiben Sie Dokumente mühelos online mit einer sicheren, schnellen und benutzerfreundlichen digitalen Signaturlösung.
|
||||
link_form_fields: Formularfelder verknüpfen
|
||||
at_least_one_field_must_be_displayed_in_the_form: Mindestens ein Feld muss im Formular angezeigt werden.
|
||||
this_template_has_multiple_parties_which_prevents_the_use_of_a_sharing_link: Diese Vorlage enthält mehrere Parteien, was die Verwendung eines Freigabelinks verhindert, da unklar ist, welche Partei für bestimmte Felder verantwortlich ist. Um dies zu beheben, definieren Sie die Standardparteidetails.
|
||||
submission_sources:
|
||||
api: API
|
||||
bulk: Massenversand
|
||||
@@ -5051,7 +5117,13 @@ pl:
|
||||
digitally_signed_by: Podpis cyfrowy przez
|
||||
form_expired_at_html: 'Formularz wygasł o <span class="font-semibold">%{time}</span>'
|
||||
role: Rola
|
||||
provide_your_email_to_start: Podaj swój adres email, aby rozpocząć
|
||||
provide_your_email_to_start: Podaj swój e-mail, aby rozpocząć
|
||||
provide_your_email: Podaj swój e-mail
|
||||
provide_your_name_to_start: Podaj swoje imię, aby rozpocząć
|
||||
provide_your_name: Podaj swoje imię
|
||||
provide_your_phone_in_international_format_to_start: Podaj numer telefonu w formacie międzynarodowym, aby rozpocząć
|
||||
provide_your_phone_in_international_format: Podaj numer telefonu w formacie międzynarodowym
|
||||
use_international_format_1xxx_: 'Użyj formatu międzynarodowego: +1xxx...'
|
||||
start: Rozpocznij
|
||||
reason: Powód
|
||||
starting: Rozpoczynanie
|
||||
@@ -5124,7 +5196,13 @@ uk:
|
||||
digitally_signed_by: Цифровий підпис від
|
||||
verification_code_code: 'Код підтвердження: %{code}'
|
||||
role: Роль
|
||||
provide_your_email_to_start: Введіть свій email, щоб почати
|
||||
provide_your_email_to_start: Введіть свій емейл, щоб почати
|
||||
provide_your_email: Введіть свій емейл
|
||||
provide_your_name_to_start: Введіть своє ім’я, щоб почати
|
||||
provide_your_name: Введіть своє ім’я
|
||||
provide_your_phone_in_international_format_to_start: Введіть номер телефону у міжнародному форматі, щоб почати
|
||||
provide_your_phone_in_international_format: Введіть номер телефону у міжнародному форматі
|
||||
use_international_format_1xxx_: 'Використовуйте міжнародний формат: +1xxx...'
|
||||
form_expired_at_html: 'Строк подачі завершився о <span class="font-semibold">%{time}</span>'
|
||||
start: Почати
|
||||
reason: Причина
|
||||
@@ -5198,7 +5276,13 @@ cs:
|
||||
digitally_signed_by: Digitálně podepsáno uživatelem
|
||||
verification_code_code: 'Ověřovací kód: %{code}'
|
||||
role: Role
|
||||
provide_your_email_to_start: Zadejte svůj email pro zahájení
|
||||
provide_your_email_to_start: Zadejte svůj e-mail pro zahájení
|
||||
provide_your_email: Zadejte svůj e-mail
|
||||
provide_your_name_to_start: Zadejte své jméno pro zahájení
|
||||
provide_your_name: Zadejte své jméno
|
||||
provide_your_phone_in_international_format_to_start: Zadejte své telefonní číslo v mezinárodním formátu pro zahájení
|
||||
provide_your_phone_in_international_format: Zadejte své telefonní číslo v mezinárodním formátu
|
||||
use_international_format_1xxx_: 'Použijte mezinárodní formát: +1xxx...'
|
||||
reason: Důvod
|
||||
form_expired_at_html: 'Formulář vypršel <span class="font-semibold">%{time}</span>'
|
||||
start: Zahájit
|
||||
@@ -5273,7 +5357,13 @@ he:
|
||||
role: תפקיד
|
||||
reason: סיבה
|
||||
verification_code_code: 'קוד אימות: %{code}'
|
||||
provide_your_email_to_start: ספק את כתובת הדוא"ל שלך כדי להתחיל
|
||||
provide_your_email_to_start: הזן את האימייל שלך כדי להתחיל
|
||||
provide_your_email: הזן את האימייל שלך
|
||||
provide_your_name_to_start: הזן את שמך כדי להתחיל
|
||||
provide_your_name: הזן את שמך
|
||||
provide_your_phone_in_international_format_to_start: הזן את מספר הטלפון שלך בפורמט בינלאומי כדי להתחיל
|
||||
provide_your_phone_in_international_format: הזן את מספר הטלפון שלך בפורמט בינלאומי
|
||||
use_international_format_1xxx_: 'השתמש בפורמט בינלאומי: +1xxx...'
|
||||
start: התחל
|
||||
starting: מתחיל
|
||||
form_expired_at_html: 'הטופס פג תוקף ב- <span class="font-semibold">%{time}</span>'
|
||||
@@ -5346,7 +5436,13 @@ nl:
|
||||
digitally_signed_by: Digitaal ondertekend door
|
||||
role: Rol
|
||||
verification_code_code: 'Verificatiecode: %{code}'
|
||||
provide_your_email_to_start: Geef uw e-mailadres om te beginnen
|
||||
provide_your_email_to_start: Voer je e-mailadres in om te beginnen
|
||||
provide_your_email: Voer je e-mailadres in
|
||||
provide_your_name_to_start: Voer je naam in om te beginnen
|
||||
provide_your_name: Voer je naam in
|
||||
provide_your_phone_in_international_format_to_start: Voer je telefoonnummer in internationaal formaat in om te beginnen
|
||||
provide_your_phone_in_international_format: Voer je telefoonnummer in internationaal formaat in
|
||||
use_international_format_1xxx_: 'Gebruik het internationale formaat: +1xxx...'
|
||||
start: Start
|
||||
reason: Reden
|
||||
form_expired_at_html: 'Formulier is verlopen op <span class="font-semibold">%{time}</span>'
|
||||
@@ -5419,7 +5515,13 @@ ar:
|
||||
email: البريد الإلكتروني
|
||||
digitally_signed_by: تم التوقيع الرقمي بواسطة
|
||||
role: الدور
|
||||
provide_your_email_to_start: قدم بريدك الإلكتروني للبدء
|
||||
provide_your_email_to_start: أدخل بريدك الإلكتروني للبدء
|
||||
provide_your_email: أدخل بريدك الإلكتروني
|
||||
provide_your_name_to_start: أدخل اسمك للبدء
|
||||
provide_your_name: أدخل اسمك
|
||||
provide_your_phone_in_international_format_to_start: أدخل رقم هاتفك بصيغة دولية للبدء
|
||||
provide_your_phone_in_international_format: أدخل رقم هاتفك بصيغة دولية
|
||||
use_international_format_1xxx_: 'استخدم التنسيق الدولي: +1xxx...'
|
||||
start: بدء
|
||||
starting: بداية
|
||||
verification_code_code: 'رمز التحقق: %{code}'
|
||||
@@ -5493,7 +5595,13 @@ ko:
|
||||
email: 이메일
|
||||
digitally_signed_by: 디지털로 서명됨
|
||||
role: 역할
|
||||
provide_your_email_to_start: 시작하려면 이메일을 제공하세요
|
||||
provide_your_email_to_start: 시작하려면 이메일을 입력하세요
|
||||
provide_your_email: 이메일을 입력하세요
|
||||
provide_your_name_to_start: 시작하려면 이름을 입력하세요
|
||||
provide_your_name: 이름을 입력하세요
|
||||
provide_your_phone_in_international_format_to_start: 시작하려면 국제 형식의 전화번호를 입력하세요
|
||||
provide_your_phone_in_international_format: 국제 형식의 전화번호를 입력하세요
|
||||
use_international_format_1xxx_: '국제 형식을 사용하세요: +1xxx...'
|
||||
start: 시작
|
||||
reason: 이유
|
||||
starting: 시작 중
|
||||
@@ -5568,6 +5676,12 @@ ja:
|
||||
digitally_signed_by: 電子署名者
|
||||
role: 役割
|
||||
provide_your_email_to_start: 開始するにはメールアドレスを入力してください
|
||||
provide_your_email: メールアドレスを入力してください
|
||||
provide_your_name_to_start: 開始するには名前を入力してください
|
||||
provide_your_name: 名前を入力してください
|
||||
provide_your_phone_in_international_format_to_start: 開始するには国際形式の電話番号を入力してください
|
||||
provide_your_phone_in_international_format: 国際形式の電話番号を入力してください
|
||||
use_international_format_1xxx_: '国際形式を使用してください:+1xxx...'
|
||||
start: 開始
|
||||
reason: 理由
|
||||
starting: 開始中
|
||||
|
||||
+1203
File diff suppressed because it is too large
Load Diff
+1272
File diff suppressed because it is too large
Load Diff
+1200
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1290
File diff suppressed because it is too large
Load Diff
+1266
File diff suppressed because it is too large
Load Diff
+1275
File diff suppressed because it is too large
Load Diff
+1275
File diff suppressed because it is too large
Load Diff
+1200
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -132,7 +132,27 @@ const token = jwt.sign({
|
||||
"fieldTypes": {
|
||||
"type": "array",
|
||||
"required": false,
|
||||
"description": "Field type names to be used in the form builder. All field types are used by default."
|
||||
"description": "Field type names to be used in the form builder. All field types are used by default.",
|
||||
"enum": [
|
||||
"heading",
|
||||
"text",
|
||||
"signature",
|
||||
"initials",
|
||||
"date",
|
||||
"datenow",
|
||||
"number",
|
||||
"image",
|
||||
"checkbox",
|
||||
"multiple",
|
||||
"file",
|
||||
"radio",
|
||||
"select",
|
||||
"cells",
|
||||
"stamp",
|
||||
"payment",
|
||||
"phone",
|
||||
"verification"
|
||||
]
|
||||
},
|
||||
"drawFieldType": {
|
||||
"type": "string",
|
||||
|
||||
@@ -317,7 +317,27 @@
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Comma separated field type names to be used in the form builder. All field types are used by default.",
|
||||
"example": "text,date"
|
||||
"example": "text,date",
|
||||
"enum": [
|
||||
"heading",
|
||||
"text",
|
||||
"signature",
|
||||
"initials",
|
||||
"date",
|
||||
"datenow",
|
||||
"number",
|
||||
"image",
|
||||
"checkbox",
|
||||
"multiple",
|
||||
"file",
|
||||
"radio",
|
||||
"select",
|
||||
"cells",
|
||||
"stamp",
|
||||
"payment",
|
||||
"phone",
|
||||
"verification"
|
||||
]
|
||||
},
|
||||
"data-draw-field-type": {
|
||||
"type": "string",
|
||||
|
||||
@@ -123,7 +123,27 @@ const token = jwt.sign({
|
||||
"fieldTypes": {
|
||||
"type": "array",
|
||||
"required": false,
|
||||
"description": "Field type names to be used in the form builder. All field types are used by default."
|
||||
"description": "Field type names to be used in the form builder. All field types are used by default.",
|
||||
"enum": [
|
||||
"heading",
|
||||
"text",
|
||||
"signature",
|
||||
"initials",
|
||||
"date",
|
||||
"datenow",
|
||||
"number",
|
||||
"image",
|
||||
"checkbox",
|
||||
"multiple",
|
||||
"file",
|
||||
"radio",
|
||||
"select",
|
||||
"cells",
|
||||
"stamp",
|
||||
"payment",
|
||||
"phone",
|
||||
"verification"
|
||||
]
|
||||
},
|
||||
"drawFieldType": {
|
||||
"type": "string",
|
||||
|
||||
@@ -144,7 +144,27 @@ const token = jwt.sign({
|
||||
"field-types": {
|
||||
"type": "array",
|
||||
"required": false,
|
||||
"description": "Field type names to be used in the form builder. All field types are used by default."
|
||||
"description": "Field type names to be used in the form builder. All field types are used by default.",
|
||||
"enum": [
|
||||
"heading",
|
||||
"text",
|
||||
"signature",
|
||||
"initials",
|
||||
"date",
|
||||
"datenow",
|
||||
"number",
|
||||
"image",
|
||||
"checkbox",
|
||||
"multiple",
|
||||
"file",
|
||||
"radio",
|
||||
"select",
|
||||
"cells",
|
||||
"stamp",
|
||||
"payment",
|
||||
"phone",
|
||||
"verification"
|
||||
]
|
||||
},
|
||||
"draw-field-type": {
|
||||
"type": "string",
|
||||
|
||||
@@ -48,6 +48,41 @@ export class AppComponent {}
|
||||
"description": "The role name or title of the signer.",
|
||||
"example": "First Party"
|
||||
},
|
||||
"token": {
|
||||
"type": "string",
|
||||
"doc_type": "object",
|
||||
"description": "JSON Web Token (JWT HS256) with a payload signed using the API key. <b>JWT can be generated only on the backend.</b>.",
|
||||
"required": false,
|
||||
"properties": {
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"description": "Template or Submitter slug. When Submitter slug is used no need to pass additional email param."
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Email address of the signer. Additional email form step will be displayed if the email attribute is not specified with Template slug."
|
||||
},
|
||||
"external_id": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Your application-specific unique string key to identify signer within your app."
|
||||
},
|
||||
"preview": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Show form in preview mode without ability to submit it."
|
||||
}
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Show form in preview mode without ability to submit it. Completed documents embedded in preview mode require `data-token` authentication."
|
||||
},
|
||||
"expand": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
@@ -66,11 +101,6 @@ export class AppComponent {}
|
||||
"default": false,
|
||||
"description": "Order form fields based on their position on the pages."
|
||||
},
|
||||
"externalId": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Your application-specific unique string key to identify signer within your app."
|
||||
},
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
@@ -87,12 +117,6 @@ export class AppComponent {}
|
||||
"default": "{}",
|
||||
"description": "Object that contains i18n keys to replace the default UI text with custom values. See <a href=\"https://github.com/docusealco/docuseal/blob/master/app/javascript/submission_form/i18n.js\" class=\"link\" target=\"_blank\" rel=\"nofollow\">submission_form/i18n.js</a> for available i18n keys."
|
||||
},
|
||||
"preview": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Show form in preview mode without ability to submit it."
|
||||
},
|
||||
"goToLast": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
@@ -242,6 +266,11 @@ export class AppComponent {}
|
||||
"description": "Pre-assigned values for form fields.",
|
||||
"example": "{ 'First Name': 'Jon', 'Last Name': 'Doe' }"
|
||||
},
|
||||
"externalId": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Your application-specific unique string key to identify signer within your app."
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"required": false,
|
||||
|
||||
@@ -42,6 +42,41 @@
|
||||
"description": "The role name or title of the signer.",
|
||||
"example": "First Party"
|
||||
},
|
||||
"data-token": {
|
||||
"type": "string",
|
||||
"doc_type": "object",
|
||||
"description": "JSON Web Token (JWT HS256) with a payload signed using the API key. <b>JWT can be generated only on the backend.</b>.",
|
||||
"required": false,
|
||||
"properties": {
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"description": "Template or Submitter slug. When Submitter slug is used no need to pass additional email param."
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Email address of the signer. Additional email form step will be displayed if the email attribute is not specified with Template slug."
|
||||
},
|
||||
"external_id": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Your application-specific unique string key to identify signer within your app."
|
||||
},
|
||||
"preview": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Show form in preview mode without ability to submit it."
|
||||
}
|
||||
}
|
||||
},
|
||||
"data-preview": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Show form in preview mode without ability to submit it. Completed documents embedded in preview mode require `data-token` authentication."
|
||||
},
|
||||
"data-expand": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
@@ -60,12 +95,6 @@
|
||||
"default": false,
|
||||
"description": "Order form fields based on their position on the pages."
|
||||
},
|
||||
"data-preview": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Show form in preview mode without ability to submit it."
|
||||
},
|
||||
"data-logo": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
|
||||
@@ -45,6 +45,41 @@ export function App() {
|
||||
"description": "The role name or title of the signer.",
|
||||
"example": "First Party"
|
||||
},
|
||||
"token": {
|
||||
"type": "string",
|
||||
"doc_type": "object",
|
||||
"description": "JSON Web Token (JWT HS256) with a payload signed using the API key. <b>JWT can be generated only on the backend.</b>.",
|
||||
"required": false,
|
||||
"properties": {
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"description": "Template or Submitter slug. When Submitter slug is used no need to pass additional email param."
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Email address of the signer. Additional email form step will be displayed if the email attribute is not specified with Template slug."
|
||||
},
|
||||
"external_id": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Your application-specific unique string key to identify signer within your app."
|
||||
},
|
||||
"preview": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Show form in preview mode without ability to submit it."
|
||||
}
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Show form in preview mode without ability to submit it. Completed documents embedded in preview mode require `token` authentication."
|
||||
},
|
||||
"expand": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
@@ -63,11 +98,6 @@ export function App() {
|
||||
"default": false,
|
||||
"description": "Order form fields based on their position on the pages."
|
||||
},
|
||||
"externalId": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Your application-specific unique string key to identify signer within your app."
|
||||
},
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
@@ -84,12 +114,6 @@ export function App() {
|
||||
"default": "{}",
|
||||
"description": "Object that contains i18n keys to replace the default UI text with custom values. See <a href=\"https://github.com/docusealco/docuseal/blob/master/app/javascript/submission_form/i18n.js\" class=\"link\" target=\"_blank\" rel=\"nofollow\">submission_form/i18n.js</a> for available i18n keys."
|
||||
},
|
||||
"preview": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Show form in preview mode without ability to submit it."
|
||||
},
|
||||
"goToLast": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
@@ -239,6 +263,11 @@ export function App() {
|
||||
"description": "Pre-assigned values for form fields.",
|
||||
"example": "{ 'First Name': 'Jon', 'Last Name': 'Doe' }"
|
||||
},
|
||||
"externalId": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Your application-specific unique string key to identify signer within your app."
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"required": false,
|
||||
|
||||
@@ -54,10 +54,40 @@ export default {
|
||||
"description": "The role name or title of the signer.",
|
||||
"example": "First Party"
|
||||
},
|
||||
"external-id": {
|
||||
"token": {
|
||||
"type": "string",
|
||||
"doc_type": "object",
|
||||
"description": "JSON Web Token (JWT HS256) with a payload signed using the API key. <b>JWT can be generated only on the backend.</b>.",
|
||||
"required": false,
|
||||
"description": "Your application-specific unique string key to identify signer within your app."
|
||||
"properties": {
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"description": "Template or Submitter slug. When Submitter slug is used no need to pass additional email param."
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Email address of the signer. Additional email form step will be displayed if the email attribute is not specified with Template slug."
|
||||
},
|
||||
"external_id": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Your application-specific unique string key to identify signer within your app."
|
||||
},
|
||||
"preview": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Show form in preview mode without ability to submit it."
|
||||
}
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Show form in preview mode without ability to submit it. Completed documents embedded in preview mode require `data-token` authentication."
|
||||
},
|
||||
"expand": {
|
||||
"type": "boolean",
|
||||
@@ -93,12 +123,6 @@ export default {
|
||||
"default": "{}",
|
||||
"description": "Object that contains i18n keys to replace the default UI text with custom values. See <a href=\"https://github.com/docusealco/docuseal/blob/master/app/javascript/submission_form/i18n.js\" class=\"link\" target=\"_blank\" rel=\"nofollow\">submission_form/i18n.js</a> for available i18n keys."
|
||||
},
|
||||
"preview": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Show form in preview mode without ability to submit it."
|
||||
},
|
||||
"go-to-last": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
@@ -249,6 +273,11 @@ export default {
|
||||
"description": "Pre-assigned values for form fields.",
|
||||
"example": "{ 'First Name': 'Jon', 'Last Name': 'Doe' }"
|
||||
},
|
||||
"external-id": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Your application-specific unique string key to identify signer within your app."
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"required": false,
|
||||
|
||||
+1845
-18
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,7 @@ module Abilities
|
||||
TemplateSharing.where({ ability:, account_id: [user.account_id, TemplateSharing::ALL_ID] }.compact)
|
||||
.select(:template_id)
|
||||
|
||||
Template.where(Template.arel_table[:id].in(Arel::Nodes::Union.new(templates.select(:id).arel, shared_ids.arel)))
|
||||
Template.where(Template.arel_table[:id].in(templates.select(:id).arel.union(shared_ids.arel)))
|
||||
end
|
||||
|
||||
def entity(template, user:, ability: nil)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ImageUtils
|
||||
module_function
|
||||
|
||||
def blank?(image)
|
||||
min = (0...image.bands).map { |i| image.stats.getpoint(0, i)[0] }
|
||||
max = (0...image.bands).map { |i| image.stats.getpoint(1, i)[0] }
|
||||
|
||||
return true if min.all?(255) && max.all?(255)
|
||||
return true if min.all?(0) && max.all?(0)
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
@@ -24,6 +24,7 @@ class Pdfium
|
||||
typedef :pointer, :FPDF_PAGE
|
||||
typedef :pointer, :FPDF_BITMAP
|
||||
typedef :pointer, :FPDF_FORMHANDLE
|
||||
typedef :pointer, :FPDF_TEXTPAGE
|
||||
|
||||
MAX_SIZE = 32_767
|
||||
|
||||
@@ -72,6 +73,11 @@ class Pdfium
|
||||
|
||||
attach_function :FPDF_RenderPageBitmap, %i[FPDF_BITMAP FPDF_PAGE int int int int int int], :void
|
||||
|
||||
attach_function :FPDFText_LoadPage, [:FPDF_PAGE], :FPDF_TEXTPAGE
|
||||
attach_function :FPDFText_ClosePage, [:FPDF_TEXTPAGE], :void
|
||||
attach_function :FPDFText_CountChars, [:FPDF_TEXTPAGE], :int
|
||||
attach_function :FPDFText_GetText, %i[FPDF_TEXTPAGE int int pointer], :int
|
||||
|
||||
typedef :int, :FPDF_BOOL
|
||||
typedef :pointer, :IPDF_JSPLATFORM
|
||||
|
||||
@@ -346,6 +352,40 @@ class Pdfium
|
||||
Pdfium.FPDFBitmap_Destroy(bitmap_ptr) if bitmap_ptr && !bitmap_ptr.null?
|
||||
end
|
||||
|
||||
def text
|
||||
return @text if @text
|
||||
|
||||
ensure_not_closed!
|
||||
|
||||
text_page = Pdfium.FPDFText_LoadPage(page_ptr)
|
||||
|
||||
if text_page.null?
|
||||
Pdfium.check_last_error("Failed to load text page #{page_index}")
|
||||
|
||||
raise PdfiumError, "Failed to load text page #{page_index}, pointer is NULL."
|
||||
end
|
||||
|
||||
char_count = Pdfium.FPDFText_CountChars(text_page)
|
||||
|
||||
return @text = '' if char_count.zero?
|
||||
|
||||
buffer_char_capacity = char_count + 1
|
||||
|
||||
buffer = FFI::MemoryPointer.new(:uint16, buffer_char_capacity)
|
||||
|
||||
chars_written = Pdfium.FPDFText_GetText(text_page, 0, buffer_char_capacity, buffer)
|
||||
|
||||
if chars_written <= 0
|
||||
Pdfium.check_last_error("Failed to extract text from page #{page_index}")
|
||||
|
||||
return @text = ''
|
||||
end
|
||||
|
||||
@text = buffer.read_bytes((chars_written * 2) - 2).force_encoding('UTF-16LE').encode('UTF-8')
|
||||
ensure
|
||||
Pdfium.FPDFText_ClosePage(text_page) if text_page && !text_page.null?
|
||||
end
|
||||
|
||||
def close
|
||||
return if closed?
|
||||
|
||||
|
||||
+21
-9
@@ -56,7 +56,7 @@ module SearchEntries
|
||||
end
|
||||
|
||||
[sql, number, number.length > 1 ? number.delete_prefix('0') : number, keyword]
|
||||
elsif keyword.match?(/[^\p{L}\d&@._\-]/) || keyword.match?(/\A['"].*['"]\z/)
|
||||
elsif keyword.match?(/[^\p{L}\d&@.\-]/) || keyword.match?(/\A['"].*['"]\z/)
|
||||
['tsvector @@ plainto_tsquery(?)', TextUtils.transliterate(keyword.downcase)]
|
||||
else
|
||||
keyword = TextUtils.transliterate(keyword.downcase).squish
|
||||
@@ -180,15 +180,21 @@ module SearchEntries
|
||||
end
|
||||
|
||||
def index_template(template)
|
||||
sql = SearchEntry.sanitize_sql_array(
|
||||
["SELECT to_tsvector(:text), to_tsvector('simple', :text)",
|
||||
{ text: TextUtils.transliterate(template.name.to_s.downcase).delete("\0") }]
|
||||
)
|
||||
text = TextUtils.transliterate(template.name.to_s.downcase.squish).delete("\0")
|
||||
|
||||
sql = SearchEntry.sanitize_sql_array(["SELECT to_tsvector(:text), to_tsvector('simple', :text)", { text: }])
|
||||
|
||||
entry = template.search_entry || template.build_search_entry
|
||||
|
||||
entry.account_id = template.account_id
|
||||
entry.tsvector, ngram = SearchEntry.connection.select_rows(sql).first
|
||||
|
||||
hyphens = text.scan(/\b[^\s]*?\d-[^\s]+?\b/) + text.scan(/\b[^\s]+-\d[^\s]*?\b/)
|
||||
|
||||
hyphens.uniq.each_with_index do |item, index|
|
||||
entry.tsvector += " '#{item.delete("'")}':#{index + 1}" unless entry.tsvector.include?(item)
|
||||
end
|
||||
|
||||
entry.ngram = build_ngram(ngram)
|
||||
|
||||
return if entry.tsvector.blank?
|
||||
@@ -205,15 +211,21 @@ module SearchEntries
|
||||
def index_submission(submission)
|
||||
return if submission.name.blank?
|
||||
|
||||
sql = SearchEntry.sanitize_sql_array(
|
||||
["SELECT to_tsvector(:text), to_tsvector('simple', :text)",
|
||||
{ text: TextUtils.transliterate(submission.name.to_s.downcase).delete("\0") }]
|
||||
)
|
||||
text = TextUtils.transliterate(submission.name.to_s.downcase.squish).delete("\0")
|
||||
|
||||
sql = SearchEntry.sanitize_sql_array(["SELECT to_tsvector(:text), to_tsvector('simple', :text)", { text: }])
|
||||
|
||||
entry = submission.search_entry || submission.build_search_entry
|
||||
|
||||
entry.account_id = submission.account_id
|
||||
entry.tsvector, ngram = SearchEntry.connection.select_rows(sql).first
|
||||
|
||||
hyphens = text.scan(/\b[^\s]*?\d-[^\s]+?\b/) + text.scan(/\b[^\s]+-\d[^\s]*?\b/)
|
||||
|
||||
hyphens.uniq.each_with_index do |item, index|
|
||||
entry.tsvector += " '#{item.delete("'")}':#{index + 1}" unless entry.tsvector.include?(item)
|
||||
end
|
||||
|
||||
entry.ngram = build_ngram(ngram)
|
||||
|
||||
return if entry.tsvector.blank?
|
||||
|
||||
+2
-1
@@ -50,7 +50,8 @@ module Submissions
|
||||
arel,
|
||||
Submission.where(
|
||||
template_id: SearchEntry.where(record_type: 'Template')
|
||||
.where(account_id: current_user.account_id)
|
||||
.where(account_id: [current_user.account_id,
|
||||
current_user.account.linked_account_account&.account_id].compact)
|
||||
.where(*SearchEntries.build_tsquery(keyword))
|
||||
.select(:record_id)
|
||||
).select(:id).arel
|
||||
|
||||
@@ -312,8 +312,9 @@ module Submissions
|
||||
text_align: field_name.to_s.match?(RTL_REGEXP) ? :right : :left,
|
||||
line_spacing: 1.3, padding: [0, 0, 2, 0]
|
||||
),
|
||||
if field['type'].in?(%w[image signature initials stamp])
|
||||
attachment = submitter.attachments.find { |a| a.uuid == value }
|
||||
if field['type'].in?(%w[image signature initials stamp]) &&
|
||||
(attachment = submitter.attachments.find { |a| a.uuid == value }) &&
|
||||
attachment.image?
|
||||
|
||||
image =
|
||||
begin
|
||||
@@ -340,7 +341,7 @@ module Submissions
|
||||
|
||||
composer.image(io, width:, height:, margin: [5, 0, 10, 0])
|
||||
composer.formatted_text_box([{ text: '' }])
|
||||
elsif field['type'].in?(%w[file payment])
|
||||
elsif field['type'].in?(%w[file payment image])
|
||||
if field['type'] == 'payment'
|
||||
unit = CURRENCY_SYMBOLS[field['preferences']['currency']] || field['preferences']['currency']
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ module Submissions
|
||||
font_name = FONT_NAME unless font_name.in?(DEFAULT_FONTS)
|
||||
|
||||
if font_variant != :none && font_name == FONT_NAME
|
||||
font_name = FONT_VARIANS[font_variant]
|
||||
font_name = FONT_VARIANS[font_variant] if FONT_VARIANS[font_variant]
|
||||
font_variant = nil unless font_name.in?(DEFAULT_FONTS)
|
||||
end
|
||||
|
||||
@@ -252,7 +252,11 @@ module Submissions
|
||||
canvas = page.canvas(type: :overlay)
|
||||
canvas.font(FONT_NAME, size: font_size)
|
||||
|
||||
case field['type']
|
||||
field_type = field['type']
|
||||
field_type = 'file' if field_type == 'image' &&
|
||||
!submitter.attachments.find { |a| a.uuid == value }.image?
|
||||
|
||||
case field_type
|
||||
when ->(type) { type == 'signature' && (with_signature_id || field.dig('preferences', 'reason_field_uuid')) }
|
||||
attachment = submitter.attachments.find { |a| a.uuid == value }
|
||||
|
||||
|
||||
+2
-1
@@ -53,7 +53,7 @@ module Submitters
|
||||
end
|
||||
|
||||
[sql, number, weight, number.length > 1 ? number.delete_prefix('0') : number, weight]
|
||||
elsif keyword.match?(/[^\p{L}\d&@._\-]/)
|
||||
elsif keyword.match?(/[^\p{L}\d&@.\-]/)
|
||||
terms = TextUtils.transliterate(keyword.downcase).split(/\b/).map(&:squish).compact_blank.uniq
|
||||
|
||||
if terms.size > 1
|
||||
@@ -135,6 +135,7 @@ module Submitters
|
||||
preferences['email_message_uuid'] = email_message.uuid if email_message
|
||||
preferences['send_email'] = params['send_email'].in?(TRUE_VALUES) if params.key?('send_email')
|
||||
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['bcc_completed'] = params['bcc_completed'] if params.key?('bcc_completed')
|
||||
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')
|
||||
|
||||
@@ -7,6 +7,8 @@ module Submitters
|
||||
module_function
|
||||
|
||||
def call(submitter, params, cookies = nil, attachments = [])
|
||||
attachments = attachments.select { |e| e.record_id == submitter.id && e.record_type == 'Submitter' }
|
||||
|
||||
if (value = params[:signature_src].presence || params[:signature].presence)
|
||||
find_or_create_signature_from_value(submitter, value, attachments)
|
||||
elsif params[:signed_signature_uuids].present?
|
||||
|
||||
@@ -219,8 +219,7 @@ module Submitters
|
||||
submitters_values = nil
|
||||
has_other_submitters = submission.template_submitters.size > 1
|
||||
|
||||
has_document_conditions =
|
||||
(submission.template_schema || submission.template.schema).any? { |e| e['conditions'].present? }
|
||||
has_document_conditions = submission_has_document_conditions?(submission)
|
||||
|
||||
attachments_index =
|
||||
if has_document_conditions
|
||||
@@ -234,7 +233,7 @@ module Submitters
|
||||
|
||||
if has_document_conditions && !check_field_areas_attachments(field, attachments_index)
|
||||
submitter.values.delete(field['uuid'])
|
||||
required_field_uuids_acc.delete(field['uuid'])
|
||||
required_field_uuids_acc&.delete(field['uuid'])
|
||||
end
|
||||
|
||||
if has_other_submitters && !submitters_values &&
|
||||
@@ -244,13 +243,17 @@ module Submitters
|
||||
|
||||
unless check_field_conditions(submitters_values || submitter.values, field, submission.fields_uuid_index)
|
||||
submitter.values.delete(field['uuid'])
|
||||
required_field_uuids_acc.delete(field['uuid'])
|
||||
required_field_uuids_acc&.delete(field['uuid'])
|
||||
end
|
||||
end
|
||||
|
||||
submitter.values
|
||||
end
|
||||
|
||||
def submission_has_document_conditions?(submission)
|
||||
(submission.template_schema || submission.template.schema).any? { |e| e['conditions'].present? }
|
||||
end
|
||||
|
||||
def required_editable_field?(field)
|
||||
return false if NONEDITABLE_FIELD_TYPES.include?(field['type'])
|
||||
|
||||
|
||||
+2
-1
@@ -56,7 +56,8 @@ module Templates
|
||||
|
||||
templates.where(
|
||||
id: SearchEntry.where(record_type: 'Template')
|
||||
.where(account_id: current_user.account_id)
|
||||
.where(account_id: [current_user.account_id,
|
||||
current_user.account.linked_account_account&.account_id].compact)
|
||||
.where(*SearchEntries.build_tsquery(keyword))
|
||||
.select(:record_id)
|
||||
)
|
||||
|
||||
@@ -69,7 +69,7 @@ module Templates
|
||||
bitdepth = 2**image.stats.to_a[1..3].pluck(2).uniq.size
|
||||
|
||||
io = StringIO.new(image.write_to_buffer(FORMAT, compression: 7, filter: 0, bitdepth:,
|
||||
palette: true, Q: bitdepth == 8 ? Q : 5, dither: 0))
|
||||
palette: true, Q: Q, dither: 0))
|
||||
|
||||
ActiveStorage::Attachment.create!(
|
||||
blob: ActiveStorage::Blob.create_and_upload!(
|
||||
@@ -141,7 +141,7 @@ module Templates
|
||||
bitdepth = 2**page.stats.to_a[1..3].pluck(2).uniq.size
|
||||
|
||||
page.write_to_buffer(format, compression: 7, filter: 0, bitdepth:,
|
||||
palette: true, Q: bitdepth == 8 ? Q : 5, dither: 0)
|
||||
palette: true, Q: Q, dither: 0)
|
||||
else
|
||||
page.write_to_buffer(format, interlace: true, Q: JPEG_Q)
|
||||
end
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
"@babel/plugin-transform-runtime": "7.21.4",
|
||||
"@babel/preset-env": "7.21.5",
|
||||
"@babel/runtime": "7.21.5",
|
||||
"@eid-easy/eideasy-widget": "^2.132.1",
|
||||
"@eid-easy/eideasy-widget": "^2.159.0",
|
||||
"@github/catalyst": "^2.0.0-beta",
|
||||
"@hotwired/turbo": "https://github.com/docusealco/turbo#main",
|
||||
"@hotwired/turbo-rails": "^7.3.0",
|
||||
|
||||
@@ -9,18 +9,78 @@ RSpec.describe 'Signing Form' do
|
||||
create(:template, shared_link: true, account:, author:, except_field_types: %w[phone payment stamp])
|
||||
end
|
||||
|
||||
before do
|
||||
it 'displays only the email step when only email is required' do
|
||||
visit start_form_path(slug: template.slug)
|
||||
end
|
||||
|
||||
it 'shows the email step' do
|
||||
expect(page).to have_content('You have been invited to submit a form')
|
||||
expect(page).to have_content("Invited by #{account.name}")
|
||||
expect(page).to have_field('Email', type: 'email')
|
||||
expect(page).to have_field('Email', type: 'email', placeholder: 'Provide your email to start')
|
||||
expect(page).not_to have_field('Phone', type: 'tel')
|
||||
expect(page).not_to have_field('Name', type: 'text')
|
||||
expect(page).to have_button('Start')
|
||||
end
|
||||
|
||||
it 'displays name, email, and phone fields together when all are required' do
|
||||
template.update(preferences: { link_form_fields: %w[email name phone] })
|
||||
|
||||
visit start_form_path(slug: template.slug)
|
||||
|
||||
expect(page).to have_content('You have been invited to submit a form')
|
||||
expect(page).to have_content("Invited by #{account.name}")
|
||||
expect(page).to have_field('Email', type: 'email', placeholder: 'Provide your email')
|
||||
expect(page).to have_field('Name', type: 'text', placeholder: 'Provide your name')
|
||||
expect(page).to have_field('Phone', type: 'tel', placeholder: 'Provide your phone in international format')
|
||||
expect(page).to have_button('Start')
|
||||
end
|
||||
|
||||
it 'displays only the name step when only name is required' do
|
||||
template.update(preferences: { link_form_fields: %w[name] })
|
||||
|
||||
visit start_form_path(slug: template.slug)
|
||||
|
||||
expect(page).to have_content('You have been invited to submit a form')
|
||||
expect(page).to have_content("Invited by #{account.name}")
|
||||
expect(page).to have_field('Name', type: 'text', placeholder: 'Provide your name to start')
|
||||
expect(page).not_to have_field('Phone', type: 'tel')
|
||||
expect(page).not_to have_field('Email', type: 'email')
|
||||
expect(page).to have_button('Start')
|
||||
end
|
||||
|
||||
it 'displays only the phone step when only phone is required' do
|
||||
template.update(preferences: { link_form_fields: %w[phone] })
|
||||
|
||||
visit start_form_path(slug: template.slug)
|
||||
|
||||
expect(page).to have_content('You have been invited to submit a form')
|
||||
expect(page).to have_content("Invited by #{account.name}")
|
||||
expect(page).to have_field('Phone', type: 'tel',
|
||||
placeholder: 'Provide your phone in international format to start')
|
||||
expect(page).not_to have_field('Name', type: 'text')
|
||||
expect(page).not_to have_field('Email', type: 'email')
|
||||
expect(page).to have_button('Start')
|
||||
end
|
||||
|
||||
it 'prevents starting the form if phone is not in international format' do
|
||||
template.update(preferences: { link_form_fields: %w[phone] })
|
||||
|
||||
visit start_form_path(slug: template.slug)
|
||||
|
||||
fill_in 'Phone', with: '12345'
|
||||
|
||||
expect { click_button 'Start' }.not_to(change { current_path })
|
||||
end
|
||||
|
||||
it 'prevents starting the form if email is invali' do
|
||||
visit start_form_path(slug: template.slug)
|
||||
|
||||
fill_in 'Email', with: 'invalid-email'
|
||||
|
||||
expect { click_button 'Start' }.not_to(change { current_path })
|
||||
end
|
||||
|
||||
it 'completes the form' do
|
||||
visit start_form_path(slug: template.slug)
|
||||
|
||||
# Submit's email step
|
||||
fill_in 'Email', with: 'john.dou@example.com'
|
||||
click_button 'Start'
|
||||
@@ -100,6 +160,97 @@ RSpec.describe 'Signing Form' do
|
||||
expect(field_value(submitter, 'Attachment')).to be_present
|
||||
expect(field_value(submitter, 'Cell code')).to eq '123'
|
||||
end
|
||||
|
||||
# rubocop:disable RSpec/ExampleLength
|
||||
it 'completes the form when name, email, and phone are required' do
|
||||
template.update(preferences: { link_form_fields: %w[email name phone] })
|
||||
|
||||
visit start_form_path(slug: template.slug)
|
||||
|
||||
# Submit's name, email, and phone step
|
||||
fill_in 'Email', with: 'john.dou@example.com'
|
||||
fill_in 'Name', with: 'John Doe'
|
||||
fill_in 'Phone', with: '+17732298825'
|
||||
click_button 'Start'
|
||||
|
||||
# Text step
|
||||
fill_in 'First Name', with: 'John'
|
||||
click_button 'next'
|
||||
|
||||
# Date step
|
||||
fill_in 'Birthday', with: I18n.l(20.years.ago, format: '%Y-%m-%d')
|
||||
click_button 'next'
|
||||
|
||||
# Checkbox step
|
||||
check 'Do you agree?'
|
||||
click_button 'next'
|
||||
|
||||
# Radio step
|
||||
choose 'Boy'
|
||||
click_button 'next'
|
||||
|
||||
# Signature step
|
||||
draw_canvas
|
||||
click_button 'next'
|
||||
|
||||
# Number step
|
||||
fill_in 'House number', with: '123'
|
||||
click_button 'next'
|
||||
|
||||
# Multiple choice step
|
||||
%w[Red Blue].each { |color| check color }
|
||||
click_button 'next'
|
||||
|
||||
# Select step
|
||||
select 'Male', from: 'Gender'
|
||||
click_button 'next'
|
||||
|
||||
# Initials step
|
||||
draw_canvas
|
||||
click_button 'next'
|
||||
|
||||
# Image step
|
||||
find('#dropzone').click
|
||||
find('input[type="file"]', visible: false).attach_file(Rails.root.join('spec/fixtures/sample-image.png'))
|
||||
click_button 'next'
|
||||
|
||||
# File step
|
||||
find('#dropzone').click
|
||||
find('input[type="file"]', visible: false).attach_file(Rails.root.join('spec/fixtures/sample-document.pdf'))
|
||||
click_button 'next'
|
||||
|
||||
# Cell step
|
||||
fill_in 'Cell code', with: '123'
|
||||
click_on 'Complete'
|
||||
|
||||
expect(page).to have_button('Download')
|
||||
expect(page).to have_content('Document has been signed!')
|
||||
|
||||
submitter = template.submissions.last.submitters.last
|
||||
|
||||
expect(submitter.email).to eq('john.dou@example.com')
|
||||
expect(submitter.name).to eq('John Doe')
|
||||
expect(submitter.phone).to eq('+17732298825')
|
||||
expect(submitter.ip).to eq('127.0.0.1')
|
||||
expect(submitter.ua).to be_present
|
||||
expect(submitter.opened_at).to be_present
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(submitter.declined_at).to be_nil
|
||||
|
||||
expect(field_value(submitter, 'First Name')).to eq 'John'
|
||||
expect(field_value(submitter, 'Birthday')).to eq 20.years.ago.strftime('%Y-%m-%d')
|
||||
expect(field_value(submitter, 'Do you agree?')).to be_truthy
|
||||
expect(field_value(submitter, 'First child')).to eq 'Boy'
|
||||
expect(field_value(submitter, 'Signature')).to be_present
|
||||
expect(field_value(submitter, 'House number')).to eq 123
|
||||
expect(field_value(submitter, 'Colors')).to contain_exactly('Red', 'Blue')
|
||||
expect(field_value(submitter, 'Gender')).to eq 'Male'
|
||||
expect(field_value(submitter, 'Initials')).to be_present
|
||||
expect(field_value(submitter, 'Avatar')).to be_present
|
||||
expect(field_value(submitter, 'Attachment')).to be_present
|
||||
expect(field_value(submitter, 'Cell code')).to eq '123'
|
||||
end
|
||||
# rubocop:enable RSpec/ExampleLength
|
||||
end
|
||||
|
||||
context 'when the submitter form link is opened' do
|
||||
@@ -848,6 +999,33 @@ RSpec.describe 'Signing Form' do
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the template shared link is disabled' do
|
||||
let(:template) do
|
||||
create(:template, shared_link: false, account:, author:, only_field_types: %w[text])
|
||||
end
|
||||
|
||||
context 'when user is logged in' do
|
||||
before do
|
||||
login_as author
|
||||
visit start_form_path(slug: template.slug)
|
||||
end
|
||||
|
||||
it 'shows a warning that the shared link is disabled and provides an option to enable it' do
|
||||
expect(page).to have_content('Share link is currently disabled')
|
||||
expect(page).to have_content(template.name)
|
||||
expect(page).to have_button('Enable shared link')
|
||||
end
|
||||
|
||||
it 'enables the shared link' do
|
||||
expect do
|
||||
click_button 'Enable shared link'
|
||||
end.to change { template.reload.shared_link }.from(false).to(true)
|
||||
|
||||
expect(page).to have_content('You have been invited to submit a form')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'sends completed email' do
|
||||
template = create(:template, account:, author:, only_field_types: %w[text signature])
|
||||
submission = create(:submission, template:)
|
||||
|
||||
@@ -1035,22 +1035,22 @@
|
||||
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
|
||||
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
|
||||
|
||||
"@eid-easy/eideasy-browser-client@2.104.1":
|
||||
version "2.104.1"
|
||||
resolved "https://registry.yarnpkg.com/@eid-easy/eideasy-browser-client/-/eideasy-browser-client-2.104.1.tgz#e492e0fc8ef1ff470c66522d530d5636264f30b3"
|
||||
integrity sha512-d4fgqF5U+pWuPpfgbb/+BxI2CYThkbviAfl9NbLoKMOMO4bh7HZFxLdW5uU2O8yIeldGFunJWxEbMTLv/2PlTA==
|
||||
"@eid-easy/eideasy-browser-client@2.124.0":
|
||||
version "2.124.0"
|
||||
resolved "https://registry.yarnpkg.com/@eid-easy/eideasy-browser-client/-/eideasy-browser-client-2.124.0.tgz#940ccb9d4d853f0bd32e49cc455f2c7b13f3bbd1"
|
||||
integrity sha512-hZuUeg0CcyvgJSseRBQixRStAIr58bulmNcikcASBo6z8wv+/R8nAnUoE7qdNW1l4ZyYxLmVOwDs6+W+FHy6hQ==
|
||||
dependencies:
|
||||
axios "1.7.8"
|
||||
axios "1.8.2"
|
||||
jsencrypt "3.2.1"
|
||||
lodash "^4.17.21"
|
||||
serialize-error "^9.1.1"
|
||||
|
||||
"@eid-easy/eideasy-widget@^2.132.1":
|
||||
version "2.132.1"
|
||||
resolved "https://registry.yarnpkg.com/@eid-easy/eideasy-widget/-/eideasy-widget-2.132.1.tgz#634c077c55d7e582846dbeae47f6f37796b76bd6"
|
||||
integrity sha512-bZNdKsxja4iIUm6B4x6pOyiBdoMNNUB3615oJgNUWallrfeiIbBQxLwG+m5nmivmHMgdaIQylmtfrhc6e7kgYA==
|
||||
"@eid-easy/eideasy-widget@^2.159.0":
|
||||
version "2.159.0"
|
||||
resolved "https://registry.yarnpkg.com/@eid-easy/eideasy-widget/-/eideasy-widget-2.159.0.tgz#f2291dad292bd5f7941496f0db0aa0a180226b6b"
|
||||
integrity sha512-527uCNrN5MVY/PaOUoZ3J2XZ0C+xt6057sJ4xSO0/FPYj2cxOn+qyCJODQjOhjBL7GcnpShWPpYAfv1OIStYwQ==
|
||||
dependencies:
|
||||
"@eid-easy/eideasy-browser-client" "2.104.1"
|
||||
"@eid-easy/eideasy-browser-client" "2.124.0"
|
||||
core-js "^3.8.3"
|
||||
i18n-iso-countries "^6.7.0"
|
||||
lodash.defaultsdeep "^4.6.1"
|
||||
@@ -1914,10 +1914,10 @@ available-typed-arrays@^1.0.5:
|
||||
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
|
||||
integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
|
||||
|
||||
axios@1.7.8:
|
||||
version "1.7.8"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.8.tgz#1997b1496b394c21953e68c14aaa51b7b5de3d6e"
|
||||
integrity sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==
|
||||
axios@1.8.2:
|
||||
version "1.8.2"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.8.2.tgz#fabe06e241dfe83071d4edfbcaa7b1c3a40f7979"
|
||||
integrity sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==
|
||||
dependencies:
|
||||
follow-redirects "^1.15.6"
|
||||
form-data "^4.0.0"
|
||||
|
||||
Reference in New Issue
Block a user