Merge pull from docusealco/wip
This commit is contained in:
+6
-6
@@ -338,18 +338,18 @@ GEM
|
|||||||
net-smtp (0.5.0)
|
net-smtp (0.5.0)
|
||||||
net-protocol
|
net-protocol
|
||||||
nio4r (2.7.4)
|
nio4r (2.7.4)
|
||||||
nokogiri (1.18.5)
|
nokogiri (1.18.8)
|
||||||
mini_portile2 (~> 2.8.2)
|
mini_portile2 (~> 2.8.2)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.18.5-aarch64-linux-gnu)
|
nokogiri (1.18.8-aarch64-linux-gnu)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.18.5-aarch64-linux-musl)
|
nokogiri (1.18.8-aarch64-linux-musl)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.18.5-arm64-darwin)
|
nokogiri (1.18.8-arm64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.18.5-x86_64-linux-gnu)
|
nokogiri (1.18.8-x86_64-linux-gnu)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.18.5-x86_64-linux-musl)
|
nokogiri (1.18.8-x86_64-linux-musl)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
oj (3.16.8)
|
oj (3.16.8)
|
||||||
bigdecimal (>= 3.0)
|
bigdecimal (>= 3.0)
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ DocuSeal is an open source platform that provides secure and efficient digital d
|
|||||||
- PDF signature verification
|
- PDF signature verification
|
||||||
- Users management
|
- Users management
|
||||||
- Mobile-optimized
|
- Mobile-optimized
|
||||||
- 6 UI languages with signing available in 13 languages
|
- 6 UI languages with signing available in 14 languages
|
||||||
- API and Webhooks for integrations
|
- API and Webhooks for integrations
|
||||||
- Easy to deploy in minutes
|
- Easy to deploy in minutes
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class AccountsController < ApplicationController
|
|||||||
def destroy
|
def destroy
|
||||||
authorize!(:manage, current_account)
|
authorize!(:manage, current_account)
|
||||||
|
|
||||||
true_user.update!(locked_at: Time.current)
|
true_user.update!(locked_at: Time.current, email: true_user.email.sub('@', '+removed@'))
|
||||||
|
|
||||||
# rubocop:disable Layout/LineLength
|
# rubocop:disable Layout/LineLength
|
||||||
render turbo_stream: turbo_stream.replace(
|
render turbo_stream: turbo_stream.replace(
|
||||||
|
|||||||
@@ -11,17 +11,7 @@ module Api
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
submissions = Submissions.search(@submissions, params[:q])
|
submissions = Submissions.search(@submissions, params[:q])
|
||||||
submissions = submissions.where(template_id: params[:template_id]) if params[:template_id].present?
|
submissions = filter_submissions(submissions, params)
|
||||||
|
|
||||||
if params[:template_folder].present?
|
|
||||||
submissions = submissions.joins(template: :folder).where(folder: { name: params[:template_folder] })
|
|
||||||
end
|
|
||||||
|
|
||||||
if params.key?(:archived)
|
|
||||||
submissions = params[:archived].in?(['true', true]) ? submissions.archived : submissions.active
|
|
||||||
end
|
|
||||||
|
|
||||||
submissions = Submissions::Filter.call(submissions, current_user, params)
|
|
||||||
|
|
||||||
submissions = paginate(submissions.preload(:created_by_user, :submitters,
|
submissions = paginate(submissions.preload(:created_by_user, :submitters,
|
||||||
template: :folder,
|
template: :folder,
|
||||||
@@ -115,6 +105,21 @@ module Api
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def filter_submissions(submissions, params)
|
||||||
|
submissions = submissions.where(template_id: params[:template_id]) if params[:template_id].present?
|
||||||
|
submissions = submissions.where(slug: params[:slug]) if params[:slug].present?
|
||||||
|
|
||||||
|
if params[:template_folder].present?
|
||||||
|
submissions = submissions.joins(template: :folder).where(folder: { name: params[:template_folder] })
|
||||||
|
end
|
||||||
|
|
||||||
|
if params.key?(:archived)
|
||||||
|
submissions = params[:archived].in?(['true', true]) ? submissions.archived : submissions.active
|
||||||
|
end
|
||||||
|
|
||||||
|
Submissions::Filter.call(submissions, current_user, params)
|
||||||
|
end
|
||||||
|
|
||||||
def build_create_json(submissions)
|
def build_create_json(submissions)
|
||||||
json = submissions.flat_map do |submission|
|
json = submissions.flat_map do |submission|
|
||||||
submission.submitters.map do |s|
|
submission.submitters.map do |s|
|
||||||
|
|||||||
@@ -7,15 +7,7 @@ module Api
|
|||||||
def index
|
def index
|
||||||
submitters = Submitters.search(@submitters, params[:q])
|
submitters = Submitters.search(@submitters, params[:q])
|
||||||
|
|
||||||
submitters = submitters.where(external_id: params[:application_key]) if params[:application_key].present?
|
submitters = filter_submitters(submitters, params)
|
||||||
submitters = submitters.where(external_id: params[:external_id]) if params[:external_id].present?
|
|
||||||
submitters = submitters.where(submission_id: params[:submission_id]) if params[:submission_id].present?
|
|
||||||
|
|
||||||
if params[:template_id].present?
|
|
||||||
submitters = submitters.joins(:submission).where(submission: { template_id: params[:template_id] })
|
|
||||||
end
|
|
||||||
|
|
||||||
submitters = maybe_filder_by_completed_at(submitters, params)
|
|
||||||
|
|
||||||
submitters = paginate(
|
submitters = paginate(
|
||||||
submitters.preload(:template, :submission, :submission_events,
|
submitters.preload(:template, :submission, :submission_events,
|
||||||
@@ -163,6 +155,19 @@ module Api
|
|||||||
submitter
|
submitter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def filter_submitters(submitters, params)
|
||||||
|
submitters = submitters.where(external_id: params[:application_key]) if params[:application_key].present?
|
||||||
|
submitters = submitters.where(external_id: params[:external_id]) if params[:external_id].present?
|
||||||
|
submitters = submitters.where(slug: params[:slug]) if params[:slug].present?
|
||||||
|
submitters = submitters.where(submission_id: params[:submission_id]) if params[:submission_id].present?
|
||||||
|
|
||||||
|
if params[:template_id].present?
|
||||||
|
submitters = submitters.joins(:submission).where(submission: { template_id: params[:template_id] })
|
||||||
|
end
|
||||||
|
|
||||||
|
maybe_filder_by_completed_at(submitters, params)
|
||||||
|
end
|
||||||
|
|
||||||
def assign_external_id(submitter, attrs)
|
def assign_external_id(submitter, attrs)
|
||||||
submitter.external_id = attrs[:application_key] if attrs.key?(:application_key)
|
submitter.external_id = attrs[:application_key] if attrs.key?(:application_key)
|
||||||
submitter.external_id = attrs[:external_id] if attrs.key?(:external_id)
|
submitter.external_id = attrs[:external_id] if attrs.key?(:external_id)
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ module Api
|
|||||||
templates = params[:archived].in?(['true', true]) ? templates.archived : templates.active
|
templates = params[:archived].in?(['true', true]) ? templates.archived : templates.active
|
||||||
templates = templates.where(external_id: params[:application_key]) if params[:application_key].present?
|
templates = templates.where(external_id: params[:application_key]) if params[:application_key].present?
|
||||||
templates = templates.where(external_id: params[:external_id]) if params[:external_id].present?
|
templates = templates.where(external_id: params[:external_id]) if params[:external_id].present?
|
||||||
|
templates = templates.where(slug: params[:slug]) if params[:slug].present?
|
||||||
templates = templates.joins(:folder).where(folder: { name: params[:folder] }) if params[:folder].present?
|
templates = templates.joins(:folder).where(folder: { name: params[:folder] }) if params[:folder].present?
|
||||||
|
|
||||||
templates
|
templates
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ class StartFormController < ApplicationController
|
|||||||
before_action :load_template
|
before_action :load_template
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
raise ActionController::RoutingError, I18n.t('not_found') if @template.preferences['require_phone_2fa'] == true
|
||||||
|
|
||||||
@submitter = @template.submissions.new(account_id: @template.account_id)
|
@submitter = @template.submissions.new(account_id: @template.account_id)
|
||||||
.submitters.new(uuid: (filter_undefined_submitters(@template).first ||
|
.submitters.new(uuid: (filter_undefined_submitters(@template).first ||
|
||||||
@template.submitters.first)['uuid'])
|
@template.submitters.first)['uuid'])
|
||||||
|
|||||||
@@ -57,6 +57,11 @@ class SubmissionsController < ApplicationController
|
|||||||
Submissions.send_signature_requests(submissions)
|
Submissions.send_signature_requests(submissions)
|
||||||
|
|
||||||
redirect_to template_path(@template), notice: I18n.t('new_recipients_have_been_added')
|
redirect_to template_path(@template), notice: I18n.t('new_recipients_have_been_added')
|
||||||
|
rescue Submissions::CreateFromSubmitters::BaseError => e
|
||||||
|
render turbo_stream: turbo_stream.replace(:submitters_error,
|
||||||
|
partial: 'submissions/error',
|
||||||
|
locals: { error: e.message }),
|
||||||
|
status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ class SubmitFormController < ApplicationController
|
|||||||
skip_before_action :authenticate_user!
|
skip_before_action :authenticate_user!
|
||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
|
before_action :load_submitter, only: %i[show update completed]
|
||||||
|
|
||||||
CONFIG_KEYS = [].freeze
|
CONFIG_KEYS = [].freeze
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@submitter = Submitter.find_by!(slug: params[:slug])
|
|
||||||
|
|
||||||
submission = @submitter.submission
|
submission = @submitter.submission
|
||||||
|
|
||||||
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
|
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
|
||||||
@@ -50,44 +50,44 @@ class SubmitFormController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
submitter = Submitter.find_by!(slug: params[:slug])
|
if @submitter.completed_at?
|
||||||
|
|
||||||
if submitter.completed_at?
|
|
||||||
return render json: { error: I18n.t('form_has_been_completed_already') }, status: :unprocessable_entity
|
return render json: { error: I18n.t('form_has_been_completed_already') }, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
if submitter.template.archived_at? || submitter.submission.archived_at?
|
if @submitter.template.archived_at? || @submitter.submission.archived_at?
|
||||||
return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_entity
|
return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
if submitter.submission.expired?
|
if @submitter.submission.expired?
|
||||||
return render json: { error: I18n.t('form_has_been_expired') }, status: :unprocessable_entity
|
return render json: { error: I18n.t('form_has_been_expired') }, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
if submitter.declined_at?
|
if @submitter.declined_at?
|
||||||
return render json: { error: I18n.t('form_has_been_declined') },
|
return render json: { error: I18n.t('form_has_been_declined') },
|
||||||
status: :unprocessable_entity
|
status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
Submitters::SubmitValues.call(submitter, params, request)
|
Submitters::SubmitValues.call(@submitter, params, request)
|
||||||
|
|
||||||
head :ok
|
head :ok
|
||||||
rescue Submitters::SubmitValues::RequiredFieldError => e
|
rescue Submitters::SubmitValues::RequiredFieldError => e
|
||||||
Rollbar.warning("Required field #{submitter.id}: #{e.message}") if defined?(Rollbar)
|
Rollbar.warning("Required field #{@submitter.id}: #{e.message}") if defined?(Rollbar)
|
||||||
|
|
||||||
render json: { field_uuid: e.message }, status: :unprocessable_entity
|
render json: { field_uuid: e.message }, status: :unprocessable_entity
|
||||||
rescue Submitters::SubmitValues::ValidationError => e
|
rescue Submitters::SubmitValues::ValidationError => e
|
||||||
render json: { error: e.message }, status: :unprocessable_entity
|
render json: { error: e.message }, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
def completed
|
def completed; end
|
||||||
@submitter = Submitter.completed.find_by!(slug: params[:submit_form_slug])
|
|
||||||
end
|
|
||||||
|
|
||||||
def success; end
|
def success; end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def load_submitter
|
||||||
|
@submitter = Submitter.find_by!(slug: params[:slug] || params[:submit_form_slug])
|
||||||
|
end
|
||||||
|
|
||||||
def build_attachments_index(submission)
|
def build_attachments_index(submission)
|
||||||
ActiveStorage::Attachment.where(record: submission.submitters, name: :attachments)
|
ActiveStorage::Attachment.where(record: submission.submitters, name: :attachments)
|
||||||
.preload(:blob).index_by(&:uuid)
|
.preload(:blob).index_by(&:uuid)
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ class TemplateFoldersController < ApplicationController
|
|||||||
|
|
||||||
def show
|
def show
|
||||||
@templates = @template_folder.templates.active.accessible_by(current_ability)
|
@templates = @template_folder.templates.active.accessible_by(current_ability)
|
||||||
.preload(:author, :template_accesses).order(id: :desc)
|
.preload(:author, :template_accesses)
|
||||||
@templates = Templates.search(@templates, params[:q])
|
@templates = Templates.search(@templates, params[:q])
|
||||||
|
@templates = Templates::Order.call(@templates, current_user, cookies.permanent[:dashboard_templates_order])
|
||||||
|
|
||||||
@pagy, @templates = pagy(@templates, limit: 12)
|
@pagy, @templates = pagy(@templates, limit: 12)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ class TemplatesDashboardController < ApplicationController
|
|||||||
FOLDERS_PER_PAGE = 18
|
FOLDERS_PER_PAGE = 18
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@template_folders = @template_folders.where(id: @templates.active.select(:folder_id)).order(id: :desc)
|
@template_folders = @template_folders.where(id: @templates.active.select(:folder_id))
|
||||||
|
|
||||||
@template_folders = TemplateFolders.search(@template_folders, params[:q])
|
@template_folders = TemplateFolders.search(@template_folders, params[:q])
|
||||||
|
@template_folders = sort_template_folders(@template_folders, current_user,
|
||||||
|
cookies.permanent[:dashboard_templates_order])
|
||||||
|
|
||||||
@pagy, @template_folders = pagy(
|
@pagy, @template_folders = pagy(
|
||||||
@template_folders,
|
@template_folders,
|
||||||
@@ -24,6 +26,7 @@ class TemplatesDashboardController < ApplicationController
|
|||||||
else
|
else
|
||||||
@template_folders = @template_folders.reject { |e| e.name == TemplateFolder::DEFAULT_NAME }
|
@template_folders = @template_folders.reject { |e| e.name == TemplateFolder::DEFAULT_NAME }
|
||||||
@templates = filter_templates(@templates)
|
@templates = filter_templates(@templates)
|
||||||
|
@templates = Templates::Order.call(@templates, current_user, cookies.permanent[:dashboard_templates_order])
|
||||||
|
|
||||||
limit =
|
limit =
|
||||||
if @template_folders.size < 4
|
if @template_folders.size < 4
|
||||||
@@ -39,7 +42,7 @@ class TemplatesDashboardController < ApplicationController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def filter_templates(templates)
|
def filter_templates(templates)
|
||||||
rel = templates.active.preload(:author, :template_accesses).order(id: :desc)
|
rel = templates.active.preload(:author, :template_accesses)
|
||||||
|
|
||||||
if params[:q].blank?
|
if params[:q].blank?
|
||||||
if Docuseal.multitenant? && !current_account.testing?
|
if Docuseal.multitenant? && !current_account.testing?
|
||||||
@@ -54,4 +57,38 @@ class TemplatesDashboardController < ApplicationController
|
|||||||
|
|
||||||
Templates.search(rel, params[:q])
|
Templates.search(rel, params[:q])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def sort_template_folders(template_folders, current_user, order)
|
||||||
|
case order
|
||||||
|
when 'used_at'
|
||||||
|
subquery =
|
||||||
|
Template.left_joins(:submissions)
|
||||||
|
.group(:folder_id)
|
||||||
|
.where(account_id: current_user.account_id)
|
||||||
|
.select(
|
||||||
|
:folder_id,
|
||||||
|
Template.arel_table[:updated_at].maximum.as('updated_at_max'),
|
||||||
|
Submission.arel_table[:created_at].maximum.as('submission_created_at_max')
|
||||||
|
)
|
||||||
|
|
||||||
|
template_folders = template_folders.joins(
|
||||||
|
Template.arel_table
|
||||||
|
.join(subquery.arel.as('templates'), Arel::Nodes::OuterJoin)
|
||||||
|
.on(TemplateFolder.arel_table[:id].eq(Template.arel_table[:folder_id]))
|
||||||
|
.join_sources
|
||||||
|
)
|
||||||
|
|
||||||
|
template_folders.order(
|
||||||
|
Arel::Nodes::Case.new
|
||||||
|
.when(Template.arel_table[:submission_created_at_max].gt(Template.arel_table[:updated_at_max]))
|
||||||
|
.then(Template.arel_table[:submission_created_at_max])
|
||||||
|
.else(Template.arel_table[:updated_at_max])
|
||||||
|
.desc
|
||||||
|
)
|
||||||
|
when 'name'
|
||||||
|
template_folders.order(name: :asc)
|
||||||
|
else
|
||||||
|
template_folders.order(id: :desc)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ class TemplatesPreferencesController < ApplicationController
|
|||||||
documents_copy_email_enabled documents_copy_email_attach_audit
|
documents_copy_email_enabled documents_copy_email_attach_audit
|
||||||
documents_copy_email_attach_documents documents_copy_email_reply_to
|
documents_copy_email_attach_documents documents_copy_email_reply_to
|
||||||
completed_notification_email_attach_documents
|
completed_notification_email_attach_documents
|
||||||
completed_redirect_url
|
completed_redirect_url validate_unique_submitters
|
||||||
submitters_order
|
submitters_order require_phone_2fa
|
||||||
completed_notification_email_subject completed_notification_email_body
|
completed_notification_email_subject completed_notification_email_body
|
||||||
completed_notification_email_enabled completed_notification_email_attach_audit] +
|
completed_notification_email_enabled completed_notification_email_attach_audit] +
|
||||||
[completed_message: %i[title body],
|
[completed_message: %i[title body],
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
this.form.addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
this.submit()
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this.dataset.onload === 'true') {
|
||||||
|
this.form.querySelector('button').click()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
submit () {
|
||||||
|
fetch(this.form.action, {
|
||||||
|
method: this.form.method,
|
||||||
|
body: new FormData(this.form)
|
||||||
|
}).then(async (resp) => {
|
||||||
|
if (!resp.ok) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(await resp.text())
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
alert(data.error)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get form () {
|
||||||
|
return this.querySelector('form')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
this.header = document.querySelector('#signing_form_header')
|
||||||
|
|
||||||
|
window.addEventListener('scroll', this.onScroll)
|
||||||
|
window.addEventListener('resize', this.onResize)
|
||||||
|
|
||||||
|
if (!this.isNarrow() && this.isHeaderNotVisible()) {
|
||||||
|
this.showButtons({ animate: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback () {
|
||||||
|
window.removeEventListener('scroll', this.onScroll)
|
||||||
|
window.removeEventListener('resize', this.onResize)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResize = () => {
|
||||||
|
if (this.isNarrow()) {
|
||||||
|
this.hideButtons(true)
|
||||||
|
} else if (this.isHeaderNotVisible()) {
|
||||||
|
this.showButtons()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isNarrow () {
|
||||||
|
return window.innerWidth < 1230
|
||||||
|
}
|
||||||
|
|
||||||
|
onScroll = () => {
|
||||||
|
if (this.isHeaderNotVisible() && !this.isNarrow()) {
|
||||||
|
this.showButtons()
|
||||||
|
} else {
|
||||||
|
this.hideButtons()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isHeaderNotVisible () {
|
||||||
|
const rect = this.header.getBoundingClientRect()
|
||||||
|
return rect.bottom <= 0 || rect.top >= window.innerHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
showButtons ({ animate } = { animate: true }) {
|
||||||
|
if (animate) {
|
||||||
|
this.classList.add('transition-transform', 'duration-300')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.classList.remove('hidden', '-translate-y-10', 'opacity-0')
|
||||||
|
this.classList.add('translate-y-0', 'opacity-100')
|
||||||
|
}
|
||||||
|
|
||||||
|
hideButtons () {
|
||||||
|
this.classList.remove('translate-y-0', 'opacity-100')
|
||||||
|
this.classList.add('-translate-y-10', 'opacity-0')
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.classList.contains('-translate-y-10')) {
|
||||||
|
this.classList.add('hidden')
|
||||||
|
}
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,11 +3,15 @@ import { createApp, reactive } from 'vue'
|
|||||||
import Form from './submission_form/form'
|
import Form from './submission_form/form'
|
||||||
import DownloadButton from './elements/download_button'
|
import DownloadButton from './elements/download_button'
|
||||||
import ToggleSubmit from './elements/toggle_submit'
|
import ToggleSubmit from './elements/toggle_submit'
|
||||||
|
import FetchForm from './elements/fetch_form'
|
||||||
|
import ScrollButtons from './elements/scroll_buttons'
|
||||||
|
|
||||||
const safeRegisterElement = (name, element, options = {}) => !window.customElements.get(name) && window.customElements.define(name, element, options)
|
const safeRegisterElement = (name, element, options = {}) => !window.customElements.get(name) && window.customElements.define(name, element, options)
|
||||||
|
|
||||||
safeRegisterElement('download-button', DownloadButton)
|
safeRegisterElement('download-button', DownloadButton)
|
||||||
safeRegisterElement('toggle-submit', ToggleSubmit)
|
safeRegisterElement('toggle-submit', ToggleSubmit)
|
||||||
|
safeRegisterElement('fetch-form', FetchForm)
|
||||||
|
safeRegisterElement('scroll-buttons', ScrollButtons)
|
||||||
safeRegisterElement('submission-form', class extends HTMLElement {
|
safeRegisterElement('submission-form', class extends HTMLElement {
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
this.appElem = document.createElement('div')
|
this.appElem = document.createElement('div')
|
||||||
|
|||||||
@@ -183,7 +183,7 @@
|
|||||||
v-else
|
v-else
|
||||||
ref="textContainer"
|
ref="textContainer"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="flex items-center px-0.5 w-full"
|
class="flex px-0.5 w-full"
|
||||||
:class="{ ...alignClasses, ...fontClasses }"
|
:class="{ ...alignClasses, ...fontClasses }"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -327,13 +327,16 @@ export default {
|
|||||||
},
|
},
|
||||||
alignClasses () {
|
alignClasses () {
|
||||||
if (!this.field.preferences) {
|
if (!this.field.preferences) {
|
||||||
return {}
|
return { 'items-center': true }
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'text-center': this.field.preferences.align === 'center',
|
'text-center': this.field.preferences.align === 'center',
|
||||||
'text-left': this.field.preferences.align === 'left',
|
'text-left': this.field.preferences.align === 'left',
|
||||||
'text-right': this.field.preferences.align === 'right'
|
'text-right': this.field.preferences.align === 'right',
|
||||||
|
'items-center': !this.field.preferences.valign || this.field.preferences.valign === 'center',
|
||||||
|
'items-start': this.field.preferences.valign === 'top',
|
||||||
|
'items-end': this.field.preferences.valign === 'bottom'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fontClasses () {
|
fontClasses () {
|
||||||
|
|||||||
@@ -193,9 +193,9 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.decline_button) {
|
document.querySelectorAll('#decline_button').forEach((button) => {
|
||||||
window.decline_button.setAttribute('disabled', 'true')
|
button.setAttribute('disabled', 'true')
|
||||||
}
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
sendCopyToEmail () {
|
sendCopyToEmail () {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
function cropCanvasAndExportToPNG (canvas) {
|
function cropCanvasAndExportToPNG (canvas, { errorOnTooSmall } = { errorOnTooSmall: false }) {
|
||||||
const ctx = canvas.getContext('2d')
|
const ctx = canvas.getContext('2d')
|
||||||
|
|
||||||
const width = canvas.width
|
const width = canvas.width
|
||||||
@@ -33,6 +33,10 @@ function cropCanvasAndExportToPNG (canvas) {
|
|||||||
croppedCanvas.height = croppedHeight
|
croppedCanvas.height = croppedHeight
|
||||||
const croppedCtx = croppedCanvas.getContext('2d')
|
const croppedCtx = croppedCanvas.getContext('2d')
|
||||||
|
|
||||||
|
if (errorOnTooSmall && (croppedWidth < 20 || croppedHeight < 20)) {
|
||||||
|
return Promise.reject(new Error('Image too small'))
|
||||||
|
}
|
||||||
|
|
||||||
croppedCtx.drawImage(canvas, leftmost, topmost, croppedWidth, croppedHeight, 0, 0, croppedWidth, croppedHeight)
|
croppedCtx.drawImage(canvas, leftmost, topmost, croppedWidth, croppedHeight, 0, 0, croppedWidth, croppedHeight)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
:with-label="!isAnonymousChecboxes && showFieldNames"
|
:with-label="!isAnonymousChecboxes && showFieldNames"
|
||||||
:current-step="currentStepFields"
|
:current-step="currentStepFields"
|
||||||
:scroll-padding="scrollPadding"
|
:scroll-padding="scrollPadding"
|
||||||
@focus-step="[saveStep(), goToStep($event, false, true), currentField.type !== 'checkbox' ? isFormVisible = true : '']"
|
@focus-step="[saveStep(), currentField.type !== 'checkbox' ? isFormVisible = true : '', goToStep($event, false, true)]"
|
||||||
/>
|
/>
|
||||||
<FieldAreas
|
<FieldAreas
|
||||||
:steps="readonlyConditionalFields.map((e) => [e])"
|
:steps="readonlyConditionalFields.map((e) => [e])"
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ const es = {
|
|||||||
sign_now: 'Firmar ahora',
|
sign_now: 'Firmar ahora',
|
||||||
type_here_: 'Escribe aquí...',
|
type_here_: 'Escribe aquí...',
|
||||||
optional: 'opcional',
|
optional: 'opcional',
|
||||||
|
option: 'Opción',
|
||||||
appears_on: 'Aparece en',
|
appears_on: 'Aparece en',
|
||||||
page: 'Página',
|
page: 'Página',
|
||||||
select_your_option: 'Selecciona tu opción',
|
select_your_option: 'Selecciona tu opción',
|
||||||
@@ -245,6 +246,7 @@ const it = {
|
|||||||
sign_now: 'Firma ora',
|
sign_now: 'Firma ora',
|
||||||
type_here_: 'Digita qui...',
|
type_here_: 'Digita qui...',
|
||||||
optional: 'opzionale',
|
optional: 'opzionale',
|
||||||
|
option: 'Opzione',
|
||||||
appears_on: 'Compare su',
|
appears_on: 'Compare su',
|
||||||
page: 'Pagina',
|
page: 'Pagina',
|
||||||
take_photo: 'Scattare una foto',
|
take_photo: 'Scattare una foto',
|
||||||
@@ -265,7 +267,7 @@ const it = {
|
|||||||
document_has_been_signed: 'Il documento è stato firmato!',
|
document_has_been_signed: 'Il documento è stato firmato!',
|
||||||
documents_have_been_signed: 'I documenti sono stati firmati!',
|
documents_have_been_signed: 'I documenti sono stati firmati!',
|
||||||
create_a_free_account: 'Crea un Account Gratuito',
|
create_a_free_account: 'Crea un Account Gratuito',
|
||||||
powered_by: 'Desarrollado por',
|
powered_by: 'Fornito da',
|
||||||
please_check_the_box_to_continue: 'Si prega di spuntare la casella per continuare.',
|
please_check_the_box_to_continue: 'Si prega di spuntare la casella per continuare.',
|
||||||
open_source_documents_software: 'software di documenti open source',
|
open_source_documents_software: 'software di documenti open source',
|
||||||
verified_phone_number: 'Verifica numero di telefono',
|
verified_phone_number: 'Verifica numero di telefono',
|
||||||
@@ -343,6 +345,7 @@ const de = {
|
|||||||
sign_now: 'Jetzt unterschreiben',
|
sign_now: 'Jetzt unterschreiben',
|
||||||
type_here_: 'Hier eingeben...',
|
type_here_: 'Hier eingeben...',
|
||||||
optional: 'optional',
|
optional: 'optional',
|
||||||
|
option: 'Option',
|
||||||
appears_on: 'Erscheint auf',
|
appears_on: 'Erscheint auf',
|
||||||
page: 'Seite',
|
page: 'Seite',
|
||||||
take_photo: 'Foto aufnehmen',
|
take_photo: 'Foto aufnehmen',
|
||||||
@@ -393,8 +396,8 @@ const de = {
|
|||||||
|
|
||||||
const fr = {
|
const fr = {
|
||||||
complete_all_required_fields_to_proceed_with_identity_verification: "Veuillez remplir tous les champs obligatoires pour continuer la vérification de l'identité.",
|
complete_all_required_fields_to_proceed_with_identity_verification: "Veuillez remplir tous les champs obligatoires pour continuer la vérification de l'identité.",
|
||||||
verif_id: "Vérification de l'ID",
|
verify_id: "Vérification de l'ID",
|
||||||
verif_identite: "Vérification de l'identité",
|
identity_verification: "Vérification de l'identité",
|
||||||
complete: 'Terminer',
|
complete: 'Terminer',
|
||||||
fill_all_required_fields_to_complete: 'Veuillez remplir tous les champs obligatoires pour compléter',
|
fill_all_required_fields_to_complete: 'Veuillez remplir tous les champs obligatoires pour compléter',
|
||||||
sign_and_complete: 'Signer et Terminer',
|
sign_and_complete: 'Signer et Terminer',
|
||||||
@@ -441,6 +444,7 @@ const fr = {
|
|||||||
sign_now: 'Signer maintenant',
|
sign_now: 'Signer maintenant',
|
||||||
type_here_: 'Tapez ici...',
|
type_here_: 'Tapez ici...',
|
||||||
optional: 'facultatif',
|
optional: 'facultatif',
|
||||||
|
option: 'Option',
|
||||||
appears_on: 'Apparaît sur',
|
appears_on: 'Apparaît sur',
|
||||||
page: 'Page',
|
page: 'Page',
|
||||||
take_photo: 'Prendre une photo',
|
take_photo: 'Prendre une photo',
|
||||||
@@ -539,6 +543,7 @@ const pl = {
|
|||||||
sign_now: 'Podpisz teraz',
|
sign_now: 'Podpisz teraz',
|
||||||
type_here_: 'Wpisz tutaj...',
|
type_here_: 'Wpisz tutaj...',
|
||||||
optional: 'opcjonalny',
|
optional: 'opcjonalny',
|
||||||
|
option: 'Opcja',
|
||||||
appears_on: 'Pojawia się na',
|
appears_on: 'Pojawia się na',
|
||||||
page: 'Strona',
|
page: 'Strona',
|
||||||
select_your_option: 'Wybierz swoją opcję',
|
select_your_option: 'Wybierz swoją opcję',
|
||||||
@@ -583,7 +588,8 @@ const pl = {
|
|||||||
reupload: 'Ponowne przesłanie',
|
reupload: 'Ponowne przesłanie',
|
||||||
upload: 'Przesyłanie',
|
upload: 'Przesyłanie',
|
||||||
files: 'Pliki',
|
files: 'Pliki',
|
||||||
signature_is_too_small_or_simple_please_redraw: 'Podpis jest zbyt mały lub zbyt prosty. Proszę narysować go ponownie.'
|
signature_is_too_small_or_simple_please_redraw: 'Podpis jest zbyt mały lub zbyt prosty. Proszę narysować go ponownie.',
|
||||||
|
wait_countdown_seconds: 'Poczekaj {countdown} sekund'
|
||||||
}
|
}
|
||||||
|
|
||||||
const uk = {
|
const uk = {
|
||||||
@@ -636,6 +642,7 @@ const uk = {
|
|||||||
sign_now: 'Підписати зараз',
|
sign_now: 'Підписати зараз',
|
||||||
type_here_: 'Введіть тут',
|
type_here_: 'Введіть тут',
|
||||||
optional: 'необов’язково',
|
optional: 'необов’язково',
|
||||||
|
option: 'Опція',
|
||||||
appears_on: "З'являється на",
|
appears_on: "З'являється на",
|
||||||
page: 'Сторінка',
|
page: 'Сторінка',
|
||||||
take_photo: 'Зробити фото',
|
take_photo: 'Зробити фото',
|
||||||
@@ -658,7 +665,7 @@ const uk = {
|
|||||||
create_a_free_account: 'Створити безкоштовний обліковий запис',
|
create_a_free_account: 'Створити безкоштовний обліковий запис',
|
||||||
powered_by: 'Працює на базі',
|
powered_by: 'Працює на базі',
|
||||||
please_check_the_box_to_continue: 'Будь ласка, позначте прапорець, щоб продовжити.',
|
please_check_the_box_to_continue: 'Будь ласка, позначте прапорець, щоб продовжити.',
|
||||||
open_source_documents_software: 'відкритий програмний засіб для документів',
|
open_source_documents_software: 'відкрите програмне забезпечення для документів',
|
||||||
verified_phone_number: 'Підтвердіть номер телефону',
|
verified_phone_number: 'Підтвердіть номер телефону',
|
||||||
use_international_format: 'Використовуйте міжнародний формат: +1xxx',
|
use_international_format: 'Використовуйте міжнародний формат: +1xxx',
|
||||||
six_digits_code: '6-значний код',
|
six_digits_code: '6-значний код',
|
||||||
@@ -734,6 +741,7 @@ const cs = {
|
|||||||
sign_now: 'Podepsat nyní',
|
sign_now: 'Podepsat nyní',
|
||||||
type_here_: 'Zadejte zde',
|
type_here_: 'Zadejte zde',
|
||||||
optional: 'volitelné',
|
optional: 'volitelné',
|
||||||
|
option: 'Možnost',
|
||||||
appears_on: 'Zobrazuje se na',
|
appears_on: 'Zobrazuje se na',
|
||||||
page: 'Stránka',
|
page: 'Stránka',
|
||||||
select_your_option: 'Vyberte svou volbu',
|
select_your_option: 'Vyberte svou volbu',
|
||||||
@@ -832,6 +840,7 @@ const pt = {
|
|||||||
sign_now: 'Assinar agora',
|
sign_now: 'Assinar agora',
|
||||||
type_here_: 'Digite aqui',
|
type_here_: 'Digite aqui',
|
||||||
optional: 'opcional',
|
optional: 'opcional',
|
||||||
|
option: 'Opção',
|
||||||
appears_on: 'Aparece em',
|
appears_on: 'Aparece em',
|
||||||
page: 'Página',
|
page: 'Página',
|
||||||
take_photo: 'Tirar foto',
|
take_photo: 'Tirar foto',
|
||||||
@@ -1097,6 +1106,7 @@ const ar = {
|
|||||||
select_a_reason: 'اختر سببًا',
|
select_a_reason: 'اختر سببًا',
|
||||||
value_is_invalid: 'القيمة غير صالحة',
|
value_is_invalid: 'القيمة غير صالحة',
|
||||||
verification_code_is_invalid: 'رمز التحقق غير صالح',
|
verification_code_is_invalid: 'رمز التحقق غير صالح',
|
||||||
|
already_paid: 'تم الدفع بالفعل',
|
||||||
drawn_signature_on_a_touchscreen_device: 'توقيع مرسوم على جهاز بشاشة تعمل باللمس',
|
drawn_signature_on_a_touchscreen_device: 'توقيع مرسوم على جهاز بشاشة تعمل باللمس',
|
||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'امسح رمز الاستجابة السريعة باستخدام تطبيق الكاميرا لفتح النموذج على الهاتف المحمول ورسم توقيعك',
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'امسح رمز الاستجابة السريعة باستخدام تطبيق الكاميرا لفتح النموذج على الهاتف المحمول ورسم توقيعك',
|
||||||
by_clicking_you_agree_to_the: 'بالنقر فوق "{button}"، أنت توافق على',
|
by_clicking_you_agree_to_the: 'بالنقر فوق "{button}"، أنت توافق على',
|
||||||
@@ -1198,6 +1208,8 @@ const ko = {
|
|||||||
by_clicking_you_agree_to_the: '"{button}"를 클릭함으로써, 다음에 동의하게 됩니다',
|
by_clicking_you_agree_to_the: '"{button}"를 클릭함으로써, 다음에 동의하게 됩니다',
|
||||||
electronic_signature_disclosure: '전자 서명 공개',
|
electronic_signature_disclosure: '전자 서명 공개',
|
||||||
esignature_disclosure: '전자 서명 공개',
|
esignature_disclosure: '전자 서명 공개',
|
||||||
|
value_is_invalid: '값이 올바르지 않습니다',
|
||||||
|
verification_code_is_invalid: '인증 코드가 올바르지 않습니다',
|
||||||
already_paid: '이미 지불됨',
|
already_paid: '이미 지불됨',
|
||||||
text: '텍스트',
|
text: '텍스트',
|
||||||
signature: '서명',
|
signature: '서명',
|
||||||
@@ -1273,6 +1285,105 @@ const ko = {
|
|||||||
wait_countdown_seconds: '{countdown}초 기다리세요'
|
wait_countdown_seconds: '{countdown}초 기다리세요'
|
||||||
}
|
}
|
||||||
|
|
||||||
const i18n = { en, es, it, de, fr, pl, uk, cs, pt, he, nl, ar, ko }
|
const ja = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: '本人確認を進めるには、すべての必須項目を入力してください。',
|
||||||
|
verify_id: '本人確認',
|
||||||
|
identity_verification: '本人確認',
|
||||||
|
complete: '完了',
|
||||||
|
fill_all_required_fields_to_complete: '完了するにはすべての必須項目を入力してください',
|
||||||
|
sign_and_complete: '署名して完了',
|
||||||
|
text: 'テキスト',
|
||||||
|
by_clicking_you_agree_to_the: '"{button}" をクリックすることで、次に同意したことになります:',
|
||||||
|
electronic_signature_disclosure: '電子署名に関する開示',
|
||||||
|
esignature_disclosure: '電子署名開示',
|
||||||
|
signature: '署名',
|
||||||
|
initials: 'イニシャル',
|
||||||
|
drawn_signature_on_a_touchscreen_device: 'タッチスクリーンデバイスで描かれた署名',
|
||||||
|
approved: '承認済み',
|
||||||
|
reviewed: '確認済み',
|
||||||
|
other: 'その他',
|
||||||
|
authored_by_me: '自分が作成',
|
||||||
|
invite: '招待',
|
||||||
|
email: 'メール',
|
||||||
|
approved_by: '承認者',
|
||||||
|
reviewed_by: '確認者',
|
||||||
|
authored_by: '作成者',
|
||||||
|
select_a_reason: '理由を選択',
|
||||||
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'QRコードをカメラアプリで読み取り、モバイルでフォームを開いて署名を描いてください',
|
||||||
|
date: '日付',
|
||||||
|
number: '数値',
|
||||||
|
value_is_invalid: '無効な値です',
|
||||||
|
verification_code_is_invalid: '認証コードが無効です',
|
||||||
|
already_paid: 'すでに支払い済み',
|
||||||
|
image: '画像',
|
||||||
|
pay: '支払う',
|
||||||
|
take_photo: '写真を撮る',
|
||||||
|
number_phone_is_invalid: '{number} の電話番号は無効です',
|
||||||
|
file: 'ファイル',
|
||||||
|
digitally_signed_by: '電子署名者:',
|
||||||
|
reason: '理由',
|
||||||
|
select: '選択',
|
||||||
|
checkbox: 'チェックボックス',
|
||||||
|
multiple: '複数選択',
|
||||||
|
radio: 'ラジオボタン',
|
||||||
|
cells: 'セル',
|
||||||
|
stamp: 'スタンプ',
|
||||||
|
minimize: '最小化',
|
||||||
|
payment: '支払い',
|
||||||
|
phone: '電話',
|
||||||
|
start_now: '今すぐ開始',
|
||||||
|
continue: '続行',
|
||||||
|
sign_now: '今すぐ署名',
|
||||||
|
type_here_: 'ここに入力...',
|
||||||
|
optional: '任意',
|
||||||
|
option: 'オプション',
|
||||||
|
appears_on: '表示ページ',
|
||||||
|
page: 'ページ',
|
||||||
|
select_your_option: 'オプションを選択',
|
||||||
|
complete_hightlighted_checkboxes_and_click: 'ハイライトされたチェックボックスを完了し、クリックしてください',
|
||||||
|
submit: '送信',
|
||||||
|
next: '次へ',
|
||||||
|
click_to_upload: 'クリックしてアップロード',
|
||||||
|
or_drag_and_drop_files: 'またはファイルをドラッグ&ドロップ',
|
||||||
|
send_copy_via_email: 'メールでコピーを送信',
|
||||||
|
download: 'ダウンロード',
|
||||||
|
clear: 'クリア',
|
||||||
|
redraw: '描き直す',
|
||||||
|
draw_initials: 'イニシャルを描く',
|
||||||
|
type_signature_here: 'ここに署名を入力',
|
||||||
|
type_initial_here: 'ここにイニシャルを入力',
|
||||||
|
form_has_been_completed: 'フォームが完了しました!',
|
||||||
|
document_has_been_signed: 'ドキュメントが署名されました!',
|
||||||
|
documents_have_been_signed: 'ドキュメントがすべて署名されました!',
|
||||||
|
create_a_free_account: '無料アカウントを作成',
|
||||||
|
powered_by: '提供元:',
|
||||||
|
please_check_the_box_to_continue: '続行するにはボックスにチェックを入れてください。',
|
||||||
|
open_source_documents_software: 'オープンソースのドキュメントソフトウェア',
|
||||||
|
verified_phone_number: '電話番号を認証',
|
||||||
|
use_international_format: '国際形式を使用してください:+1xxx',
|
||||||
|
six_digits_code: '6桁のコード',
|
||||||
|
change_phone_number: '電話番号を変更',
|
||||||
|
sending: '送信中...',
|
||||||
|
resend_code: 'コードを再送信',
|
||||||
|
verification_code_has_been_resent: '認証コードがSMSで再送信されました',
|
||||||
|
please_fill_all_required_fields: 'すべての必須項目を入力してください',
|
||||||
|
set_today: '今日の日付を設定',
|
||||||
|
toggle_multiline_text: '複数行テキスト切り替え',
|
||||||
|
draw_signature: '署名を描く',
|
||||||
|
type_initial: 'イニシャルを入力',
|
||||||
|
draw: '描く',
|
||||||
|
type: '入力',
|
||||||
|
type_text: 'テキストを入力',
|
||||||
|
email_has_been_sent: 'メールが送信されました',
|
||||||
|
processing: '処理中',
|
||||||
|
pay_with_strip: 'Stripeで支払う',
|
||||||
|
reupload: '再アップロード',
|
||||||
|
upload: 'アップロード',
|
||||||
|
files: 'ファイル',
|
||||||
|
signature_is_too_small_or_simple_please_redraw: '署名が小さすぎるか単純すぎます。もう一度描いてください。',
|
||||||
|
wait_countdown_seconds: '{countdown} 秒お待ちください'
|
||||||
|
}
|
||||||
|
|
||||||
|
const i18n = { en, es, it, de, fr, pl, uk, cs, pt, he, nl, ar, ko, ja }
|
||||||
|
|
||||||
export default i18n
|
export default i18n
|
||||||
|
|||||||
@@ -339,8 +339,8 @@ export default {
|
|||||||
return Promise.resolve({})
|
return Promise.resolve({})
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve, reject) => {
|
||||||
cropCanvasAndExportToPNG(this.$refs.canvas).then(async (blob) => {
|
cropCanvasAndExportToPNG(this.$refs.canvas, { errorOnTooSmall: true }).then(async (blob) => {
|
||||||
const file = new File([blob], 'initials.png', { type: 'image/png' })
|
const file = new File([blob], 'initials.png', { type: 'image/png' })
|
||||||
|
|
||||||
if (this.dryRun) {
|
if (this.dryRun) {
|
||||||
@@ -373,6 +373,14 @@ export default {
|
|||||||
return resolve(attachment)
|
return resolve(attachment)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
if (this.field.required === true) {
|
||||||
|
alert(this.t('signature_is_too_small_or_simple_please_redraw'))
|
||||||
|
|
||||||
|
return reject(error)
|
||||||
|
} else {
|
||||||
|
return resolve({})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -683,13 +683,17 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.isSignatureStarted && this.pad.toData().length > 0 && !isValidSignatureCanvas(this.pad.toData())) {
|
if (this.isSignatureStarted && this.pad.toData().length > 0 && !isValidSignatureCanvas(this.pad.toData())) {
|
||||||
alert(this.t('signature_is_too_small_or_simple_please_redraw'))
|
if (this.field.required === true || this.pad.toData().length > 0) {
|
||||||
|
alert(this.t('signature_is_too_small_or_simple_please_redraw'))
|
||||||
|
|
||||||
return Promise.reject(new Error('Image too small or simple'))
|
return Promise.reject(new Error('Image too small or simple'))
|
||||||
|
} else {
|
||||||
|
Promise.resolve({})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve, reject) => {
|
||||||
cropCanvasAndExportToPNG(this.$refs.canvas).then(async (blob) => {
|
cropCanvasAndExportToPNG(this.$refs.canvas, { errorOnTooSmall: true }).then(async (blob) => {
|
||||||
const file = new File([blob], 'signature.png', { type: 'image/png' })
|
const file = new File([blob], 'signature.png', { type: 'image/png' })
|
||||||
|
|
||||||
if (this.dryRun) {
|
if (this.dryRun) {
|
||||||
@@ -725,6 +729,14 @@ export default {
|
|||||||
return resolve(attachment)
|
return resolve(attachment)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
if (this.field.required === true) {
|
||||||
|
alert(this.t('signature_is_too_small_or_simple_please_redraw'))
|
||||||
|
|
||||||
|
return reject(error)
|
||||||
|
} else {
|
||||||
|
return resolve({})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,15 +160,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
ref="touchValueTarget"
|
ref="touchValueTarget"
|
||||||
class="flex items-center h-full w-full field-area"
|
class="flex h-full w-full field-area"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
:class="[isValueInput ? 'bg-opacity-50' : 'bg-opacity-80', field.type === 'heading' ? 'bg-gray-50' : bgColors[submitterIndex % bgColors.length], isDefaultValuePresent || isValueInput || (withFieldPlaceholder && field.areas) ? fontClasses : 'justify-center']"
|
:class="[isValueInput ? 'bg-opacity-50' : 'bg-opacity-80', field.type === 'heading' ? 'bg-gray-50' : bgColors[submitterIndex % bgColors.length], isDefaultValuePresent || isValueInput || (withFieldPlaceholder && field.areas) ? fontClasses : 'justify-center items-center']"
|
||||||
@click="focusValueInput"
|
@click="focusValueInput"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="field"
|
v-if="field"
|
||||||
class="flex justify-center items-center space-x-1"
|
class="flex justify-center items-center space-x-1"
|
||||||
:class="{ 'w-full': ['cells', 'checkbox'].includes(field.type), 'h-full': !isValueInput }"
|
:class="{ 'w-full': ['cells', 'checkbox'].includes(field.type), 'h-full': !isValueInput && !isDefaultValuePresent }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="isDefaultValuePresent || isValueInput || (withFieldPlaceholder && field.areas && field.type !== 'checkbox')"
|
v-if="isDefaultValuePresent || isValueInput || (withFieldPlaceholder && field.areas && field.type !== 'checkbox')"
|
||||||
@@ -403,10 +403,13 @@ export default {
|
|||||||
},
|
},
|
||||||
fontClasses () {
|
fontClasses () {
|
||||||
if (!this.field.preferences) {
|
if (!this.field.preferences) {
|
||||||
return {}
|
return { 'items-center': true }
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
'items-center': !this.field.preferences.valign || this.field.preferences.valign === 'center',
|
||||||
|
'items-start': this.field.preferences.valign === 'top',
|
||||||
|
'items-end': this.field.preferences.valign === 'bottom',
|
||||||
'justify-center': this.field.preferences.align === 'center',
|
'justify-center': this.field.preferences.align === 'center',
|
||||||
'justify-start': this.field.preferences.align === 'left',
|
'justify-start': this.field.preferences.align === 'left',
|
||||||
'justify-end': this.field.preferences.align === 'right',
|
'justify-end': this.field.preferences.align === 'right',
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ export default {
|
|||||||
fields () {
|
fields () {
|
||||||
if (this.item.submitter_uuid) {
|
if (this.item.submitter_uuid) {
|
||||||
return this.template.fields.reduce((acc, f) => {
|
return this.template.fields.reduce((acc, f) => {
|
||||||
if (f !== this.item) {
|
if (f !== this.item && (!f.conditions?.length || !f.conditions.find((c) => c.field_uuid === this.item.uuid))) {
|
||||||
acc.push(f)
|
acc.push(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
<span class="relative">
|
<span class="relative">
|
||||||
<select
|
<select
|
||||||
class="select input-bordered bg-white select-sm text-center pl-2"
|
class="select input-bordered bg-white select-sm text-center pl-2"
|
||||||
style="font-size: 16px; width: 86px; text-align-last: center;"
|
style="font-size: 16px; line-height: 12px; width: 86px; text-align-last: center;"
|
||||||
@change="$event.target.value ? preferences.font_size = parseInt($event.target.value) : delete preferences.font_size"
|
@change="$event.target.value ? preferences.font_size = parseInt($event.target.value) : delete preferences.font_size"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
@@ -77,12 +77,12 @@
|
|||||||
</select>
|
</select>
|
||||||
<span
|
<span
|
||||||
class="border-l pl-1.5 absolute bg-white bottom-0 pointer-events-none text-sm h-5"
|
class="border-l pl-1.5 absolute bg-white bottom-0 pointer-events-none text-sm h-5"
|
||||||
style="right: 13px; top: 7px"
|
style="right: 13px; top: 6px"
|
||||||
>
|
>
|
||||||
pt
|
pt
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span class="flex">
|
||||||
<div
|
<div
|
||||||
class="join"
|
class="join"
|
||||||
style="height: 32px"
|
style="height: 32px"
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span class="flex">
|
||||||
<div
|
<div
|
||||||
class="join"
|
class="join"
|
||||||
style="height: 32px"
|
style="height: 32px"
|
||||||
@@ -114,6 +114,32 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
|
<span class="flex">
|
||||||
|
<div class="dropdown modal-field-font-dropdown">
|
||||||
|
<label
|
||||||
|
tabindex="0"
|
||||||
|
class="cursor-pointer flex bg-white border input-bordered rounded-md h-8 items-center justify-center px-1"
|
||||||
|
style="-webkit-appearance: none; -moz-appearance: none;"
|
||||||
|
>
|
||||||
|
<component :is="valigns.find((v) => v.value === (preferences.valign || 'center'))?.icon" />
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
tabindex="0"
|
||||||
|
class="dropdown-content p-0 mt-1 block z-10 menu shadow bg-white border border-base-300 rounded-md"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(valign, index) in valigns"
|
||||||
|
:key="index"
|
||||||
|
:value="valign.value"
|
||||||
|
:class="{ 'bg-base-300': preferences.valign == valign.value }"
|
||||||
|
class="hover:bg-base-300 px-2 py-1.5 cursor-pointer"
|
||||||
|
@click="[valign.value ? preferences.valign = valign.value : delete preferences.valign, closeDropdown()]"
|
||||||
|
>
|
||||||
|
<component :is="valign.icon" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<select
|
<select
|
||||||
class="input input-bordered bg-white input-sm text-lg rounded-md"
|
class="input input-bordered bg-white input-sm text-lg rounded-md"
|
||||||
@@ -134,7 +160,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<div
|
<div
|
||||||
class="flex items-center border border-base-content/20 rounded-xl bg-white px-4 h-16 modal-field-font-preview"
|
class="flex border border-base-content/20 rounded-xl bg-white px-4 h-16 modal-field-font-preview"
|
||||||
:style="{
|
:style="{
|
||||||
color: preferences.color || 'black',
|
color: preferences.color || 'black',
|
||||||
fontSize: (preferences.font_size || 12) + 'pt',
|
fontSize: (preferences.font_size || 12) + 'pt',
|
||||||
@@ -163,7 +189,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconChevronDown, IconBold, IconItalic, IconAlignLeft, IconAlignRight, IconAlignCenter } from '@tabler/icons-vue'
|
import { IconChevronDown, IconBold, IconItalic, IconAlignLeft, IconAlignRight, IconAlignCenter, IconAlignBoxCenterTop, IconAlignBoxCenterBottom, IconAlignBoxCenterMiddle } from '@tabler/icons-vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FontModal',
|
name: 'FontModal',
|
||||||
@@ -213,6 +239,13 @@ export default {
|
|||||||
{ icon: IconAlignRight, value: 'right' }
|
{ icon: IconAlignRight, value: 'right' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
valigns () {
|
||||||
|
return [
|
||||||
|
{ icon: IconAlignBoxCenterTop, value: 'top' },
|
||||||
|
{ icon: IconAlignBoxCenterMiddle, value: 'center' },
|
||||||
|
{ icon: IconAlignBoxCenterBottom, value: 'bottom' }
|
||||||
|
]
|
||||||
|
},
|
||||||
sizes () {
|
sizes () {
|
||||||
return [...Array(23).keys()].map(i => i + 6)
|
return [...Array(23).keys()].map(i => i + 6)
|
||||||
},
|
},
|
||||||
@@ -230,12 +263,15 @@ export default {
|
|||||||
'justify-center': this.preferences.align === 'center',
|
'justify-center': this.preferences.align === 'center',
|
||||||
'justify-start': this.preferences.align === 'left',
|
'justify-start': this.preferences.align === 'left',
|
||||||
'justify-end': this.preferences.align === 'right',
|
'justify-end': this.preferences.align === 'right',
|
||||||
|
'items-center': !this.preferences.valign || this.preferences.valign === 'center',
|
||||||
|
'items-start': this.preferences.valign === 'top',
|
||||||
|
'items-end': this.preferences.valign === 'bottom',
|
||||||
'font-bold': ['bold_italic', 'bold'].includes(this.preferences.font_type),
|
'font-bold': ['bold_italic', 'bold'].includes(this.preferences.font_type),
|
||||||
italic: ['bold_italic', 'italic'].includes(this.preferences.font_type)
|
italic: ['bold_italic', 'italic'].includes(this.preferences.font_type)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
keys () {
|
keys () {
|
||||||
return ['font_type', 'font_size', 'color', 'align', 'font']
|
return ['font_type', 'font_size', 'color', 'align', 'valign', 'font']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
fields () {
|
fields () {
|
||||||
return this.template.fields.reduce((acc, f) => {
|
return this.template.fields.reduce((acc, f) => {
|
||||||
if (f !== this.field && f.submitter_uuid === this.field.submitter_uuid && ['number'].includes(f.type) && !f.preferences?.formula) {
|
if (f !== this.field && ['number'].includes(f.type) && (!f.preferences?.formula || f.submitter_uuid !== this.field.submitter_uuid)) {
|
||||||
acc.push(f)
|
acc.push(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,10 @@ class Submission < ApplicationRecord
|
|||||||
expire_at && expire_at <= Time.current
|
expire_at && expire_at <= Time.current
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fields_uuid_index
|
||||||
|
@fields_uuid_index ||= (template_fields || template.fields).index_by { |f| f['uuid'] }
|
||||||
|
end
|
||||||
|
|
||||||
def audit_trail_url
|
def audit_trail_url
|
||||||
return if audit_trail.blank?
|
return if audit_trail.blank?
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="#2c3e50" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M3 9l4 -4l4 4m-4 -4v14" />
|
||||||
|
<path d="M21 15l-4 4l-4 -4m4 4v-14" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 357 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" width="24" height="24" stroke-width="2">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M12 19h-7a2 2 0 0 1 -2 -2v-10a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v5.5" /><path d="M19 22v-6" /><path d="M22 19l-3 -3l-3 3" /><path d="M3 7l9 6l9 -6" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 437 B |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" width="24" height="24" stroke-width="2">
|
||||||
|
<path d="M15 10v-5c0 -1.38 .62 -2 2 -2s2 .62 2 2v5m0 -3h-4"></path>
|
||||||
|
<path d="M19 21h-4l4 -7h-4"></path> <path d="M4 15l3 3l3 -3"></path>
|
||||||
|
<path d="M7 6v12"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 395 B |
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" width="24" height="24" stroke-width="2">
|
||||||
|
<path d="M4 15l3 3l3 -3"></path>
|
||||||
|
<path d="M7 6v12"></path>
|
||||||
|
<path d="M17 14a2 2 0 0 1 2 2v3a2 2 0 1 1 -4 0v-3a2 2 0 0 1 2 -2z"></path>
|
||||||
|
<path d="M17 5m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"></path>
|
||||||
|
<path d="M19 5v3a2 2 0 0 1 -2 2h-1.5"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 474 B |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" width="24" height="24" stroke-width="2">
|
||||||
|
<path d="M10 15l-3 3l-3 -3"></path>
|
||||||
|
<path d="M7 6v12"></path>
|
||||||
|
<path d="M14 18.333c0 .369 .298 .667 .667 .667h2.666a.667 .667 0 0 0 .667 -.667v-2.666a.667 .667 0 0 0 -.667 -.667h-2.666a.667 .667 0 0 0 -.667 .667v2.666z"></path>
|
||||||
|
<path d="M14 10.833c0 .645 .522 1.167 1.167 1.167h4.666c.645 0 1.167 -.522 1.167 -1.167v-4.666c0 -.645 -.522 -1.167 -1.167 -1.167h-4.666c-.645 0 -1.167 .522 -1.167 1.167v4.666z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 647 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M16 18a2 2 0 0 1 2 2a2 2 0 0 1 2 -2a2 2 0 0 1 -2 -2a2 2 0 0 1 -2 2zm0 -12a2 2 0 0 1 2 2a2 2 0 0 1 2 -2a2 2 0 0 1 -2 -2a2 2 0 0 1 -2 2zm-7 12a6 6 0 0 1 6 -6a6 6 0 0 1 -6 -6a6 6 0 0 1 -6 6a6 6 0 0 1 6 6z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 497 B |
@@ -12,7 +12,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= f.label :last_name, t('last_name'), class: 'label' %>
|
<%= f.label :last_name, t('last_name'), class: 'label' %>
|
||||||
<%= f.text_field :last_name, required: true, class: 'base-input', dir: 'auto' %>
|
<%= f.text_field :last_name, required: false, class: 'base-input', dir: 'auto' %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<% if current_user.created_at > 2.weeks.ago || params[:tour] == 'true' %>
|
<% if current_user.created_at > 2.weeks.ago || params[:tour] == 'true' %>
|
||||||
<% user_config = current_user.user_configs.find_or_initialize_by(key: UserConfig::SHOW_APP_TOUR) %>
|
<% user_config = current_user.user_configs.find_by(key: UserConfig::SHOW_APP_TOUR) || UserConfig.new(key: UserConfig::SHOW_APP_TOUR, user: current_user) %>
|
||||||
<% if user_config.new_record? || user_config.value || params[:tour] == 'true' %>
|
<% if user_config.new_record? || user_config.value || params[:tour] == 'true' %>
|
||||||
<app-tour data-show-tour="<%= params[:tour] == 'true' || user_config.value %>" data-type="<%= local_assigns[:type] %>" data-next-page-path="<%= local_assigns[:next_page_path] %>" data-i18n="<%= t('app_tour').to_json %>"></app-tour>
|
<app-tour data-show-tour="<%= params[:tour] == 'true' || user_config.value %>" data-type="<%= local_assigns[:type] %>" data-next-page-path="<%= local_assigns[:next_page_path] %>" data-i18n="<%= t('app_tour').to_json %>"></app-tour>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<% uuid = SecureRandom.uuid %>
|
<% uuid = local_assigns[:uuid] || SecureRandom.uuid %>
|
||||||
<label id="<%= local_assigns[:button_id] %>" for="<%= uuid %>" class="<%= local_assigns[:btn_class] %>"><%= local_assigns[:btn_text] %></label>
|
<label id="<%= local_assigns[:button_id] %>" for="<%= uuid %>" class="<%= local_assigns[:btn_class] %>"><%= local_assigns[:btn_text] %></label>
|
||||||
<input type="checkbox" id="<%= uuid %>" class="modal-toggle">
|
<input type="checkbox" id="<%= uuid %>" class="modal-toggle">
|
||||||
<div id="<%= local_assigns[:id] %>" class="modal items-start !animate-none overflow-y-auto">
|
<div id="<%= local_assigns[:id] %>" class="modal items-start !animate-none overflow-y-auto">
|
||||||
|
|||||||
@@ -50,6 +50,14 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if Docuseal.multitenant? || current_user.role == 'superadmin' %>
|
||||||
|
<li>
|
||||||
|
<%= link_to Docuseal::CHATGPT_URL, target: 'blank', class: 'flex items-center' do %>
|
||||||
|
<%= svg_icon('sparkles', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
|
||||||
|
<span class="mr-1 whitespace-nowrap"><%= t('ask_ai') %></span>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
<% if (can?(:manage, EncryptedConfig) && current_user == true_user) || (current_user != true_user && current_account.testing?) %>
|
<% if (can?(:manage, EncryptedConfig) && current_user == true_user) || (current_user != true_user && current_account.testing?) %>
|
||||||
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'w-full py-1' } do |f| %>
|
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'w-full py-1' } do |f| %>
|
||||||
<label class="flex items-center pl-6 pr-4 py-2 border-y border-base-300 -ml-2 -mr-2" for="testing_toggle">
|
<label class="flex items-center pl-6 pr-4 py-2 border-y border-base-300 -ml-2 -mr-2" for="testing_toggle">
|
||||||
|
|||||||
@@ -5,20 +5,23 @@
|
|||||||
<%= @pagy.from %>-<%= local_assigns.fetch(:to, @pagy.to) %> of <%= local_assigns.fetch(:count, @pagy.count) %> <%= local_assigns[:items_name] || 'items' %>
|
<%= @pagy.from %>-<%= local_assigns.fetch(:to, @pagy.to) %> of <%= local_assigns.fetch(:count, @pagy.count) %> <%= local_assigns[:items_name] || 'items' %>
|
||||||
<%= local_assigns[:left_additional_html] %>
|
<%= local_assigns[:left_additional_html] %>
|
||||||
</div>
|
</div>
|
||||||
<div class="join">
|
<div class="flex items-center space-x-1.5">
|
||||||
<% if @pagy.prev %>
|
<%= local_assigns[:right_additional_html] %>
|
||||||
<%== link.call(@pagy.prev, '«', classes: 'join-item btn min-h-full h-10') %>
|
<div class="join">
|
||||||
<% else %>
|
<% if @pagy.prev %>
|
||||||
<span class="join-item btn btn-disabled !bg-base-200 min-h-full h-10">«</span>
|
<%== link.call(@pagy.prev, '«', classes: 'join-item btn min-h-full h-10') %>
|
||||||
<% end %>
|
<% else %>
|
||||||
<span class="join-item btn uppercase min-h-full h-10">
|
<span class="join-item btn btn-disabled !bg-base-200 min-h-full h-10">«</span>
|
||||||
<%= t('page_number', number: @pagy.page) %>
|
<% end %>
|
||||||
</span>
|
<span class="join-item btn font-medium uppercase min-h-full h-10">
|
||||||
<% if @pagy.next %>
|
<%= t('page_number', number: @pagy.page) %>
|
||||||
<%== link.call(@pagy.next, '»', classes: 'join-item btn min-h-full h-10') %>
|
</span>
|
||||||
<% else %>
|
<% if @pagy.next %>
|
||||||
<span class="join-item btn btn-disabled !bg-base-200 min-h-full h-10">»</span>
|
<%== link.call(@pagy.next, '»', classes: 'join-item btn min-h-full h-10') %>
|
||||||
<% end %>
|
<% else %>
|
||||||
|
<span class="join-item btn btn-disabled !bg-base-200 min-h-full h-10">»</span>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<% dashboard_templates_order = cookies.permanent[:dashboard_templates_order] || 'created_at' %>
|
||||||
|
<form action="<%= url_for %>" method="get" class="dropdown dropdown-top hidden md:inline">
|
||||||
|
<label tabindex="0" class="btn btn-sm h-10">
|
||||||
|
<%= svg_icon('arrow_sort', class: 'w-5 h-5') %>
|
||||||
|
</label>
|
||||||
|
<ul tabindex="0" class="dropdown-content z-[10] menu p-2 shadow bg-base-100 rounded-box mb-1 min-w-48">
|
||||||
|
<toggle-cookies data-value="created_at" data-key="dashboard_templates_order">
|
||||||
|
<li>
|
||||||
|
<button class="<%= 'bg-base-200' if dashboard_templates_order == 'created_at' %>">
|
||||||
|
<%= svg_icon('sort_descending_numbers', class: 'w-4 h-4') %>
|
||||||
|
<span class="whitespace-nowrap"><%= t('newest_first') %></span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</toggle-cookies>
|
||||||
|
<% if local_assigns[:with_recently_used] != false %>
|
||||||
|
<toggle-cookies data-value="used_at" data-key="dashboard_templates_order">
|
||||||
|
<li>
|
||||||
|
<button class="<%= 'bg-base-200' if dashboard_templates_order == 'used_at' %>">
|
||||||
|
<%= svg_icon('sort_descending_small_big', class: 'w-4 h-4') %>
|
||||||
|
<span class="whitespace-nowrap"><%= t('recently_used') %></span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</toggle-cookies>
|
||||||
|
<% end %>
|
||||||
|
<toggle-cookies data-value="name" data-key="dashboard_templates_order">
|
||||||
|
<li>
|
||||||
|
<button class="<%= 'bg-base-200' if dashboard_templates_order == 'name' %>">
|
||||||
|
<%= svg_icon('sort_ascending_letters', class: 'w-4 h-4') %>
|
||||||
|
<span class="whitespace-nowrap"><%= t('name_a_z') %></span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</toggle-cookies>
|
||||||
|
</ul>
|
||||||
|
</form>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
</submitters-autocomplete>
|
</submitters-autocomplete>
|
||||||
<submitters-autocomplete data-field="phone">
|
<submitters-autocomplete data-field="phone">
|
||||||
<linked-input data-target-id="<%= "detailed_phone_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
|
<linked-input data-target-id="<%= "detailed_phone_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
|
||||||
<input type="tel" pattern="^\+[0-9\s\-]+$" oninvalid="this.value ? this.setCustomValidity('<%= t('use_international_format_1xxx_') %>') : ''" oninput="this.setCustomValidity('')" name="submission[1][submitters][][phone]" autocomplete="off" class="base-input !h-10 mt-1.5 w-full" placeholder="<%= "#{t('phone')} (#{t('optional')})" %>" id="detailed_phone_<%= item['uuid'] %>">
|
<%= tag.input type: 'tel', pattern: '^\+[0-9\s\-]+$', oninvalid: "this.value ? this.setCustomValidity('#{t('use_international_format_1xxx_')}') : ''", oninput: "this.setCustomValidity('')", name: 'submission[1][submitters][][phone]', autocomplete: 'off', class: 'base-input !h-10 mt-1.5 w-full', placeholder: local_assigns[:require_phone_2fa] == true ? t(:phone) : "#{t('phone')} (#{t('optional')})", id: "detailed_phone_#{item['uuid']}", required: local_assigns[:require_phone_2fa] == true %>
|
||||||
</linked-input>
|
</linked-input>
|
||||||
</submitters-autocomplete>
|
</submitters-autocomplete>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<div id="submitters_error">
|
||||||
|
<% if local_assigns[:error] %>
|
||||||
|
<div class="text-center text-red-500">
|
||||||
|
<%= local_assigns[:error] %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
<% align = field.dig('preferences', 'align') %>
|
<% align = field.dig('preferences', 'align') %>
|
||||||
|
<% valign = field.dig('preferences', 'valign') %>
|
||||||
<% color = field.dig('preferences', 'color') %>
|
<% color = field.dig('preferences', 'color') %>
|
||||||
<% font = field.dig('preferences', 'font') %>
|
<% font = field.dig('preferences', 'font') %>
|
||||||
<% font_type = field.dig('preferences', 'font_type') %>
|
<% font_type = field.dig('preferences', 'font_type') %>
|
||||||
<field-value dir="auto" class="flex absolute text-[1.6vw] lg:text-base <%= 'font-mono' if font == 'Courier' %> <%= 'font-serif' if font == 'Times' %> <%= 'font-bold' if font_type == 'bold' || font_type == 'bold_italic' %> <%= 'italic' if font_type == 'italic' || font_type == 'bold_italic' %> <%= align == 'right' ? 'justify-end' : (align == 'center' ? 'justify-center' : '') %>" style="<%= "color: #{color}; " if color.present? %>width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; <%= "font-size: clamp(4pt, 1.6vw, #{field['preferences']['font_size'].to_i * 1.23}pt); line-height: `clamp(6pt, 2.0vw, #{(field['preferences']['font_size'].to_i * 1.23) + 3}pt)`" if field.dig('preferences', 'font_size') %>">
|
<field-value dir="auto" class="flex absolute text-[1.6vw] lg:text-base <%= 'font-mono' if font == 'Courier' %> <%= 'font-serif' if font == 'Times' %> <%= 'font-bold' if font_type == 'bold' || font_type == 'bold_italic' %> <%= 'italic' if font_type == 'italic' || font_type == 'bold_italic' %> <%= align == 'right' ? 'text-right' : (align == 'center' ? 'text-center' : '') %>" style="<%= "color: #{color}; " if color.present? %>width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; <%= "font-size: clamp(4pt, 1.6vw, #{field['preferences']['font_size'].to_i * 1.23}pt); line-height: `clamp(6pt, 2.0vw, #{(field['preferences']['font_size'].to_i * 1.23) + 3}pt)`" if field.dig('preferences', 'font_size') %>">
|
||||||
<% if field['type'] == 'signature' %>
|
<% if field['type'] == 'signature' %>
|
||||||
<div class="flex flex-col justify-between h-full overflow-hidden">
|
<div class="flex flex-col justify-between h-full overflow-hidden">
|
||||||
<div class="flex-grow flex overflow-hidden" style="min-height: 50%">
|
<div class="flex-grow flex overflow-hidden" style="min-height: 50%">
|
||||||
@@ -61,17 +62,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<% elsif field['type'] == 'date' %>
|
<% elsif field['type'] == 'date' %>
|
||||||
<autosize-field></autosize-field>
|
<autosize-field></autosize-field>
|
||||||
<div class="flex items-center px-0.5">
|
<div class="flex w-full px-0.5 <%= valign == 'top' ? 'items-start' : (valign == 'bottom' ? 'items-end' : 'items-center') %>">
|
||||||
<% value = Time.current.in_time_zone(local_assigns[:timezone]).to_date.to_s if value == '{{date}}' %>
|
<% value = Time.current.in_time_zone(local_assigns[:timezone]).to_date.to_s if value == '{{date}}' %>
|
||||||
<%= TimeUtils.format_date_string(value, field.dig('preferences', 'format'), local_assigns[:locale]) %>
|
<div class="w-full"><%= TimeUtils.format_date_string(value, field.dig('preferences', 'format'), local_assigns[:locale]) %></div>
|
||||||
</div>
|
</div>
|
||||||
<% elsif field['type'] == 'number' %>
|
<% elsif field['type'] == 'number' %>
|
||||||
<autosize-field></autosize-field>
|
<autosize-field></autosize-field>
|
||||||
<div class="flex items-center px-0.5 whitespace-nowrap">
|
<div class="flex w-full px-0.5 whitespace-nowrap <%= valign == 'top' ? 'items-start' : (valign == 'bottom' ? 'items-end' : 'items-center') %>">
|
||||||
<%= NumberUtils.format_number(value, field.dig('preferences', 'format')) %>
|
<div class="w-full"><%= NumberUtils.format_number(value, field.dig('preferences', 'format')) %></div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<autosize-field></autosize-field>
|
<autosize-field></autosize-field>
|
||||||
<div class="flex items-center px-0.5 whitespace-pre-wrap"><%= Array.wrap(value).join(', ') %></div>
|
<div class="flex w-full px-0.5 whitespace-pre-wrap <%= valign == 'top' ? 'items-start' : (valign == 'bottom' ? 'items-end' : 'items-center') %>">
|
||||||
|
<div class="w-full"><%= Array.wrap(value).join(', ') %></div>
|
||||||
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</field-value>
|
</field-value>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
<% require_phone_2fa = @template.preferences['require_phone_2fa'] == true %>
|
||||||
<%= render 'shared/turbo_modal_large', title: params[:selfsign] ? t('add_recipients') : t('add_new_recipients') do %>
|
<%= render 'shared/turbo_modal_large', title: params[:selfsign] ? t('add_recipients') : t('add_new_recipients') do %>
|
||||||
<% options = [[t('via_email'), 'email'], [t('via_phone'), 'phone'], [t('detailed'), 'detailed'], [t('upload_list'), 'list']].compact %>
|
<% options = [require_phone_2fa ? nil : [t('via_email'), 'email'], require_phone_2fa ? nil : [t('via_phone'), 'phone'], [t('detailed'), 'detailed'], [t('upload_list'), 'list']].compact %>
|
||||||
<toggle-visible data-element-ids="<%= options.map(&:last).to_json %>" class="relative text-center px-2 mt-4 block">
|
<toggle-visible data-element-ids="<%= options.map(&:last).to_json %>" class="relative text-center px-2 mt-4 block">
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<% options.each_with_index do |(label, value), index| %>
|
<% options.each_with_index do |(label, value), index| %>
|
||||||
<div>
|
<div>
|
||||||
<%= radio_button_tag 'option', value, value == 'email', class: 'peer hidden', data: { action: 'change:toggle-visible#trigger' } %>
|
<%= radio_button_tag 'option', value, value == (require_phone_2fa ? 'detailed' : 'email'), class: 'peer hidden', data: { action: 'change:toggle-visible#trigger' } %>
|
||||||
<label for="option_<%= value %>" class="block bg-base-200 md:min-w-[112px] text-sm font-semibold whitespace-nowrap py-1.5 px-4 peer-checked:bg-base-300 <%= 'hidden sm:inline-block' if value == 'list' %> <%= 'rounded-l-3xl' if index.zero? %> <%= 'rounded-r-3xl sm:rounded-r-none' if value == 'detailed' %> <%= 'rounded-r-3xl' if index == options.size - 1 %>">
|
<label for="option_<%= value %>" class="block bg-base-200 md:min-w-[112px] text-sm font-semibold whitespace-nowrap py-1.5 px-4 peer-checked:bg-base-300 <%= 'hidden sm:inline-block' if value == 'list' %> <%= 'rounded-l-3xl' if index.zero? %> <%= 'rounded-r-3xl sm:rounded-r-none' if value == 'detailed' %> <%= 'rounded-r-3xl' if index == options.size - 1 %>">
|
||||||
<%= label %>
|
<%= label %>
|
||||||
</label>
|
</label>
|
||||||
@@ -13,18 +14,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</toggle-visible>
|
</toggle-visible>
|
||||||
<div class="px-5 mb-5 mt-4">
|
<div class="px-5 mb-5 mt-4">
|
||||||
<div id="email">
|
<% unless require_phone_2fa %>
|
||||||
<%= render 'email_form', template: @template %>
|
<div id="email">
|
||||||
</div>
|
<%= render 'email_form', template: @template %>
|
||||||
<div id="phone" class="hidden">
|
</div>
|
||||||
<%= render 'phone_form', template: @template %>
|
<div id="phone" class="hidden">
|
||||||
</div>
|
<%= render 'phone_form', template: @template %>
|
||||||
<div id="detailed" class="hidden">
|
</div>
|
||||||
<%= render 'detailed_form', template: @template %>
|
<% end %>
|
||||||
|
<div id="detailed" class="<%= 'hidden' unless require_phone_2fa %>">
|
||||||
|
<%= render 'detailed_form', template: @template, require_phone_2fa: %>
|
||||||
</div>
|
</div>
|
||||||
<div id="list" class="hidden">
|
<div id="list" class="hidden">
|
||||||
<%= render 'list_form', template: @template %>
|
<%= render 'list_form', template: @template %>
|
||||||
</div>
|
</div>
|
||||||
|
<%= render 'submissions/error' %>
|
||||||
</div>
|
</div>
|
||||||
<%= content_for(:modal_extra) %>
|
<%= content_for(:modal_extra) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
<div class="flex items-end flex-col md:flex-row gap-2 w-full md:w-fit">
|
<div class="flex items-end flex-col md:flex-row gap-2 w-full md:w-fit">
|
||||||
<%= render 'submissions_filters/applied_filters', filter_params: %>
|
<%= render 'submissions_filters/applied_filters', filter_params: %>
|
||||||
<%= render 'submissions_filters/filter_button', filter_params: %>
|
<%= render 'submissions_filters/filter_button', filter_params: %>
|
||||||
|
<%= render 'submissions_filters/actions', filter_params: %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -8,17 +8,18 @@
|
|||||||
<div style="max-height: -webkit-fill-available;">
|
<div style="max-height: -webkit-fill-available;">
|
||||||
<div id="scrollbox">
|
<div id="scrollbox">
|
||||||
<div class="mx-auto block pb-72" style="max-width: 1000px">
|
<div class="mx-auto block pb-72" style="max-width: 1000px">
|
||||||
<%# flex block w-full sticky top-0 z-50 space-x-2 items-center bg-yellow-100 p-2 border-y border-yellow-200 %>
|
<%# flex block w-full sticky top-0 z-50 space-x-2 items-center bg-yellow-100 p-2 border-y border-yellow-200 transition-transform duration-300 %>
|
||||||
<%= local_assigns[:banner_html] || capture do %>
|
<%= local_assigns[:banner_html] || capture do %>
|
||||||
<%= render('submit_form/banner') %>
|
<%= render('submit_form/banner') %>
|
||||||
<div class="sticky top-0 z-50 bg-base-100 py-2 px-2 flex items-center md:-mx-[8px]" style="margin-bottom: -16px">
|
<div id="signing_form_header" class="sticky min-[1230px]:static top-0 z-50 bg-base-100 py-2 px-2 flex items-center md:-mx-[8px]" style="margin-bottom: -16px">
|
||||||
<div class="text-xl md:text-2xl font-medium focus:text-clip" style="width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
<div class="text-xl md:text-2xl font-medium focus:text-clip" style="width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||||
<%= @submitter.submission.template.name %>
|
<%= @submitter.submission.template.name %>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-2" style="margin-left: 20px; flex-shrink: 0">
|
<div class="flex items-center space-x-2" style="margin-left: 20px; flex-shrink: 0">
|
||||||
<% if @form_configs[:with_decline] %>
|
<% if @form_configs[:with_decline] %>
|
||||||
|
<% decline_modal_checkbox_uuid = SecureRandom.uuid %>
|
||||||
<div>
|
<div>
|
||||||
<%= render 'shared/html_modal', title: t(:decline), btn_text: t(:decline), btn_class: 'btn btn-sm !px-5', button_id: 'decline_button' do %>
|
<%= render 'shared/html_modal', title: t(:decline), btn_text: t(:decline), btn_class: 'btn btn-sm !px-5', button_id: 'decline_button', uuid: decline_modal_checkbox_uuid do %>
|
||||||
<%= render 'submit_form/decline_form', submitter: @submitter %>
|
<%= render 'submit_form/decline_form', submitter: @submitter %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
@@ -35,6 +36,26 @@
|
|||||||
</download-button>
|
</download-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<scroll-buttons class="fixed right-5 top-2 hidden md:flex gap-1 z-50 ease-in-out opacity-0 -translate-y-10">
|
||||||
|
<% if @form_configs[:with_decline] %>
|
||||||
|
<label id="decline_button" for="<%= decline_modal_checkbox_uuid %>" class="btn btn-sm px-0">
|
||||||
|
<span class="min-[1366px]:inline hidden px-3">
|
||||||
|
<%= t(:decline) %>
|
||||||
|
</span>
|
||||||
|
<span class="inline min-[1366px]:hidden px-2">
|
||||||
|
<%= svg_icon('x', class: 'w-5 h-5') %>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<% end %>
|
||||||
|
<download-button data-src="<%= submit_form_download_index_path(@submitter.slug) %>" class="btn btn-neutral text-white btn-sm px-2">
|
||||||
|
<span data-target="download-button.defaultButton">
|
||||||
|
<%= svg_icon('download', class: 'w-5 h-5') %>
|
||||||
|
</span>
|
||||||
|
<span class="hidden" data-target="download-button.loadingButton">
|
||||||
|
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin') %>
|
||||||
|
</span>
|
||||||
|
</download-button>
|
||||||
|
</scroll-buttons>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% schema.each do |item| %>
|
<% schema.each do |item| %>
|
||||||
<% document = @submitter.submission.template_schema_documents.find { |a| a.uuid == item['attachment_uuid'] } %>
|
<% document = @submitter.submission.template_schema_documents.find { |a| a.uuid == item['attachment_uuid'] } %>
|
||||||
|
|||||||
@@ -35,7 +35,12 @@
|
|||||||
<div class="grid gap-4 md:grid-cols-3">
|
<div class="grid gap-4 md:grid-cols-3">
|
||||||
<%= render partial: 'templates/template', collection: @templates %>
|
<%= render partial: 'templates/template', collection: @templates %>
|
||||||
</div>
|
</div>
|
||||||
<%= render 'shared/pagination', pagy: @pagy, items_name: 'templates' %>
|
<% templates_order_select_html = capture do %>
|
||||||
|
<% if params[:q].blank? && @pagy.pages > 1 %>
|
||||||
|
<%= render('shared/templates_order_select', with_recently_used: @pagy.count < 10_000) %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<%= render 'shared/pagination', pagy: @pagy, items_name: 'templates', right_additional_html: templates_order_select_html %>
|
||||||
<% elsif params[:q].present? %>
|
<% elsif params[:q].present? %>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="mt-16 text-3xl font-semibold">
|
<div class="mt-16 text-3xl font-semibold">
|
||||||
|
|||||||
@@ -61,6 +61,7 @@
|
|||||||
<div class="flex items-end flex-col md:flex-row gap-2 w-full md:w-fit">
|
<div class="flex items-end flex-col md:flex-row gap-2 w-full md:w-fit">
|
||||||
<%= render 'submissions_filters/applied_filters', filter_params: %>
|
<%= render 'submissions_filters/applied_filters', filter_params: %>
|
||||||
<%= render 'submissions_filters/filter_button', filter_params: %>
|
<%= render 'submissions_filters/filter_button', filter_params: %>
|
||||||
|
<%= render 'submissions_filters/actions', filter_params: filter_params.merge(template_id: @template.id) %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -34,6 +34,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% templates_order_select_html = capture do %>
|
||||||
|
<% if params[:q].blank? && @pagy.pages > 1 %>
|
||||||
|
<%= render('shared/templates_order_select', with_recently_used: @pagy.count < 10_000) %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
<% if @template_folders.present? %>
|
<% if @template_folders.present? %>
|
||||||
<div class="grid gap-4 md:grid-cols-3 <%= 'mb-6' if @templates.present? %>">
|
<div class="grid gap-4 md:grid-cols-3 <%= 'mb-6' if @templates.present? %>">
|
||||||
<%= render partial: 'template_folders/folder', collection: @template_folders, as: :folder %>
|
<%= render partial: 'template_folders/folder', collection: @template_folders, as: :folder %>
|
||||||
@@ -71,7 +76,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% if @templates.present? || params[:q].blank? %>
|
<% if @templates.present? || params[:q].blank? %>
|
||||||
<% if @pagy.pages > 1 %>
|
<% if @pagy.pages > 1 %>
|
||||||
<%= render 'shared/pagination', pagy: @pagy, items_name: 'templates', left_additional_html: view_archived_html %>
|
<%= render 'shared/pagination', pagy: @pagy, items_name: 'templates', left_additional_html: view_archived_html, right_additional_html: templates_order_select_html %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<%= view_archived_html %>
|
<%= view_archived_html %>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
<%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1' }, data: { close_on_submit: false } do |f| %>
|
<%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1' }, data: { close_on_submit: false } do |f| %>
|
||||||
<toggle-on-submit data-element-id="form_saved_alert"></toggle-on-submit>
|
<toggle-on-submit data-element-id="form_saved_alert"></toggle-on-submit>
|
||||||
<% configs = AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY).value %>
|
<% configs = AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY).value %>
|
||||||
<%= f.fields_for :preferences, Struct.new(:completed_redirect_url, :completed_message).new(@template.preferences['completed_redirect_url'].presence, Struct.new(:title, :body).new(*(@template.preferences['completed_message'] || {}).values_at('title', 'body'))) do |ff| %>
|
<%= f.fields_for :preferences, Struct.new(:completed_redirect_url, :completed_message, :require_phone_2fa).new(@template.preferences['completed_redirect_url'].presence, Struct.new(:title, :body).new(*(@template.preferences['completed_message'] || {}).values_at('title', 'body')), @template.preferences['require_phone_2fa'] == true) do |ff| %>
|
||||||
<div class="form-control mb-2">
|
<div class="form-control mb-2">
|
||||||
<%= ff.label :completed_redirect_url, t('redirect_on_completion_url'), class: 'label' %>
|
<%= ff.label :completed_redirect_url, t('redirect_on_completion_url'), class: 'label' %>
|
||||||
<%= ff.url_field :completed_redirect_url, required: false, class: 'base-input', dir: 'auto' %>
|
<%= ff.url_field :completed_redirect_url, required: false, class: 'base-input', dir: 'auto' %>
|
||||||
@@ -64,6 +64,7 @@
|
|||||||
</autoresize-textarea>
|
</autoresize-textarea>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<%= render 'templates_preferences/form_fields', ff: %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="form-control pt-2">
|
<div class="form-control pt-2">
|
||||||
<%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button' %>
|
<%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button' %>
|
||||||
@@ -334,6 +335,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% 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 %>
|
||||||
<div class="form-control mt-5 pb-2">
|
<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 %>
|
<%= button_tag button_title(title: t('save'), disabled_with: t('updating')), class: 'base-button', form: :submitters_form %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ module DocuSeal
|
|||||||
|
|
||||||
config.active_storage.draw_routes = ENV['MULTITENANT'] != 'true'
|
config.active_storage.draw_routes = ENV['MULTITENANT'] != 'true'
|
||||||
|
|
||||||
config.i18n.available_locales = %i[en en-US en-GB es-ES fr-FR pt-PT de-DE it-IT es it de fr pl uk cs pt he nl ar ko]
|
config.i18n.available_locales = %i[en en-US en-GB es-ES fr-FR pt-PT de-DE it-IT
|
||||||
|
es it de fr pl uk cs pt he nl ar ko ja]
|
||||||
config.i18n.fallbacks = [:en]
|
config.i18n.fallbacks = [:en]
|
||||||
|
|
||||||
config.exceptions_app = ->(env) { ErrorsController.action(:show).call(env) }
|
config.exceptions_app = ->(env) { ErrorsController.action(:show).call(env) }
|
||||||
|
|||||||
+197
-1
@@ -18,14 +18,22 @@ en: &en
|
|||||||
language_nl: Nederlands
|
language_nl: Nederlands
|
||||||
language_ar: العربية
|
language_ar: العربية
|
||||||
language_ko: 한국어
|
language_ko: 한국어
|
||||||
|
language_ja: 日本語
|
||||||
hi_there: Hi there
|
hi_there: Hi there
|
||||||
thanks: Thanks
|
thanks: Thanks
|
||||||
bcc_recipients: BCC recipients
|
bcc_recipients: BCC recipients
|
||||||
|
resend_pending: Re-send pending
|
||||||
always_enforce_signing_order: Always enforce the signing order
|
always_enforce_signing_order: Always enforce the signing order
|
||||||
|
ensure_unique_recipients: Ensure unique recipients
|
||||||
edit_per_party: Edit per party
|
edit_per_party: Edit per party
|
||||||
reply_to: Reply to
|
reply_to: Reply to
|
||||||
pending_by_me: Pending by me
|
pending_by_me: Pending by me
|
||||||
partially_completed: Partially completed
|
partially_completed: Partially completed
|
||||||
|
require_phone_2fa_to_open: Require phone 2FA to open
|
||||||
|
the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: The sender has requested a two factor authentication via one time password sent to your <b>%{phone}</b> phone number.
|
||||||
|
send_verification_code: Send verification code
|
||||||
|
code_has_been_resent: Code has been re-sent
|
||||||
|
invalid_code: Invalid code
|
||||||
unarchive: Unarchive
|
unarchive: Unarchive
|
||||||
signed: Signed
|
signed: Signed
|
||||||
first_party: 'First Party'
|
first_party: 'First Party'
|
||||||
@@ -47,6 +55,8 @@ en: &en
|
|||||||
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'You have been invited to %{account_name} %{product_name}. Please sign up using the link below:'
|
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'You have been invited to %{account_name} %{product_name}. Please sign up using the link below:'
|
||||||
sent_using_product_name_in_testing_mode_html: 'Sent using <a href="%{product_url}">%{product_name}</a> in testing mode'
|
sent_using_product_name_in_testing_mode_html: 'Sent using <a href="%{product_url}">%{product_name}</a> in testing mode'
|
||||||
sent_using_product_name_free_document_signing_html: 'Sent using <a href="%{product_url}">%{product_name}</a> free document signing.'
|
sent_using_product_name_free_document_signing_html: 'Sent using <a href="%{product_url}">%{product_name}</a> free document signing.'
|
||||||
|
sent_with_docuseal_pro_html: 'Sent with <a href="%{product_url}">DocuSeal Pro</a>'
|
||||||
|
show_send_with_docuseal_pro_attribution_in_emails_html: Show "Sent with <span class="link">DocuSeal Pro</span>" attribution in emails
|
||||||
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Sign documents with trusted certificate provided by DocuSeal. Your documents and data are never shared with DocuSeal. PDF checksum is provided to generate a trusted signature.
|
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Sign documents with trusted certificate provided by DocuSeal. Your documents and data are never shared with DocuSeal. PDF checksum is provided to generate a trusted signature.
|
||||||
you_have_been_invited_to_submit_the_name_form: 'You have been invited to submit the "%{name}" form.'
|
you_have_been_invited_to_submit_the_name_form: 'You have been invited to submit the "%{name}" form.'
|
||||||
you_have_been_invited_to_sign_the_name: 'You have been invited to sign the "%{name}".'
|
you_have_been_invited_to_sign_the_name: 'You have been invited to sign the "%{name}".'
|
||||||
@@ -704,6 +714,9 @@ en: &en
|
|||||||
welcome_to_docuseal: Welcome to DocuSeal
|
welcome_to_docuseal: Welcome to DocuSeal
|
||||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: Start a quick tour to learn how to create an send your first document
|
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: Start a quick tour to learn how to create an send your first document
|
||||||
start_tour: Start Tour
|
start_tour: Start Tour
|
||||||
|
name_a_z: Name A-Z
|
||||||
|
recently_used: Recently used
|
||||||
|
newest_first: Newest first
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Bulk Send
|
bulk: Bulk Send
|
||||||
@@ -788,6 +801,13 @@ en: &en
|
|||||||
read: Read your data
|
read: Read your data
|
||||||
|
|
||||||
es: &es
|
es: &es
|
||||||
|
resend_pending: Reenviar pendiente
|
||||||
|
ensure_unique_recipients: Asegurar destinatarios únicos
|
||||||
|
require_phone_2fa_to_open: Requiere 2FA por teléfono para abrir
|
||||||
|
the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: El remitente ha solicitado una autenticación de dos factores mediante una contraseña de un solo uso enviada a su número de teléfono <b>%{phone}</b>.
|
||||||
|
send_verification_code: Enviar código de verificación
|
||||||
|
code_has_been_resent: El código ha sido reenviado
|
||||||
|
invalid_code: Código inválido
|
||||||
always_enforce_signing_order: Siempre imponer el orden de firma
|
always_enforce_signing_order: Siempre imponer el orden de firma
|
||||||
bcc_recipients: Destinatarios CCO
|
bcc_recipients: Destinatarios CCO
|
||||||
edit_per_party: Editar por parte
|
edit_per_party: Editar por parte
|
||||||
@@ -817,6 +837,8 @@ es: &es
|
|||||||
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Has sido invitado a %{account_name} %{product_name}. Por favor, regístrate usando el enlace a continuación:'
|
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Has sido invitado a %{account_name} %{product_name}. Por favor, regístrate usando el enlace a continuación:'
|
||||||
sent_using_product_name_in_testing_mode_html: 'Enviado usando <a href="%{product_url}">%{product_name}</a> en Modo de Prueba'
|
sent_using_product_name_in_testing_mode_html: 'Enviado usando <a href="%{product_url}">%{product_name}</a> en Modo de Prueba'
|
||||||
sent_using_product_name_free_document_signing_html: 'Enviado usando la firma de documentos gratuita de <a href="%{product_url}">%{product_name}</a>.'
|
sent_using_product_name_free_document_signing_html: 'Enviado usando la firma de documentos gratuita de <a href="%{product_url}">%{product_name}</a>.'
|
||||||
|
sent_with_docuseal_pro_html: 'Enviado con <a href="%{product_url}">DocuSeal Pro</a>'
|
||||||
|
show_send_with_docuseal_pro_attribution_in_emails_html: Mostrar el mensaje "Enviado con <span class="link">DocuSeal Pro</span>" en los correos electrónicos
|
||||||
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Firme documentos con un certificado de confianza proporcionado por DocuSeal. Sus documentos y datos nunca se comparten con DocuSeal. Se proporciona un checksum de PDF para generar una firma de confianza.
|
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Firme documentos con un certificado de confianza proporcionado por DocuSeal. Sus documentos y datos nunca se comparten con DocuSeal. Se proporciona un checksum de PDF para generar una firma de confianza.
|
||||||
hi_there: Hola
|
hi_there: Hola
|
||||||
thanks: Gracias
|
thanks: Gracias
|
||||||
@@ -1474,6 +1496,9 @@ es: &es
|
|||||||
welcome_to_docuseal: Bienvenido a DocuSeal
|
welcome_to_docuseal: Bienvenido a DocuSeal
|
||||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: Inicia una guía rápida para aprender a crear y enviar tu primer documento.
|
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: Inicia una guía rápida para aprender a crear y enviar tu primer documento.
|
||||||
start_tour: Iniciar guía
|
start_tour: Iniciar guía
|
||||||
|
name_a_z: Nombre A-Z
|
||||||
|
recently_used: Usado recientemente
|
||||||
|
newest_first: Más reciente primero
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Envío masivo
|
bulk: Envío masivo
|
||||||
@@ -1558,6 +1583,13 @@ es: &es
|
|||||||
read: Leer tus datos
|
read: Leer tus datos
|
||||||
|
|
||||||
it: &it
|
it: &it
|
||||||
|
resend_pending: Reinvia in sospeso
|
||||||
|
ensure_unique_recipients: Assicurarsi destinatari unici
|
||||||
|
require_phone_2fa_to_open: Richiedi l'autenticazione a due fattori tramite telefono per aprire
|
||||||
|
the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: Il mittente ha richiesto un'autenticazione a due fattori tramite una password monouso inviata al tuo numero di telefono <b>%{phone}</b>.
|
||||||
|
send_verification_code: Invia codice di verifica
|
||||||
|
code_has_been_resent: Il codice è stato inviato di nuovo
|
||||||
|
invalid_code: Codice non valido
|
||||||
always_enforce_signing_order: Applicare sempre l'ordine di firma
|
always_enforce_signing_order: Applicare sempre l'ordine di firma
|
||||||
bcc_recipients: Destinatari BCC
|
bcc_recipients: Destinatari BCC
|
||||||
edit_per_party: Modifica per partito
|
edit_per_party: Modifica per partito
|
||||||
@@ -1586,6 +1618,8 @@ it: &it
|
|||||||
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Sei stato invitato a %{account_name} %{product_name}. Registrati utilizzando il link qui sotto:'
|
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Sei stato invitato a %{account_name} %{product_name}. Registrati utilizzando il link qui sotto:'
|
||||||
sent_using_product_name_in_testing_mode_html: 'Inviato utilizzando <a href="%{product_url}">%{product_name}</a> in Modalità di Test'
|
sent_using_product_name_in_testing_mode_html: 'Inviato utilizzando <a href="%{product_url}">%{product_name}</a> in Modalità di Test'
|
||||||
sent_using_product_name_free_document_signing_html: 'Inviato utilizzando la firma di documenti gratuita di <a href="%{product_url}">%{product_name}</a>.'
|
sent_using_product_name_free_document_signing_html: 'Inviato utilizzando la firma di documenti gratuita di <a href="%{product_url}">%{product_name}</a>.'
|
||||||
|
sent_with_docuseal_pro_html: 'Inviato con <a href="%{product_url}">DocuSeal Pro</a>'
|
||||||
|
show_send_with_docuseal_pro_attribution_in_emails_html: Mostra la dicitura "Inviato con <span class="link">DocuSeal Pro</span>" nelle email
|
||||||
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: "Firma documenti con un certificato di fiducia fornito da DocuSeal. I tuoi documenti e i tuoi dati non vengono mai condivisi con DocuSeal. Il checksum PDF è fornito per generare una firma di fiducia."
|
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: "Firma documenti con un certificato di fiducia fornito da DocuSeal. I tuoi documenti e i tuoi dati non vengono mai condivisi con DocuSeal. Il checksum PDF è fornito per generare una firma di fiducia."
|
||||||
hi_there: Ciao
|
hi_there: Ciao
|
||||||
thanks: Grazie
|
thanks: Grazie
|
||||||
@@ -2243,6 +2277,9 @@ it: &it
|
|||||||
welcome_to_docuseal: Benvenuto in DocuSeal
|
welcome_to_docuseal: Benvenuto in DocuSeal
|
||||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: Inizia un tour rapido per imparare a creare e inviare il tuo primo documento.
|
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: Inizia un tour rapido per imparare a creare e inviare il tuo primo documento.
|
||||||
start_tour: Inizia il tour
|
start_tour: Inizia il tour
|
||||||
|
name_a_z: Nome A-Z
|
||||||
|
recently_used: Recentemente usato
|
||||||
|
newest_first: Più recenti prima
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Invio massivo
|
bulk: Invio massivo
|
||||||
@@ -2327,6 +2364,13 @@ it: &it
|
|||||||
read: Leggi i tuoi dati
|
read: Leggi i tuoi dati
|
||||||
|
|
||||||
fr: &fr
|
fr: &fr
|
||||||
|
resend_pending: Renvoyer en attente
|
||||||
|
ensure_unique_recipients: Assurer l'unicité des destinataires
|
||||||
|
require_phone_2fa_to_open: Requiert une 2FA par téléphone pour ouvrir
|
||||||
|
the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: L'expéditeur a demandé une authentification à deux facteurs via un mot de passe à usage unique envoyé à votre numéro de téléphone <b>%{phone}</b>.
|
||||||
|
send_verification_code: Envoyer le code de vérification
|
||||||
|
code_has_been_resent: Le code a été renvoyé
|
||||||
|
invalid_code: Code invalide
|
||||||
always_enforce_signing_order: Toujours appliquer l'ordre de signature
|
always_enforce_signing_order: Toujours appliquer l'ordre de signature
|
||||||
bcc_recipients: Destinataires en CCI
|
bcc_recipients: Destinataires en CCI
|
||||||
edit_per_party: Éditer par partie
|
edit_per_party: Éditer par partie
|
||||||
@@ -2356,6 +2400,8 @@ fr: &fr
|
|||||||
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Vous avez été invité à %{account_name} %{product_name}. Veuillez vous inscrire en utilisant le lien ci-dessous:'
|
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Vous avez été invité à %{account_name} %{product_name}. Veuillez vous inscrire en utilisant le lien ci-dessous:'
|
||||||
sent_using_product_name_in_testing_mode_html: 'Envoyé en utilisant <a href="%{product_url}">%{product_name}</a> en Mode Test'
|
sent_using_product_name_in_testing_mode_html: 'Envoyé en utilisant <a href="%{product_url}">%{product_name}</a> en Mode Test'
|
||||||
sent_using_product_name_free_document_signing_html: 'Envoyé en utilisant la signature de documents gratuite de <a href="%{product_url}">%{product_name}</a>.'
|
sent_using_product_name_free_document_signing_html: 'Envoyé en utilisant la signature de documents gratuite de <a href="%{product_url}">%{product_name}</a>.'
|
||||||
|
sent_with_docuseal_pro_html: 'Envoyé avec <a href="%{product_url}">DocuSeal Pro</a>'
|
||||||
|
show_send_with_docuseal_pro_attribution_in_emails_html: Afficher "Envoyé avec <span class="link">DocuSeal Pro</span>" dans les e-mails
|
||||||
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Signez des documents avec un certificat de confiance fourni par DocuSeal. Vos documents et données ne sont jamais partagés avec DocuSeal. Un checksum PDF est fourni pour générer une signature de confiance.
|
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Signez des documents avec un certificat de confiance fourni par DocuSeal. Vos documents et données ne sont jamais partagés avec DocuSeal. Un checksum PDF est fourni pour générer une signature de confiance.
|
||||||
hi_there: Bonjour
|
hi_there: Bonjour
|
||||||
thanks: Merci
|
thanks: Merci
|
||||||
@@ -3014,6 +3060,9 @@ fr: &fr
|
|||||||
welcome_to_docuseal: Bienvenue sur DocuSeal
|
welcome_to_docuseal: Bienvenue sur DocuSeal
|
||||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: Lancez une visite rapide pour apprendre à créer et envoyer votre premier document.
|
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: Lancez une visite rapide pour apprendre à créer et envoyer votre premier document.
|
||||||
start_tour: Démarrer
|
start_tour: Démarrer
|
||||||
|
name_a_z: Nom A-Z
|
||||||
|
recently_used: Récemment utilisé
|
||||||
|
newest_first: Le plus récent d'abord
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Envoi en masse
|
bulk: Envoi en masse
|
||||||
@@ -3098,6 +3147,13 @@ fr: &fr
|
|||||||
read: Lire vos données
|
read: Lire vos données
|
||||||
|
|
||||||
pt: &pt
|
pt: &pt
|
||||||
|
resend_pending: Re-enviar pendente
|
||||||
|
ensure_unique_recipients: Garantir destinatários únicos
|
||||||
|
require_phone_2fa_to_open: Necessário autenticação de dois fatores via telefone para abrir
|
||||||
|
the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: O remetente solicitou uma autenticação de dois fatores via senha de uso único enviada para seu número <b>%{phone}</b>.
|
||||||
|
send_verification_code: Enviar código de verificação
|
||||||
|
code_has_been_resent: Código foi reenviado
|
||||||
|
invalid_code: Código inválido
|
||||||
always_enforce_signing_order: Sempre impor a ordem de assinatura
|
always_enforce_signing_order: Sempre impor a ordem de assinatura
|
||||||
bcc_recipients: Destinatários BCC
|
bcc_recipients: Destinatários BCC
|
||||||
edit_per_party: Edita por festa
|
edit_per_party: Edita por festa
|
||||||
@@ -3127,6 +3183,8 @@ pt: &pt
|
|||||||
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Você foi convidado para %{account_name} %{product_name}. Inscreva-se usando o link abaixo:'
|
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Você foi convidado para %{account_name} %{product_name}. Inscreva-se usando o link abaixo:'
|
||||||
sent_using_product_name_in_testing_mode_html: 'Enviado usando <a href="%{product_url}">%{product_name}</a> no Modo de Teste'
|
sent_using_product_name_in_testing_mode_html: 'Enviado usando <a href="%{product_url}">%{product_name}</a> no Modo de Teste'
|
||||||
sent_using_product_name_free_document_signing_html: 'Enviado usando a assinatura gratuita de documentos de <a href="%{product_url}">%{product_name}</a>.'
|
sent_using_product_name_free_document_signing_html: 'Enviado usando a assinatura gratuita de documentos de <a href="%{product_url}">%{product_name}</a>.'
|
||||||
|
sent_with_docuseal_pro_html: 'Enviado com <a href="%{product_url}">DocuSeal Pro</a>'
|
||||||
|
show_send_with_docuseal_pro_attribution_in_emails_html: Mostrar "Enviado com <span class="link">DocuSeal Pro</span>" nos e-mails
|
||||||
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Assine documentos com certificado confiável fornecido pela DocuSeal. Seus documentos e dados nunca são compartilhados com a DocuSeal. O checksum do PDF é fornecido para gerar uma assinatura confiável.
|
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Assine documentos com certificado confiável fornecido pela DocuSeal. Seus documentos e dados nunca são compartilhados com a DocuSeal. O checksum do PDF é fornecido para gerar uma assinatura confiável.
|
||||||
hi_there: Olá
|
hi_there: Olá
|
||||||
thanks: Obrigado
|
thanks: Obrigado
|
||||||
@@ -3395,7 +3453,7 @@ pt: &pt
|
|||||||
verify_pdf: Verificar PDF
|
verify_pdf: Verificar PDF
|
||||||
sign_out: Sair
|
sign_out: Sair
|
||||||
page_number: 'Página %{number}'
|
page_number: 'Página %{number}'
|
||||||
powered_by: Oferecido por
|
powered_by: Desenvolvido por
|
||||||
count_documents_signed_with_html: '<b>%{count}</b> documentos assinados com'
|
count_documents_signed_with_html: '<b>%{count}</b> documentos assinados com'
|
||||||
storage: Armazenamento
|
storage: Armazenamento
|
||||||
notifications: Notificações
|
notifications: Notificações
|
||||||
@@ -3784,6 +3842,9 @@ pt: &pt
|
|||||||
welcome_to_docuseal: Bem-vindo ao DocuSeal
|
welcome_to_docuseal: Bem-vindo ao DocuSeal
|
||||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: Comece um tour rápido para aprender a criar e enviar seu primeiro documento.
|
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: Comece um tour rápido para aprender a criar e enviar seu primeiro documento.
|
||||||
start_tour: Iniciar tour
|
start_tour: Iniciar tour
|
||||||
|
name_a_z: Nome A-Z
|
||||||
|
recently_used: Recentemente usado
|
||||||
|
newest_first: Mais recente primeiro
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Envio em massa
|
bulk: Envio em massa
|
||||||
@@ -3869,6 +3930,13 @@ pt: &pt
|
|||||||
read: Ler seus dados
|
read: Ler seus dados
|
||||||
|
|
||||||
de: &de
|
de: &de
|
||||||
|
resend_pending: Ausstehende erneut senden
|
||||||
|
ensure_unique_recipients: Stellen Sie einzigartige Empfänger sicher
|
||||||
|
require_phone_2fa_to_open: Telefon-2FA zum Öffnen erforderlich
|
||||||
|
the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: Der Absender hat eine Zwei-Faktor-Authentifizierung per Einmalpasswort angefordert, das an Ihre <b>%{phone}</b>-Telefonnummer gesendet wurde.
|
||||||
|
send_verification_code: Bestätigungscode senden
|
||||||
|
code_has_been_resent: Code wurde erneut gesendet
|
||||||
|
invalid_code: Ungültiger Code
|
||||||
always_enforce_signing_order: Immer die Reihenfolge der Unterschriften erzwingen
|
always_enforce_signing_order: Immer die Reihenfolge der Unterschriften erzwingen
|
||||||
bcc_recipients: BCC-Empfänger
|
bcc_recipients: BCC-Empfänger
|
||||||
edit_per_party: Bearbeiten pro Partei
|
edit_per_party: Bearbeiten pro Partei
|
||||||
@@ -3898,6 +3966,8 @@ de: &de
|
|||||||
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Sie wurden zu %{account_name} %{product_name} eingeladen. Bitte registrieren Sie sich über den folgenden Link:'
|
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Sie wurden zu %{account_name} %{product_name} eingeladen. Bitte registrieren Sie sich über den folgenden Link:'
|
||||||
sent_using_product_name_in_testing_mode_html: 'Gesendet über <a href="%{product_url}">%{product_name}</a> im Testmodus'
|
sent_using_product_name_in_testing_mode_html: 'Gesendet über <a href="%{product_url}">%{product_name}</a> im Testmodus'
|
||||||
sent_using_product_name_free_document_signing_html: 'Gesendet mit der kostenlosen Dokumentensignierung von <a href="%{product_url}">%{product_name}</a>.'
|
sent_using_product_name_free_document_signing_html: 'Gesendet mit der kostenlosen Dokumentensignierung von <a href="%{product_url}">%{product_name}</a>.'
|
||||||
|
sent_with_docuseal_pro_html: Gesendet mit <a href="%{product_url}">DocuSeal Pro</a>
|
||||||
|
show_send_with_docuseal_pro_attribution_in_emails_html: '"Gesendet mit <span class="link">DocuSeal Pro</span>" in E-Mails anzeigen'
|
||||||
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Unterzeichnen Sie Dokumente mit einem vertrauenswürdigen Zertifikat von DocuSeal. Ihre Dokumente und Daten werden niemals mit DocuSeal geteilt. Eine PDF-Prüfziffer wird bereitgestellt, um eine vertrauenswürdige Signatur zu generieren.
|
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Unterzeichnen Sie Dokumente mit einem vertrauenswürdigen Zertifikat von DocuSeal. Ihre Dokumente und Daten werden niemals mit DocuSeal geteilt. Eine PDF-Prüfziffer wird bereitgestellt, um eine vertrauenswürdige Signatur zu generieren.
|
||||||
hi_there: Hallo
|
hi_there: Hallo
|
||||||
thanks: Danke
|
thanks: Danke
|
||||||
@@ -4555,6 +4625,9 @@ de: &de
|
|||||||
welcome_to_docuseal: Willkommen bei DocuSeal
|
welcome_to_docuseal: Willkommen bei DocuSeal
|
||||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: Starte eine kurze Tour, um zu lernen, wie du dein erstes Dokument erstellst und versendest.
|
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: Starte eine kurze Tour, um zu lernen, wie du dein erstes Dokument erstellst und versendest.
|
||||||
start_tour: Starten
|
start_tour: Starten
|
||||||
|
name_a_z: Name A-Z
|
||||||
|
recently_used: Kürzlich verwendet
|
||||||
|
newest_first: Neueste zuerst
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Massenversand
|
bulk: Massenversand
|
||||||
@@ -4639,6 +4712,11 @@ de: &de
|
|||||||
read: Lese deine Daten
|
read: Lese deine Daten
|
||||||
|
|
||||||
pl:
|
pl:
|
||||||
|
require_phone_2fa_to_open: Wymagaj uwierzytelniania telefonicznego 2FA do otwarcia
|
||||||
|
the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: Nadawca zażądał uwierzytelnienia dwuetapowego poprzez jednorazowe hasło wysłane na Twój <b>%{phone}</b> numer telefonu.
|
||||||
|
send_verification_code: Wyślij kod weryfikacyjny
|
||||||
|
code_has_been_resent: Kod został wysłany ponownie
|
||||||
|
invalid_code: Niepoprawny kod
|
||||||
awaiting_completion_by_the_other_party: "Oczekuje na dokończenie przez drugą stronę"
|
awaiting_completion_by_the_other_party: "Oczekuje na dokończenie przez drugą stronę"
|
||||||
view: Widok
|
view: Widok
|
||||||
hi_there: Cześć,
|
hi_there: Cześć,
|
||||||
@@ -4696,8 +4774,16 @@ pl:
|
|||||||
too_many_attempts: Zbyt wiele prób.
|
too_many_attempts: Zbyt wiele prób.
|
||||||
verification_code: Kod Weryfikacyjny
|
verification_code: Kod Weryfikacyjny
|
||||||
resend_code: Wyślij Kod Ponownie
|
resend_code: Wyślij Kod Ponownie
|
||||||
|
powered_by: 'Napędzany prze'
|
||||||
|
count_documents_signed_with_html: '<b>%{count}</b> dokumentów podpisanych za pomocą'
|
||||||
|
open_source_documents_software: 'oprogramowanie do dokumentów open source'
|
||||||
|
|
||||||
uk:
|
uk:
|
||||||
|
require_phone_2fa_to_open: Вимагати двофакторну автентифікацію через телефон для відкриття
|
||||||
|
the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: Відправник запросив двофакторну автентифікацію за допомогою одноразового пароля, відправленого на ваш номер телефону <b>%{phone}</b>.
|
||||||
|
send_verification_code: Надіслати код підтвердження
|
||||||
|
code_has_been_resent: Код повторно надіслано
|
||||||
|
invalid_code: Невірний код
|
||||||
awaiting_completion_by_the_other_party: "Очікує завершення з боку іншої сторони"
|
awaiting_completion_by_the_other_party: "Очікує завершення з боку іншої сторони"
|
||||||
view: Переглянути
|
view: Переглянути
|
||||||
hi_there: Привіт,
|
hi_there: Привіт,
|
||||||
@@ -4755,8 +4841,16 @@ uk:
|
|||||||
too_many_attempts: Забагато спроб.
|
too_many_attempts: Забагато спроб.
|
||||||
verification_code: Код перевірки
|
verification_code: Код перевірки
|
||||||
resend_code: Відправити код знову
|
resend_code: Відправити код знову
|
||||||
|
powered_by: 'Працює на базі'
|
||||||
|
count_documents_signed_with_html: '<b>%{count}</b> документів підписано за допомогою'
|
||||||
|
open_source_documents_software: 'відкрите програмне забезпечення для документів'
|
||||||
|
|
||||||
cs:
|
cs:
|
||||||
|
require_phone_2fa_to_open: Vyžadovat otevření pomocí telefonního 2FA
|
||||||
|
the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: Odesílatel požádal o dvoufaktorové ověření pomocí jednorázového hesla zaslaného na vaše telefonní číslo <b>%{phone}</b>.
|
||||||
|
send_verification_code: Odeslat ověřovací kód
|
||||||
|
code_has_been_resent: Kód byl znovu odeslán
|
||||||
|
invalid_code: Neplatný kód
|
||||||
awaiting_completion_by_the_other_party: "Čeká se na dokončení druhou stranou"
|
awaiting_completion_by_the_other_party: "Čeká se na dokončení druhou stranou"
|
||||||
view: Zobrazit
|
view: Zobrazit
|
||||||
hi_there: Ahoj,
|
hi_there: Ahoj,
|
||||||
@@ -4814,8 +4908,16 @@ cs:
|
|||||||
too_many_attempts: Příliš mnoho pokusů.
|
too_many_attempts: Příliš mnoho pokusů.
|
||||||
verification_code: Ověřovací Kód
|
verification_code: Ověřovací Kód
|
||||||
resend_code: Znovu Odeslat Kód
|
resend_code: Znovu Odeslat Kód
|
||||||
|
powered_by: 'Poháněno'
|
||||||
|
count_documents_signed_with_html: '<b>%{count}</b> dokumentů podepsáno pomocí'
|
||||||
|
open_source_documents_software: 'open source software pro dokumenty'
|
||||||
|
|
||||||
he:
|
he:
|
||||||
|
require_phone_2fa_to_open: דרוש אימות דו-שלבי באמצעות טלפון לפתיחה
|
||||||
|
the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: השולח ביקש אימות דו-שלבי באמצעות סיסמה חד פעמית שנשלחה למספר הטלפון שלך <b>%{phone}</b>.
|
||||||
|
send_verification_code: שלח קוד אימות
|
||||||
|
code_has_been_resent: הקוד נשלח מחדש
|
||||||
|
invalid_code: קוד שגוי
|
||||||
awaiting_completion_by_the_other_party: "המתנה להשלמה מצד הצד השני"
|
awaiting_completion_by_the_other_party: "המתנה להשלמה מצד הצד השני"
|
||||||
view: תצוגה
|
view: תצוגה
|
||||||
hi_there: שלום,
|
hi_there: שלום,
|
||||||
@@ -4873,8 +4975,16 @@ he:
|
|||||||
too_many_attempts: יותר מדי ניסיונות.
|
too_many_attempts: יותר מדי ניסיונות.
|
||||||
verification_code: קוד אימות
|
verification_code: קוד אימות
|
||||||
resend_code: שלח קוד מחדש
|
resend_code: שלח קוד מחדש
|
||||||
|
powered_by: 'מופעל על ידי'
|
||||||
|
count_documents_signed_with_html: '<b>%{count}</b> מסמכים נחתמו באמצעות'
|
||||||
|
open_source_documents_software: 'תוכנה בקוד פתוח למסמכים'
|
||||||
|
|
||||||
nl:
|
nl:
|
||||||
|
require_phone_2fa_to_open: Vereis telefoon 2FA om te openen
|
||||||
|
the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: De afzender heeft gevraagd om tweefactorauthenticatie via een eenmalig wachtwoord, verzonden naar uw <b>%{phone}</b> telefoonnummer.
|
||||||
|
send_verification_code: Verificatiecode verzenden
|
||||||
|
code_has_been_resent: Code is opnieuw verzonden
|
||||||
|
invalid_code: Invalid code
|
||||||
awaiting_completion_by_the_other_party: "In afwachting van voltooiing door de andere partij"
|
awaiting_completion_by_the_other_party: "In afwachting van voltooiing door de andere partij"
|
||||||
view: Bekijken
|
view: Bekijken
|
||||||
hi_there: Hallo,
|
hi_there: Hallo,
|
||||||
@@ -4932,8 +5042,16 @@ nl:
|
|||||||
too_many_attempts: Te veel pogingen.
|
too_many_attempts: Te veel pogingen.
|
||||||
verification_code: Verificatiecode
|
verification_code: Verificatiecode
|
||||||
resend_code: Code Opnieuw Verzenden
|
resend_code: Code Opnieuw Verzenden
|
||||||
|
powered_by: 'Aangedreven door'
|
||||||
|
count_documents_signed_with_html: '<b>%{count}</b> documenten ondertekend met'
|
||||||
|
open_source_documents_software: 'open source documenten software'
|
||||||
|
|
||||||
ar:
|
ar:
|
||||||
|
require_phone_2fa_to_open: "تطلب فتح عبر تحقق الهاتف ذو العاملين"
|
||||||
|
the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: "المرسل طلب تحقق ذو عاملين عبر كلمة مرور لمرة واحدة مرسل إلى رقم هاتفك <b>%{phone}</b>."
|
||||||
|
send_verification_code: "إرسال رمز التحقق"
|
||||||
|
code_has_been_resent: "تم إعادة إرسال الرمز"
|
||||||
|
invalid_code: "رمز غير صالح"
|
||||||
awaiting_completion_by_the_other_party: "في انتظار إكتمال الطرف الآخر"
|
awaiting_completion_by_the_other_party: "في انتظار إكتمال الطرف الآخر"
|
||||||
view: عرض
|
view: عرض
|
||||||
hi_there: مرحبا,
|
hi_there: مرحبا,
|
||||||
@@ -4991,8 +5109,16 @@ ar:
|
|||||||
too_many_attempts: عدد المحاولات كبير جدًا.
|
too_many_attempts: عدد المحاولات كبير جدًا.
|
||||||
verification_code: رمز التحقق
|
verification_code: رمز التحقق
|
||||||
resend_code: إعادة إرسال الرمز
|
resend_code: إعادة إرسال الرمز
|
||||||
|
powered_by: 'مشغل بواسطة'
|
||||||
|
count_documents_signed_with_html: '<b>%{count}</b> مستندات تم توقيعها باستخدام'
|
||||||
|
open_source_documents_software: 'برنامج مستندات مفتوح المصدر'
|
||||||
|
|
||||||
ko:
|
ko:
|
||||||
|
require_phone_2fa_to_open: 휴대폰 2FA를 열 때 요구함
|
||||||
|
the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: 발신자가 <b>%{phone}</b> 전화번호로 보내진 일회용 비밀번호를 통해 이중 인증을 요청했습니다.
|
||||||
|
send_verification_code: 인증 코드 보내기
|
||||||
|
code_has_been_resent: 코드가 재전송되었습니다.
|
||||||
|
invalid_code: 잘못된 코드
|
||||||
awaiting_completion_by_the_other_party: "다른 당사자의 완료를 기다리고 있습니다"
|
awaiting_completion_by_the_other_party: "다른 당사자의 완료를 기다리고 있습니다"
|
||||||
view: 보기
|
view: 보기
|
||||||
hi_there: 안녕하세요,
|
hi_there: 안녕하세요,
|
||||||
@@ -5050,6 +5176,76 @@ ko:
|
|||||||
too_many_attempts: 시도 횟수가 너무 많습니다.
|
too_many_attempts: 시도 횟수가 너무 많습니다.
|
||||||
verification_code: 인증 코드
|
verification_code: 인증 코드
|
||||||
resend_code: 코드 재전송
|
resend_code: 코드 재전송
|
||||||
|
powered_by: '제공:'
|
||||||
|
count_documents_signed_with_html: '<b>%{count}</b>개의 문서가 다음을 통해 서명됨'
|
||||||
|
open_source_documents_software: '오픈소스 문서 소프트웨어'
|
||||||
|
|
||||||
|
ja:
|
||||||
|
require_phone_2fa_to_open: 電話による2段階認証が必要です
|
||||||
|
the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: 送信者は、<b>%{phone}</b> に送信されたワンタイムパスワードによる2段階認証を要求しました。
|
||||||
|
send_verification_code: 認証コードを送信
|
||||||
|
code_has_been_resent: コードが再送信されました
|
||||||
|
invalid_code: 無効なコードです
|
||||||
|
awaiting_completion_by_the_other_party: 他の当事者による完了を待機中
|
||||||
|
view: 表示
|
||||||
|
hi_there: こんにちは
|
||||||
|
download: ダウンロード
|
||||||
|
decline: 辞退
|
||||||
|
declined: 辞退済み
|
||||||
|
decline_reason: 辞退の理由
|
||||||
|
provide_a_reason: 理由を入力してください
|
||||||
|
notify_the_sender_with_the_reason_you_declined: 辞退理由を送信者に通知してください
|
||||||
|
form_has_been_declined_on_html: '<span class="font-semibold">%{time}</span> にフォームが辞退されました'
|
||||||
|
name_declined_by_submitter: '"%{name}" は %{submitter} により辞退されました'
|
||||||
|
name_declined_by_submitter_with_the_following_reason: '"%{name}" は %{submitter} により次の理由で辞退されました:'
|
||||||
|
email: メール
|
||||||
|
digitally_signed_by: 電子署名者
|
||||||
|
role: 役割
|
||||||
|
provide_your_email_to_start: 開始するにはメールアドレスを入力してください
|
||||||
|
start: 開始
|
||||||
|
reason: 理由
|
||||||
|
starting: 開始中
|
||||||
|
form_expired_at_html: '<span class="font-semibold">%{time}</span> にフォームの有効期限が切れました'
|
||||||
|
invited_by_html: '<span class="font-semibold">%{name}</span> により招待されました'
|
||||||
|
you_have_been_invited_to_submit_a_form: フォームの提出に招待されました
|
||||||
|
verification_code_code: '認証コード: %{code}'
|
||||||
|
signed_on_time: '%{time} に署名済み'
|
||||||
|
completed_on_time: '%{time} に完了'
|
||||||
|
document_has_been_signed_already: ドキュメントはすでに署名されています
|
||||||
|
form_has_been_submitted_already: フォームはすでに送信されています
|
||||||
|
send_copy_to_email: メールにコピーを送信
|
||||||
|
sending: 送信中
|
||||||
|
resubmit: 再送信
|
||||||
|
form_has_been_deleted_by_html: '<span class="font-semibold">%{name}</span> によりフォームが削除されました。'
|
||||||
|
or: または
|
||||||
|
download_documents: ドキュメントをダウンロード
|
||||||
|
downloading: ダウンロード中
|
||||||
|
completed_successfully: 正常に完了しました
|
||||||
|
password: パスワード
|
||||||
|
sign_in: サインイン
|
||||||
|
signing_in: サインイン中
|
||||||
|
sign_in_with_microsoft: Microsoftでサインイン
|
||||||
|
sign_in_with_google: Googleでサインイン
|
||||||
|
forgot_your_password_: パスワードをお忘れですか?
|
||||||
|
create_free_account: 無料アカウントを作成
|
||||||
|
already_have_an_account: すでにアカウントをお持ちですか?
|
||||||
|
first_name: 名
|
||||||
|
last_name: 姓
|
||||||
|
sign_up: 登録
|
||||||
|
signing_up: 登録中
|
||||||
|
profile_details: プロフィールの詳細
|
||||||
|
sign_up_with_google: Googleで登録
|
||||||
|
sign_up_with_microsoft: Microsoftで登録
|
||||||
|
by_creating_an_account_you_agree_to_our_html: '<a target="_blank" href="https://www.docuseal.com/privacy">プライバシーポリシー</a>および<a target="_blank" href="https://www.docuseal.com/terms">利用規約</a>に同意の上、アカウントを作成します。'
|
||||||
|
enter_email_to_continue: 続行するにはメールを入力してください
|
||||||
|
the_code_has_been_sent_to_your_email: コードがあなたのメールに送信されました
|
||||||
|
enter_the_verification_code_from_your_email: メールに記載された認証コードを入力してください
|
||||||
|
too_many_attempts: 試行回数が多すぎます
|
||||||
|
verification_code: 認証コード
|
||||||
|
resend_code: コードを再送信
|
||||||
|
powered_by: '提供元:'
|
||||||
|
count_documents_signed_with_html: '<b>%{count}</b> 件のドキュメントが以下で署名されました'
|
||||||
|
open_source_documents_software: 'オープンソースのドキュメントソフトウェア'
|
||||||
|
|
||||||
en-US:
|
en-US:
|
||||||
<<: *en
|
<<: *en
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ class ApiPathConsiderJsonMiddleware
|
|||||||
def call(env)
|
def call(env)
|
||||||
if env['PATH_INFO'].starts_with?('/api') &&
|
if env['PATH_INFO'].starts_with?('/api') &&
|
||||||
(!env['PATH_INFO'].ends_with?('/documents') || env['REQUEST_METHOD'] != 'POST') &&
|
(!env['PATH_INFO'].ends_with?('/documents') || env['REQUEST_METHOD'] != 'POST') &&
|
||||||
!env['PATH_INFO'].ends_with?('/attachments')
|
!env['PATH_INFO'].ends_with?('/attachments') &&
|
||||||
|
!env['PATH_INFO'].ends_with?('/submitter_sms_clicks') &&
|
||||||
|
!env['PATH_INFO'].ends_with?('/submitter_email_clicks')
|
||||||
env['CONTENT_TYPE'] = 'application/json'
|
env['CONTENT_TYPE'] = 'application/json'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ module Docuseal
|
|||||||
DISCORD_URL = 'https://discord.gg/qygYCDGck9'
|
DISCORD_URL = 'https://discord.gg/qygYCDGck9'
|
||||||
TWITTER_URL = 'https://twitter.com/docusealco'
|
TWITTER_URL = 'https://twitter.com/docusealco'
|
||||||
TWITTER_HANDLE = '@docusealco'
|
TWITTER_HANDLE = '@docusealco'
|
||||||
CHATGPT_URL = 'https://chatgpt.com/g/g-9hg8AAw0r-docuseal'
|
CHATGPT_URL = "#{PRODUCT_URL}/chat".freeze
|
||||||
SUPPORT_EMAIL = 'support@docuseal.com'
|
SUPPORT_EMAIL = 'support@docuseal.com'
|
||||||
HOST = ENV.fetch('HOST', 'localhost')
|
HOST = ENV.fetch('HOST', 'localhost')
|
||||||
AATL_CERT_NAME = 'docuseal_aatl'
|
AATL_CERT_NAME = 'docuseal_aatl'
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ module ReplaceEmailVariables
|
|||||||
SUBMITTERS_N_EMAIL = /\{+submitters\[(?<index>\d+)\]\.email\}+/i
|
SUBMITTERS_N_EMAIL = /\{+submitters\[(?<index>\d+)\]\.email\}+/i
|
||||||
SUBMITTERS_N_NAME = /\{+submitters\[(?<index>\d+)\]\.name\}+/i
|
SUBMITTERS_N_NAME = /\{+submitters\[(?<index>\d+)\]\.name\}+/i
|
||||||
SUBMITTERS_N_FIRST_NAME = /\{+submitters\[(?<index>\d+)\]\.first_name\}+/i
|
SUBMITTERS_N_FIRST_NAME = /\{+submitters\[(?<index>\d+)\]\.first_name\}+/i
|
||||||
|
SUBMITTERS_N_FIELD_VALUE = /\{+submitters\[(?<index>\d+)\]\.(?<field_name>[^}]+)\}+/i
|
||||||
DOCUMENTS_LINKS = /\{+documents\.links\}+/i
|
DOCUMENTS_LINKS = /\{+documents\.links\}+/i
|
||||||
DOCUMENTS_LINK = /\{+documents\.link\}+/i
|
DOCUMENTS_LINK = /\{+documents\.link\}+/i
|
||||||
|
|
||||||
@@ -59,6 +60,10 @@ module ReplaceEmailVariables
|
|||||||
build_submitters_n_field(submitter.submission, match[:index].to_i - 1, :first_name)
|
build_submitters_n_field(submitter.submission, match[:index].to_i - 1, :first_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
text = replace(text, SUBMITTERS_N_FIELD_VALUE, html_escape:) do |match|
|
||||||
|
build_submitters_n_field(submitter.submission, match[:index].to_i - 1, :values, match[:field_name].to_s.strip)
|
||||||
|
end
|
||||||
|
|
||||||
replace(text, SENDER_EMAIL, html_escape:) { submitter.submission.created_by_user&.email.to_s.sub(/\+\w+@/, '@') }
|
replace(text, SENDER_EMAIL, html_escape:) { submitter.submission.created_by_user&.email.to_s.sub(/\+\w+@/, '@') }
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics
|
# rubocop:enable Metrics
|
||||||
@@ -69,10 +74,33 @@ module ReplaceEmailVariables
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_submitters_n_field(submission, index, field_name)
|
def build_submitters_n_field(submission, index, field_name, value_name = nil)
|
||||||
uuid = (submission.template_submitters || submission.template.submitters).dig(index, 'uuid')
|
uuid = (submission.template_submitters || submission.template.submitters).dig(index, 'uuid')
|
||||||
|
|
||||||
submission.submitters.find { |s| s.uuid == uuid }.try(field_name)
|
submitter = submission.submitters.find { |s| s.uuid == uuid }
|
||||||
|
|
||||||
|
return unless submitter
|
||||||
|
|
||||||
|
value = submitter.try(field_name)
|
||||||
|
|
||||||
|
if value_name
|
||||||
|
field = (submission.template_fields || submission.template.fields).find { |e| e['name'] == value_name }
|
||||||
|
|
||||||
|
return unless field
|
||||||
|
|
||||||
|
value =
|
||||||
|
if field['type'].in?(%w[image signature initials stamp payment file])
|
||||||
|
attachment_uuid = Array.wrap(value[field['uuid']]).first
|
||||||
|
|
||||||
|
attachment = submitter.attachments.find { |e| e.uuid == attachment_uuid }
|
||||||
|
|
||||||
|
ActiveStorage::Blob.proxy_url(attachment.blob) if attachment
|
||||||
|
else
|
||||||
|
value[field&.dig('uuid')]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
value
|
||||||
end
|
end
|
||||||
|
|
||||||
def replace(text, var, html_escape: false)
|
def replace(text, var, html_escape: false)
|
||||||
|
|||||||
+6
-11
@@ -134,16 +134,11 @@ module Submissions
|
|||||||
end
|
end
|
||||||
|
|
||||||
def filtered_conditions_schema(submission, values: nil, include_submitter_uuid: nil)
|
def filtered_conditions_schema(submission, values: nil, include_submitter_uuid: nil)
|
||||||
fields_uuid_index = nil
|
|
||||||
|
|
||||||
(submission.template_schema || submission.template.schema).filter_map do |item|
|
(submission.template_schema || submission.template.schema).filter_map do |item|
|
||||||
if item['conditions'].present?
|
if item['conditions'].present?
|
||||||
fields_uuid_index ||=
|
|
||||||
(submission.template_fields || submission.template.fields).index_by { |f| f['uuid'] }
|
|
||||||
|
|
||||||
values ||= submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) }
|
values ||= submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) }
|
||||||
|
|
||||||
next unless check_item_conditions(item, values, fields_uuid_index, include_submitter_uuid:)
|
next unless check_item_conditions(item, values, submission.fields_uuid_index, include_submitter_uuid:)
|
||||||
end
|
end
|
||||||
|
|
||||||
item
|
item
|
||||||
@@ -151,21 +146,21 @@ module Submissions
|
|||||||
end
|
end
|
||||||
|
|
||||||
def filtered_conditions_fields(submitter, only_submitter_fields: true)
|
def filtered_conditions_fields(submitter, only_submitter_fields: true)
|
||||||
fields = submitter.submission.template_fields || submitter.submission.template.fields
|
submission = submitter.submission
|
||||||
|
|
||||||
|
fields = submission.template_fields || submission.template.fields
|
||||||
|
|
||||||
fields_uuid_index = nil
|
|
||||||
values = nil
|
values = nil
|
||||||
|
|
||||||
fields.filter_map do |field|
|
fields.filter_map do |field|
|
||||||
next if field['submitter_uuid'] != submitter.uuid && only_submitter_fields
|
next if field['submitter_uuid'] != submitter.uuid && only_submitter_fields
|
||||||
|
|
||||||
if field['conditions'].present?
|
if field['conditions'].present?
|
||||||
fields_uuid_index ||= fields.index_by { |f| f['uuid'] }
|
values ||= submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) }
|
||||||
values ||= submitter.submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) }
|
|
||||||
|
|
||||||
submitter_conditions = []
|
submitter_conditions = []
|
||||||
|
|
||||||
next unless check_item_conditions(field, values, fields_uuid_index,
|
next unless check_item_conditions(field, values, submission.fields_uuid_index,
|
||||||
include_submitter_uuid: submitter.uuid,
|
include_submitter_uuid: submitter.uuid,
|
||||||
submitter_conditions_acc: submitter_conditions)
|
submitter_conditions_acc: submitter_conditions)
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,12 @@ module Submissions
|
|||||||
raise BaseError, 'Defined more signing parties than in template'
|
raise BaseError, 'Defined more signing parties than in template'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if template.preferences['validate_unique_submitters'] == true
|
||||||
|
submission_emails = submission.submitters.filter_map(&:email)
|
||||||
|
|
||||||
|
raise BaseError, 'Recipient emails should differ' if submission_emails.uniq.size != submission_emails.size
|
||||||
|
end
|
||||||
|
|
||||||
next if submission.submitters.blank?
|
next if submission.submitters.blank?
|
||||||
|
|
||||||
maybe_add_invite_submitters(submission, template)
|
maybe_add_invite_submitters(submission, template)
|
||||||
|
|||||||
@@ -50,12 +50,12 @@ module Submissions
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.sign(io, **sign_params)
|
document.sign(io, **sign_params)
|
||||||
|
|
||||||
|
Submissions::GenerateResultAttachments.maybe_enable_ltv(io, sign_params)
|
||||||
else
|
else
|
||||||
document.write(io)
|
document.write(io)
|
||||||
end
|
end
|
||||||
|
|
||||||
Submissions::GenerateResultAttachments.maybe_enable_ltv(io, sign_params)
|
|
||||||
|
|
||||||
ActiveStorage::Attachment.create!(
|
ActiveStorage::Attachment.create!(
|
||||||
blob: ActiveStorage::Blob.create_and_upload!(
|
blob: ActiveStorage::Blob.create_and_upload!(
|
||||||
io: io.tap(&:rewind), filename: "#{I18n.t('audit_log')} - #{submission.template.name}.pdf"
|
io: io.tap(&:rewind), filename: "#{I18n.t('audit_log')} - #{submission.template.name}.pdf"
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ module Submissions
|
|||||||
}
|
}
|
||||||
|
|
||||||
sign_pdf(io, pdf, sign_params)
|
sign_pdf(io, pdf, sign_params)
|
||||||
|
|
||||||
|
Submissions::GenerateResultAttachments.maybe_enable_ltv(io, sign_params)
|
||||||
else
|
else
|
||||||
pdf.write(io, incremental: true, validate: true)
|
pdf.write(io, incremental: true, validate: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
Submissions::GenerateResultAttachments.maybe_enable_ltv(io, sign_params)
|
|
||||||
|
|
||||||
ActiveStorage::Attachment.create!(
|
ActiveStorage::Attachment.create!(
|
||||||
blob: ActiveStorage::Blob.create_and_upload!(
|
blob: ActiveStorage::Blob.create_and_upload!(
|
||||||
io: io.tap(&:rewind), filename: "#{submission.template.name}.pdf"
|
io: io.tap(&:rewind), filename: "#{submission.template.name}.pdf"
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ module Submissions
|
|||||||
font_variant = nil unless font_name.in?(DEFAULT_FONTS)
|
font_variant = nil unless font_name.in?(DEFAULT_FONTS)
|
||||||
end
|
end
|
||||||
|
|
||||||
font = pdf.fonts.add(font_name, variant: font_variant)
|
font = pdf.fonts.add(font_name, variant: font_variant, custom_encoding: font_name.in?(DEFAULT_FONTS))
|
||||||
|
|
||||||
value = submitter.values[field['uuid']]
|
value = submitter.values[field['uuid']]
|
||||||
value = field['default_value'] if field['type'] == 'heading'
|
value = field['default_value'] if field['type'] == 'heading'
|
||||||
@@ -229,8 +229,9 @@ module Submissions
|
|||||||
text_align = field.dig('preferences', 'align').to_s.to_sym.presence ||
|
text_align = field.dig('preferences', 'align').to_s.to_sym.presence ||
|
||||||
(value.to_s.match?(RTL_REGEXP) ? :right : :left)
|
(value.to_s.match?(RTL_REGEXP) ? :right : :left)
|
||||||
|
|
||||||
layouter = HexaPDF::Layout::TextLayouter.new(text_valign: :center, text_align:,
|
text_valign = (field.dig('preferences', 'valign').to_s.presence || 'center').to_sym
|
||||||
font:, font_size:)
|
|
||||||
|
layouter = HexaPDF::Layout::TextLayouter.new(text_valign:, text_align:, font:, font_size:)
|
||||||
|
|
||||||
next if Array.wrap(value).compact_blank.blank?
|
next if Array.wrap(value).compact_blank.blank?
|
||||||
|
|
||||||
@@ -515,10 +516,19 @@ module Submissions
|
|||||||
0
|
0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
align_y_diff =
|
||||||
|
if text_valign == :top
|
||||||
|
0
|
||||||
|
elsif text_valign == :bottom
|
||||||
|
height_diff + TEXT_TOP_MARGIN
|
||||||
|
else
|
||||||
|
height_diff / 2
|
||||||
|
end
|
||||||
|
|
||||||
layouter.fit([text], field['type'].in?(%w[date number]) ? width : area['w'] * width,
|
layouter.fit([text], field['type'].in?(%w[date number]) ? width : area['w'] * width,
|
||||||
height_diff.positive? ? box_height : area['h'] * height)
|
height_diff.positive? ? box_height : area['h'] * height)
|
||||||
.draw(canvas, (area['x'] * width) - right_align_x_adjustment + TEXT_LEFT_MARGIN,
|
.draw(canvas, (area['x'] * width) - right_align_x_adjustment + TEXT_LEFT_MARGIN,
|
||||||
height - (area['y'] * height) + height_diff - TEXT_TOP_MARGIN)
|
height - (area['y'] * height) + align_y_diff - TEXT_TOP_MARGIN)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ module Submitters
|
|||||||
def send_signature_requests(submitters, delay_seconds: nil)
|
def send_signature_requests(submitters, delay_seconds: nil)
|
||||||
submitters.each_with_index do |submitter, index|
|
submitters.each_with_index do |submitter, index|
|
||||||
next if submitter.email.blank?
|
next if submitter.email.blank?
|
||||||
|
next if submitter.declined_at?
|
||||||
next if submitter.preferences['send_email'] == false
|
next if submitter.preferences['send_email'] == false
|
||||||
|
|
||||||
if delay_seconds
|
if delay_seconds
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ module Submitters
|
|||||||
|
|
||||||
def serialize_events(events)
|
def serialize_events(events)
|
||||||
events.map do |event|
|
events.map do |event|
|
||||||
event.as_json(only: %i[id submitter_id event_type event_timestamp]).merge('data' => event.data.slice('reason'))
|
event.as_json(only: %i[id submitter_id event_type event_timestamp])
|
||||||
|
.merge('data' => event.data.slice('reason', 'firstname', 'lastname', 'method', 'country'))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -171,6 +171,8 @@ module Submitters
|
|||||||
end
|
end
|
||||||
|
|
||||||
def build_formula_values(submitter)
|
def build_formula_values(submitter)
|
||||||
|
submission_values = nil
|
||||||
|
|
||||||
computed_values = submitter.submission.template_fields.each_with_object({}) do |field, acc|
|
computed_values = submitter.submission.template_fields.each_with_object({}) do |field, acc|
|
||||||
next if field['submitter_uuid'] != submitter.uuid
|
next if field['submitter_uuid'] != submitter.uuid
|
||||||
next if field['type'] == 'payment'
|
next if field['type'] == 'payment'
|
||||||
@@ -179,7 +181,14 @@ module Submitters
|
|||||||
|
|
||||||
next if formula.blank?
|
next if formula.blank?
|
||||||
|
|
||||||
acc[field['uuid']] = calculate_formula_value(formula, submitter.values.merge(acc.compact_blank))
|
submission_values ||=
|
||||||
|
if submitter.submission.template_submitters.size > 1
|
||||||
|
merge_submitters_values(submitter)
|
||||||
|
else
|
||||||
|
submitter.values
|
||||||
|
end
|
||||||
|
|
||||||
|
acc[field['uuid']] = calculate_formula_value(formula, submission_values.merge(acc.compact_blank))
|
||||||
end
|
end
|
||||||
|
|
||||||
computed_values.compact_blank
|
computed_values.compact_blank
|
||||||
@@ -204,8 +213,6 @@ module Submitters
|
|||||||
def maybe_remove_condition_values(submitter, required_field_uuids_acc: nil)
|
def maybe_remove_condition_values(submitter, required_field_uuids_acc: nil)
|
||||||
submission = submitter.submission
|
submission = submitter.submission
|
||||||
|
|
||||||
fields_uuid_index = submission.template_fields.index_by { |e| e['uuid'] }
|
|
||||||
|
|
||||||
submitters_values = nil
|
submitters_values = nil
|
||||||
has_other_submitters = submission.template_submitters.size > 1
|
has_other_submitters = submission.template_submitters.size > 1
|
||||||
|
|
||||||
@@ -228,11 +235,11 @@ module Submitters
|
|||||||
end
|
end
|
||||||
|
|
||||||
if has_other_submitters && !submitters_values &&
|
if has_other_submitters && !submitters_values &&
|
||||||
field_conditions_other_submitter?(submitter, field, fields_uuid_index)
|
field_conditions_other_submitter?(submitter, field, submission.fields_uuid_index)
|
||||||
submitters_values = merge_submitters_values(submitter)
|
submitters_values = merge_submitters_values(submitter)
|
||||||
end
|
end
|
||||||
|
|
||||||
unless check_field_conditions(submitters_values || submitter.values, field, fields_uuid_index)
|
unless check_field_conditions(submitters_values || submitter.values, field, submission.fields_uuid_index)
|
||||||
submitter.values.delete(field['uuid'])
|
submitter.values.delete(field['uuid'])
|
||||||
required_field_uuids_acc.delete(field['uuid'])
|
required_field_uuids_acc.delete(field['uuid'])
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Templates
|
||||||
|
module Order
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def call(templates, current_user, order)
|
||||||
|
case order
|
||||||
|
when 'used_at'
|
||||||
|
subquery = Submission.select(:template_id, Submission.arel_table[:created_at].maximum.as('created_at'))
|
||||||
|
.where(account_id: current_user.account_id)
|
||||||
|
.group(:template_id)
|
||||||
|
|
||||||
|
templates = templates.joins(
|
||||||
|
Template.arel_table
|
||||||
|
.join(subquery.arel.as('submissions'), Arel::Nodes::OuterJoin)
|
||||||
|
.on(Template.arel_table[:id].eq(Submission.arel_table[:template_id]))
|
||||||
|
.join_sources
|
||||||
|
)
|
||||||
|
|
||||||
|
templates.order(
|
||||||
|
Arel::Nodes::Case.new
|
||||||
|
.when(Submission.arel_table[:created_at].gt(Template.arel_table[:updated_at]))
|
||||||
|
.then(Submission.arel_table[:created_at])
|
||||||
|
.else(Template.arel_table[:updated_at])
|
||||||
|
.desc
|
||||||
|
)
|
||||||
|
when 'name'
|
||||||
|
templates.order(name: :asc)
|
||||||
|
else
|
||||||
|
templates.order(id: :desc)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
+1
-1
@@ -57,7 +57,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"last 3 years"
|
"last 5 years"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/eslint-parser": "^7.21.8",
|
"@babel/eslint-parser": "^7.21.8",
|
||||||
|
|||||||
@@ -506,6 +506,9 @@ RSpec.describe 'Signing Form', type: :system do
|
|||||||
find('#expand_form_button').click
|
find('#expand_form_button').click
|
||||||
find('span[data-tip="Click to upload"]').click
|
find('span[data-tip="Click to upload"]').click
|
||||||
find('input[type="file"]', visible: false).attach_file(Rails.root.join('spec/fixtures/sample-image.png'))
|
find('input[type="file"]', visible: false).attach_file(Rails.root.join('spec/fixtures/sample-image.png'))
|
||||||
|
|
||||||
|
sleep 0.1
|
||||||
|
|
||||||
click_button 'Complete'
|
click_button 'Complete'
|
||||||
|
|
||||||
expect(page).to have_content('Document has been signed!')
|
expect(page).to have_content('Document has been signed!')
|
||||||
|
|||||||
Reference in New Issue
Block a user