Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
528a1216f8 | ||
|
|
f2479bd259 | ||
|
|
135f0826d2 | ||
|
|
5710f0177b | ||
|
|
1a3a0528ba | ||
|
|
59793ff374 | ||
|
|
3c4ed42419 | ||
|
|
6a17b61550 | ||
|
|
76659497b7 | ||
|
|
ee50c957cb | ||
|
|
a9dd200919 | ||
|
|
1fa953104a | ||
|
|
50eb5b070e | ||
|
|
10fd624bec | ||
|
|
04ec2f8260 | ||
|
|
d828d79574 | ||
|
|
01dd3fefe5 | ||
|
|
45ae954c0c | ||
|
|
1304849b55 | ||
|
|
0875faa079 | ||
|
|
3aa15d6ea6 | ||
|
|
6557329e97 | ||
|
|
be27ce4161 | ||
|
|
a3391a970e | ||
|
|
744d45d2c5 | ||
|
|
310b16f54a | ||
|
|
6d3ad4a869 | ||
|
|
1ce2448820 | ||
|
|
07b4ada5be | ||
|
|
f70bf530ba | ||
|
|
a26aa0f411 | ||
|
|
584577c21f | ||
|
|
1503375eed | ||
|
|
2dec099f20 | ||
|
|
bf6eed4ea2 | ||
|
|
e0dc33cfb4 | ||
|
|
9c9dc27537 | ||
|
|
7305637b8c | ||
|
|
a6e22adf2e | ||
|
|
809c6b1aa8 | ||
|
|
1536ded2a6 | ||
|
|
76d54971ce | ||
|
|
6dec56198c | ||
|
|
d96d252df3 | ||
|
|
3fca76a716 | ||
|
|
a184fa11f1 | ||
|
|
e9c0b6a703 | ||
|
|
ec7df08496 | ||
|
|
6627d7eac5 | ||
|
|
1f8945d710 | ||
|
|
daaa289a5c | ||
|
|
81a6a48d70 | ||
|
|
b93c7cd261 | ||
|
|
570482e12d | ||
|
|
39407557f2 | ||
|
|
3b040d558d | ||
|
|
56ba3c2a52 | ||
|
|
c7afe33c72 | ||
|
|
82500c2d7d | ||
|
|
fb1c1f117b | ||
|
|
7c6a7513fd | ||
|
|
6d13119ee0 | ||
|
|
1e2c752937 | ||
|
|
e82faf8320 | ||
|
|
4adb1001b0 | ||
|
|
992a1b26c0 | ||
|
|
c07bc6687c | ||
|
|
eea44bda34 | ||
|
|
7cdf263da1 | ||
|
|
ee65a5693c | ||
|
|
0af6ccf35f | ||
|
|
888f1ec6df | ||
|
|
c95a8616ac | ||
|
|
5ea6289b7a | ||
|
|
d4a79ca5db | ||
|
|
70015ce1c4 | ||
|
|
6b85c28944 | ||
|
|
6c289cf273 | ||
|
|
a64bc3c618 | ||
|
|
46cf1e3067 | ||
|
|
565e1eb2bc | ||
|
|
e689687805 | ||
|
|
3c3b61fb47 | ||
|
|
1355d350c5 | ||
|
|
fda911e178 | ||
|
|
f995e1864c |
No files matched your search
@@ -28,7 +28,7 @@ Lint/MissingSuper:
|
|||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Metrics/ParameterLists:
|
Metrics/ParameterLists:
|
||||||
Max: 10
|
Max: 12
|
||||||
|
|
||||||
Metrics/MethodLength:
|
Metrics/MethodLength:
|
||||||
Max: 30
|
Max: 30
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ ENV OPENSSL_CONF=/etc/openssl_legacy.cnf
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apk add --no-cache libpq vips redis vips-heif fontconfig onnxruntime
|
RUN apk add --no-cache libpq vips redis vips-heif onnxruntime
|
||||||
|
|
||||||
RUN addgroup -g 2000 docuseal && adduser -u 2000 -G docuseal -s /bin/sh -D -h /home/docuseal docuseal
|
RUN addgroup -g 2000 docuseal && adduser -u 2000 -G docuseal -s /bin/sh -D -h /home/docuseal docuseal
|
||||||
|
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ GEM
|
|||||||
dotenv (3.2.0)
|
dotenv (3.2.0)
|
||||||
drb (2.2.3)
|
drb (2.2.3)
|
||||||
email_typo (0.2.3)
|
email_typo (0.2.3)
|
||||||
erb (6.0.2)
|
erb (6.0.4)
|
||||||
erb_lint (0.9.0)
|
erb_lint (0.9.0)
|
||||||
activesupport
|
activesupport
|
||||||
better_html (>= 2.0.1)
|
better_html (>= 2.0.1)
|
||||||
@@ -257,7 +257,7 @@ GEM
|
|||||||
os (>= 0.9, < 2.0)
|
os (>= 0.9, < 2.0)
|
||||||
signet (>= 0.16, < 2.a)
|
signet (>= 0.16, < 2.a)
|
||||||
hashdiff (1.2.1)
|
hashdiff (1.2.1)
|
||||||
hexapdf (1.6.0)
|
hexapdf (1.7.0)
|
||||||
cmdparse (~> 3.0, >= 3.0.3)
|
cmdparse (~> 3.0, >= 3.0.3)
|
||||||
geom2d (~> 0.4, >= 0.4.1)
|
geom2d (~> 0.4, >= 0.4.1)
|
||||||
openssl (>= 2.2.1)
|
openssl (>= 2.2.1)
|
||||||
@@ -318,7 +318,7 @@ GEM
|
|||||||
multi_json (1.19.1)
|
multi_json (1.19.1)
|
||||||
net-http (0.9.1)
|
net-http (0.9.1)
|
||||||
uri (>= 0.11.1)
|
uri (>= 0.11.1)
|
||||||
net-imap (0.6.3)
|
net-imap (0.6.4)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
net-pop (0.1.2)
|
net-pop (0.1.2)
|
||||||
@@ -328,15 +328,15 @@ GEM
|
|||||||
net-smtp (0.5.1)
|
net-smtp (0.5.1)
|
||||||
net-protocol
|
net-protocol
|
||||||
nio4r (2.7.5)
|
nio4r (2.7.5)
|
||||||
nokogiri (1.19.2-aarch64-linux-gnu)
|
nokogiri (1.19.3-aarch64-linux-gnu)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.19.2-aarch64-linux-musl)
|
nokogiri (1.19.3-aarch64-linux-musl)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.19.2-arm64-darwin)
|
nokogiri (1.19.3-arm64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.19.2-x86_64-linux-gnu)
|
nokogiri (1.19.3-x86_64-linux-gnu)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.19.2-x86_64-linux-musl)
|
nokogiri (1.19.3-x86_64-linux-musl)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
numo-narray-alt (0.10.3)
|
numo-narray-alt (0.10.3)
|
||||||
oj (3.16.16)
|
oj (3.16.16)
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ DocuSeal is an open source platform that provides secure and efficient digital d
|
|||||||
- Company logo and white-label
|
- Company logo and white-label
|
||||||
- User roles
|
- User roles
|
||||||
- Automated reminders
|
- Automated reminders
|
||||||
- Invitation and identify verification via SMS
|
- Invitation and identity verification via SMS
|
||||||
- Conditional fields and formulas
|
- Conditional fields and formulas
|
||||||
- Bulk send with CSV, XLSX spreadsheet import
|
- Bulk send with CSV, XLSX spreadsheet import
|
||||||
- SSO / SAML
|
- SSO / SAML
|
||||||
@@ -74,7 +74,7 @@ DocuSeal is an open source platform that provides secure and efficient digital d
|
|||||||
docker run --name docuseal -p 3000:3000 -v.:/data docuseal/docuseal
|
docker run --name docuseal -p 3000:3000 -v.:/data docuseal/docuseal
|
||||||
```
|
```
|
||||||
|
|
||||||
By default DocuSeal docker container uses an SQLite database to store data and configurations. Alternatively, it is possible use PostgreSQL or MySQL databases by specifying the `DATABASE_URL` env variable.
|
By default DocuSeal docker container uses an SQLite database to store data and configurations. Alternatively, it is possible to use PostgreSQL or MySQL databases by specifying the `DATABASE_URL` env variable.
|
||||||
|
|
||||||
#### Docker Compose
|
#### Docker Compose
|
||||||
|
|
||||||
|
|||||||
@@ -33,9 +33,15 @@ module Api
|
|||||||
else
|
else
|
||||||
http_cache_forever public: true do
|
http_cache_forever public: true do
|
||||||
response.headers['Accept-Ranges'] = 'bytes'
|
response.headers['Accept-Ranges'] = 'bytes'
|
||||||
response.headers['Content-Length'] = blob.byte_size.to_s
|
|
||||||
|
|
||||||
send_blob_stream blob, disposition: params[:disposition]
|
if request.head?
|
||||||
|
response.headers['Content-Type'] = blob.content_type_for_serving
|
||||||
|
head :ok
|
||||||
|
else
|
||||||
|
send_blob_stream blob, disposition: params[:disposition]
|
||||||
|
end
|
||||||
|
|
||||||
|
response.headers['Content-Length'] = blob.byte_size.to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -57,8 +63,6 @@ module Api
|
|||||||
return if !require_ttl && !require_auth
|
return if !require_ttl && !require_auth
|
||||||
end
|
end
|
||||||
|
|
||||||
Rollbar.error('Blob unauthorized') if defined?(Rollbar)
|
|
||||||
|
|
||||||
raise CanCan::AccessDenied
|
raise CanCan::AccessDenied
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ module Api
|
|||||||
COOKIE_STORE_LIMIT = 10
|
COOKIE_STORE_LIMIT = 10
|
||||||
|
|
||||||
def create
|
def create
|
||||||
submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
@submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
||||||
|
|
||||||
unless can_upload?(submitter)
|
unless can_upload?(@submitter)
|
||||||
Rollbar.error("Can't upload: #{submitter.id}") if defined?(Rollbar)
|
Rollbar.error("Can't upload: #{@submitter.id}") if defined?(Rollbar)
|
||||||
|
|
||||||
return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_content
|
return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_content
|
||||||
end
|
end
|
||||||
@@ -20,23 +20,23 @@ module Api
|
|||||||
image = Vips::Image.new_from_file(params[:file].path)
|
image = Vips::Image.new_from_file(params[:file].path)
|
||||||
|
|
||||||
if ImageUtils.blank?(image)
|
if ImageUtils.blank?(image)
|
||||||
Rollbar.error("Empty signature: #{submitter.id}") if defined?(Rollbar)
|
Rollbar.error("Empty signature: #{@submitter.id}") if defined?(Rollbar)
|
||||||
|
|
||||||
return render json: { error: "#{params[:type]} is empty" }, status: :unprocessable_content
|
return render json: { error: "#{params[:type]} is empty" }, status: :unprocessable_content
|
||||||
end
|
end
|
||||||
|
|
||||||
if ImageUtils.error?(image)
|
if ImageUtils.error?(image)
|
||||||
Rollbar.error("Error signature: #{submitter.id}") if defined?(Rollbar)
|
Rollbar.error("Error signature: #{@submitter.id}") if defined?(Rollbar)
|
||||||
|
|
||||||
return render json: { error: "#{params[:type]} error, try to sign on another device" },
|
return render json: { error: "#{params[:type]} error, try to sign on another device" },
|
||||||
status: :unprocessable_content
|
status: :unprocessable_content
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
attachment = Submitters.create_attachment!(submitter, params)
|
attachment = Submitters.create_attachment!(@submitter, params)
|
||||||
|
|
||||||
if params[:remember_signature] == 'true' && submitter.email.present?
|
if params[:remember_signature] == 'true' && @submitter.email.present?
|
||||||
cookies.encrypted[:signature_uuids] = build_new_cookie_signatures_json(submitter, attachment)
|
cookies.encrypted[:signature_uuids] = build_new_cookie_signatures_json(@submitter, attachment)
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: attachment.as_json(only: %i[uuid created_at], methods: %i[url filename content_type])
|
render json: attachment.as_json(only: %i[uuid created_at], methods: %i[url filename content_type])
|
||||||
|
|||||||
@@ -161,9 +161,7 @@ module Api
|
|||||||
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
|
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
|
||||||
end
|
end
|
||||||
|
|
||||||
submitter.values = submitter.values.transform_values do |v|
|
submitter.values = Submitters::SubmitValues.replace_current_date_placeholders(submitter)
|
||||||
v == '{{date}}' ? Time.current.in_time_zone(submitter.account.timezone).to_date.to_s : v
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
submitter
|
submitter
|
||||||
@@ -205,10 +203,15 @@ module Api
|
|||||||
|
|
||||||
submitter.preferences['send_sms'] = submitter_preferences['send_sms'] if submitter_preferences.key?('send_sms')
|
submitter.preferences['send_sms'] = submitter_preferences['send_sms'] if submitter_preferences.key?('send_sms')
|
||||||
submitter.preferences['reply_to'] = submitter_preferences['reply_to'] if submitter_preferences.key?('reply_to')
|
submitter.preferences['reply_to'] = submitter_preferences['reply_to'] if submitter_preferences.key?('reply_to')
|
||||||
|
|
||||||
if submitter_preferences.key?('require_phone_2fa')
|
if submitter_preferences.key?('require_phone_2fa')
|
||||||
submitter.preferences['require_phone_2fa'] = submitter_preferences['require_phone_2fa']
|
submitter.preferences['require_phone_2fa'] = submitter_preferences['require_phone_2fa']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if submitter_preferences.key?('require_email_2fa')
|
||||||
|
submitter.preferences['require_email_2fa'] = submitter_preferences['require_email_2fa']
|
||||||
|
end
|
||||||
|
|
||||||
if submitter_preferences.key?('go_to_last')
|
if submitter_preferences.key?('go_to_last')
|
||||||
submitter.preferences['go_to_last'] = submitter_preferences['go_to_last']
|
submitter.preferences['go_to_last'] = submitter_preferences['go_to_last']
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class PreviewDocumentPageController < ActionController::API
|
|||||||
end
|
end
|
||||||
|
|
||||||
def find_or_create_document_tempfile_path(attachment)
|
def find_or_create_document_tempfile_path(attachment)
|
||||||
file_path = "#{Dir.tmpdir}/#{attachment.uuid}"
|
file_path = "#{Dir.tmpdir}/attachment-#{Digest::SHA1.hexdigest("#{attachment.id}-#{attachment.uuid}")}"
|
||||||
|
|
||||||
File.open(file_path, File::RDWR | File::CREAT, 0o644) do |f|
|
File.open(file_path, File::RDWR | File::CREAT, 0o644) do |f|
|
||||||
f.flock(File::LOCK_EX)
|
f.flock(File::LOCK_EX)
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class RevealAccessTokenController < ApplicationController
|
class RevealAccessTokenController < ApplicationController
|
||||||
|
rate_limit to: 4, within: 1.minute, only: %i[create], by: -> { current_user.id }, with: lambda {
|
||||||
|
Rollbar.error('Rate limit api key') if defined?(Rollbar)
|
||||||
|
|
||||||
|
render turbo_stream: turbo_stream.replace(:modal, template: 'reveal_access_token/show',
|
||||||
|
locals: { error_message: I18n.t(:too_many_attempts) }),
|
||||||
|
status: :unprocessable_content
|
||||||
|
}
|
||||||
|
|
||||||
def show
|
def show
|
||||||
authorize!(:manage, current_user.access_token)
|
authorize!(:manage, current_user.access_token)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class SendSubmissionEmailController < ApplicationController
|
|||||||
template = Template.find_by!(slug: params[:template_slug])
|
template = Template.find_by!(slug: params[:template_slug])
|
||||||
|
|
||||||
@submitter =
|
@submitter =
|
||||||
Submitter.completed.where(submission: template.submissions).find_by!(email: params[:email].to_s.downcase)
|
Submitter.completed.where(submission: template.submissions).find_by(email: params[:email].to_s.downcase)
|
||||||
elsif params[:submission_slug]
|
elsif params[:submission_slug]
|
||||||
submission = Submission.find_by(slug: params[:submission_slug])
|
submission = Submission.find_by(slug: params[:submission_slug])
|
||||||
|
|
||||||
@@ -27,9 +27,11 @@ class SendSubmissionEmailController < ApplicationController
|
|||||||
@submitter = Submitter.completed.find_by!(slug: params[:submitter_slug])
|
@submitter = Submitter.completed.find_by!(slug: params[:submitter_slug])
|
||||||
end
|
end
|
||||||
|
|
||||||
RateLimit.call("send-email-#{@submitter.id}", limit: 2, ttl: 5.minutes)
|
if @submitter
|
||||||
|
RateLimit.call("send-email-#{@submitter.id}", limit: 2, ttl: 5.minutes)
|
||||||
|
|
||||||
SubmitterMailer.documents_copy_email(@submitter, sig: true).deliver_later! if can_send?(@submitter)
|
SubmitterMailer.documents_copy_email(@submitter, sig: true).deliver_later! if can_send?(@submitter)
|
||||||
|
end
|
||||||
|
|
||||||
respond_to do |f|
|
respond_to do |f|
|
||||||
f.html { render :success }
|
f.html { render :success }
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ class SubmitFormController < ApplicationController
|
|||||||
|
|
||||||
render json: { field_uuid: e.message }, status: :unprocessable_content
|
render json: { field_uuid: e.message }, status: :unprocessable_content
|
||||||
rescue Submitters::SubmitValues::ValidationError => e
|
rescue Submitters::SubmitValues::ValidationError => e
|
||||||
|
Rollbar.warning("Validation error #{@submitter.id}: #{e.message}") if defined?(Rollbar)
|
||||||
|
|
||||||
render json: { error: e.message }, status: :unprocessable_content
|
render json: { error: e.message }, status: :unprocessable_content
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ class SubmitFormInviteController < ApplicationController
|
|||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
def create
|
def create
|
||||||
submitter = Submitter.find_by!(slug: params[:submit_form_slug])
|
@submitter = Submitter.find_by!(slug: params[:submit_form_slug])
|
||||||
|
|
||||||
return head :unprocessable_content unless can_invite?(submitter)
|
return head :unprocessable_content unless can_invite?(@submitter)
|
||||||
|
|
||||||
invite_submitters = filter_invite_submitters(submitter, 'invite_by_uuid')
|
invite_submitters = filter_invite_submitters(@submitter, 'invite_by_uuid')
|
||||||
optional_invite_submitters = filter_invite_submitters(submitter, 'optional_invite_by_uuid')
|
optional_invite_submitters = filter_invite_submitters(@submitter, 'optional_invite_by_uuid')
|
||||||
|
|
||||||
ApplicationRecord.transaction do
|
ApplicationRecord.transaction do
|
||||||
(invite_submitters + optional_invite_submitters).each do |item|
|
(invite_submitters + optional_invite_submitters).each do |item|
|
||||||
@@ -21,18 +21,18 @@ class SubmitFormInviteController < ApplicationController
|
|||||||
|
|
||||||
email = Submissions.normalize_email(attrs[:email])
|
email = Submissions.normalize_email(attrs[:email])
|
||||||
|
|
||||||
submitter.submission.submitters.create!(uuid: attrs[:uuid], email:, account_id: submitter.account_id)
|
@submitter.submission.submitters.create!(uuid: attrs[:uuid], email:, account_id: @submitter.account_id)
|
||||||
|
|
||||||
SubmissionEvents.create_with_tracking_data(submitter, 'invite_party', request, { uuid: submitter.uuid })
|
SubmissionEvents.create_with_tracking_data(@submitter, 'invite_party', request, { uuid: @submitter.uuid })
|
||||||
end
|
end
|
||||||
|
|
||||||
submitter.submission.update!(submitters_order: :preserved)
|
@submitter.submission.update!(submitters_order: :preserved)
|
||||||
end
|
end
|
||||||
|
|
||||||
submitter.submission.submitters.reload
|
@submitter.submission.submitters.reload
|
||||||
|
|
||||||
if invite_submitters.all? { |s| submitter.submission.submitters.any? { |e| e.uuid == s['uuid'] } }
|
if invite_submitters.all? { |s| @submitter.submission.submitters.any? { |e| e.uuid == s['uuid'] } }
|
||||||
Submitters::SubmitValues.call(submitter, ActionController::Parameters.new(completed: 'true'), request)
|
Submitters::SubmitValues.call(@submitter, ActionController::Parameters.new(completed: 'true'), request)
|
||||||
|
|
||||||
head :ok
|
head :ok
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SubmitFormMetadataController < ApplicationController
|
||||||
|
skip_before_action :authenticate_user!
|
||||||
|
skip_authorization_check
|
||||||
|
|
||||||
|
def index
|
||||||
|
@submitter = Submitter.find_by!(slug: params[:submit_form_slug])
|
||||||
|
|
||||||
|
return head :not_found if @submitter.declined_at? ||
|
||||||
|
@submitter.completed_at? ||
|
||||||
|
@submitter.submission.archived_at? ||
|
||||||
|
@submitter.submission.expired? ||
|
||||||
|
@submitter.submission.template&.archived_at? ||
|
||||||
|
@submitter.account.archived_at? ||
|
||||||
|
!Submitters::AuthorizedForForm.call(@submitter, current_user, request)
|
||||||
|
|
||||||
|
submission = @submitter.submission
|
||||||
|
values = submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) }
|
||||||
|
schema = Submissions.filtered_conditions_schema(submission, values:, include_submitter_uuid: @submitter.uuid)
|
||||||
|
|
||||||
|
documents = schema.filter_map do |item|
|
||||||
|
submission.schema_documents.find { |a| a.uuid == item['attachment_uuid'] }
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveRecord::Associations::Preloader.new(records: documents, associations: %i[blob record]).call
|
||||||
|
|
||||||
|
text_runs = documents.to_h do |document|
|
||||||
|
[
|
||||||
|
document.uuid,
|
||||||
|
DocumentMetadatas.find_or_create_for_document(document, account_id: document.record.account_id).text_runs
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
render json: { text_runs: }
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -5,21 +5,21 @@ class SubmitFormValuesController < ApplicationController
|
|||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
def index
|
def index
|
||||||
submitter = Submitter.find_by!(slug: params[:submit_form_slug])
|
@submitter = Submitter.find_by!(slug: params[:submit_form_slug])
|
||||||
|
|
||||||
return render json: {} if submitter.completed_at? ||
|
return render json: {} if @submitter.completed_at? ||
|
||||||
submitter.declined_at? ||
|
@submitter.declined_at? ||
|
||||||
submitter.submission.template&.archived_at? ||
|
@submitter.submission.template&.archived_at? ||
|
||||||
submitter.submission.archived_at? ||
|
@submitter.submission.archived_at? ||
|
||||||
submitter.submission.expired? ||
|
@submitter.submission.expired? ||
|
||||||
!Submitters::AuthorizedForForm.call(submitter, current_user, request)
|
!Submitters::AuthorizedForForm.call(@submitter, current_user, request)
|
||||||
|
|
||||||
value = submitter.values[params['field_uuid']]
|
value = @submitter.values[params['field_uuid']]
|
||||||
attachment = submitter.attachments.where(created_at: params[:after]..).find_by(uuid: value) if value.present?
|
attachment = @submitter.attachments.where(created_at: params[:after]..).find_by(uuid: value) if value.present?
|
||||||
|
|
||||||
render json: {
|
render json: {
|
||||||
value:,
|
value:,
|
||||||
attachment: attachment&.as_json(only: %i[uuid created_at], methods: %i[url filename content_type])
|
attachment: attachment&.as_json(only: %i[uuid created_at], methods: %i[url filename content_type])
|
||||||
}, head: :ok
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class TemplatesController < ApplicationController
|
class TemplatesController < ApplicationController
|
||||||
|
TEMPLATE_FIELDS = %i[id author_id folder_id external_id name slug
|
||||||
|
schema fields submitters variables_schema preferences
|
||||||
|
shared_link source archived_at created_at updated_at].freeze
|
||||||
|
|
||||||
load_and_authorize_resource :template
|
load_and_authorize_resource :template
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@@ -33,10 +37,11 @@ class TemplatesController < ApplicationController
|
|||||||
).call
|
).call
|
||||||
|
|
||||||
@template_data =
|
@template_data =
|
||||||
@template.as_json.merge(
|
@template.as_json(only: TEMPLATE_FIELDS).merge(
|
||||||
documents: @template.schema_documents.as_json(
|
documents: @template.schema_documents.as_json(
|
||||||
|
only: %i[id uuid],
|
||||||
methods: %i[metadata signed_key],
|
methods: %i[metadata signed_key],
|
||||||
include: { preview_images: { methods: %i[url metadata filename] } }
|
include: { preview_images: { only: %i[id], methods: %i[url metadata filename] } }
|
||||||
)
|
)
|
||||||
).to_json
|
).to_json
|
||||||
|
|
||||||
@@ -72,6 +77,8 @@ class TemplatesController < ApplicationController
|
|||||||
|
|
||||||
WebhookUrls.enqueue_events(@template, 'template.updated')
|
WebhookUrls.enqueue_events(@template, 'template.updated')
|
||||||
|
|
||||||
|
TemplateVersions.find_or_create_for(@template, author: current_user) if params[:revision]
|
||||||
|
|
||||||
head :ok
|
head :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TemplatesShareLinkQrController < ApplicationController
|
||||||
|
load_and_authorize_resource :template
|
||||||
|
|
||||||
|
def show
|
||||||
|
return render :disabled, layout: 'plain' unless @template.shared_link?
|
||||||
|
|
||||||
|
shared_link_url = start_form_url(slug: @template.slug, host: form_link_host)
|
||||||
|
|
||||||
|
@qr_svg_code = RQRCode::QRCode.new(shared_link_url, level: :m).as_svg(viewbox: true)
|
||||||
|
|
||||||
|
@page_size =
|
||||||
|
if TimeUtils.timezone_abbr(current_account.timezone, Time.current.beginning_of_year).in?(TimeUtils::US_TIMEZONES)
|
||||||
|
'Letter'
|
||||||
|
else
|
||||||
|
'A4'
|
||||||
|
end
|
||||||
|
|
||||||
|
render :show, layout: false
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TemplatesVersionsController < ApplicationController
|
||||||
|
load_and_authorize_resource :template
|
||||||
|
|
||||||
|
def index
|
||||||
|
versions = @template.template_versions.order(id: :desc).preload(:author)
|
||||||
|
|
||||||
|
render json: versions.as_json(TemplateVersions::SERIALIZE_PARAMS)
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
version = @template.template_versions.find(params[:id])
|
||||||
|
|
||||||
|
render json: TemplateVersions.serialize(version)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
class TestingAccountsController < ApplicationController
|
class TestingAccountsController < ApplicationController
|
||||||
skip_authorization_check only: :destroy
|
skip_authorization_check only: :destroy
|
||||||
|
|
||||||
def show
|
def create
|
||||||
authorize!(:manage, current_account)
|
authorize!(:manage, current_account)
|
||||||
authorize!(:manage, current_user)
|
authorize!(:manage, current_user)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class WebhookHmacController < ApplicationController
|
||||||
|
load_and_authorize_resource :webhook_url, parent: false
|
||||||
|
|
||||||
|
def show; end
|
||||||
|
end
|
||||||
@@ -32,9 +32,13 @@ class WebhookSettingsController < ApplicationController
|
|||||||
def new; end
|
def new; end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@webhook_url.save!
|
if @webhook_url.url.present?
|
||||||
|
@webhook_url.save!
|
||||||
|
|
||||||
redirect_to settings_webhooks_path, notice: I18n.t('webhook_url_has_been_saved')
|
redirect_to settings_webhooks_path, notice: I18n.t('webhook_url_has_been_saved')
|
||||||
|
else
|
||||||
|
redirect_back fallback_location: settings_webhooks_path
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
|||||||
@@ -169,6 +169,9 @@ safeRegisterElement('template-builder', class extends HTMLElement {
|
|||||||
withKba: ['true', 'false'].includes(this.dataset.withKba) ? this.dataset.withKba === 'true' : null,
|
withKba: ['true', 'false'].includes(this.dataset.withKba) ? this.dataset.withKba === 'true' : null,
|
||||||
withLogo: this.dataset.withLogo !== 'false',
|
withLogo: this.dataset.withLogo !== 'false',
|
||||||
withFieldsDetection: this.dataset.withFieldsDetection === 'true',
|
withFieldsDetection: this.dataset.withFieldsDetection === 'true',
|
||||||
|
withDetectExistingFields: this.dataset.withDetectExistingFields === 'true',
|
||||||
|
withRevisions: true,
|
||||||
|
withRevisionsMenu: this.dataset.withRevisionsMenu === 'true',
|
||||||
editable: this.dataset.editable !== 'false',
|
editable: this.dataset.editable !== 'false',
|
||||||
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
|
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
|
||||||
withCustomFields: true,
|
withCustomFields: true,
|
||||||
@@ -180,6 +183,7 @@ safeRegisterElement('template-builder', class extends HTMLElement {
|
|||||||
withConditions: this.dataset.withConditions === 'true',
|
withConditions: this.dataset.withConditions === 'true',
|
||||||
withDynamicDocuments: this.dataset.withDynamicDocuments === 'true',
|
withDynamicDocuments: this.dataset.withDynamicDocuments === 'true',
|
||||||
withGoogleDrive: this.dataset.withGoogleDrive === 'true',
|
withGoogleDrive: this.dataset.withGoogleDrive === 'true',
|
||||||
|
pagePreviewFormat: this.dataset.pagePreviewFormat || '.jpg',
|
||||||
withReplaceAndCloneUpload: true,
|
withReplaceAndCloneUpload: true,
|
||||||
withDownload: true,
|
withDownload: true,
|
||||||
currencies: (this.dataset.currencies || '').split(',').filter(Boolean),
|
currencies: (this.dataset.currencies || '').split(',').filter(Boolean),
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ window.customElements.define('draw-signature', class extends HTMLElement {
|
|||||||
this.resizeObserver = new ResizeObserver(() => {
|
this.resizeObserver = new ResizeObserver(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (!this.canvas) return
|
if (!this.canvas) return
|
||||||
|
if (!this.canvas.parentNode?.clientWidth) return
|
||||||
|
|
||||||
const { width, height } = this.canvas
|
const { width, height } = this.canvas
|
||||||
|
|
||||||
@@ -89,7 +90,7 @@ window.customElements.define('draw-signature', class extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
redrawCanvas (oldWidth, oldHeight) {
|
redrawCanvas (oldWidth, oldHeight) {
|
||||||
if (this.pad && !this.pad.isEmpty() && oldWidth > 0 && oldHeight > 0) {
|
if (this.pad && !this.pad.isEmpty() && oldWidth > 0 && oldHeight > 0 && this.canvas.width > 0 && this.canvas.height > 0) {
|
||||||
const sx = this.canvas.width / oldWidth
|
const sx = this.canvas.width / oldWidth
|
||||||
const sy = this.canvas.height / oldHeight
|
const sy = this.canvas.height / oldHeight
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ export default targetable(class extends HTMLElement {
|
|||||||
|
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
this.addEventListener('click', () => this.downloadFiles())
|
this.addEventListener('click', () => this.downloadFiles())
|
||||||
|
this.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault()
|
||||||
|
this.downloadFiles()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleState () {
|
toggleState () {
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
const dialog = document.getElementById(this.dataset.target)
|
||||||
|
|
||||||
|
this.querySelector('button').addEventListener('click', () => {
|
||||||
|
if (dialog) {
|
||||||
|
dialog.inert = false
|
||||||
|
dialog.showModal()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (dialog) {
|
||||||
|
dialog.addEventListener('close', () => {
|
||||||
|
dialog.inert = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,11 +47,13 @@ export default class extends HTMLElement {
|
|||||||
|
|
||||||
this.classList.remove('hidden', '-translate-y-10', 'opacity-0')
|
this.classList.remove('hidden', '-translate-y-10', 'opacity-0')
|
||||||
this.classList.add('translate-y-0', 'opacity-100')
|
this.classList.add('translate-y-0', 'opacity-100')
|
||||||
|
this.inert = false
|
||||||
}
|
}
|
||||||
|
|
||||||
hideButtons () {
|
hideButtons () {
|
||||||
this.classList.remove('translate-y-0', 'opacity-100')
|
this.classList.remove('translate-y-0', 'opacity-100')
|
||||||
this.classList.add('-translate-y-10', 'opacity-0')
|
this.classList.add('-translate-y-10', 'opacity-0')
|
||||||
|
this.inert = true
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.classList.contains('-translate-y-10')) {
|
if (this.classList.contains('-translate-y-10')) {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export default targetable(class extends HTMLElement {
|
|||||||
this.resizeObserver = new ResizeObserver(() => {
|
this.resizeObserver = new ResizeObserver(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (!this.canvas) return
|
if (!this.canvas) return
|
||||||
|
if (!this.canvas.parentNode?.clientWidth) return
|
||||||
|
|
||||||
const { width, height } = this.canvas
|
const { width, height } = this.canvas
|
||||||
|
|
||||||
@@ -80,7 +81,7 @@ export default targetable(class extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
redrawCanvas (oldWidth, oldHeight) {
|
redrawCanvas (oldWidth, oldHeight) {
|
||||||
if (this.pad && !this.pad.isEmpty() && oldWidth > 0 && oldHeight > 0) {
|
if (this.pad && !this.pad.isEmpty() && oldWidth > 0 && oldHeight > 0 && this.canvas.width > 0 && this.canvas.height > 0) {
|
||||||
const sx = this.canvas.width / oldWidth
|
const sx = this.canvas.width / oldWidth
|
||||||
const sy = this.canvas.height / oldHeight
|
const sy = this.canvas.height / oldHeight
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import FetchForm from './elements/fetch_form'
|
|||||||
import ScrollButtons from './elements/scroll_buttons'
|
import ScrollButtons from './elements/scroll_buttons'
|
||||||
import PageContainer from './elements/page_container'
|
import PageContainer from './elements/page_container'
|
||||||
import SubmitForm from './elements/submit_form'
|
import SubmitForm from './elements/submit_form'
|
||||||
|
import ModalButton from './elements/modal_button'
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ safeRegisterElement('fetch-form', FetchForm)
|
|||||||
safeRegisterElement('scroll-buttons', ScrollButtons)
|
safeRegisterElement('scroll-buttons', ScrollButtons)
|
||||||
safeRegisterElement('page-container', PageContainer)
|
safeRegisterElement('page-container', PageContainer)
|
||||||
safeRegisterElement('submit-form', SubmitForm)
|
safeRegisterElement('submit-form', SubmitForm)
|
||||||
|
safeRegisterElement('modal-button', ModalButton)
|
||||||
safeRegisterElement('submission-form', class extends HTMLElement {
|
safeRegisterElement('submission-form', class extends HTMLElement {
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
this.appElem = document.createElement('div')
|
this.appElem = document.createElement('div')
|
||||||
|
|||||||
@@ -0,0 +1,434 @@
|
|||||||
|
<template>
|
||||||
|
<template
|
||||||
|
v-for="(pages, docUuid) in textRuns"
|
||||||
|
:key="docUuid"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-for="(_, pageIndex) in pages"
|
||||||
|
:key="pageIndex"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-for="(pageElem, i) in [findPageElement(docUuid, pageIndex)]"
|
||||||
|
:key="i"
|
||||||
|
>
|
||||||
|
<Teleport
|
||||||
|
v-if="pageElem"
|
||||||
|
:to="pageElem"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-for="(item, index) in sortedItemsForPage(docUuid, pageIndex)"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<template v-if="item.type === 'text_group'">
|
||||||
|
<span
|
||||||
|
class="absolute overflow-hidden text-transparent select-none pointer-events-none"
|
||||||
|
:style="{ left: item.x * 100 + '%', top: item.y * 100 + '%', width: item.w * 100 + '%', height: item.h * 100 + '%' }"
|
||||||
|
>{{ item.text }}</span>
|
||||||
|
<span
|
||||||
|
v-for="(run, runIndex) in item.items"
|
||||||
|
:key="runIndex"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="absolute overflow-hidden text-transparent"
|
||||||
|
:style="{ left: run.x * 100 + '%', top: run.y * 100 + '%', width: run.w * 100 + '%', height: run.h * 100 + '%', fontSize: run.font_size ? (run.font_size / 10) + 'cqmin' : undefined, textAlign: 'justify', textAlignLast: 'justify', textJustify: 'inter-character' }"
|
||||||
|
>{{ run.text }}</span>
|
||||||
|
</template>
|
||||||
|
<FieldArea
|
||||||
|
v-else-if="item.type === 'field_area'"
|
||||||
|
:ref="setAreaRef"
|
||||||
|
v-model="values[item.field.uuid]"
|
||||||
|
:values="values"
|
||||||
|
:field="item.field"
|
||||||
|
:area="item.area"
|
||||||
|
:submittable="true"
|
||||||
|
:page-width="1400"
|
||||||
|
:page-height="(1400.0 / pageElem.offsetWidth) * pageElem.offsetHeight"
|
||||||
|
:field-index="item.fieldIndex"
|
||||||
|
:is-inline-size="isInlineSize"
|
||||||
|
:scroll-padding="scrollPadding"
|
||||||
|
:submitter="submitter"
|
||||||
|
:with-field-placeholder="withFieldPlaceholder"
|
||||||
|
:with-signature-id="withSignatureId"
|
||||||
|
:is-active="currentStep === item.step"
|
||||||
|
:with-label="withLabel && !withFieldPlaceholder && item.step.length < 2"
|
||||||
|
:is-value-set="item.step.some((f) => f.uuid in values)"
|
||||||
|
:attachments-index="attachmentsIndex"
|
||||||
|
@click="[$emit('focus-step', item.stepIndex), maybeScrollOnClick(item.field, item.area)]"
|
||||||
|
/>
|
||||||
|
<FieldArea
|
||||||
|
v-else-if="item.type === 'readonly_field_area'"
|
||||||
|
:model-value="readonlyConditionalFieldValues[item.field.uuid]"
|
||||||
|
:values="readonlyConditionalFieldValues"
|
||||||
|
:field="item.field"
|
||||||
|
:area="item.area"
|
||||||
|
:submittable="false"
|
||||||
|
:page-width="1400"
|
||||||
|
:page-height="(1400.0 / pageElem.offsetWidth) * pageElem.offsetHeight"
|
||||||
|
:field-index="item.fieldIndex"
|
||||||
|
:is-inline-size="isInlineSize"
|
||||||
|
:submitter="submitter"
|
||||||
|
:attachments-index="attachmentsIndex"
|
||||||
|
/>
|
||||||
|
<FieldArea
|
||||||
|
v-else-if="item.type === 'formula_area' && isMathLoaded"
|
||||||
|
:model-value="calculateFormula(item.field)"
|
||||||
|
:is-inline-size="isInlineSize"
|
||||||
|
:field="item.field"
|
||||||
|
:area="item.area"
|
||||||
|
:submittable="false"
|
||||||
|
:field-index="item.fieldIndex"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import FieldArea from './area'
|
||||||
|
import FormulaAreas from './formula_areas'
|
||||||
|
import FieldAreas from './areas'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AccessibilityAreas',
|
||||||
|
components: {
|
||||||
|
FieldArea
|
||||||
|
},
|
||||||
|
inject: ['baseUrl', 't'],
|
||||||
|
props: {
|
||||||
|
submitterSlug: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
filledFieldsIndex: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
steps: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
readonlyConditionalFields: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
readonlyConditionalFieldValues: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
formulaFields: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
readonlyValues: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
submitter: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
currentStep: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
withFieldPlaceholder: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
withSignatureId: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
withLabel: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
scrollPadding: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '-80px'
|
||||||
|
},
|
||||||
|
scrollEl: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
attachmentsIndex: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
fetchOptions: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['focus-step'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
isMathLoaded: false,
|
||||||
|
math: null,
|
||||||
|
textRuns: {},
|
||||||
|
areaRefs: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
fieldValuesIndex () {
|
||||||
|
return this.filledFieldsIndex || this.extractStaticValues()
|
||||||
|
},
|
||||||
|
isMobileContainer: FieldAreas.computed.isMobileContainer,
|
||||||
|
isInlineSize: FieldAreas.computed.isInlineSize,
|
||||||
|
fieldsUuidIndex () {
|
||||||
|
return this.formulaFields.reduce((acc, field) => {
|
||||||
|
acc[field.uuid] = field
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
},
|
||||||
|
fieldAreasIndex () {
|
||||||
|
const index = Object.create(null)
|
||||||
|
|
||||||
|
this.steps.forEach((step, stepIndex) => {
|
||||||
|
step.forEach((field, fieldIndex) => {
|
||||||
|
(field.areas || []).forEach((area) => {
|
||||||
|
index[area.attachment_uuid] ||= Object.create(null)
|
||||||
|
index[area.attachment_uuid][area.page] ||= []
|
||||||
|
index[area.attachment_uuid][area.page].push({
|
||||||
|
type: 'field_area',
|
||||||
|
field,
|
||||||
|
area,
|
||||||
|
step,
|
||||||
|
stepIndex,
|
||||||
|
fieldIndex,
|
||||||
|
x: area.x,
|
||||||
|
y: area.y,
|
||||||
|
w: area.w,
|
||||||
|
h: area.h
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return index
|
||||||
|
},
|
||||||
|
formulaAreasIndex () {
|
||||||
|
const index = Object.create(null)
|
||||||
|
|
||||||
|
this.formulaFields.forEach((field, fieldIndex) => {
|
||||||
|
(field.areas || []).forEach((area) => {
|
||||||
|
index[area.attachment_uuid] ||= Object.create(null)
|
||||||
|
index[area.attachment_uuid][area.page] ||= []
|
||||||
|
index[area.attachment_uuid][area.page].push({
|
||||||
|
type: 'formula_area',
|
||||||
|
field,
|
||||||
|
area,
|
||||||
|
fieldIndex,
|
||||||
|
x: area.x,
|
||||||
|
y: area.y,
|
||||||
|
w: area.w,
|
||||||
|
h: area.h
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return index
|
||||||
|
},
|
||||||
|
readonlyFieldAreasIndex () {
|
||||||
|
const index = Object.create(null)
|
||||||
|
|
||||||
|
this.readonlyConditionalFields.forEach((field, fieldIndex) => {
|
||||||
|
(field.areas || []).forEach((area) => {
|
||||||
|
index[area.attachment_uuid] ||= Object.create(null)
|
||||||
|
index[area.attachment_uuid][area.page] ||= []
|
||||||
|
index[area.attachment_uuid][area.page].push({
|
||||||
|
type: 'readonly_field_area',
|
||||||
|
field,
|
||||||
|
area,
|
||||||
|
fieldIndex,
|
||||||
|
x: area.x,
|
||||||
|
y: area.y,
|
||||||
|
w: area.w,
|
||||||
|
h: area.h
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeUpdate () {
|
||||||
|
this.areaRefs = []
|
||||||
|
},
|
||||||
|
async mounted () {
|
||||||
|
const [metadataResult] = await Promise.all([
|
||||||
|
fetch(this.baseUrl + `/s/${this.submitterSlug}/metadata`, {
|
||||||
|
...this.fetchOptions
|
||||||
|
}).then((r) => r.json()).catch(() => ({})),
|
||||||
|
this.loadMath()
|
||||||
|
])
|
||||||
|
|
||||||
|
this.textRuns = metadataResult.text_runs || {}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
normalizeFormula: FormulaAreas.methods.normalizeFormula,
|
||||||
|
calculateFormula: FormulaAreas.methods.calculateFormula,
|
||||||
|
scrollInContainer: FieldAreas.methods.scrollInContainer,
|
||||||
|
scrollIntoArea: FieldAreas.methods.scrollIntoArea,
|
||||||
|
scrollIntoField: FieldAreas.methods.scrollIntoField,
|
||||||
|
maybeScrollOnClick: FieldAreas.methods.maybeScrollOnClick,
|
||||||
|
setAreaRef (el) {
|
||||||
|
if (el) {
|
||||||
|
this.areaRefs.push(el)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async loadMath () {
|
||||||
|
if (this.formulaFields.length && !this.isMathLoaded) {
|
||||||
|
const { Calculator } = await import('./calculator')
|
||||||
|
|
||||||
|
this.math = new Calculator()
|
||||||
|
this.isMathLoaded = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
extractStaticValues () {
|
||||||
|
const result = Object.create(null)
|
||||||
|
const root = this.$root.$el?.parentNode?.getRootNode() || document
|
||||||
|
const pageContainers = root.querySelectorAll('page-container')
|
||||||
|
|
||||||
|
pageContainers.forEach((container) => {
|
||||||
|
const overlay = container.querySelector('[id^="page-"]')
|
||||||
|
|
||||||
|
if (!overlay) return
|
||||||
|
|
||||||
|
const parts = overlay.id.split('-')
|
||||||
|
const pageIndex = parseInt(parts[parts.length - 1])
|
||||||
|
const docUuid = parts.slice(1, -1).join('-')
|
||||||
|
|
||||||
|
const fieldValues = overlay.querySelectorAll('field-value')
|
||||||
|
|
||||||
|
if (!fieldValues.length) return
|
||||||
|
|
||||||
|
result[docUuid] ||= Object.create(null)
|
||||||
|
result[docUuid][pageIndex] = []
|
||||||
|
|
||||||
|
fieldValues.forEach((el) => {
|
||||||
|
const style = el.style
|
||||||
|
const x = parseFloat(style.left) / 100
|
||||||
|
const y = parseFloat(style.top) / 100
|
||||||
|
const w = parseFloat(style.width) / 100
|
||||||
|
const h = parseFloat(style.height) / 100
|
||||||
|
const text = el.textContent.trim()
|
||||||
|
|
||||||
|
if (text) {
|
||||||
|
result[docUuid][pageIndex].push({ type: 'static_value', text, x, y, w, h })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
findPageElement (docUuid, pageIndex) {
|
||||||
|
return (this.$root.$el?.parentNode?.getRootNode() || document).getElementById(`page-${docUuid}-${pageIndex}`)
|
||||||
|
},
|
||||||
|
sortedItemsForPage (docUuid, pageIndex) {
|
||||||
|
const items = []
|
||||||
|
|
||||||
|
const pageTextRuns = this.textRuns[docUuid]?.[pageIndex] || []
|
||||||
|
|
||||||
|
pageTextRuns.forEach((run) => {
|
||||||
|
items.push({
|
||||||
|
type: 'text_run',
|
||||||
|
text: run.text,
|
||||||
|
x: run.x,
|
||||||
|
y: run.y,
|
||||||
|
w: run.w,
|
||||||
|
h: run.h,
|
||||||
|
font_size: run.font_size
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const fieldAreas = this.fieldAreasIndex[docUuid]?.[pageIndex] || []
|
||||||
|
items.push(...fieldAreas)
|
||||||
|
|
||||||
|
const readonlyFieldAreas = this.readonlyFieldAreasIndex[docUuid]?.[pageIndex] || []
|
||||||
|
items.push(...readonlyFieldAreas)
|
||||||
|
|
||||||
|
const formulaAreas = this.formulaAreasIndex[docUuid]?.[pageIndex] || []
|
||||||
|
items.push(...formulaAreas)
|
||||||
|
|
||||||
|
const pageFieldValues = this.fieldValuesIndex[docUuid]?.[pageIndex] || []
|
||||||
|
items.push(...pageFieldValues)
|
||||||
|
|
||||||
|
items.sort((a, b) => {
|
||||||
|
const aCenterY = a.y + a.h / 2
|
||||||
|
const bCenterY = b.y + b.h / 2
|
||||||
|
const lineThreshold = Math.min(a.h, b.h) / 2
|
||||||
|
|
||||||
|
if (Math.abs(aCenterY - bCenterY) < lineThreshold) {
|
||||||
|
return a.x - b.x
|
||||||
|
}
|
||||||
|
|
||||||
|
return aCenterY - bCenterY
|
||||||
|
})
|
||||||
|
|
||||||
|
const grouped = []
|
||||||
|
let currentGroup = null
|
||||||
|
|
||||||
|
const closeGroup = () => {
|
||||||
|
if (!currentGroup) return
|
||||||
|
|
||||||
|
const groupItems = currentGroup.items
|
||||||
|
const minX = Math.min(...groupItems.map((i) => i.x))
|
||||||
|
const minY = Math.min(...groupItems.map((i) => i.y))
|
||||||
|
const maxEndX = Math.max(...groupItems.map((i) => i.x + i.w))
|
||||||
|
const maxEndY = Math.max(...groupItems.map((i) => i.y + i.h))
|
||||||
|
|
||||||
|
currentGroup.x = minX
|
||||||
|
currentGroup.y = minY
|
||||||
|
currentGroup.w = maxEndX - minX
|
||||||
|
currentGroup.h = maxEndY - minY
|
||||||
|
currentGroup.text = groupItems.map((i) => i.text).join(' ').replace(/\s+/g, ' ').trim()
|
||||||
|
|
||||||
|
grouped.push(currentGroup)
|
||||||
|
currentGroup = null
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
const isTextLike = item.type === 'text_run' || item.type === 'static_value'
|
||||||
|
|
||||||
|
if (!isTextLike) {
|
||||||
|
closeGroup()
|
||||||
|
grouped.push(item)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentGroup) {
|
||||||
|
currentGroup = { type: 'text_group', items: [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
currentGroup.items.push(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
closeGroup()
|
||||||
|
|
||||||
|
return grouped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -42,10 +42,25 @@ export default {
|
|||||||
const areas = {}
|
const areas = {}
|
||||||
|
|
||||||
this.field.areas?.forEach((area) => {
|
this.field.areas?.forEach((area) => {
|
||||||
areas[area.attachment_uuid + area.page] ||= area
|
areas[area.attachment_uuid] ||= []
|
||||||
|
areas[area.attachment_uuid].push(area)
|
||||||
})
|
})
|
||||||
|
|
||||||
return Object.values(areas).slice(0, 6)
|
const sortedAreas = Object.values(areas).reduce((acc, group) => {
|
||||||
|
const seen = {}
|
||||||
|
const sortedGroup = [...group].sort((a, b) => a.page - b.page)
|
||||||
|
|
||||||
|
sortedGroup.forEach((area) => {
|
||||||
|
if (!seen[area.page]) {
|
||||||
|
seen[area.page] = true
|
||||||
|
acc.push(area)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return sortedAreas.slice(0, 6)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="flex absolute lg:text-base -outline-offset-1 field-area"
|
class="flex absolute lg:text-base -outline-offset-1 focus-visible:outline-blue-500 focus-visible:outline-2 focus-visible:outline field-area"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
:style="[computedStyle, fontStyle]"
|
:style="[computedStyle, fontStyle]"
|
||||||
:class="{ 'cursor-default': !submittable, 'border border-red-100 bg-red-100 cursor-pointer': submittable, 'border border-red-100': !isActive && submittable, 'bg-opacity-80': !isActive && !isValueSet && submittable, 'outline-red-500 outline-dashed outline-2 z-10 field-area-active': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
|
:class="{ 'cursor-default': !submittable, 'border border-red-100 bg-red-100 cursor-pointer': submittable, 'border border-red-100': !isActive && submittable, 'bg-opacity-80': !isActive && !isValueSet && submittable, 'outline-red-500 outline-dashed outline-2 z-10 field-area-active': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
|
||||||
|
:role="submittable && !isNativeInputField ? 'button' : undefined"
|
||||||
|
:tabindex="submittable && !isNativeInputField ? 0 : undefined"
|
||||||
|
:aria-label="submittable && !isNativeInputField ? fieldAreaLabel : undefined"
|
||||||
|
@keydown.enter.prevent="submittable && !isNativeInputField ? $el.click() : undefined"
|
||||||
|
@keydown.space.prevent="submittable && !isNativeInputField ? $el.click() : undefined"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="(!withFieldPlaceholder || !field.name || field.type === 'cells') && !isActive && !isValueSet && field.type !== 'checkbox' && submittable && !area.option_uuid"
|
v-if="(!withFieldPlaceholder || !field.name || field.type === 'cells') && !isActive && !isValueSet && field.type !== 'checkbox' && submittable && !area.option_uuid"
|
||||||
@@ -18,6 +23,7 @@
|
|||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
class="max-h-10 text-base-content"
|
class="max-h-10 text-base-content"
|
||||||
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -129,6 +135,7 @@
|
|||||||
v-if="submittable"
|
v-if="submittable"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:value="false"
|
:value="false"
|
||||||
|
:aria-label="field.name || fieldNames[field.type]"
|
||||||
class="aspect-square base-checkbox"
|
class="aspect-square base-checkbox"
|
||||||
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
||||||
:checked="!!modelValue"
|
:checked="!!modelValue"
|
||||||
@@ -149,6 +156,8 @@
|
|||||||
v-if="submittable"
|
v-if="submittable"
|
||||||
type="radio"
|
type="radio"
|
||||||
:value="false"
|
:value="false"
|
||||||
|
:name="`radio-area-${field.uuid}`"
|
||||||
|
:aria-label="optionValue(option)"
|
||||||
class="aspect-square checked:checkbox checked:checkbox-xs"
|
class="aspect-square checked:checkbox checked:checkbox-xs"
|
||||||
:class="{ 'base-radio': !modelValue || modelValue !== optionValue(option), '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
:class="{ 'base-radio': !modelValue || modelValue !== optionValue(option), '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
||||||
:checked="!!modelValue && modelValue === optionValue(option)"
|
:checked="!!modelValue && modelValue === optionValue(option)"
|
||||||
@@ -169,6 +178,7 @@
|
|||||||
v-if="submittable"
|
v-if="submittable"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:value="false"
|
:value="false"
|
||||||
|
:aria-label="optionValue(option)"
|
||||||
class="aspect-square base-checkbox"
|
class="aspect-square base-checkbox"
|
||||||
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
||||||
:checked="!!modelValue && modelValue.includes(optionValue(option))"
|
:checked="!!modelValue && modelValue.includes(optionValue(option))"
|
||||||
@@ -400,6 +410,16 @@ export default {
|
|||||||
kba: this.t('kba')
|
kba: this.t('kba')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
isNativeInputField () {
|
||||||
|
return ['checkbox', 'radio', 'multiple'].includes(this.field.type)
|
||||||
|
},
|
||||||
|
fieldAreaLabel () {
|
||||||
|
const name = this.field.name || this.fieldNames[this.field.type] || this.field.type
|
||||||
|
if (this.area.option_uuid && this.option) {
|
||||||
|
return `${name} - ${this.optionValue(this.option)}`
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
},
|
||||||
strikethroughWidth () {
|
strikethroughWidth () {
|
||||||
if (this.isInlineSize) {
|
if (this.isInlineSize) {
|
||||||
return '0.6cqmin'
|
return '0.6cqmin'
|
||||||
@@ -507,7 +527,8 @@ export default {
|
|||||||
try {
|
try {
|
||||||
return this.formatDate(
|
return this.formatDate(
|
||||||
this.modelValue === '{{date}}' ? new Date() : new Date(this.modelValue),
|
this.modelValue === '{{date}}' ? new Date() : new Date(this.modelValue),
|
||||||
this.field.preferences?.format || (this.locale.endsWith('-US') ? 'MM/DD/YYYY' : 'DD/MM/YYYY')
|
this.field.preferences?.format || (this.locale.endsWith('-US') ? 'MM/DD/YYYY' : 'DD/MM/YYYY'),
|
||||||
|
{ withTimePlaceholders: this.modelValue === '{{date}}' }
|
||||||
)
|
)
|
||||||
} catch {
|
} catch {
|
||||||
return this.modelValue
|
return this.modelValue
|
||||||
@@ -626,36 +647,55 @@ export default {
|
|||||||
return number
|
return number
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
formatDate (date, format) {
|
formatDate (date, format, { withTimePlaceholders = false } = {}) {
|
||||||
const monthFormats = {
|
const monthFormats = { M: 'numeric', MM: '2-digit', MMM: 'short', MMMM: 'long' }
|
||||||
M: 'numeric',
|
const dayFormats = { D: 'numeric', DD: '2-digit' }
|
||||||
MM: '2-digit',
|
const yearFormats = { YYYY: 'numeric', YYY: 'numeric', YY: '2-digit' }
|
||||||
MMM: 'short',
|
const hourFormats = { H: 'numeric', HH: '2-digit', h: 'numeric', hh: '2-digit' }
|
||||||
MMMM: 'long'
|
const minuteFormats = { m: 'numeric', mm: '2-digit' }
|
||||||
}
|
const secondFormats = { s: 'numeric', ss: '2-digit' }
|
||||||
|
|
||||||
const dayFormats = {
|
const hasTime = /[HhAasz]/.test(format)
|
||||||
D: 'numeric',
|
|
||||||
DD: '2-digit'
|
|
||||||
}
|
|
||||||
|
|
||||||
const yearFormats = {
|
const opts = {
|
||||||
YYYY: 'numeric',
|
|
||||||
YYY: 'numeric',
|
|
||||||
YY: '2-digit'
|
|
||||||
}
|
|
||||||
|
|
||||||
const parts = new Intl.DateTimeFormat([], {
|
|
||||||
day: dayFormats[format.match(/D+/)],
|
day: dayFormats[format.match(/D+/)],
|
||||||
month: monthFormats[format.match(/M+/)],
|
month: monthFormats[format.match(/M+/)],
|
||||||
year: yearFormats[format.match(/Y+/)],
|
year: yearFormats[format.match(/Y+/)]
|
||||||
timeZone: 'UTC'
|
}
|
||||||
}).formatToParts(date)
|
|
||||||
|
|
||||||
return format
|
if (format.match(/H+/)) { opts.hour = hourFormats[format.match(/H+/)[0]]; opts.hour12 = false }
|
||||||
.replace(/D+/, parts.find((p) => p.type === 'day').value)
|
if (format.match(/h+/)) { opts.hour = hourFormats[format.match(/h+/)[0]]; opts.hour12 = true }
|
||||||
.replace(/M+/, parts.find((p) => p.type === 'month').value)
|
if (/[Aa]/.test(format) && opts.hour12 === undefined) opts.hour12 = true
|
||||||
.replace(/Y+/, parts.find((p) => p.type === 'year').value)
|
if (format.match(/m+/)) opts.minute = minuteFormats[format.match(/m+/)[0]]
|
||||||
|
if (format.match(/s+/)) opts.second = secondFormats[format.match(/s+/)[0]]
|
||||||
|
if (/z/.test(format)) opts.timeZoneName = 'short'
|
||||||
|
if (!hasTime) opts.timeZone = 'UTC'
|
||||||
|
|
||||||
|
const partTypes = {
|
||||||
|
M: 'month',
|
||||||
|
D: 'day',
|
||||||
|
Y: 'year',
|
||||||
|
H: 'hour',
|
||||||
|
h: 'hour',
|
||||||
|
m: 'minute',
|
||||||
|
s: 'second',
|
||||||
|
z: 'timeZoneName',
|
||||||
|
A: 'dayPeriod',
|
||||||
|
a: 'dayPeriod'
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = new Intl.DateTimeFormat([], opts).formatToParts(date)
|
||||||
|
|
||||||
|
return format.replace(/MMMM|MMM|MM|M|DD|D|YYYY|YYY|YY|HH|hh|H|h|mm|m|ss|s|A|a|z/g, (token) => {
|
||||||
|
if (withTimePlaceholders && /^(HH|hh|H|h|mm|m|ss|s|A|a)$/.test(token)) return '--'
|
||||||
|
|
||||||
|
const value = parts.find((p) => p.type === partTypes[token[0]])?.value
|
||||||
|
|
||||||
|
if (token === 'A') return (value || '').toUpperCase()
|
||||||
|
if (token === 'a') return (value || '').toLowerCase()
|
||||||
|
|
||||||
|
return value
|
||||||
|
})
|
||||||
},
|
},
|
||||||
updateMultipleSelectValue (value) {
|
updateMultipleSelectValue (value) {
|
||||||
if (this.modelValue?.includes(value)) {
|
if (this.modelValue?.includes(value)) {
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="value.length">
|
<ul
|
||||||
<div
|
v-if="value.length"
|
||||||
|
:aria-label="t('uploaded_files')"
|
||||||
|
class="list-none p-0 m-0"
|
||||||
|
>
|
||||||
|
<li
|
||||||
v-for="(val, index) in value"
|
v-for="(val, index) in value"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="flex mb-2"
|
class="flex mb-2"
|
||||||
@@ -20,23 +24,27 @@
|
|||||||
<IconPaperclip
|
<IconPaperclip
|
||||||
:width="16"
|
:width="16"
|
||||||
class="flex-none"
|
class="flex-none"
|
||||||
:heigh="16"
|
:height="16"
|
||||||
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
{{ attachmentsIndex[val].filename }}
|
{{ attachmentsIndex[val].filename }}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
class="remove-attachment-button"
|
class="remove-attachment-button"
|
||||||
|
:aria-label="`${t('clear')} ${attachmentsIndex[val].filename}`"
|
||||||
@click.prevent="removeAttachment(val)"
|
@click.prevent="removeAttachment(val)"
|
||||||
>
|
>
|
||||||
<IconTrashX
|
<IconTrashX
|
||||||
:width="18"
|
:width="18"
|
||||||
:heigh="19"
|
:height="19"
|
||||||
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</li>
|
||||||
</div>
|
</ul>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<input
|
<input
|
||||||
value=""
|
value=""
|
||||||
|
|||||||
@@ -3,10 +3,13 @@
|
|||||||
id="form_completed"
|
id="form_completed"
|
||||||
class="mx-auto max-w-md flex flex-col completed-form"
|
class="mx-auto max-w-md flex flex-col completed-form"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
|
role="status"
|
||||||
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<div class="font-medium text-2xl flex items-center space-x-1.5 mx-auto">
|
<div class="font-medium text-2xl flex items-center space-x-1.5 mx-auto">
|
||||||
<IconCircleCheck
|
<IconCircleCheck
|
||||||
class="inline text-green-600"
|
class="inline text-green-600"
|
||||||
|
aria-hidden="true"
|
||||||
:width="30"
|
:width="30"
|
||||||
:height="30"
|
:height="30"
|
||||||
/>
|
/>
|
||||||
@@ -42,8 +45,12 @@
|
|||||||
<IconInnerShadowTop
|
<IconInnerShadowTop
|
||||||
v-if="isSendingCopy"
|
v-if="isSendingCopy"
|
||||||
class="animate-spin"
|
class="animate-spin"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<IconMail
|
||||||
|
v-else
|
||||||
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
<IconMail v-else />
|
|
||||||
<span>
|
<span>
|
||||||
{{ t('send_copy_via_email') }}
|
{{ t('send_copy_via_email') }}
|
||||||
</span>
|
</span>
|
||||||
@@ -57,8 +64,12 @@
|
|||||||
<IconInnerShadowTop
|
<IconInnerShadowTop
|
||||||
v-if="isDownloading"
|
v-if="isDownloading"
|
||||||
class="animate-spin"
|
class="animate-spin"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<IconDownload
|
||||||
|
v-else
|
||||||
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
<IconDownload v-else />
|
|
||||||
<span>
|
<span>
|
||||||
{{ t('download') }}
|
{{ t('download') }}
|
||||||
</span>
|
</span>
|
||||||
@@ -199,7 +210,7 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelectorAll('#decline_button').forEach((button) => {
|
document.querySelectorAll('#decline_button, #decline_button_mobile, #delegate_button, #delegate_button_mobile').forEach((button) => {
|
||||||
button.setAttribute('disabled', 'true')
|
button.setAttribute('disabled', 'true')
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,12 +30,16 @@
|
|||||||
class="btn btn-outline btn-sm !normal-case font-normal set-current-date-button"
|
class="btn btn-outline btn-sm !normal-case font-normal set-current-date-button"
|
||||||
@click.prevent="[setCurrentDate(), $emit('focus')]"
|
@click.prevent="[setCurrentDate(), $emit('focus')]"
|
||||||
>
|
>
|
||||||
<IconCalendarCheck :width="16" />
|
<IconCalendarCheck
|
||||||
|
:width="16"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
{{ t('set_today') }}
|
{{ t('set_today') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
|
:id="field.uuid + '-desc'"
|
||||||
class="mb-3 px-1 field-description-text"
|
class="mb-3 px-1 field-description-text"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
>
|
>
|
||||||
@@ -51,12 +55,19 @@
|
|||||||
:max="validationMax"
|
:max="validationMax"
|
||||||
class="base-input !text-2xl text-center w-full"
|
class="base-input !text-2xl text-center w-full"
|
||||||
:required="field.required"
|
:required="field.required"
|
||||||
type="date"
|
:aria-describedby="field.description ? field.uuid + '-desc' : undefined"
|
||||||
:name="`values[${field.uuid}]`"
|
:type="inputType"
|
||||||
|
:name="formatType === 'datetime' ? undefined : `values[${field.uuid}]`"
|
||||||
@keydown.enter="onEnter"
|
@keydown.enter="onEnter"
|
||||||
@focus="$emit('focus')"
|
@focus="$emit('focus')"
|
||||||
@paste="onPaste"
|
@paste="onPaste"
|
||||||
>
|
>
|
||||||
|
<input
|
||||||
|
v-if="formatType === 'datetime'"
|
||||||
|
type="hidden"
|
||||||
|
:name="`values[${field.uuid}]`"
|
||||||
|
:value="modelValue"
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -92,14 +103,19 @@ export default {
|
|||||||
},
|
},
|
||||||
emits: ['update:model-value', 'focus', 'submit'],
|
emits: ['update:model-value', 'focus', 'submit'],
|
||||||
computed: {
|
computed: {
|
||||||
|
formatType () {
|
||||||
|
const format = this.field.preferences?.format || ''
|
||||||
|
|
||||||
|
if (/[HhAasz]/.test(format)) return 'datetime'
|
||||||
|
if (format && !/[Dd]/.test(format)) return 'month'
|
||||||
|
|
||||||
|
return 'date'
|
||||||
|
},
|
||||||
|
inputType () {
|
||||||
|
return { datetime: 'datetime-local', month: 'month', date: 'date' }[this.formatType]
|
||||||
|
},
|
||||||
dateNowString () {
|
dateNowString () {
|
||||||
const today = new Date()
|
return this.formatDateValue(new Date())
|
||||||
|
|
||||||
const yyyy = today.getFullYear()
|
|
||||||
const mm = String(today.getMonth() + 1).padStart(2, '0')
|
|
||||||
const dd = String(today.getDate()).padStart(2, '0')
|
|
||||||
|
|
||||||
return `${yyyy}-${mm}-${dd}`
|
|
||||||
},
|
},
|
||||||
validationMin () {
|
validationMin () {
|
||||||
if (this.field.validation?.min) {
|
if (this.field.validation?.min) {
|
||||||
@@ -116,6 +132,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
withToday () {
|
withToday () {
|
||||||
|
if (this.formatType === 'datetime') return false
|
||||||
|
|
||||||
const todayDate = new Date().setHours(0, 0, 0, 0)
|
const todayDate = new Date().setHours(0, 0, 0, 0)
|
||||||
|
|
||||||
if (this.validationMin) {
|
if (this.validationMin) {
|
||||||
@@ -132,9 +150,25 @@ export default {
|
|||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
set (value) {
|
set (value) {
|
||||||
|
if (this.formatType === 'datetime' && value) {
|
||||||
|
const d = new Date(value)
|
||||||
|
|
||||||
|
if (!isNaN(d)) {
|
||||||
|
this.$emit('update:model-value', d.toISOString())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.$emit('update:model-value', value)
|
this.$emit('update:model-value', value)
|
||||||
},
|
},
|
||||||
get () {
|
get () {
|
||||||
|
if (this.formatType === 'datetime') {
|
||||||
|
const d = new Date(this.modelValue)
|
||||||
|
|
||||||
|
return isNaN(d) ? '' : this.formatDateValue(d)
|
||||||
|
}
|
||||||
|
|
||||||
return this.modelValue
|
return this.modelValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,20 +192,32 @@ export default {
|
|||||||
|
|
||||||
const parsedDate = new Date(pasteData)
|
const parsedDate = new Date(pasteData)
|
||||||
|
|
||||||
if (!isNaN(parsedDate)) {
|
if (isNaN(parsedDate)) return
|
||||||
const inputEl = this.$refs.input
|
|
||||||
|
|
||||||
inputEl.valueAsDate = new Date(parsedDate.getTime() - parsedDate.getTimezoneOffset() * 60000)
|
this.setInputValue(parsedDate)
|
||||||
|
|
||||||
inputEl.dispatchEvent(new Event('input', { bubbles: true }))
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
setCurrentDate () {
|
setCurrentDate () {
|
||||||
|
this.setInputValue(new Date())
|
||||||
|
},
|
||||||
|
setInputValue (date) {
|
||||||
const inputEl = this.$refs.input
|
const inputEl = this.$refs.input
|
||||||
|
|
||||||
inputEl.valueAsDate = new Date(new Date().getTime() - new Date().getTimezoneOffset() * 60000)
|
if (this.formatType === 'date') {
|
||||||
|
inputEl.valueAsDate = new Date(date.getTime() - date.getTimezoneOffset() * 60000)
|
||||||
|
} else {
|
||||||
|
inputEl.value = this.formatDateValue(date)
|
||||||
|
}
|
||||||
|
|
||||||
inputEl.dispatchEvent(new Event('input', { bubbles: true }))
|
inputEl.dispatchEvent(new Event('input', { bubbles: true }))
|
||||||
|
},
|
||||||
|
formatDateValue (date) {
|
||||||
|
const pad = (n) => String(n).padStart(2, '0')
|
||||||
|
const ymd = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
|
||||||
|
|
||||||
|
if (this.formatType === 'month') return ymd.slice(0, 7)
|
||||||
|
if (this.formatType === 'datetime') return `${ymd}T${pad(date.getHours())}:${pad(date.getMinutes())}`
|
||||||
|
|
||||||
|
return ymd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,8 +39,9 @@
|
|||||||
ref="input"
|
ref="input"
|
||||||
:multiple="multiple"
|
:multiple="multiple"
|
||||||
:accept="accept"
|
:accept="accept"
|
||||||
|
:aria-label="message"
|
||||||
type="file"
|
type="file"
|
||||||
class="hidden"
|
class="sr-only"
|
||||||
@change="onSelectFiles"
|
@change="onSelectFiles"
|
||||||
>
|
>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -1,5 +1,52 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<Teleport
|
||||||
|
v-if="withAccessibilityAreas === null && !isAccessibilityMode"
|
||||||
|
to="#sr_only_content"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-[100] focus:px-4 focus:py-2 focus:bg-base-100 focus:text-base-content focus:rounded focus:shadow-lg"
|
||||||
|
@click="isAccessibilityMode = true"
|
||||||
|
>
|
||||||
|
{{ t('enter_screen_reader_mode') }}
|
||||||
|
</button>
|
||||||
|
</Teleport>
|
||||||
|
<Teleport
|
||||||
|
v-for="item in (withAccessibilityAreas === null ? schema : [])"
|
||||||
|
:key="item.attachment_uuid"
|
||||||
|
:to="`#document-${item.attachment_uuid} .sr_only_content`"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-if="!isAccessibilityMode"
|
||||||
|
class="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-[100] focus:px-4 focus:py-2 focus:bg-base-100 focus:text-base-content focus:rounded focus:shadow-lg"
|
||||||
|
@click="isAccessibilityMode = true"
|
||||||
|
>
|
||||||
|
{{ t('enter_screen_reader_mode') }}
|
||||||
|
</button>
|
||||||
|
</Teleport>
|
||||||
|
<AccessibilityAreas
|
||||||
|
v-if="withAccessibilityAreas || isAccessibilityMode"
|
||||||
|
ref="areas"
|
||||||
|
:submitter-slug="submitterSlug"
|
||||||
|
:steps="stepFields"
|
||||||
|
:readonly-conditional-fields="readonlyConditionalFields"
|
||||||
|
:readonly-conditional-field-values="readonlyConditionalFieldValues"
|
||||||
|
:formula-fields="formulaFields"
|
||||||
|
:values="values"
|
||||||
|
:readonly-values="readonlyFieldValues"
|
||||||
|
:submitter="submitter"
|
||||||
|
:scroll-el="scrollEl"
|
||||||
|
:current-step="currentStepFields"
|
||||||
|
:with-field-placeholder="withFieldPlaceholder"
|
||||||
|
:with-signature-id="withSignatureId"
|
||||||
|
:with-label="withFieldLabels && !isAnonymousChecboxes && showFieldNames"
|
||||||
|
:scroll-padding="scrollPadding"
|
||||||
|
:attachments-index="attachmentsIndex"
|
||||||
|
:fetch-options="fetchOptions"
|
||||||
|
:filled-fields-index="filledFieldsIndex"
|
||||||
|
@focus-step="[saveStep(), currentField.type !== 'checkbox' ? isFormVisible = true : '', goToStep($event, false, true)]"
|
||||||
|
/>
|
||||||
<FieldAreas
|
<FieldAreas
|
||||||
|
v-if="!withAccessibilityAreas && !isAccessibilityMode"
|
||||||
ref="areas"
|
ref="areas"
|
||||||
:steps="stepFields"
|
:steps="stepFields"
|
||||||
:values="values"
|
:values="values"
|
||||||
@@ -14,6 +61,7 @@
|
|||||||
@focus-step="[saveStep(), currentField.type !== 'checkbox' ? isFormVisible = true : '', goToStep($event, false, true)]"
|
@focus-step="[saveStep(), currentField.type !== 'checkbox' ? isFormVisible = true : '', goToStep($event, false, true)]"
|
||||||
/>
|
/>
|
||||||
<FieldAreas
|
<FieldAreas
|
||||||
|
v-if="!withAccessibilityAreas && !isAccessibilityMode"
|
||||||
:steps="readonlyConditionalFields.map((e) => [e])"
|
:steps="readonlyConditionalFields.map((e) => [e])"
|
||||||
:values="readonlyConditionalFieldValues"
|
:values="readonlyConditionalFieldValues"
|
||||||
:submitter="submitter"
|
:submitter="submitter"
|
||||||
@@ -21,7 +69,7 @@
|
|||||||
:submittable="false"
|
:submittable="false"
|
||||||
/>
|
/>
|
||||||
<FormulaFieldAreas
|
<FormulaFieldAreas
|
||||||
v-if="formulaFields.length"
|
v-if="!withAccessibilityAreas && !isAccessibilityMode && formulaFields.length"
|
||||||
:fields="formulaFields"
|
:fields="formulaFields"
|
||||||
:readonly-values="readonlyFieldValues"
|
:readonly-values="readonlyFieldValues"
|
||||||
:values="values"
|
:values="values"
|
||||||
@@ -57,6 +105,7 @@
|
|||||||
<IconInnerShadowTop
|
<IconInnerShadowTop
|
||||||
v-if="isSubmittingComplete"
|
v-if="isSubmittingComplete"
|
||||||
class="mr-1 animate-spin w-5 h-5"
|
class="mr-1 animate-spin w-5 h-5"
|
||||||
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
{{ t('complete') }}
|
{{ t('complete') }}
|
||||||
@@ -88,6 +137,7 @@
|
|||||||
class="absolute right-0 mr-4"
|
class="absolute right-0 mr-4"
|
||||||
:width="20"
|
:width="20"
|
||||||
:height="20"
|
:height="20"
|
||||||
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
@@ -103,11 +153,13 @@
|
|||||||
class="absolute right-0 top-0 minimize-form-button"
|
class="absolute right-0 top-0 minimize-form-button"
|
||||||
:class="currentField?.description?.length > 100 ? 'mr-1 mt-1 md:mr-2 md:mt-2': 'mr-2 mt-2 hidden md:block'"
|
:class="currentField?.description?.length > 100 ? 'mr-1 mt-1 md:mr-2 md:mt-2': 'mr-2 mt-2 hidden md:block'"
|
||||||
:title="t('minimize')"
|
:title="t('minimize')"
|
||||||
|
:aria-label="t('minimize')"
|
||||||
@click.prevent="minimizeForm"
|
@click.prevent="minimizeForm"
|
||||||
>
|
>
|
||||||
<IconArrowsDiagonalMinimize2
|
<IconArrowsDiagonalMinimize2
|
||||||
:width="20"
|
:width="20"
|
||||||
:height="20"
|
:height="20"
|
||||||
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
@@ -193,6 +245,7 @@
|
|||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="currentField.description"
|
v-if="currentField.description"
|
||||||
|
:id="currentField.uuid + '-desc'"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1 field-description-text"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
@@ -203,6 +256,7 @@
|
|||||||
:id="currentField.uuid"
|
:id="currentField.uuid"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
:required="currentField.required"
|
:required="currentField.required"
|
||||||
|
:aria-describedby="currentField.description ? currentField.uuid + '-desc' : undefined"
|
||||||
class="select base-input !text-2xl w-full text-center font-normal"
|
class="select base-input !text-2xl w-full text-center font-normal"
|
||||||
:class="{ 'text-gray-300': !values[currentField.uuid] }"
|
:class="{ 'text-gray-300': !values[currentField.uuid] }"
|
||||||
:name="`values[${currentField.uuid}]`"
|
:name="`values[${currentField.uuid}]`"
|
||||||
@@ -250,6 +304,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
v-if="currentField.description"
|
v-if="currentField.description"
|
||||||
|
:id="currentField.uuid + '-desc'"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1 field-description-text"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
@@ -309,6 +364,7 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="currentField.description"
|
v-if="currentField.description"
|
||||||
|
:id="currentField.uuid + '-desc'"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1 field-description-text"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
@@ -511,6 +567,7 @@
|
|||||||
<IconInnerShadowTop
|
<IconInnerShadowTop
|
||||||
v-if="isSubmitting"
|
v-if="isSubmitting"
|
||||||
class="mr-1 animate-spin"
|
class="mr-1 animate-spin"
|
||||||
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
{{ submitButtonText }}
|
{{ submitButtonText }}
|
||||||
@@ -522,6 +579,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
v-if="showFillAllRequiredFields"
|
v-if="showFillAllRequiredFields"
|
||||||
|
role="alert"
|
||||||
class="text-center mt-1"
|
class="text-center mt-1"
|
||||||
>
|
>
|
||||||
{{ t('please_fill_all_required_fields') }}
|
{{ t('please_fill_all_required_fields') }}
|
||||||
@@ -554,8 +612,10 @@
|
|||||||
:can-send-email="canSendEmail && !!submitter.email"
|
:can-send-email="canSendEmail && !!submitter.email"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
/>
|
/>
|
||||||
<div
|
<nav
|
||||||
v-if="stepFields.length < 80"
|
v-if="stepFields.length < 80"
|
||||||
|
:aria-label="t('form_progress')"
|
||||||
|
:aria-hidden="isCompleted"
|
||||||
class="flex justify-center mt-3 sm:mt-4 mb-0 sm:mb-1 select-none"
|
class="flex justify-center mt-3 sm:mt-4 mb-0 sm:mb-1 select-none"
|
||||||
>
|
>
|
||||||
<div class="flex items-center flex-wrap steps-progress">
|
<div class="flex items-center flex-wrap steps-progress">
|
||||||
@@ -563,16 +623,18 @@
|
|||||||
v-for="(step, index) in stepFields"
|
v-for="(step, index) in stepFields"
|
||||||
:key="step[0].uuid"
|
:key="step[0].uuid"
|
||||||
>
|
>
|
||||||
<a
|
<button
|
||||||
v-if="!onlyRequiredFields || step.some((f) => f.required)"
|
v-if="!onlyRequiredFields || step.some((f) => f.required)"
|
||||||
href="#"
|
type="button"
|
||||||
class="inline border border-base-300 h-3 w-3 rounded-full mx-1 mt-1"
|
:aria-label="`${t('step')} ${index + 1}`"
|
||||||
|
:aria-current="index === currentStep ? 'step' : undefined"
|
||||||
|
class="inline border border-base-300 h-3 w-3 rounded-full mx-1 mt-1 p-0"
|
||||||
:class="{ 'bg-base-300 steps-progress-current': index === currentStep, 'bg-base-content': (index < currentStep && stepFields[index].every((f) => !f.required || ![null, undefined, ''].includes(values[f.uuid]))) || isCompleted, 'bg-white': index > currentStep }"
|
:class="{ 'bg-base-300 steps-progress-current': index === currentStep, 'bg-base-content': (index < currentStep && stepFields[index].every((f) => !f.required || ![null, undefined, ''].includes(values[f.uuid]))) || isCompleted, 'bg-white': index > currentStep }"
|
||||||
@click.prevent="isCompleted ? '' : [saveStep(), goToStep(index, true)]"
|
@click="isCompleted ? '' : [saveStep(), goToStep(index, true)]"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</nav>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="mt-5"
|
class="mt-5"
|
||||||
@@ -584,6 +646,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import FieldAreas from './areas'
|
import FieldAreas from './areas'
|
||||||
import FormulaFieldAreas from './formula_areas'
|
import FormulaFieldAreas from './formula_areas'
|
||||||
|
import AccessibilityAreas from './accessibility_areas'
|
||||||
import ImageStep from './image_step'
|
import ImageStep from './image_step'
|
||||||
import SignatureStep from './signature_step'
|
import SignatureStep from './signature_step'
|
||||||
import InitialsStep from './initials_step'
|
import InitialsStep from './initials_step'
|
||||||
@@ -642,6 +705,7 @@ export default {
|
|||||||
name: 'SubmissionForm',
|
name: 'SubmissionForm',
|
||||||
components: {
|
components: {
|
||||||
FieldAreas,
|
FieldAreas,
|
||||||
|
AccessibilityAreas,
|
||||||
ImageStep,
|
ImageStep,
|
||||||
SignatureStep,
|
SignatureStep,
|
||||||
AppearsOn,
|
AppearsOn,
|
||||||
@@ -824,6 +888,16 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
|
filledFieldsIndex: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
withAccessibilityAreas: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
fields: {
|
fields: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -934,7 +1008,8 @@ export default {
|
|||||||
isSubmitting: false,
|
isSubmitting: false,
|
||||||
isSubmittingComplete: false,
|
isSubmittingComplete: false,
|
||||||
submittedValues: {},
|
submittedValues: {},
|
||||||
recalculateButtonDisabledKey: ''
|
recalculateButtonDisabledKey: '',
|
||||||
|
isAccessibilityMode: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -1458,7 +1533,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.enableScrollIntoField = false
|
this.enableScrollIntoField = false
|
||||||
this.$refs.form.querySelector('input[type="date"], input[type="number"], input[type="text"], select')?.focus()
|
this.$refs.form.querySelector('input[type="date"], input[type="number"], input[type="text"], input[type="tel"], textarea, select')?.focus()
|
||||||
this.enableScrollIntoField = true
|
this.enableScrollIntoField = true
|
||||||
|
|
||||||
if (clickUpload && !this.values[this.currentField.uuid] && ['file', 'image'].includes(this.currentField.type)) {
|
if (clickUpload && !this.values[this.currentField.uuid] && ['file', 'image'].includes(this.currentField.type)) {
|
||||||
@@ -1629,6 +1704,15 @@ export default {
|
|||||||
|
|
||||||
if (this.completedRedirectUrl) {
|
if (this.completedRedirectUrl) {
|
||||||
window.location.href = sanitizeUrl(this.completedRedirectUrl)
|
window.location.href = sanitizeUrl(this.completedRedirectUrl)
|
||||||
|
} else {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const root = this.$root.$el.parentNode.getRootNode()
|
||||||
|
const completedEl = root.getElementById('form_completed')
|
||||||
|
|
||||||
|
if (completedEl) {
|
||||||
|
completedEl.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
const en = {
|
const en = {
|
||||||
|
step: 'Step',
|
||||||
|
form_progress: 'Form progress',
|
||||||
|
close: 'Close',
|
||||||
|
uploaded_files: 'Uploaded files',
|
||||||
|
signature_drawing_area: 'Signature drawing area. Use mouse or touch to draw your signature.',
|
||||||
kba: 'KBA',
|
kba: 'KBA',
|
||||||
please_upload_an_image_file: 'Please upload an image file',
|
please_upload_an_image_file: 'Please upload an image file',
|
||||||
must_be_characters_length: 'Must be {number} characters long',
|
must_be_characters_length: 'Must be {number} characters long',
|
||||||
@@ -98,10 +103,16 @@ const en = {
|
|||||||
files: 'Files',
|
files: 'Files',
|
||||||
signature_is_too_small_or_simple_please_redraw: 'Signature is too small or simple. Please redraw.',
|
signature_is_too_small_or_simple_please_redraw: 'Signature is too small or simple. Please redraw.',
|
||||||
browser_privacy_settings_block_canvas: 'Your browser privacy settings restrict use of the drawing canvas. Please use a different browser or device, or disable privacy settings that block canvas in order to sign.',
|
browser_privacy_settings_block_canvas: 'Your browser privacy settings restrict use of the drawing canvas. Please use a different browser or device, or disable privacy settings that block canvas in order to sign.',
|
||||||
wait_countdown_seconds: 'Wait {countdown} seconds'
|
wait_countdown_seconds: 'Wait {countdown} seconds',
|
||||||
|
enter_screen_reader_mode: 'Enter screen reader mode'
|
||||||
}
|
}
|
||||||
|
|
||||||
const es = {
|
const es = {
|
||||||
|
step: 'Paso',
|
||||||
|
form_progress: 'Progreso del formulario',
|
||||||
|
close: 'Cerrar',
|
||||||
|
uploaded_files: 'Archivos subidos',
|
||||||
|
signature_drawing_area: 'Área de dibujo de firma. Use el ratón o toque para dibujar su firma.',
|
||||||
kba: 'KBA',
|
kba: 'KBA',
|
||||||
please_upload_an_image_file: 'Por favor, sube un archivo de imagen',
|
please_upload_an_image_file: 'Por favor, sube un archivo de imagen',
|
||||||
must_be_characters_length: 'Debe tener {number} caracteres de longitud',
|
must_be_characters_length: 'Debe tener {number} caracteres de longitud',
|
||||||
@@ -125,7 +136,7 @@ const es = {
|
|||||||
verification_code_is_invalid: 'El código de verificación no es válido',
|
verification_code_is_invalid: 'El código de verificación no es válido',
|
||||||
sign_on_the_touchscreen: 'Firmar en pantalla táctil',
|
sign_on_the_touchscreen: 'Firmar en pantalla táctil',
|
||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Escanea el código QR con la aplicación de la cámara para abrir el formulario en el móvil y dibujar tu firma',
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Escanea el código QR con la aplicación de la cámara para abrir el formulario en el móvil y dibujar tu firma',
|
||||||
by_clicking_you_agree_to_the: 'Al hacer clic en "{button}", usted acepta el',
|
by_clicking_you_agree_to_the: 'Al hacer clic en "{button}", usted acepta la',
|
||||||
electronic_signature_disclosure: 'Divulgación de Firma Electrónica',
|
electronic_signature_disclosure: 'Divulgación de Firma Electrónica',
|
||||||
esignature_disclosure: 'Divulgación de eFirma',
|
esignature_disclosure: 'Divulgación de eFirma',
|
||||||
already_paid: 'Ya pagado',
|
already_paid: 'Ya pagado',
|
||||||
@@ -201,10 +212,16 @@ const es = {
|
|||||||
files: 'Archivos',
|
files: 'Archivos',
|
||||||
signature_is_too_small_or_simple_please_redraw: 'La firma es demasiado pequeña o simple. Por favor, vuelve a dibujarla.',
|
signature_is_too_small_or_simple_please_redraw: 'La firma es demasiado pequeña o simple. Por favor, vuelve a dibujarla.',
|
||||||
browser_privacy_settings_block_canvas: 'La configuración de privacidad de su navegador restringe el uso del lienzo de dibujo. Utilice un navegador o dispositivo diferente, o desactive la configuración de privacidad que bloquea el lienzo para firmar.',
|
browser_privacy_settings_block_canvas: 'La configuración de privacidad de su navegador restringe el uso del lienzo de dibujo. Utilice un navegador o dispositivo diferente, o desactive la configuración de privacidad que bloquea el lienzo para firmar.',
|
||||||
wait_countdown_seconds: 'Espera {countdown} segundos'
|
wait_countdown_seconds: 'Espera {countdown} segundos',
|
||||||
|
enter_screen_reader_mode: 'Activar modo lector de pantalla'
|
||||||
}
|
}
|
||||||
|
|
||||||
const it = {
|
const it = {
|
||||||
|
step: 'Passo',
|
||||||
|
form_progress: 'Progresso del modulo',
|
||||||
|
close: 'Chiudi',
|
||||||
|
uploaded_files: 'File caricati',
|
||||||
|
signature_drawing_area: 'Area di disegno della firma. Usa il mouse o il tocco per disegnare la tua firma.',
|
||||||
kba: 'KBA',
|
kba: 'KBA',
|
||||||
please_upload_an_image_file: 'Per favore carica un file immagine',
|
please_upload_an_image_file: 'Per favore carica un file immagine',
|
||||||
must_be_characters_length: 'Deve essere lungo {number} caratteri',
|
must_be_characters_length: 'Deve essere lungo {number} caratteri',
|
||||||
@@ -228,7 +245,7 @@ const it = {
|
|||||||
verification_code_is_invalid: 'Il codice di verifica non è valido',
|
verification_code_is_invalid: 'Il codice di verifica non è valido',
|
||||||
sign_on_the_touchscreen: 'Firma su schermo tattile',
|
sign_on_the_touchscreen: 'Firma su schermo tattile',
|
||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: "Scansiona il codice QR con l'app della fotocamera per aprire il modulo sul cellulare e disegnare la tua firma",
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: "Scansiona il codice QR con l'app della fotocamera per aprire il modulo sul cellulare e disegnare la tua firma",
|
||||||
by_clicking_you_agree_to_the: 'Cliccando su "{button}", accetti il',
|
by_clicking_you_agree_to_the: 'Cliccando su "{button}", accetti la',
|
||||||
electronic_signature_disclosure: 'Divulgazione della Firma Elettronica',
|
electronic_signature_disclosure: 'Divulgazione della Firma Elettronica',
|
||||||
esignature_disclosure: 'Divulgazione della eFirma',
|
esignature_disclosure: 'Divulgazione della eFirma',
|
||||||
minimize: 'Minimizza',
|
minimize: 'Minimizza',
|
||||||
@@ -241,7 +258,7 @@ const it = {
|
|||||||
date: 'Data',
|
date: 'Data',
|
||||||
number: 'Numero',
|
number: 'Numero',
|
||||||
image: 'Immagine',
|
image: 'Immagine',
|
||||||
pay: 'Pagamento',
|
pay: 'Paga',
|
||||||
file: 'File',
|
file: 'File',
|
||||||
select: 'Seleziona',
|
select: 'Seleziona',
|
||||||
checkbox: 'Checkbox',
|
checkbox: 'Checkbox',
|
||||||
@@ -304,10 +321,16 @@ const it = {
|
|||||||
files: 'File',
|
files: 'File',
|
||||||
signature_is_too_small_or_simple_please_redraw: 'La firma è troppo piccola o semplice. Ridisegnala, per favore.',
|
signature_is_too_small_or_simple_please_redraw: 'La firma è troppo piccola o semplice. Ridisegnala, per favore.',
|
||||||
browser_privacy_settings_block_canvas: 'Le impostazioni sulla privacy del browser limitano l\'uso dell\'area di disegno. Utilizza un browser o dispositivo diverso oppure disattiva le impostazioni sulla privacy che bloccano il canvas per firmare.',
|
browser_privacy_settings_block_canvas: 'Le impostazioni sulla privacy del browser limitano l\'uso dell\'area di disegno. Utilizza un browser o dispositivo diverso oppure disattiva le impostazioni sulla privacy che bloccano il canvas per firmare.',
|
||||||
wait_countdown_seconds: 'Attendi {countdown} secondi'
|
wait_countdown_seconds: 'Attendi {countdown} secondi',
|
||||||
|
enter_screen_reader_mode: 'Attiva modalità lettore di schermo'
|
||||||
}
|
}
|
||||||
|
|
||||||
const de = {
|
const de = {
|
||||||
|
step: 'Schritt',
|
||||||
|
form_progress: 'Formularfortschritt',
|
||||||
|
close: 'Schließen',
|
||||||
|
uploaded_files: 'Hochgeladene Dateien',
|
||||||
|
signature_drawing_area: 'Unterschriftszeichenbereich. Verwenden Sie die Maus oder Berührung, um Ihre Unterschrift zu zeichnen.',
|
||||||
kba: 'KBA',
|
kba: 'KBA',
|
||||||
please_upload_an_image_file: 'Bitte laden Sie eine Bilddatei hoch',
|
please_upload_an_image_file: 'Bitte laden Sie eine Bilddatei hoch',
|
||||||
must_be_characters_length: 'Muss {number} Zeichen lang sein',
|
must_be_characters_length: 'Muss {number} Zeichen lang sein',
|
||||||
@@ -407,10 +430,16 @@ const de = {
|
|||||||
files: 'Dateien',
|
files: 'Dateien',
|
||||||
signature_is_too_small_or_simple_please_redraw: 'Die Unterschrift ist zu klein oder zu einfach. Bitte neu zeichnen.',
|
signature_is_too_small_or_simple_please_redraw: 'Die Unterschrift ist zu klein oder zu einfach. Bitte neu zeichnen.',
|
||||||
browser_privacy_settings_block_canvas: 'Die Datenschutzeinstellungen Ihres Browsers schränken die Nutzung der Zeichenfläche ein. Bitte verwenden Sie einen anderen Browser oder ein anderes Gerät oder deaktivieren Sie die Datenschutzeinstellungen, die Canvas blockieren, um zu unterschreiben.',
|
browser_privacy_settings_block_canvas: 'Die Datenschutzeinstellungen Ihres Browsers schränken die Nutzung der Zeichenfläche ein. Bitte verwenden Sie einen anderen Browser oder ein anderes Gerät oder deaktivieren Sie die Datenschutzeinstellungen, die Canvas blockieren, um zu unterschreiben.',
|
||||||
wait_countdown_seconds: 'Bitte {countdown} Sekunden warten'
|
wait_countdown_seconds: 'Bitte {countdown} Sekunden warten',
|
||||||
|
enter_screen_reader_mode: 'Screenreader-Modus aktivieren'
|
||||||
}
|
}
|
||||||
|
|
||||||
const fr = {
|
const fr = {
|
||||||
|
step: 'Étape',
|
||||||
|
form_progress: 'Progression du formulaire',
|
||||||
|
close: 'Fermer',
|
||||||
|
uploaded_files: 'Fichiers téléchargés',
|
||||||
|
signature_drawing_area: 'Zone de dessin de signature. Utilisez la souris ou le toucher pour dessiner votre signature.',
|
||||||
kba: 'KBA',
|
kba: 'KBA',
|
||||||
please_upload_an_image_file: 'Veuillez téléverser un fichier image',
|
please_upload_an_image_file: 'Veuillez téléverser un fichier image',
|
||||||
must_be_characters_length: 'Doit comporter {number} caractères',
|
must_be_characters_length: 'Doit comporter {number} caractères',
|
||||||
@@ -510,10 +539,16 @@ const fr = {
|
|||||||
files: 'Fichiers',
|
files: 'Fichiers',
|
||||||
signature_is_too_small_or_simple_please_redraw: 'La signature est trop petite ou trop simple. Veuillez la redessiner.',
|
signature_is_too_small_or_simple_please_redraw: 'La signature est trop petite ou trop simple. Veuillez la redessiner.',
|
||||||
browser_privacy_settings_block_canvas: 'Les paramètres de confidentialité de votre navigateur empêchent l\'utilisation du canevas de dessin. Veuillez utiliser un autre navigateur ou appareil, ou désactiver les paramètres de confidentialité qui bloquent le canevas pour signer.',
|
browser_privacy_settings_block_canvas: 'Les paramètres de confidentialité de votre navigateur empêchent l\'utilisation du canevas de dessin. Veuillez utiliser un autre navigateur ou appareil, ou désactiver les paramètres de confidentialité qui bloquent le canevas pour signer.',
|
||||||
wait_countdown_seconds: 'Veuillez patienter {countdown} secondes'
|
wait_countdown_seconds: 'Veuillez patienter {countdown} secondes',
|
||||||
|
enter_screen_reader_mode: 'Activer le mode lecteur d\'écran'
|
||||||
}
|
}
|
||||||
|
|
||||||
const pl = {
|
const pl = {
|
||||||
|
step: 'Krok',
|
||||||
|
form_progress: 'Postęp formularza',
|
||||||
|
close: 'Zamknij',
|
||||||
|
uploaded_files: 'Przesłane pliki',
|
||||||
|
signature_drawing_area: 'Obszar rysowania podpisu. Użyj myszy lub dotyku, aby narysować swój podpis.',
|
||||||
kba: 'KBA',
|
kba: 'KBA',
|
||||||
please_upload_an_image_file: 'Proszę przesłać plik obrazu',
|
please_upload_an_image_file: 'Proszę przesłać plik obrazu',
|
||||||
must_be_characters_length: 'Musi mieć długość {number} znaków',
|
must_be_characters_length: 'Musi mieć długość {number} znaków',
|
||||||
@@ -539,7 +574,7 @@ const pl = {
|
|||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Zeskanuj kod QR za pomocą aplikacji aparatu, aby otworzyć formularz na telefonie i narysować swój podpis',
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Zeskanuj kod QR za pomocą aplikacji aparatu, aby otworzyć formularz na telefonie i narysować swój podpis',
|
||||||
by_clicking_you_agree_to_the: 'Klikając na "{button}", zgadzasz się na',
|
by_clicking_you_agree_to_the: 'Klikając na "{button}", zgadzasz się na',
|
||||||
electronic_signature_disclosure: 'Ujawnienie Elektronicznej Sygnatury',
|
electronic_signature_disclosure: 'Ujawnienie Elektronicznej Sygnatury',
|
||||||
esignature_disclosure: 'Ujawnienie ePodpisu',
|
esignature_disclosure: 'informację o e-podpisie',
|
||||||
minimize: 'Zminimalizuj',
|
minimize: 'Zminimalizuj',
|
||||||
text: 'Tekst',
|
text: 'Tekst',
|
||||||
already_paid: 'Już zapłacono',
|
already_paid: 'Już zapłacono',
|
||||||
@@ -607,16 +642,22 @@ const pl = {
|
|||||||
toggle_multiline_text: 'Przełącz Tekst Wielolinijkowy',
|
toggle_multiline_text: 'Przełącz Tekst Wielolinijkowy',
|
||||||
email_has_been_sent: 'E-mail został wysłany',
|
email_has_been_sent: 'E-mail został wysłany',
|
||||||
processing: 'Przetwarzanie',
|
processing: 'Przetwarzanie',
|
||||||
pay_with_stripe: 'Płatność za pomocą Stripe',
|
pay_with_stripe: 'Zapłać za pomocą Stripe',
|
||||||
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.',
|
||||||
browser_privacy_settings_block_canvas: 'Ustawienia prywatności przeglądarki blokują użycie obszaru rysowania. Użyj innej przeglądarki lub urządzenia albo wyłącz ustawienia prywatności blokujące canvas, aby podpisać.',
|
browser_privacy_settings_block_canvas: 'Ustawienia prywatności przeglądarki blokują użycie obszaru rysowania. Użyj innej przeglądarki lub urządzenia albo wyłącz ustawienia prywatności blokujące canvas, aby podpisać.',
|
||||||
wait_countdown_seconds: 'Poczekaj {countdown} sekund'
|
wait_countdown_seconds: 'Poczekaj {countdown} sekund',
|
||||||
|
enter_screen_reader_mode: 'Włącz tryb czytnika ekranu'
|
||||||
}
|
}
|
||||||
|
|
||||||
const uk = {
|
const uk = {
|
||||||
|
step: 'Крок',
|
||||||
|
form_progress: 'Прогрес форми',
|
||||||
|
close: 'Закрити',
|
||||||
|
uploaded_files: 'Завантажені файли',
|
||||||
|
signature_drawing_area: 'Область малювання підпису. Використовуйте мишу або дотик, щоб намалювати свій підпис.',
|
||||||
kba: 'KBA',
|
kba: 'KBA',
|
||||||
please_upload_an_image_file: 'Будь ласка, завантажте файл зображення',
|
please_upload_an_image_file: 'Будь ласка, завантажте файл зображення',
|
||||||
must_be_characters_length: 'Має містити {number} символів',
|
must_be_characters_length: 'Має містити {number} символів',
|
||||||
@@ -637,12 +678,12 @@ const uk = {
|
|||||||
authored_by: 'Автор',
|
authored_by: 'Автор',
|
||||||
select_a_reason: 'Виберіть причину',
|
select_a_reason: 'Виберіть причину',
|
||||||
value_is_invalid: 'Значення є неправильним',
|
value_is_invalid: 'Значення є неправильним',
|
||||||
verification_code_is_invalid: 'Код підтвердження є неправильним',
|
verification_code_is_invalid: 'Невірний код підтвердження',
|
||||||
sign_on_the_touchscreen: 'Підписати на сенсорному екрані',
|
sign_on_the_touchscreen: 'Підписати на сенсорному екрані',
|
||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Скануйте QR-код за допомогою програми камери, щоб відкрити форму на мобільному пристрої та намалювати свій підпис',
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Скануйте QR-код за допомогою програми камери, щоб відкрити форму на мобільному пристрої та намалювати свій підпис',
|
||||||
by_clicking_you_agree_to_the: 'Натиснувши на "{button}", ви погоджуєтеся з',
|
by_clicking_you_agree_to_the: 'Натиснувши на "{button}", ви погоджуєтеся з',
|
||||||
electronic_signature_disclosure: 'Розголошення Електронного Підпису',
|
electronic_signature_disclosure: 'інформацією про е-підпис',
|
||||||
esignature_disclosure: 'Розголошення еПідпису',
|
esignature_disclosure: 'інформацією про е-підпис',
|
||||||
minimize: 'Зменшити',
|
minimize: 'Зменшити',
|
||||||
text: 'Текст',
|
text: 'Текст',
|
||||||
already_paid: 'Вже оплачено',
|
already_paid: 'Вже оплачено',
|
||||||
@@ -710,16 +751,22 @@ const uk = {
|
|||||||
toggle_multiline_text: 'Перемкнути Багаторядковий Текст',
|
toggle_multiline_text: 'Перемкнути Багаторядковий Текст',
|
||||||
email_has_been_sent: 'Електронний лист був відправлений',
|
email_has_been_sent: 'Електронний лист був відправлений',
|
||||||
processing: 'Обробка',
|
processing: 'Обробка',
|
||||||
pay_with_strip: 'Сплатити за допомогою Stripe',
|
pay_with_stripe: 'Сплатити за допомогою Stripe',
|
||||||
reupload: 'Перезавантажити',
|
reupload: 'Перезавантажити',
|
||||||
upload: 'Завантажити',
|
upload: 'Завантажити',
|
||||||
files: 'Файли',
|
files: 'Файли',
|
||||||
signature_is_too_small_or_simple_please_redraw: 'Підпис занадто маленький або надто простий. Будь ласка, перемалюйте.',
|
signature_is_too_small_or_simple_please_redraw: 'Підпис занадто маленький або надто простий. Будь ласка, перемалюйте.',
|
||||||
browser_privacy_settings_block_canvas: 'Налаштування конфіденційності вашого браузера блокують використання полотна для малювання. Будь ласка, скористайтеся іншим браузером або пристроєм, або вимкніть налаштування конфіденційності, що блокують canvas, щоб підписати.',
|
browser_privacy_settings_block_canvas: 'Налаштування конфіденційності вашого браузера блокують використання полотна для малювання. Будь ласка, скористайтеся іншим браузером або пристроєм, або вимкніть налаштування конфіденційності, що блокують canvas, щоб підписати.',
|
||||||
wait_countdown_seconds: 'Зачекайте {countdown} секунд'
|
wait_countdown_seconds: 'Зачекайте {countdown} секунд',
|
||||||
|
enter_screen_reader_mode: 'Увімкнути режим читання з екрану'
|
||||||
}
|
}
|
||||||
|
|
||||||
const cs = {
|
const cs = {
|
||||||
|
step: 'Krok',
|
||||||
|
form_progress: 'Průběh formuláře',
|
||||||
|
close: 'Zavřít',
|
||||||
|
uploaded_files: 'Nahrané soubory',
|
||||||
|
signature_drawing_area: 'Oblast pro kreslení podpisu. Použijte myš nebo dotyk k nakreslení podpisu.',
|
||||||
kba: 'KBA',
|
kba: 'KBA',
|
||||||
please_upload_an_image_file: 'Nahrajte prosím obrázkový soubor',
|
please_upload_an_image_file: 'Nahrajte prosím obrázkový soubor',
|
||||||
must_be_characters_length: 'Musí mít délku {number} znaků',
|
must_be_characters_length: 'Musí mít délku {number} znaků',
|
||||||
@@ -727,7 +774,7 @@ const cs = {
|
|||||||
verify_id: 'Ověřit ID',
|
verify_id: 'Ověřit ID',
|
||||||
identity_verification: 'Ověření identity',
|
identity_verification: 'Ověření identity',
|
||||||
complete: 'Dokončit',
|
complete: 'Dokončit',
|
||||||
fill_all_required_fields_to_complete: 'Please complete all mandatory fields',
|
fill_all_required_fields_to_complete: 'Vyplňte povinná pole pro dokončení',
|
||||||
sign_and_complete: 'Podepsat a dokončit',
|
sign_and_complete: 'Podepsat a dokončit',
|
||||||
invite: 'Pozvat',
|
invite: 'Pozvat',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
@@ -735,8 +782,8 @@ const cs = {
|
|||||||
reviewed: 'Zkontrolováno',
|
reviewed: 'Zkontrolováno',
|
||||||
other: 'Jiné',
|
other: 'Jiné',
|
||||||
authored_by_me: 'Autorem jsem já',
|
authored_by_me: 'Autorem jsem já',
|
||||||
approved_by: 'Schváleno kým',
|
approved_by: 'Schválil(a)',
|
||||||
reviewed_by: 'Zkontrolováno kým',
|
reviewed_by: 'Zkontroloval(a)',
|
||||||
authored_by: 'Autorem',
|
authored_by: 'Autorem',
|
||||||
select_a_reason: 'Vyberte důvod',
|
select_a_reason: 'Vyberte důvod',
|
||||||
value_is_invalid: 'Hodnota je neplatná',
|
value_is_invalid: 'Hodnota je neplatná',
|
||||||
@@ -745,7 +792,7 @@ const cs = {
|
|||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Naskenujte QR kód pomocí aplikace fotoaparátu, abyste otevřeli formulář na mobilním zařízení a nakreslili svůj podpis',
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Naskenujte QR kód pomocí aplikace fotoaparátu, abyste otevřeli formulář na mobilním zařízení a nakreslili svůj podpis',
|
||||||
by_clicking_you_agree_to_the: 'Kliknutím na "{button}" souhlasíte s',
|
by_clicking_you_agree_to_the: 'Kliknutím na "{button}" souhlasíte s',
|
||||||
electronic_signature_disclosure: 'Zveřejněním Elektronického Podpisu',
|
electronic_signature_disclosure: 'Zveřejněním Elektronického Podpisu',
|
||||||
esignature_disclosure: 'Zveřejnění ePodpisu',
|
esignature_disclosure: 'informací o e-podpisu',
|
||||||
already_paid: 'Už zaplaceno',
|
already_paid: 'Už zaplaceno',
|
||||||
minimize: 'Minimalizovat',
|
minimize: 'Minimalizovat',
|
||||||
text: 'Text',
|
text: 'Text',
|
||||||
@@ -813,16 +860,22 @@ const cs = {
|
|||||||
toggle_multiline_text: 'Přepnout Víceřádkový Text',
|
toggle_multiline_text: 'Přepnout Víceřádkový Text',
|
||||||
email_has_been_sent: 'E-mail byl odeslán',
|
email_has_been_sent: 'E-mail byl odeslán',
|
||||||
processing: 'Zpracování',
|
processing: 'Zpracování',
|
||||||
pay_with_stripe: 'Zaplacení přes Stripe',
|
pay_with_stripe: 'Zaplatit přes Stripe',
|
||||||
reupload: 'Znovu nahrát',
|
reupload: 'Znovu nahrát',
|
||||||
upload: 'Nahrát',
|
upload: 'Nahrát',
|
||||||
files: 'Soubory',
|
files: 'Soubory',
|
||||||
signature_is_too_small_or_simple_please_redraw: 'Podpis je příliš malý nebo jednoduchý. Nakreslete jej prosím znovu.',
|
signature_is_too_small_or_simple_please_redraw: 'Podpis je příliš malý nebo jednoduchý. Nakreslete jej prosím znovu.',
|
||||||
browser_privacy_settings_block_canvas: 'Nastavení soukromí vašeho prohlížeče omezuje použití kreslicího plátna. Použijte prosím jiný prohlížeč nebo zařízení, nebo vypněte nastavení soukromí blokující canvas pro podepsání.',
|
browser_privacy_settings_block_canvas: 'Nastavení soukromí vašeho prohlížeče omezuje použití kreslicího plátna. Použijte prosím jiný prohlížeč nebo zařízení, nebo vypněte nastavení soukromí blokující canvas pro podepsání.',
|
||||||
wait_countdown_seconds: 'Počkejte {countdown} sekund'
|
wait_countdown_seconds: 'Počkejte {countdown} sekund',
|
||||||
|
enter_screen_reader_mode: 'Zapnout režim čtečky obrazovky'
|
||||||
}
|
}
|
||||||
|
|
||||||
const pt = {
|
const pt = {
|
||||||
|
step: 'Passo',
|
||||||
|
form_progress: 'Progresso do formulário',
|
||||||
|
close: 'Fechar',
|
||||||
|
uploaded_files: 'Arquivos enviados',
|
||||||
|
signature_drawing_area: 'Área de desenho da assinatura. Use o mouse ou toque para desenhar sua assinatura.',
|
||||||
kba: 'KBA',
|
kba: 'KBA',
|
||||||
please_upload_an_image_file: 'Por favor, envie um arquivo de imagem',
|
please_upload_an_image_file: 'Por favor, envie um arquivo de imagem',
|
||||||
must_be_characters_length: 'Deve ter {number} caracteres',
|
must_be_characters_length: 'Deve ter {number} caracteres',
|
||||||
@@ -837,18 +890,18 @@ const pt = {
|
|||||||
approved: 'Aprovado',
|
approved: 'Aprovado',
|
||||||
reviewed: 'Revisado',
|
reviewed: 'Revisado',
|
||||||
other: 'Outro',
|
other: 'Outro',
|
||||||
authored_by_me: 'Autorizado por mim',
|
authored_by_me: 'Escrito por mim',
|
||||||
approved_by: 'Aprovado por',
|
approved_by: 'Aprovado por',
|
||||||
reviewed_by: 'Revisado por',
|
reviewed_by: 'Revisado por',
|
||||||
authored_by: 'Autorizado por',
|
authored_by: 'Escrito por',
|
||||||
select_a_reason: 'Selecione um motivo',
|
select_a_reason: 'Selecione um motivo',
|
||||||
value_is_invalid: 'Valor é inválido',
|
value_is_invalid: 'Valor é inválido',
|
||||||
verification_code_is_invalid: 'Código de verificação é inválido',
|
verification_code_is_invalid: 'Código de verificação é inválido',
|
||||||
sign_on_the_touchscreen: 'Assinar na tela sensível',
|
sign_on_the_touchscreen: 'Assinar na tela sensível',
|
||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Escaneie o código QR com o aplicativo da câmera para abrir o formulário no celular e desenhar sua assinatura',
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Escaneie o código QR com o aplicativo da câmera para abrir o formulário no celular e desenhar sua assinatura',
|
||||||
by_clicking_you_agree_to_the: 'Ao clicar em "{button}", você concorda com o',
|
by_clicking_you_agree_to_the: 'Ao clicar em "{button}", você concorda com a',
|
||||||
electronic_signature_disclosure: 'Divulgação de Assinatura Eletrônica',
|
electronic_signature_disclosure: 'Divulgação de Assinatura Eletrônica',
|
||||||
esignature_disclosure: 'Divulgação da eAssinatura',
|
esignature_disclosure: 'Informação sobre eAssinatura',
|
||||||
already_paid: 'Já pago',
|
already_paid: 'Já pago',
|
||||||
minimize: 'Minimizar',
|
minimize: 'Minimizar',
|
||||||
text: 'Texto',
|
text: 'Texto',
|
||||||
@@ -917,15 +970,21 @@ const pt = {
|
|||||||
email_has_been_sent: 'Email enviado',
|
email_has_been_sent: 'Email enviado',
|
||||||
processing: 'Processamento',
|
processing: 'Processamento',
|
||||||
pay_with_stripe: 'Pagar com Stripe',
|
pay_with_stripe: 'Pagar com Stripe',
|
||||||
reupload: 'Reenviar',
|
reupload: 'Recarregar',
|
||||||
upload: 'Carregar',
|
upload: 'Carregar',
|
||||||
files: 'Arquivos',
|
files: 'Arquivos',
|
||||||
signature_is_too_small_or_simple_please_redraw: 'A assinatura é muito pequena ou simples. Por favor, redesenhe.',
|
signature_is_too_small_or_simple_please_redraw: 'A assinatura é muito pequena ou simples. Por favor, redesenhe.',
|
||||||
browser_privacy_settings_block_canvas: 'As configurações de privacidade do seu navegador restringem o uso da área de desenho. Use um navegador ou dispositivo diferente, ou desative as configurações de privacidade que bloqueiam o canvas para assinar.',
|
browser_privacy_settings_block_canvas: 'As configurações de privacidade do seu navegador restringem o uso da área de desenho. Use um navegador ou dispositivo diferente, ou desative as configurações de privacidade que bloqueiam o canvas para assinar.',
|
||||||
wait_countdown_seconds: 'Aguarde {countdown} segundos'
|
wait_countdown_seconds: 'Aguarde {countdown} segundos',
|
||||||
|
enter_screen_reader_mode: 'Ativar modo leitor de tela'
|
||||||
}
|
}
|
||||||
|
|
||||||
const he = {
|
const he = {
|
||||||
|
step: 'שלב',
|
||||||
|
form_progress: 'התקדמות הטופס',
|
||||||
|
close: 'סגור',
|
||||||
|
uploaded_files: 'קבצים שהועלו',
|
||||||
|
signature_drawing_area: 'אזור ציור חתימה. השתמש בעכבר או במגע כדי לצייר את החתימה שלך.',
|
||||||
kba: 'KBA',
|
kba: 'KBA',
|
||||||
please_upload_an_image_file: 'אנא העלה קובץ תמונה',
|
please_upload_an_image_file: 'אנא העלה קובץ תמונה',
|
||||||
must_be_characters_length: 'חייב להיות באורך של {number} תווים',
|
must_be_characters_length: 'חייב להיות באורך של {number} תווים',
|
||||||
@@ -951,8 +1010,8 @@ const he = {
|
|||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'סרוק את קוד ה-QR באמצעות אפליקציית המצלמה כדי לפתוח את הטופס במובייל ולצייר את החתימה שלך',
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'סרוק את קוד ה-QR באמצעות אפליקציית המצלמה כדי לפתוח את הטופס במובייל ולצייר את החתימה שלך',
|
||||||
by_clicking_you_agree_to_the: 'על ידי לחיצה על "{button}", אתה מסכים ל',
|
by_clicking_you_agree_to_the: 'על ידי לחיצה על "{button}", אתה מסכים ל',
|
||||||
electronic_signature_disclosure: 'חשיפת חתימה אלקטרונית',
|
electronic_signature_disclosure: 'חשיפת חתימה אלקטרונית',
|
||||||
esignature_disclosure: 'חשיפת ה-eחתימה',
|
esignature_disclosure: 'חשיפת חתימה אלקטרונית',
|
||||||
minimize: 'לקטן',
|
minimize: 'מזער',
|
||||||
text: 'טקסט',
|
text: 'טקסט',
|
||||||
already_paid: 'כבר שולם',
|
already_paid: 'כבר שולם',
|
||||||
signature: 'חתימה',
|
signature: 'חתימה',
|
||||||
@@ -963,9 +1022,9 @@ const he = {
|
|||||||
number: 'מספר',
|
number: 'מספר',
|
||||||
image: 'תמונה',
|
image: 'תמונה',
|
||||||
file: 'קובץ',
|
file: 'קובץ',
|
||||||
pay: 'תשלום',
|
pay: 'שלם',
|
||||||
select: 'בחר',
|
select: 'בחר',
|
||||||
checkbox: 'תיק בחירה',
|
checkbox: 'סימון',
|
||||||
multiple: 'רב ערכים',
|
multiple: 'רב ערכים',
|
||||||
radio: 'רדיו',
|
radio: 'רדיו',
|
||||||
cells: 'תאים',
|
cells: 'תאים',
|
||||||
@@ -992,9 +1051,9 @@ const he = {
|
|||||||
download: 'הורדה',
|
download: 'הורדה',
|
||||||
clear: 'נקה',
|
clear: 'נקה',
|
||||||
redraw: 'צייר מחדש',
|
redraw: 'צייר מחדש',
|
||||||
draw_initials: 'צייר ציוני ראשי תיבות',
|
draw_initials: 'צייר ראשי תיבות',
|
||||||
type_signature_here: 'הקלד חתימה כאן',
|
type_signature_here: 'הקלד חתימה כאן',
|
||||||
type_initial_here: 'הקלד ציוני ראשי תיבות כאן',
|
type_initial_here: 'הקלד ראשי תיבות כאן',
|
||||||
form_has_been_completed: 'הטופס הושלם',
|
form_has_been_completed: 'הטופס הושלם',
|
||||||
document_has_been_signed: 'המסמך נחתם!',
|
document_has_been_signed: 'המסמך נחתם!',
|
||||||
documents_have_been_signed: 'המסמכים נחתמו!',
|
documents_have_been_signed: 'המסמכים נחתמו!',
|
||||||
@@ -1004,7 +1063,7 @@ const he = {
|
|||||||
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: 'קוד משתמש שש ספרות',
|
six_digits_code: 'קוד שש ספרות',
|
||||||
change_phone_number: 'שינוי מספר טלפון',
|
change_phone_number: 'שינוי מספר טלפון',
|
||||||
sending: 'שולח',
|
sending: 'שולח',
|
||||||
resend_code: 'שלח מחדש קוד',
|
resend_code: 'שלח מחדש קוד',
|
||||||
@@ -1013,7 +1072,7 @@ const he = {
|
|||||||
set_today: 'קבע היום',
|
set_today: 'קבע היום',
|
||||||
toggle_multiline_text: 'שנה בין טקסט במספר שורות לטקסט בשורה אחת',
|
toggle_multiline_text: 'שנה בין טקסט במספר שורות לטקסט בשורה אחת',
|
||||||
draw_signature: 'צייר חתימה',
|
draw_signature: 'צייר חתימה',
|
||||||
type_initial: 'הקלד ציוני ראשי תיבות',
|
type_initial: 'הקלד ראשי תיבות',
|
||||||
draw: 'צייר',
|
draw: 'צייר',
|
||||||
type: 'הקלד',
|
type: 'הקלד',
|
||||||
type_text: 'הקלד טקסט',
|
type_text: 'הקלד טקסט',
|
||||||
@@ -1025,15 +1084,21 @@ const he = {
|
|||||||
files: 'קבצים',
|
files: 'קבצים',
|
||||||
signature_is_too_small_or_simple_please_redraw: 'החתימה קטנה או פשוטה מדי. אנא חתום מחדש.',
|
signature_is_too_small_or_simple_please_redraw: 'החתימה קטנה או פשוטה מדי. אנא חתום מחדש.',
|
||||||
browser_privacy_settings_block_canvas: 'הגדרות הפרטיות של הדפדפן שלך מגבילות את השימוש באזור הציור. אנא השתמש בדפדפן או מכשיר אחר, או בטל את הגדרות הפרטיות החוסמות canvas כדי לחתום.',
|
browser_privacy_settings_block_canvas: 'הגדרות הפרטיות של הדפדפן שלך מגבילות את השימוש באזור הציור. אנא השתמש בדפדפן או מכשיר אחר, או בטל את הגדרות הפרטיות החוסמות canvas כדי לחתום.',
|
||||||
wait_countdown_seconds: 'המתן {countdown} שניות'
|
wait_countdown_seconds: 'המתן {countdown} שניות',
|
||||||
|
enter_screen_reader_mode: 'הפעל מצב קורא מסך'
|
||||||
}
|
}
|
||||||
|
|
||||||
const nl = {
|
const nl = {
|
||||||
|
step: 'Stap',
|
||||||
|
form_progress: 'Formuliervoortgang',
|
||||||
|
close: 'Sluiten',
|
||||||
|
uploaded_files: 'Geüploade bestanden',
|
||||||
|
signature_drawing_area: 'Handtekening tekengebied. Gebruik de muis of aanraking om uw handtekening te tekenen.',
|
||||||
kba: 'KBA',
|
kba: 'KBA',
|
||||||
please_upload_an_image_file: 'Upload alstublieft een afbeeldingsbestand',
|
please_upload_an_image_file: 'Upload alstublieft een afbeeldingsbestand',
|
||||||
must_be_characters_length: 'Moet {number} tekens lang zijn',
|
must_be_characters_length: 'Moet {number} tekens lang zijn',
|
||||||
complete_all_required_fields_to_proceed_with_identity_verification: 'Vul alle verplichte velden in om door te gaan met de identiteitsverificatie.',
|
complete_all_required_fields_to_proceed_with_identity_verification: 'Vul alle verplichte velden in om door te gaan met de identiteitsverificatie.',
|
||||||
verify_id: 'Verifiëren ID',
|
verify_id: 'ID verifiëren',
|
||||||
identity_verification: 'Identiteitsverificatie',
|
identity_verification: 'Identiteitsverificatie',
|
||||||
complete: 'Voltooien',
|
complete: 'Voltooien',
|
||||||
fill_all_required_fields_to_complete: 'Vul alle verplichte velden in om te voltooien',
|
fill_all_required_fields_to_complete: 'Vul alle verplichte velden in om te voltooien',
|
||||||
@@ -1081,7 +1146,7 @@ const nl = {
|
|||||||
continue: 'Doorgaan',
|
continue: 'Doorgaan',
|
||||||
sign_now: 'Nu ondertekenen',
|
sign_now: 'Nu ondertekenen',
|
||||||
type_here_: 'Typ hier...',
|
type_here_: 'Typ hier...',
|
||||||
optional: 'Optioneel',
|
optional: 'optioneel',
|
||||||
option: 'Optie',
|
option: 'Optie',
|
||||||
appears_on: 'Verschijnt op',
|
appears_on: 'Verschijnt op',
|
||||||
page: 'Pagina',
|
page: 'Pagina',
|
||||||
@@ -1105,11 +1170,11 @@ const nl = {
|
|||||||
powered_by: 'Aangedreven door',
|
powered_by: 'Aangedreven door',
|
||||||
please_check_the_box_to_continue: 'Vink het vakje aan om door te gaan.',
|
please_check_the_box_to_continue: 'Vink het vakje aan om door te gaan.',
|
||||||
open_source_documents_software: 'Open source documenten software',
|
open_source_documents_software: 'Open source documenten software',
|
||||||
verified_phone_number: 'Geverifieerd telefoonnummer',
|
verified_phone_number: 'Telefoonnummer verifiëren',
|
||||||
use_international_format: 'Gebruik internationaal formaat: +1xxx',
|
use_international_format: 'Gebruik internationaal formaat: +1xxx',
|
||||||
six_digits_code: '6-cijferige code',
|
six_digits_code: '6-cijferige code',
|
||||||
change_phone_number: 'Wijzig telefoonnummer',
|
change_phone_number: 'Wijzig telefoonnummer',
|
||||||
sending: 'Voltooien...',
|
sending: 'Verzenden...',
|
||||||
resend_code: 'Code opnieuw verzenden',
|
resend_code: 'Code opnieuw verzenden',
|
||||||
verification_code_has_been_resent: 'Verificatiecode is opnieuw verzonden via SMS',
|
verification_code_has_been_resent: 'Verificatiecode is opnieuw verzonden via SMS',
|
||||||
please_fill_all_required_fields: 'Vul alle verplichte velden in',
|
please_fill_all_required_fields: 'Vul alle verplichte velden in',
|
||||||
@@ -1128,10 +1193,16 @@ const nl = {
|
|||||||
files: 'Bestanden',
|
files: 'Bestanden',
|
||||||
signature_is_too_small_or_simple_please_redraw: 'De handtekening is te klein of te eenvoudig. Teken opnieuw.',
|
signature_is_too_small_or_simple_please_redraw: 'De handtekening is te klein of te eenvoudig. Teken opnieuw.',
|
||||||
browser_privacy_settings_block_canvas: 'De privacyinstellingen van uw browser beperken het gebruik van het tekenveld. Gebruik een andere browser of ander apparaat, of schakel de privacyinstellingen uit die canvas blokkeren om te ondertekenen.',
|
browser_privacy_settings_block_canvas: 'De privacyinstellingen van uw browser beperken het gebruik van het tekenveld. Gebruik een andere browser of ander apparaat, of schakel de privacyinstellingen uit die canvas blokkeren om te ondertekenen.',
|
||||||
wait_countdown_seconds: 'Wacht {countdown} seconden'
|
wait_countdown_seconds: 'Wacht {countdown} seconden',
|
||||||
|
enter_screen_reader_mode: 'Schermlezer-modus inschakelen'
|
||||||
}
|
}
|
||||||
|
|
||||||
const ar = {
|
const ar = {
|
||||||
|
step: 'خطوة',
|
||||||
|
form_progress: 'تقدم النموذج',
|
||||||
|
close: 'إغلاق',
|
||||||
|
uploaded_files: 'الملفات المرفوعة',
|
||||||
|
signature_drawing_area: 'منطقة رسم التوقيع. استخدم الماوس أو اللمس لرسم توقيعك.',
|
||||||
kba: 'KBA',
|
kba: 'KBA',
|
||||||
please_upload_an_image_file: 'يرجى تحميل ملف صورة',
|
please_upload_an_image_file: 'يرجى تحميل ملف صورة',
|
||||||
must_be_characters_length: 'يجب أن يكون الطول {number} حرفًا',
|
must_be_characters_length: 'يجب أن يكون الطول {number} حرفًا',
|
||||||
@@ -1161,7 +1232,7 @@ const ar = {
|
|||||||
esignature_disclosure: 'كشف التوقيع الإلكتروني',
|
esignature_disclosure: 'كشف التوقيع الإلكتروني',
|
||||||
text: 'نص',
|
text: 'نص',
|
||||||
signature: 'توقيع',
|
signature: 'توقيع',
|
||||||
initials: 'الاختصارات',
|
initials: 'حروف أولى',
|
||||||
date: 'تاريخ',
|
date: 'تاريخ',
|
||||||
number: 'رقم',
|
number: 'رقم',
|
||||||
digitally_signed_by: 'تم التوقيع رقميًا بواسطة',
|
digitally_signed_by: 'تم التوقيع رقميًا بواسطة',
|
||||||
@@ -1198,9 +1269,9 @@ const ar = {
|
|||||||
download: 'تحميل',
|
download: 'تحميل',
|
||||||
clear: 'مسح',
|
clear: 'مسح',
|
||||||
redraw: 'إعادة الرسم',
|
redraw: 'إعادة الرسم',
|
||||||
draw_initials: 'ارسم الاختصارات',
|
draw_initials: 'ارسم حروف أولى',
|
||||||
type_signature_here: 'اكتب التوقيع هنا',
|
type_signature_here: 'اكتب التوقيع هنا',
|
||||||
type_initial_here: 'اكتب الاختصارات هنا',
|
type_initial_here: 'اكتب حروف أولى هنا',
|
||||||
form_has_been_completed: 'تم إكمال النموذج!',
|
form_has_been_completed: 'تم إكمال النموذج!',
|
||||||
document_has_been_signed: 'تم توقيع الوثيقة!',
|
document_has_been_signed: 'تم توقيع الوثيقة!',
|
||||||
documents_have_been_signed: 'تم توقيع الوثائق!',
|
documents_have_been_signed: 'تم توقيع الوثائق!',
|
||||||
@@ -1219,22 +1290,28 @@ const ar = {
|
|||||||
set_today: 'تعيين اليوم',
|
set_today: 'تعيين اليوم',
|
||||||
toggle_multiline_text: 'تبديل النصوص متعددة الأسطر',
|
toggle_multiline_text: 'تبديل النصوص متعددة الأسطر',
|
||||||
draw_signature: 'ارسم التوقيع',
|
draw_signature: 'ارسم التوقيع',
|
||||||
type_initial: 'اكتب الاختصارات',
|
type_initial: 'اكتب حروف أولى',
|
||||||
draw: 'ارسم',
|
draw: 'ارسم',
|
||||||
type: 'اكتب',
|
type: 'اكتب',
|
||||||
type_text: 'اكتب نصًا',
|
type_text: 'اكتب نصًا',
|
||||||
email_has_been_sent: 'تم إرسال البريد الإلكتروني',
|
email_has_been_sent: 'تم إرسال البريد الإلكتروني',
|
||||||
processing: 'جارٍ المعالجة',
|
processing: 'جارٍ المعالجة',
|
||||||
pay_with_strip: 'الدفع بواسطة Stripe',
|
pay_with_stripe: 'الدفع بواسطة Stripe',
|
||||||
reupload: 'إعادة التحميل',
|
reupload: 'إعادة التحميل',
|
||||||
upload: 'تحميل',
|
upload: 'تحميل',
|
||||||
files: 'الملفات',
|
files: 'الملفات',
|
||||||
signature_is_too_small_or_simple_please_redraw: 'التوقيع صغير جدًا أو بسيط جدًا. يرجى إعادة رسمه.',
|
signature_is_too_small_or_simple_please_redraw: 'التوقيع صغير جدًا أو بسيط جدًا. يرجى إعادة رسمه.',
|
||||||
browser_privacy_settings_block_canvas: 'إعدادات الخصوصية في متصفحك تمنع استخدام لوحة الرسم. يرجى استخدام متصفح أو جهاز مختلف، أو تعطيل إعدادات الخصوصية التي تحظر canvas للتوقيع.',
|
browser_privacy_settings_block_canvas: 'إعدادات الخصوصية في متصفحك تمنع استخدام لوحة الرسم. يرجى استخدام متصفح أو جهاز مختلف، أو تعطيل إعدادات الخصوصية التي تحظر canvas للتوقيع.',
|
||||||
wait_countdown_seconds: 'انتظر {countdown} ثانية'
|
wait_countdown_seconds: 'انتظر {countdown} ثانية',
|
||||||
|
enter_screen_reader_mode: 'تفعيل وضع قارئ الشاشة'
|
||||||
}
|
}
|
||||||
|
|
||||||
const ko = {
|
const ko = {
|
||||||
|
step: '단계',
|
||||||
|
form_progress: '양식 진행',
|
||||||
|
close: '닫기',
|
||||||
|
uploaded_files: '업로드된 파일',
|
||||||
|
signature_drawing_area: '서명 그리기 영역. 마우스 또는 터치를 사용하여 서명을 그리세요.',
|
||||||
kba: 'KBA',
|
kba: 'KBA',
|
||||||
please_upload_an_image_file: '이미지 파일을 업로드해 주세요',
|
please_upload_an_image_file: '이미지 파일을 업로드해 주세요',
|
||||||
must_be_characters_length: '{number}자여야 합니다',
|
must_be_characters_length: '{number}자여야 합니다',
|
||||||
@@ -1266,10 +1343,10 @@ const ko = {
|
|||||||
signature: '서명',
|
signature: '서명',
|
||||||
initials: '이니셜',
|
initials: '이니셜',
|
||||||
date: '날짜',
|
date: '날짜',
|
||||||
digitally_signed_by: '디지털 서명이 완료되었습니다',
|
digitally_signed_by: '디지털 서명자:',
|
||||||
reason: '이유',
|
reason: '이유',
|
||||||
number: '숫자',
|
number: '숫자',
|
||||||
pay: '급여',
|
pay: '결제',
|
||||||
image: '이미지',
|
image: '이미지',
|
||||||
take_photo: '사진 찍기',
|
take_photo: '사진 찍기',
|
||||||
file: '파일',
|
file: '파일',
|
||||||
@@ -1334,10 +1411,16 @@ const ko = {
|
|||||||
files: '파일',
|
files: '파일',
|
||||||
signature_is_too_small_or_simple_please_redraw: '서명이 너무 작거나 단순합니다. 다시 그려주세요.',
|
signature_is_too_small_or_simple_please_redraw: '서명이 너무 작거나 단순합니다. 다시 그려주세요.',
|
||||||
browser_privacy_settings_block_canvas: '브라우저 개인정보 보호 설정으로 인해 그리기 캔버스를 사용할 수 없습니다. 다른 브라우저나 기기를 사용하거나, 서명을 위해 캔버스를 차단하는 개인정보 보호 설정을 비활성화해 주세요.',
|
browser_privacy_settings_block_canvas: '브라우저 개인정보 보호 설정으로 인해 그리기 캔버스를 사용할 수 없습니다. 다른 브라우저나 기기를 사용하거나, 서명을 위해 캔버스를 차단하는 개인정보 보호 설정을 비활성화해 주세요.',
|
||||||
wait_countdown_seconds: '{countdown}초 기다리세요'
|
wait_countdown_seconds: '{countdown}초 기다리세요',
|
||||||
|
enter_screen_reader_mode: '스크린 리더 모드 활성화'
|
||||||
}
|
}
|
||||||
|
|
||||||
const ja = {
|
const ja = {
|
||||||
|
step: 'ステップ',
|
||||||
|
form_progress: 'フォームの進捗',
|
||||||
|
close: '閉じる',
|
||||||
|
uploaded_files: 'アップロードされたファイル',
|
||||||
|
signature_drawing_area: '署名描画エリア。マウスまたはタッチを使用して署名を描いてください。',
|
||||||
kba: 'KBA',
|
kba: 'KBA',
|
||||||
please_upload_an_image_file: '画像ファイルをアップロードしてください',
|
please_upload_an_image_file: '画像ファイルをアップロードしてください',
|
||||||
must_be_characters_length: '{number}文字でなければなりません',
|
must_be_characters_length: '{number}文字でなければなりません',
|
||||||
@@ -1437,7 +1520,8 @@ const ja = {
|
|||||||
files: 'ファイル',
|
files: 'ファイル',
|
||||||
signature_is_too_small_or_simple_please_redraw: '署名が小さすぎるか単純すぎます。もう一度描いてください。',
|
signature_is_too_small_or_simple_please_redraw: '署名が小さすぎるか単純すぎます。もう一度描いてください。',
|
||||||
browser_privacy_settings_block_canvas: 'ブラウザのプライバシー設定により、描画キャンバスの使用が制限されています。別のブラウザまたはデバイスを使用するか、署名するためにキャンバスをブロックするプライバシー設定を無効にしてください。',
|
browser_privacy_settings_block_canvas: 'ブラウザのプライバシー設定により、描画キャンバスの使用が制限されています。別のブラウザまたはデバイスを使用するか、署名するためにキャンバスをブロックするプライバシー設定を無効にしてください。',
|
||||||
wait_countdown_seconds: '{countdown} 秒お待ちください'
|
wait_countdown_seconds: '{countdown} 秒お待ちください',
|
||||||
|
enter_screen_reader_mode: 'スクリーンリーダーモードを有効にする'
|
||||||
}
|
}
|
||||||
|
|
||||||
const i18n = { en, es, it, de, fr, pl, uk, cs, pt, he, nl, ar, ko, ja }
|
const i18n = { en, es, it, de, fr, pl, uk, cs, pt, he, nl, ar, ko, ja }
|
||||||
|
|||||||
@@ -18,13 +18,17 @@
|
|||||||
class="btn btn-outline btn-sm reupload-button"
|
class="btn btn-outline btn-sm reupload-button"
|
||||||
@click.prevent="remove"
|
@click.prevent="remove"
|
||||||
>
|
>
|
||||||
<IconReload :width="16" />
|
<IconReload
|
||||||
|
:width="16"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
{{ t('reupload') }}
|
{{ t('reupload') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<img
|
<img
|
||||||
:src="attachmentsIndex[modelValue].url"
|
:src="attachmentsIndex[modelValue].url"
|
||||||
|
:alt="field.name || t('image')"
|
||||||
class="h-52 border border-base-300 rounded mx-auto uploaded-image-preview"
|
class="h-52 border border-base-300 rounded mx-auto uploaded-image-preview"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,43 +22,60 @@
|
|||||||
class="md:tooltip"
|
class="md:tooltip"
|
||||||
:data-tip="t('type_initial')"
|
:data-tip="t('type_initial')"
|
||||||
>
|
>
|
||||||
<a
|
<button
|
||||||
id="type_text_button"
|
id="type_text_button"
|
||||||
href="#"
|
type="button"
|
||||||
|
:aria-label="t('type_initial')"
|
||||||
class="btn btn-outline font-medium btn-sm type-text-button"
|
class="btn btn-outline font-medium btn-sm type-text-button"
|
||||||
@click.prevent="toggleTextInput"
|
@click="toggleTextInput"
|
||||||
>
|
>
|
||||||
<IconTextSize :width="16" />
|
<IconTextSize
|
||||||
|
:width="16"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
<span class="hidden sm:inline">
|
<span class="hidden sm:inline">
|
||||||
{{ t('type') }}
|
{{ t('type') }}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-else
|
v-else
|
||||||
class="md:tooltip ml-2"
|
class="md:tooltip ml-2"
|
||||||
:data-tip="t('draw_initials')"
|
:data-tip="t('draw_initials')"
|
||||||
>
|
>
|
||||||
<a
|
<button
|
||||||
id="type_text_button"
|
id="type_text_button"
|
||||||
href="#"
|
type="button"
|
||||||
|
:aria-label="t('draw_initials')"
|
||||||
class="btn btn-outline font-medium btn-sm type-text-button"
|
class="btn btn-outline font-medium btn-sm type-text-button"
|
||||||
@click.prevent="toggleTextInput"
|
@click="toggleTextInput"
|
||||||
>
|
>
|
||||||
<IconSignature :width="16" />
|
<IconSignature
|
||||||
|
:width="16"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
<span class="hidden sm:inline">
|
<span class="hidden sm:inline">
|
||||||
{{ t('draw') }}
|
{{ t('draw') }}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="md:tooltip"
|
class="md:tooltip"
|
||||||
:data-tip="t('click_to_upload')"
|
:data-tip="t('click_to_upload')"
|
||||||
>
|
>
|
||||||
<label class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap upload-image-button">
|
<button
|
||||||
<IconUpload :width="16" />
|
type="button"
|
||||||
|
:aria-label="t('click_to_upload')"
|
||||||
|
class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap upload-image-button"
|
||||||
|
@click="$refs.uploadInput.click()"
|
||||||
|
>
|
||||||
|
<IconUpload
|
||||||
|
:width="16"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
<input
|
<input
|
||||||
:key="uploadImageInputKey"
|
:key="uploadImageInputKey"
|
||||||
|
ref="uploadInput"
|
||||||
type="file"
|
type="file"
|
||||||
hidden
|
hidden
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
@@ -67,37 +84,45 @@
|
|||||||
<span class="hidden sm:inline">
|
<span class="hidden sm:inline">
|
||||||
{{ t('upload') }}
|
{{ t('upload') }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
<a
|
<button
|
||||||
v-if="modelValue || computedPreviousValue"
|
v-if="modelValue || computedPreviousValue"
|
||||||
href="#"
|
type="button"
|
||||||
class="btn font-medium btn-outline btn-sm clear-canvas-button"
|
class="btn font-medium btn-outline btn-sm clear-canvas-button"
|
||||||
@click.prevent="remove"
|
@click="remove"
|
||||||
>
|
>
|
||||||
<IconReload :width="16" />
|
<IconReload
|
||||||
|
:width="16"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
{{ t('clear') }}
|
{{ t('clear') }}
|
||||||
</a>
|
</button>
|
||||||
<a
|
<button
|
||||||
v-else
|
v-else
|
||||||
href="#"
|
type="button"
|
||||||
class="btn font-medium btn-outline btn-sm clear-canvas-button"
|
class="btn font-medium btn-outline btn-sm clear-canvas-button"
|
||||||
@click.prevent="clear"
|
@click="clear"
|
||||||
>
|
>
|
||||||
<IconReload :width="16" />
|
<IconReload
|
||||||
|
:width="16"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
{{ t('clear') }}
|
{{ t('clear') }}
|
||||||
</a>
|
</button>
|
||||||
<a
|
<button
|
||||||
|
type="button"
|
||||||
:title="t('minimize')"
|
:title="t('minimize')"
|
||||||
href="#"
|
:aria-label="t('minimize')"
|
||||||
class="py-1.5 inline md:hidden"
|
class="py-1.5 inline md:hidden"
|
||||||
@click.prevent="$emit('minimize')"
|
@click="$emit('minimize')"
|
||||||
>
|
>
|
||||||
<IconArrowsDiagonalMinimize2
|
<IconArrowsDiagonalMinimize2
|
||||||
:width="20"
|
:width="20"
|
||||||
:height="20"
|
:height="20"
|
||||||
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
|
:id="field.uuid + '-desc'"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1 field-description-text"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
@@ -59,6 +60,7 @@
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
:name="`values[${field.uuid}][]`"
|
:name="`values[${field.uuid}][]`"
|
||||||
:value="optionValue(option, index)"
|
:value="optionValue(option, index)"
|
||||||
|
:aria-describedby="field.description ? field.uuid + '-desc' : undefined"
|
||||||
class="base-checkbox !h-7 !w-7"
|
class="base-checkbox !h-7 !w-7"
|
||||||
:checked="(modelValue || []).includes(optionValue(option, index))"
|
:checked="(modelValue || []).includes(optionValue(option, index))"
|
||||||
@change="onChange"
|
@change="onChange"
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
|
:id="field.uuid + '-desc'"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1 field-description-text"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
@@ -44,6 +45,7 @@
|
|||||||
:max="field.validation?.max"
|
:max="field.validation?.max"
|
||||||
class="base-input !text-2xl w-full"
|
class="base-input !text-2xl w-full"
|
||||||
:required="field.required"
|
:required="field.required"
|
||||||
|
:aria-describedby="field.description ? field.uuid + '-desc' : undefined"
|
||||||
:placeholder="`${t('type_here_')}${field.required ? '' : ` (${t('optional')})`}`"
|
:placeholder="`${t('type_here_')}${field.required ? '' : ` (${t('optional')})`}`"
|
||||||
:name="`values[${field.uuid}]`"
|
:name="`values[${field.uuid}]`"
|
||||||
@focus="$emit('focus')"
|
@focus="$emit('focus')"
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
|
:id="field.uuid + '-desc'"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1 field-description-text"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
@@ -42,28 +43,28 @@
|
|||||||
@input="onInputCode"
|
@input="onInputCode"
|
||||||
>
|
>
|
||||||
<div class="flex justify-between mt-2 -mb-2 md:-mb-4">
|
<div class="flex justify-between mt-2 -mb-2 md:-mb-4">
|
||||||
<a
|
<button
|
||||||
v-if="!defaultValue"
|
v-if="!defaultValue"
|
||||||
href="#"
|
type="button"
|
||||||
class="link change-phone-number-link"
|
class="link change-phone-number-link"
|
||||||
@click.prevent="isCodeSent = false"
|
@click="isCodeSent = false"
|
||||||
>
|
>
|
||||||
{{ t('change_phone_number') }}
|
{{ t('change_phone_number') }}
|
||||||
</a>
|
</button>
|
||||||
<span
|
<span
|
||||||
v-if="resendCodeCountdown > 0"
|
v-if="resendCodeCountdown > 0"
|
||||||
class="link"
|
class="link"
|
||||||
>
|
>
|
||||||
{{ t('wait_countdown_seconds').replace('{countdown}', resendCodeCountdown) }}
|
{{ t('wait_countdown_seconds').replace('{countdown}', resendCodeCountdown) }}
|
||||||
</span>
|
</span>
|
||||||
<a
|
<button
|
||||||
v-else
|
v-else
|
||||||
href="#"
|
type="button"
|
||||||
class="link resend-code-link"
|
class="link resend-code-link"
|
||||||
@click.prevent="resendCode"
|
@click="resendCode"
|
||||||
>
|
>
|
||||||
{{ isResendLoading ? t('sending') : t('resend_code') }}
|
{{ isResendLoading ? t('sending') : t('resend_code') }}
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -107,6 +108,7 @@
|
|||||||
type="tel"
|
type="tel"
|
||||||
inputmode="tel"
|
inputmode="tel"
|
||||||
:required="field.required"
|
:required="field.required"
|
||||||
|
:aria-describedby="field.description ? field.uuid + '-desc' : undefined"
|
||||||
placeholder="234 567-8900"
|
placeholder="234 567-8900"
|
||||||
@input="onPhoneInput"
|
@input="onPhoneInput"
|
||||||
@focus="$emit('focus')"
|
@focus="$emit('focus')"
|
||||||
|
|||||||
@@ -25,17 +25,21 @@
|
|||||||
class="md:tooltip"
|
class="md:tooltip"
|
||||||
:data-tip="t('draw_signature')"
|
:data-tip="t('draw_signature')"
|
||||||
>
|
>
|
||||||
<a
|
<button
|
||||||
id="type_text_button"
|
id="type_text_button"
|
||||||
href="#"
|
type="button"
|
||||||
|
:aria-label="t('draw_signature')"
|
||||||
class="btn btn-outline btn-sm font-medium type-text-button"
|
class="btn btn-outline btn-sm font-medium type-text-button"
|
||||||
@click.prevent="[toggleTextInput(), hideQr()]"
|
@click="[toggleTextInput(), hideQr()]"
|
||||||
>
|
>
|
||||||
<IconSignature :width="16" />
|
<IconSignature
|
||||||
|
:width="16"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
<span class="hidden sm:inline">
|
<span class="hidden sm:inline">
|
||||||
{{ t('draw') }}
|
{{ t('draw') }}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-else-if="withTypedSignature && format !== 'drawn_or_upload' && format !== 'typed_or_upload' && format !== 'typed' && format !== 'drawn' && format !== 'upload'"
|
v-else-if="withTypedSignature && format !== 'drawn_or_upload' && format !== 'typed_or_upload' && format !== 'typed' && format !== 'drawn' && format !== 'upload'"
|
||||||
@@ -43,17 +47,21 @@
|
|||||||
:class="{ 'hidden sm:inline': modelValue || computedPreviousValue }"
|
:class="{ 'hidden sm:inline': modelValue || computedPreviousValue }"
|
||||||
:data-tip="t('type_text')"
|
:data-tip="t('type_text')"
|
||||||
>
|
>
|
||||||
<a
|
<button
|
||||||
id="type_text_button"
|
id="type_text_button"
|
||||||
href="#"
|
type="button"
|
||||||
|
:aria-label="t('type_text')"
|
||||||
class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap type-text-button"
|
class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap type-text-button"
|
||||||
@click.prevent="[toggleTextInput(), hideQr()]"
|
@click="[toggleTextInput(), hideQr()]"
|
||||||
>
|
>
|
||||||
<IconTextSize :width="16" />
|
<IconTextSize
|
||||||
|
:width="16"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
<span class="hidden sm:inline">
|
<span class="hidden sm:inline">
|
||||||
{{ t('type') }}
|
{{ t('type') }}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="format !== 'typed' && format !== 'drawn' && format !== 'upload' && format !== 'drawn_or_typed'"
|
v-if="format !== 'typed' && format !== 'drawn' && format !== 'upload' && format !== 'drawn_or_typed'"
|
||||||
@@ -61,10 +69,19 @@
|
|||||||
:class="{ 'hidden sm:inline': modelValue || computedPreviousValue }"
|
:class="{ 'hidden sm:inline': modelValue || computedPreviousValue }"
|
||||||
:data-tip="t('take_photo')"
|
:data-tip="t('take_photo')"
|
||||||
>
|
>
|
||||||
<label class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap upload-image-button">
|
<button
|
||||||
<IconCamera :width="16" />
|
type="button"
|
||||||
|
:aria-label="t('take_photo')"
|
||||||
|
class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap upload-image-button"
|
||||||
|
@click="$refs.takePhotoInput.click()"
|
||||||
|
>
|
||||||
|
<IconCamera
|
||||||
|
:width="16"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
<input
|
<input
|
||||||
:key="uploadImageInputKey"
|
:key="uploadImageInputKey"
|
||||||
|
ref="takePhotoInput"
|
||||||
type="file"
|
type="file"
|
||||||
hidden
|
hidden
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
@@ -73,49 +90,57 @@
|
|||||||
<span class="hidden sm:inline">
|
<span class="hidden sm:inline">
|
||||||
{{ t('upload') }}
|
{{ t('upload') }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
<a
|
<button
|
||||||
v-if="modelValue || computedPreviousValue"
|
v-if="modelValue || computedPreviousValue"
|
||||||
href="#"
|
type="button"
|
||||||
class="btn btn-outline btn-sm font-medium reupload-button"
|
class="btn btn-outline btn-sm font-medium reupload-button"
|
||||||
@click.prevent="remove"
|
@click="remove"
|
||||||
>
|
>
|
||||||
<IconReload :width="16" />
|
<IconReload
|
||||||
|
:width="16"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
{{ t(format === 'upload' ? 'reupload' : 'redraw') }}
|
{{ t(format === 'upload' ? 'reupload' : 'redraw') }}
|
||||||
</a>
|
</button>
|
||||||
<span
|
<span
|
||||||
v-if="withQrButton && !modelValue && !computedPreviousValue && format !== 'typed_or_upload' && format !== 'typed' && format !== 'upload'"
|
v-if="withQrButton && !modelValue && !computedPreviousValue && format !== 'typed_or_upload' && format !== 'typed' && format !== 'upload'"
|
||||||
class="md:tooltip before:translate-x-[-90%]"
|
class="md:tooltip before:translate-x-[-90%]"
|
||||||
:data-tip="t('sign_on_the_touchscreen')"
|
:data-tip="t('sign_on_the_touchscreen')"
|
||||||
>
|
>
|
||||||
<a
|
<button
|
||||||
href="#"
|
type="button"
|
||||||
|
:aria-label="t('sign_on_the_touchscreen')"
|
||||||
class="btn btn-sm btn-neutral font-medium hidden md:flex"
|
class="btn btn-sm btn-neutral font-medium hidden md:flex"
|
||||||
:class="{ 'btn-outline': !isShowQr, 'text-white': isShowQr }"
|
:class="{ 'btn-outline': !isShowQr, 'text-white': isShowQr }"
|
||||||
@click.prevent="isShowQr ? hideQr() : [isTextSignature = false, showQr()]"
|
@click="isShowQr ? hideQr() : [isTextSignature = false, showQr()]"
|
||||||
>
|
>
|
||||||
<IconQrcode
|
<IconQrcode
|
||||||
:width="19"
|
:width="19"
|
||||||
:height="19"
|
:height="19"
|
||||||
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</a>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
<a
|
<button
|
||||||
href="#"
|
type="button"
|
||||||
:title="t('minimize')"
|
:title="t('minimize')"
|
||||||
|
:aria-label="t('minimize')"
|
||||||
class="py-1.5 inline md:hidden"
|
class="py-1.5 inline md:hidden"
|
||||||
@click.prevent="$emit('minimize')"
|
@click="$emit('minimize')"
|
||||||
>
|
>
|
||||||
<IconArrowsDiagonalMinimize2
|
<IconArrowsDiagonalMinimize2
|
||||||
:width="20"
|
:width="20"
|
||||||
:height="20"
|
:height="20"
|
||||||
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
|
:id="field.uuid + '-desc'"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1 field-description-text"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
@@ -136,6 +161,7 @@
|
|||||||
<img
|
<img
|
||||||
v-if="modelValue || computedPreviousValue"
|
v-if="modelValue || computedPreviousValue"
|
||||||
:src="attachmentsIndex[modelValue || computedPreviousValue].url"
|
:src="attachmentsIndex[modelValue || computedPreviousValue].url"
|
||||||
|
:alt="field.name || t('signature')"
|
||||||
class="mx-auto bg-white border border-base-300 rounded max-h-44"
|
class="mx-auto bg-white border border-base-300 rounded max-h-44"
|
||||||
>
|
>
|
||||||
<FileDropzone
|
<FileDropzone
|
||||||
@@ -154,14 +180,17 @@
|
|||||||
v-if="!modelValue && !computedPreviousValue && !isShowQr && !isTextSignature && isSignatureStarted"
|
v-if="!modelValue && !computedPreviousValue && !isShowQr && !isTextSignature && isSignatureStarted"
|
||||||
class="absolute top-0.5 right-0.5"
|
class="absolute top-0.5 right-0.5"
|
||||||
>
|
>
|
||||||
<a
|
<button
|
||||||
href="#"
|
type="button"
|
||||||
class="btn btn-ghost font-medium btn-xs md:btn-sm"
|
class="btn btn-ghost font-medium btn-xs md:btn-sm"
|
||||||
@click.prevent="[clear(), hideQr()]"
|
@click="[clear(), hideQr()]"
|
||||||
>
|
>
|
||||||
<IconReload :width="16" />
|
<IconReload
|
||||||
|
:width="16"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
{{ t('clear') }}
|
{{ t('clear') }}
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="isTextSignature"
|
v-if="isTextSignature"
|
||||||
@@ -170,6 +199,8 @@
|
|||||||
<canvas
|
<canvas
|
||||||
v-show="!modelValue && !computedPreviousValue"
|
v-show="!modelValue && !computedPreviousValue"
|
||||||
ref="canvas"
|
ref="canvas"
|
||||||
|
role="application"
|
||||||
|
:aria-label="t('signature_drawing_area')"
|
||||||
style="padding: 1px; 0"
|
style="padding: 1px; 0"
|
||||||
class="bg-white border border-base-300 rounded-2xl w-full draw-canvas"
|
class="bg-white border border-base-300 rounded-2xl w-full draw-canvas"
|
||||||
/>
|
/>
|
||||||
@@ -182,13 +213,14 @@
|
|||||||
class="top-0 bottom-0 right-0 left-0 absolute bg-base-content/10 rounded-2xl"
|
class="top-0 bottom-0 right-0 left-0 absolute bg-base-content/10 rounded-2xl"
|
||||||
>
|
>
|
||||||
<div class="absolute top-1.5 right-1.5">
|
<div class="absolute top-1.5 right-1.5">
|
||||||
<a
|
<button
|
||||||
href="#"
|
type="button"
|
||||||
class="btn btn-sm btn-circle btn-normal btn-outline"
|
class="btn btn-sm btn-circle btn-normal btn-outline"
|
||||||
@click.prevent="hideQr"
|
:aria-label="t('close')"
|
||||||
|
@click="hideQr"
|
||||||
>
|
>
|
||||||
<IconX />
|
<IconX aria-hidden="true" />
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-center w-full h-full p-4">
|
<div class="flex items-center justify-center w-full h-full p-4">
|
||||||
<div
|
<div
|
||||||
@@ -512,6 +544,7 @@ export default {
|
|||||||
this.resizeObserver = new ResizeObserver(() => {
|
this.resizeObserver = new ResizeObserver(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (!this.$refs.canvas) return
|
if (!this.$refs.canvas) return
|
||||||
|
if (!this.$refs.canvas.parentNode?.clientWidth) return
|
||||||
|
|
||||||
const { width, height } = this.$refs.canvas
|
const { width, height } = this.$refs.canvas
|
||||||
|
|
||||||
@@ -554,7 +587,7 @@ export default {
|
|||||||
redrawCanvas (oldWidth, oldHeight) {
|
redrawCanvas (oldWidth, oldHeight) {
|
||||||
const canvas = this.$refs.canvas
|
const canvas = this.$refs.canvas
|
||||||
|
|
||||||
if (this.pad && !this.isTextSignature && !this.pad.isEmpty() && oldWidth > 0 && oldHeight > 0) {
|
if (this.pad && !this.isTextSignature && !this.pad.isEmpty() && oldWidth > 0 && oldHeight > 0 && canvas.width > 0 && canvas.height > 0) {
|
||||||
const sx = canvas.width / oldWidth
|
const sx = canvas.width / oldWidth
|
||||||
const sy = canvas.height / oldHeight
|
const sy = canvas.height / oldHeight
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
|
:id="field.uuid + '-desc'"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1 field-description-text"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
@@ -40,6 +41,7 @@
|
|||||||
:class="{ '!pr-11 -mr-10': !field.validation?.pattern }"
|
:class="{ '!pr-11 -mr-10': !field.validation?.pattern }"
|
||||||
:required="field.required"
|
:required="field.required"
|
||||||
:pattern="field.validation?.pattern"
|
:pattern="field.validation?.pattern"
|
||||||
|
:aria-describedby="field.description ? field.uuid + '-desc' : undefined"
|
||||||
:placeholder="`${t('type_here_')}${field.required ? '' : ` (${t('optional')})`}`"
|
:placeholder="`${t('type_here_')}${field.required ? '' : ` (${t('optional')})`}`"
|
||||||
type="text"
|
type="text"
|
||||||
:name="`values[${field.uuid}]`"
|
:name="`values[${field.uuid}]`"
|
||||||
@@ -54,6 +56,7 @@
|
|||||||
v-model="text"
|
v-model="text"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="base-textarea !text-2xl w-full"
|
class="base-textarea !text-2xl w-full"
|
||||||
|
:aria-describedby="field.description ? field.uuid + '-desc' : undefined"
|
||||||
:placeholder="`${t('type_here_')}${field.required ? '' : ` (${t('optional')})`}`"
|
:placeholder="`${t('type_here_')}${field.required ? '' : ` (${t('optional')})`}`"
|
||||||
:required="field.required"
|
:required="field.required"
|
||||||
:name="`values[${field.uuid}]`"
|
:name="`values[${field.uuid}]`"
|
||||||
@@ -65,13 +68,14 @@
|
|||||||
class="tooltip"
|
class="tooltip"
|
||||||
:data-tip="t('toggle_multiline_text')"
|
:data-tip="t('toggle_multiline_text')"
|
||||||
>
|
>
|
||||||
<a
|
<button
|
||||||
href="#"
|
type="button"
|
||||||
|
:aria-label="t('toggle_multiline_text')"
|
||||||
class="btn btn-ghost btn-circle btn-sm toggle-multiline-text-button"
|
class="btn btn-ghost btn-circle btn-sm toggle-multiline-text-button"
|
||||||
@click.prevent="toggleTextArea"
|
@click="toggleTextArea"
|
||||||
>
|
>
|
||||||
<IconAlignBoxLeftTop />
|
<IconAlignBoxLeftTop aria-hidden="true" />
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
<IconInnerShadowTop
|
<IconInnerShadowTop
|
||||||
width="40"
|
width="40"
|
||||||
class="animate-spin h-10"
|
class="animate-spin h-10"
|
||||||
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="redirectUrl">
|
<div v-else-if="redirectUrl">
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
@remove="$emit('remove')"
|
@remove="$emit('remove')"
|
||||||
@scroll-to="$emit('scroll-to', $event)"
|
@scroll-to="$emit('scroll-to', $event)"
|
||||||
@add-custom-field="$emit('add-custom-field')"
|
@add-custom-field="$emit('add-custom-field')"
|
||||||
|
@click-title="$emit('click-title')"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
ref="touchValueTarget"
|
ref="touchValueTarget"
|
||||||
@@ -113,7 +114,7 @@
|
|||||||
<div
|
<div
|
||||||
ref="textContainer"
|
ref="textContainer"
|
||||||
class="flex items-center px-0.5"
|
class="flex items-center px-0.5"
|
||||||
:style="{ color: field.preferences?.color }"
|
:style="{ color: isConditionMatch ? field.preferences?.color : '#9ca3af' }"
|
||||||
:class="{ 'w-full h-full': isWFullType }"
|
:class="{ 'w-full h-full': isWFullType }"
|
||||||
>
|
>
|
||||||
<IconCheck
|
<IconCheck
|
||||||
@@ -131,13 +132,13 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<span
|
<span
|
||||||
v-else-if="field.type === 'number' && !isValueInput && (field.default_value || field.default_value == 0)"
|
v-else-if="field.type === 'number' && !isContenteditable && (displayValue || displayValue == 0)"
|
||||||
class="whitespace-pre-wrap"
|
class="whitespace-pre-wrap"
|
||||||
>{{ formatNumber(field.default_value, field.preferences?.format) }}</span>
|
>{{ formatNumber(displayValue, field.preferences?.format) }}</span>
|
||||||
<span
|
<span
|
||||||
v-else-if="field.default_value === '{{date}}'"
|
v-else-if="field.default_value === '{{date}}'"
|
||||||
>
|
>
|
||||||
{{ t('signing_date') }}
|
{{ /[HhAasz]/.test(field.preferences?.format || '') ? t('signing_date_and_time') : t('signing_date') }}
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
v-else-if="field.type === 'cells' && field.default_value"
|
v-else-if="field.type === 'cells' && field.default_value"
|
||||||
@@ -183,12 +184,12 @@
|
|||||||
:contenteditable="isValueInput"
|
:contenteditable="isValueInput"
|
||||||
class="whitespace-pre-wrap outline-none empty:before:content-[attr(placeholder)] before:text-base-content/30"
|
class="whitespace-pre-wrap outline-none empty:before:content-[attr(placeholder)] before:text-base-content/30"
|
||||||
:class="{ 'cursor-text': isValueInput }"
|
:class="{ 'cursor-text': isValueInput }"
|
||||||
:placeholder="withFieldPlaceholder && !isValueInput ? defaultField?.title || field.title || field.name || defaultName : (field.type === 'date' ? field.preferences?.format || t('type_value') : t('type_value'))"
|
:placeholder="withFieldPlaceholder && !isValueInput ? defaultField?.title || field.title || field.name || defaultName : (isConditionMatch ? (field.type === 'date' ? field.preferences?.format || t('type_value') : t('type_value')) : '')"
|
||||||
@blur="onDefaultValueBlur"
|
@blur="onDefaultValueBlur"
|
||||||
@focus="selectedAreasRef.value = [area]"
|
@focus="selectedAreasRef.value = [area]"
|
||||||
@paste.prevent="onPaste"
|
@paste.prevent="onPaste"
|
||||||
@keydown.enter="onDefaultValueEnter"
|
@keydown.enter="onDefaultValueEnter"
|
||||||
>{{ field.default_value }}</span>
|
>{{ displayValue }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<component
|
<component
|
||||||
@@ -241,6 +242,16 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
conditionalFieldIndex: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
formulaValuesIndex: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
isDraw: {
|
isDraw: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -307,7 +318,7 @@ export default {
|
|||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['start-resize', 'stop-resize', 'start-drag', 'stop-drag', 'remove', 'scroll-to', 'add-custom-field'],
|
emits: ['start-resize', 'stop-resize', 'start-drag', 'stop-drag', 'remove', 'scroll-to', 'add-custom-field', 'click-title'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isContenteditable: false,
|
isContenteditable: false,
|
||||||
@@ -323,11 +334,27 @@ export default {
|
|||||||
fieldNames: FieldType.computed.fieldNames,
|
fieldNames: FieldType.computed.fieldNames,
|
||||||
fieldLabels: FieldType.computed.fieldLabels,
|
fieldLabels: FieldType.computed.fieldLabels,
|
||||||
fieldIcons: FieldType.computed.fieldIcons,
|
fieldIcons: FieldType.computed.fieldIcons,
|
||||||
|
isConditionMatch () {
|
||||||
|
return !this.inputMode || this.conditionalFieldIndex[this.field.uuid] !== false
|
||||||
|
},
|
||||||
|
displayValue () {
|
||||||
|
if (this.field.preferences?.formula && this.field.type !== 'payment') {
|
||||||
|
const computed = this.formulaValuesIndex[this.field.uuid]
|
||||||
|
|
||||||
|
if (computed != null) {
|
||||||
|
return computed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.field.default_value
|
||||||
|
},
|
||||||
bgClasses () {
|
bgClasses () {
|
||||||
if (this.field.type === 'heading') {
|
if (this.field.type === 'heading') {
|
||||||
return 'bg-gray-50'
|
return 'bg-gray-50'
|
||||||
} else if (this.field.type === 'strikethrough') {
|
} else if (this.field.type === 'strikethrough') {
|
||||||
return 'bg-transparent'
|
return 'bg-transparent'
|
||||||
|
} else if (!this.isConditionMatch) {
|
||||||
|
return 'bg-gray-100'
|
||||||
} else {
|
} else {
|
||||||
return this.bgColors[this.submitterIndex % this.bgColors.length]
|
return this.bgColors[this.submitterIndex % this.bgColors.length]
|
||||||
}
|
}
|
||||||
@@ -337,6 +364,8 @@ export default {
|
|||||||
return ''
|
return ''
|
||||||
} else if (this.field.type === 'strikethrough') {
|
} else if (this.field.type === 'strikethrough') {
|
||||||
return 'border-dashed border-gray-300'
|
return 'border-dashed border-gray-300'
|
||||||
|
} else if (!this.isConditionMatch) {
|
||||||
|
return 'border-gray-300'
|
||||||
} else {
|
} else {
|
||||||
return this.borderColors[this.submitterIndex % this.borderColors.length]
|
return this.borderColors[this.submitterIndex % this.borderColors.length]
|
||||||
}
|
}
|
||||||
@@ -390,7 +419,7 @@ export default {
|
|||||||
return this.basePageWidth / 612.0
|
return this.basePageWidth / 612.0
|
||||||
},
|
},
|
||||||
isDefaultValuePresent () {
|
isDefaultValuePresent () {
|
||||||
return this.field?.default_value || this.field?.default_value === 0
|
return this.field?.default_value || this.field?.default_value === 0 || this.displayValue || this.displayValue === 0
|
||||||
},
|
},
|
||||||
isSelectInput () {
|
isSelectInput () {
|
||||||
return this.inputMode && (this.field.type === 'select' || (this.field.type === 'radio' && this.field.areas?.length < 2))
|
return this.inputMode && (this.field.type === 'select' || (this.field.type === 'radio' && this.field.areas?.length < 2))
|
||||||
@@ -399,6 +428,8 @@ export default {
|
|||||||
return this.inputMode && (this.field.type === 'checkbox' || (['radio', 'multiple'].includes(this.field.type) && this.area.option_uuid))
|
return this.inputMode && (this.field.type === 'checkbox' || (['radio', 'multiple'].includes(this.field.type) && this.area.option_uuid))
|
||||||
},
|
},
|
||||||
isValueInput () {
|
isValueInput () {
|
||||||
|
if (this.inputMode && this.field.preferences?.formula) return false
|
||||||
|
|
||||||
return (this.field.type === 'heading' && this.isHeadingSelected) || this.isContenteditable ||
|
return (this.field.type === 'heading' && this.isHeadingSelected) || this.isContenteditable ||
|
||||||
(this.inputMode && (['text', 'number'].includes(this.field.type) || (this.field.type === 'date' && this.field.default_value !== '{{date}}')))
|
(this.inputMode && (['text', 'number'].includes(this.field.type) || (this.field.type === 'date' && this.field.default_value !== '{{date}}')))
|
||||||
},
|
},
|
||||||
@@ -511,7 +542,7 @@ export default {
|
|||||||
return option?.value || `${this.t('option')} ${this.field.options.indexOf(option) + 1}`
|
return option?.value || `${this.t('option')} ${this.field.options.indexOf(option) + 1}`
|
||||||
},
|
},
|
||||||
maybeToggleDefaultValue () {
|
maybeToggleDefaultValue () {
|
||||||
if (!this.editable || this.isCmdKeyRef.value) {
|
if (!this.editable || this.isCmdKeyRef.value || this.field.preferences?.formula) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,6 +590,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
focusValueInput (e) {
|
focusValueInput (e) {
|
||||||
|
if (this.inputMode && this.field.type === 'number' && !this.isContenteditable && !this.field.preferences?.formula) {
|
||||||
|
this.isContenteditable = true
|
||||||
|
}
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.$refs.defaultValue && this.$refs.defaultValue !== document.activeElement) {
|
if (this.$refs.defaultValue && this.$refs.defaultValue !== document.activeElement) {
|
||||||
this.$refs.defaultValue.focus()
|
this.$refs.defaultValue.focus()
|
||||||
@@ -624,6 +659,12 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDefaultValueBlur (e) {
|
onDefaultValueBlur (e) {
|
||||||
|
if (this.field.preferences?.formula) {
|
||||||
|
this.isContenteditable = false
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const text = this.$refs.defaultValue.innerText.trim()
|
const text = this.$refs.defaultValue.innerText.trim()
|
||||||
|
|
||||||
this.isContenteditable = false
|
this.isContenteditable = false
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
v-if="field?.type && (isSelected || isNameFocus) && !isInMultiSelection"
|
v-if="field?.type && (isSelected || isNameFocus) && !isInMultiSelection"
|
||||||
class="absolute bg-white rounded-t border overflow-visible whitespace-nowrap flex z-10 field-area-controls"
|
class="absolute bg-white rounded-t border overflow-visible whitespace-nowrap flex z-10 field-area-controls"
|
||||||
style="top: -25px; height: 25px"
|
style="top: -25px; height: 25px"
|
||||||
@mousedown.stop
|
@mousedown.stop="$emit('click-title')"
|
||||||
@pointerdown.stop
|
@pointerdown.stop
|
||||||
>
|
>
|
||||||
<FieldSubmitter
|
<FieldSubmitter
|
||||||
@@ -279,7 +279,7 @@ export default {
|
|||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['remove', 'scroll-to', 'add-custom-field', 'change'],
|
emits: ['remove', 'scroll-to', 'add-custom-field', 'change', 'click-title'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isShowFormulaModal: false,
|
isShowFormulaModal: false,
|
||||||
|
|||||||
@@ -51,6 +51,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="beforeRevisionSnapshot"
|
||||||
|
class="top-1.5 sticky h-0 z-20 max-w-2xl mx-auto"
|
||||||
|
>
|
||||||
|
<div class="alert border-base-content/30 py-2 px-2.5">
|
||||||
|
<IconInfoCircle class="stroke-info shrink-0 w-6 h-6" />
|
||||||
|
<span>{{ t('viewing_revision_from').replace('{date}', formatRevisionTime(beforeRevisionSnapshot.revision.created_at)) }}</span>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="btn btn-sm"
|
||||||
|
@click.prevent="cancelRevision"
|
||||||
|
>
|
||||||
|
{{ t('cancel') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="editable"
|
||||||
|
class="btn btn-sm btn-neutral text-white"
|
||||||
|
@click.prevent="applyRevision"
|
||||||
|
>
|
||||||
|
{{ t('apply') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="$slots.buttons || withTitle"
|
v-if="$slots.buttons || withTitle"
|
||||||
id="title_container"
|
id="title_container"
|
||||||
@@ -213,6 +237,18 @@
|
|||||||
<span class="whitespace-nowrap">{{ t('preferences') }}</span>
|
<span class="whitespace-nowrap">{{ t('preferences') }}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li v-if="withRevisionsMenu">
|
||||||
|
<button
|
||||||
|
class="flex space-x-2"
|
||||||
|
@click.prevent="openRevisionsModal"
|
||||||
|
@mouseenter="preloadRevisions"
|
||||||
|
>
|
||||||
|
<span class="w-6 h-6 flex-shrink-0 flex items-center justify-center">
|
||||||
|
<IconHistory class="w-5 h-5" />
|
||||||
|
</span>
|
||||||
|
<span class="whitespace-nowrap">{{ t('revisions') }}</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
<li v-if="withDownload">
|
<li v-if="withDownload">
|
||||||
<button
|
<button
|
||||||
class="flex space-x-2"
|
class="flex space-x-2"
|
||||||
@@ -381,6 +417,9 @@
|
|||||||
:document="document"
|
:document="document"
|
||||||
:is-drag="!!dragField"
|
:is-drag="!!dragField"
|
||||||
:input-mode="inputMode"
|
:input-mode="inputMode"
|
||||||
|
:conditional-field-index="conditionalFieldIndex"
|
||||||
|
:formula-values-index="formulaValuesIndex"
|
||||||
|
:page-preview-format="pagePreviewFormat"
|
||||||
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
||||||
:allow-draw="!onlyDefinedFields || drawField || drawCustomField"
|
:allow-draw="!onlyDefinedFields || drawField || drawCustomField"
|
||||||
:with-signature-id="withSignatureId"
|
:with-signature-id="withSignatureId"
|
||||||
@@ -504,11 +543,14 @@
|
|||||||
:with-custom-fields="withCustomFields"
|
:with-custom-fields="withCustomFields"
|
||||||
:with-fields-search="withFieldsSearch"
|
:with-fields-search="withFieldsSearch"
|
||||||
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
||||||
|
:with-custom-fields-tab="withCustomFieldsTab"
|
||||||
:template="template"
|
:template="template"
|
||||||
:default-required-fields="defaultRequiredFields"
|
:default-required-fields="defaultRequiredFields"
|
||||||
|
:detect-custom-fields-index="detectCustomFieldsIndex"
|
||||||
:field-types="fieldTypes"
|
:field-types="fieldTypes"
|
||||||
:with-sticky-submitters="withStickySubmitters"
|
:with-sticky-submitters="withStickySubmitters"
|
||||||
:with-fields-detection="withFieldsDetection"
|
:with-fields-detection="withFieldsDetection"
|
||||||
|
:with-detect-existing-fields="withDetectExistingFields"
|
||||||
:with-signature-id="withSignatureId"
|
:with-signature-id="withSignatureId"
|
||||||
:with-prefillable="withPrefillable"
|
:with-prefillable="withPrefillable"
|
||||||
:only-defined-fields="onlyDefinedFields"
|
:only-defined-fields="onlyDefinedFields"
|
||||||
@@ -594,7 +636,16 @@
|
|||||||
<div
|
<div
|
||||||
id="docuseal_modal_container"
|
id="docuseal_modal_container"
|
||||||
class="modal-container"
|
class="modal-container"
|
||||||
/>
|
>
|
||||||
|
<RevisionsModal
|
||||||
|
v-if="isRevisionsModalOpen"
|
||||||
|
:template="template"
|
||||||
|
:revisions="revisions"
|
||||||
|
:locale="locale"
|
||||||
|
@close="isRevisionsModalOpen = false"
|
||||||
|
@apply="onRevisionApply"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -612,11 +663,22 @@ import DocumentPreview from './preview'
|
|||||||
import DocumentControls from './controls'
|
import DocumentControls from './controls'
|
||||||
import MobileFields from './mobile_fields'
|
import MobileFields from './mobile_fields'
|
||||||
import FieldSubmitter from './field_submitter'
|
import FieldSubmitter from './field_submitter'
|
||||||
import { IconPlus, IconUsersPlus, IconDeviceFloppy, IconChevronDown, IconEye, IconWritingSign, IconInnerShadowTop, IconInfoCircle, IconAdjustments, IconDownload } from '@tabler/icons-vue'
|
import RevisionsModal from './revisions_modal'
|
||||||
|
import { IconPlus, IconUsersPlus, IconDeviceFloppy, IconChevronDown, IconEye, IconWritingSign, IconInnerShadowTop, IconInfoCircle, IconAdjustments, IconDownload, IconHistory } from '@tabler/icons-vue'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
import { ref, computed, toRaw, defineAsyncComponent } from 'vue'
|
import { ref, computed, toRaw, defineAsyncComponent } from 'vue'
|
||||||
import * as i18n from './i18n'
|
import * as i18n from './i18n'
|
||||||
|
|
||||||
|
const isEmpty = (obj) => {
|
||||||
|
if (obj == null) return true
|
||||||
|
if (Array.isArray(obj)) return obj.length === 0
|
||||||
|
if (typeof obj === 'string') return obj.trim().length === 0
|
||||||
|
if (typeof obj === 'object') return Object.keys(obj).length === 0
|
||||||
|
if (obj === false) return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TemplateBuilder',
|
name: 'TemplateBuilder',
|
||||||
components: {
|
components: {
|
||||||
@@ -642,7 +704,9 @@ export default {
|
|||||||
IconDownload,
|
IconDownload,
|
||||||
IconAdjustments,
|
IconAdjustments,
|
||||||
IconEye,
|
IconEye,
|
||||||
IconDeviceFloppy
|
IconHistory,
|
||||||
|
IconDeviceFloppy,
|
||||||
|
RevisionsModal
|
||||||
},
|
},
|
||||||
provide () {
|
provide () {
|
||||||
return {
|
return {
|
||||||
@@ -654,6 +718,7 @@ export default {
|
|||||||
locale: this.locale,
|
locale: this.locale,
|
||||||
baseFetch: this.baseFetch,
|
baseFetch: this.baseFetch,
|
||||||
fieldTypes: this.fieldTypes,
|
fieldTypes: this.fieldTypes,
|
||||||
|
dateFormats: this.dateFormats,
|
||||||
backgroundColor: this.backgroundColor,
|
backgroundColor: this.backgroundColor,
|
||||||
withPhone: this.withPhone,
|
withPhone: this.withPhone,
|
||||||
withVerification: this.withVerification,
|
withVerification: this.withVerification,
|
||||||
@@ -738,6 +803,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
withDetectExistingFields: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
withCustomFields: {
|
withCustomFields: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -778,6 +848,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
|
withCustomFieldsTab: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
withSelectedFieldType: {
|
withSelectedFieldType: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -798,6 +873,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
|
dateFormats: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
defaultSubmitters: {
|
defaultSubmitters: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -808,6 +888,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
|
pagePreviewFormat: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '.jpg'
|
||||||
|
},
|
||||||
acceptFileTypes: {
|
acceptFileTypes: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -943,6 +1028,16 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
withRevisions: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
withRevisionsMenu: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
@@ -953,6 +1048,7 @@ export default {
|
|||||||
isLoadingBlankPage: false,
|
isLoadingBlankPage: false,
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
isDetectingPageFields: false,
|
isDetectingPageFields: false,
|
||||||
|
detectFieldsQueue: [],
|
||||||
detectingAnalyzingProgress: null,
|
detectingAnalyzingProgress: null,
|
||||||
detectingFieldsAddedCount: null,
|
detectingFieldsAddedCount: null,
|
||||||
selectedSubmitter: null,
|
selectedSubmitter: null,
|
||||||
@@ -963,7 +1059,11 @@ export default {
|
|||||||
drawCustomField: null,
|
drawCustomField: null,
|
||||||
drawOption: null,
|
drawOption: null,
|
||||||
dragField: null,
|
dragField: null,
|
||||||
isDragFile: false
|
isDragFile: false,
|
||||||
|
isMathLoaded: false,
|
||||||
|
isRevisionsModalOpen: false,
|
||||||
|
revisions: [],
|
||||||
|
beforeRevisionSnapshot: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -973,6 +1073,13 @@ export default {
|
|||||||
fieldsDragFieldRef: () => ref(),
|
fieldsDragFieldRef: () => ref(),
|
||||||
customDragFieldRef: () => ref(),
|
customDragFieldRef: () => ref(),
|
||||||
selectedAreasRef: () => ref([]),
|
selectedAreasRef: () => ref([]),
|
||||||
|
attachmentUuidsIndex () {
|
||||||
|
return this.template.schema.reduce((acc, e, index) => {
|
||||||
|
acc[e.attachment_uuid] = index
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
},
|
||||||
language () {
|
language () {
|
||||||
return this.locale.split('-')[0].toLowerCase()
|
return this.locale.split('-')[0].toLowerCase()
|
||||||
},
|
},
|
||||||
@@ -1003,6 +1110,8 @@ export default {
|
|||||||
return isMobileSafariIos || /android|iphone|ipad/i.test(navigator.userAgent)
|
return isMobileSafariIos || /android|iphone|ipad/i.test(navigator.userAgent)
|
||||||
},
|
},
|
||||||
defaultDateFormat () {
|
defaultDateFormat () {
|
||||||
|
if (this.dateFormats.length) return this.dateFormats[0]
|
||||||
|
|
||||||
const isUsBrowser = Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US')
|
const isUsBrowser = Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US')
|
||||||
const isUsTimezone = new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).format(new Date()).match(/\s(?:CST|CDT|PST|PDT|EST|EDT)$/)
|
const isUsTimezone = new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).format(new Date()).match(/\s(?:CST|CDT|PST|PDT|EST|EDT)$/)
|
||||||
|
|
||||||
@@ -1044,6 +1153,43 @@ export default {
|
|||||||
|
|
||||||
return map
|
return map
|
||||||
},
|
},
|
||||||
|
fieldsUuidIndex () {
|
||||||
|
return this.template.fields.reduce((acc, f) => {
|
||||||
|
acc[f.uuid] = f
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
},
|
||||||
|
conditionalFieldIndex () {
|
||||||
|
if (!this.inputMode) return {}
|
||||||
|
|
||||||
|
const cache = {}
|
||||||
|
|
||||||
|
return this.template.fields.reduce((acc, f) => {
|
||||||
|
acc[f.uuid] = this.checkFieldConditions(f, cache)
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
},
|
||||||
|
formulaValuesIndex () {
|
||||||
|
const formulaFields = this.template.fields.filter((f) => f.preferences?.formula && f.type !== 'payment' && this.hasFormulaDependencyValue(f))
|
||||||
|
|
||||||
|
if (!formulaFields.length) return {}
|
||||||
|
|
||||||
|
if (!this.isMathLoaded) {
|
||||||
|
this.loadCalculator()
|
||||||
|
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return formulaFields.reduce((acc, f) => {
|
||||||
|
if (this.conditionalFieldIndex[f.uuid] !== false) {
|
||||||
|
acc[f.uuid] = this.calculateFormula(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
},
|
||||||
isAllRequiredFieldsAdded () {
|
isAllRequiredFieldsAdded () {
|
||||||
return !this.defaultRequiredFields?.some((f) => {
|
return !this.defaultRequiredFields?.some((f) => {
|
||||||
return !this.template.fields?.some((field) => field.name === f.name)
|
return !this.template.fields?.some((field) => field.name === f.name)
|
||||||
@@ -1052,6 +1198,39 @@ export default {
|
|||||||
selectedField () {
|
selectedField () {
|
||||||
return this.template.fields.find((f) => f.areas?.includes(this.lastSelectedArea))
|
return this.template.fields.find((f) => f.areas?.includes(this.lastSelectedArea))
|
||||||
},
|
},
|
||||||
|
detectFieldsIndex () {
|
||||||
|
const submittersByUuid = {}
|
||||||
|
|
||||||
|
this.template.submitters.forEach((s) => {
|
||||||
|
submittersByUuid[s.uuid] = s
|
||||||
|
})
|
||||||
|
|
||||||
|
const index = {}
|
||||||
|
|
||||||
|
this.template.fields.forEach((f) => {
|
||||||
|
if (!f.name) return
|
||||||
|
|
||||||
|
const role = submittersByUuid[f.submitter_uuid]?.name
|
||||||
|
const key = [f.name, role].filter(Boolean).join(':').toLowerCase()
|
||||||
|
|
||||||
|
if (!index[key]) index[key] = f
|
||||||
|
})
|
||||||
|
|
||||||
|
return index
|
||||||
|
},
|
||||||
|
detectCustomFieldsIndex () {
|
||||||
|
const index = {}
|
||||||
|
|
||||||
|
;[...this.customFields, ...this.defaultRequiredFields, ...this.defaultFields].forEach((c) => {
|
||||||
|
if (!c.name) return
|
||||||
|
|
||||||
|
const key = [c.name, c.role].filter(Boolean).join(':').toLowerCase()
|
||||||
|
|
||||||
|
if (!index[key]) index[key] = c
|
||||||
|
})
|
||||||
|
|
||||||
|
return index
|
||||||
|
},
|
||||||
sortedDocuments () {
|
sortedDocuments () {
|
||||||
return this.template.schema.map((item) => {
|
return this.template.schema.map((item) => {
|
||||||
return this.template.documents.find(doc => doc.uuid === item.attachment_uuid)
|
return this.template.documents.find(doc => doc.uuid === item.attachment_uuid)
|
||||||
@@ -1097,6 +1276,16 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const deduplicateUuidsIndex = {}
|
||||||
|
|
||||||
|
this.template.submitters.forEach((submitter) => {
|
||||||
|
if (deduplicateUuidsIndex[submitter.uuid]) {
|
||||||
|
submitter.uuid = v4()
|
||||||
|
}
|
||||||
|
|
||||||
|
deduplicateUuidsIndex[submitter.uuid] = true
|
||||||
|
})
|
||||||
|
|
||||||
this.selectedSubmitter = this.template.submitters[0]
|
this.selectedSubmitter = this.template.submitters[0]
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
@@ -1137,6 +1326,132 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toRaw,
|
toRaw,
|
||||||
|
applyCustomFieldAttributes: Fields.methods.applyCustomFieldAttributes,
|
||||||
|
buildExistingFields: Fields.methods.buildExistingFields,
|
||||||
|
async loadCalculator () {
|
||||||
|
if (this.math) return
|
||||||
|
|
||||||
|
const { Calculator } = await import('../submission_form/calculator')
|
||||||
|
|
||||||
|
this.math = new Calculator()
|
||||||
|
this.isMathLoaded = true
|
||||||
|
},
|
||||||
|
optionValue (option, index) {
|
||||||
|
if (option.value) {
|
||||||
|
return option.value
|
||||||
|
} else {
|
||||||
|
return `${this.t('option')} ${index + 1}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
checkFieldConditions (field, cache = {}) {
|
||||||
|
const cacheKey = field.uuid || field.attachment_uuid
|
||||||
|
|
||||||
|
if (cache[cacheKey] !== undefined) {
|
||||||
|
return cache[cacheKey]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.conditions?.length) {
|
||||||
|
const result = field.conditions.reduce((acc, cond) => {
|
||||||
|
if (cond.operation === 'or') {
|
||||||
|
acc.push(acc.pop() || this.checkFieldCondition(cond, cache))
|
||||||
|
} else {
|
||||||
|
acc.push(this.checkFieldCondition(cond, cache))
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
cache[cacheKey] = !result.includes(false)
|
||||||
|
} else {
|
||||||
|
cache[cacheKey] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache[cacheKey]
|
||||||
|
},
|
||||||
|
checkFieldCondition (condition, cache = {}) {
|
||||||
|
const field = this.fieldsUuidIndex[condition.field_uuid]
|
||||||
|
|
||||||
|
if (['not_empty', 'checked', 'equal', 'contains', 'greater_than', 'less_than'].includes(condition.action) && field && !this.checkFieldConditions(field, cache)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultValue = !field || isEmpty(field.default_value) ? null : field.default_value
|
||||||
|
|
||||||
|
if (['empty', 'unchecked'].includes(condition.action)) {
|
||||||
|
return isEmpty(defaultValue)
|
||||||
|
} else if (['not_empty', 'checked'].includes(condition.action)) {
|
||||||
|
return !isEmpty(defaultValue)
|
||||||
|
} else if (field?.type === 'number' && ['equal', 'not_equal', 'greater_than', 'less_than'].includes(condition.action)) {
|
||||||
|
const value = defaultValue
|
||||||
|
|
||||||
|
if (isEmpty(value) || isEmpty(condition.value)) return false
|
||||||
|
|
||||||
|
const actual = parseFloat(value)
|
||||||
|
const expected = parseFloat(condition.value)
|
||||||
|
|
||||||
|
if (Number.isNaN(actual) || Number.isNaN(expected)) return false
|
||||||
|
|
||||||
|
if (condition.action === 'equal') return Math.abs(actual - expected) < Number.EPSILON
|
||||||
|
if (condition.action === 'not_equal') return Math.abs(actual - expected) > Number.EPSILON
|
||||||
|
if (condition.action === 'greater_than') return actual > expected
|
||||||
|
if (condition.action === 'less_than') return actual < expected
|
||||||
|
|
||||||
|
return false
|
||||||
|
} else if (['equal', 'contains'].includes(condition.action) && field) {
|
||||||
|
if (field.options) {
|
||||||
|
const option = field.options.find((o) => o.uuid === condition.value)
|
||||||
|
|
||||||
|
if (option) {
|
||||||
|
const values = [defaultValue].flat()
|
||||||
|
|
||||||
|
return values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return [defaultValue].flat().includes(condition.value)
|
||||||
|
}
|
||||||
|
} else if (['not_equal', 'does_not_contain'].includes(condition.action) && field) {
|
||||||
|
if (field.options) {
|
||||||
|
const option = field.options.find((o) => o.uuid === condition.value)
|
||||||
|
|
||||||
|
if (option) {
|
||||||
|
const values = [defaultValue].flat()
|
||||||
|
|
||||||
|
return !values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
normalizeFormula (formula, depth = 0) {
|
||||||
|
if (depth > 10) return formula
|
||||||
|
|
||||||
|
return formula.replace(/{{(.*?)}}/g, (match, uuid) => {
|
||||||
|
if (this.fieldsUuidIndex[uuid]?.preferences?.formula) {
|
||||||
|
return `(${this.normalizeFormula(this.fieldsUuidIndex[uuid].preferences.formula, depth + 1)})`
|
||||||
|
} else {
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
calculateFormula (field) {
|
||||||
|
const transformedFormula = this.normalizeFormula(field.preferences.formula).replace(/{{(.*?)}}/g, (match, uuid) => {
|
||||||
|
return this.fieldsUuidIndex[uuid]?.default_value || 0.0
|
||||||
|
})
|
||||||
|
|
||||||
|
return this.math.evaluate(transformedFormula.toLowerCase())
|
||||||
|
},
|
||||||
|
hasFormulaDependencyValue (field) {
|
||||||
|
const normalized = this.normalizeFormula(field.preferences.formula)
|
||||||
|
|
||||||
|
return [...normalized.matchAll(/{{(.*?)}}/g)].some(([, uuid]) => !isEmpty(this.fieldsUuidIndex[uuid]?.default_value))
|
||||||
|
},
|
||||||
addCustomField (field) {
|
addCustomField (field) {
|
||||||
return this.$refs.fields.addCustomField(field)
|
return this.$refs.fields.addCustomField(field)
|
||||||
},
|
},
|
||||||
@@ -1416,33 +1731,26 @@ export default {
|
|||||||
this.save()
|
this.save()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
compareAreas (a, b) {
|
||||||
|
const aAttIdx = this.attachmentUuidsIndex[a.attachment_uuid]
|
||||||
|
const bAttIdx = this.attachmentUuidsIndex[b.attachment_uuid]
|
||||||
|
|
||||||
|
if (aAttIdx !== bAttIdx) return aAttIdx - bAttIdx
|
||||||
|
if (a.page !== b.page) return a.page - b.page
|
||||||
|
|
||||||
|
const aY = a.y + a.h
|
||||||
|
const bY = b.y + b.h
|
||||||
|
|
||||||
|
if (Math.abs(aY - bY) < 0.01) return a.x - b.x
|
||||||
|
if (a.h < b.h ? a.y >= b.y && aY <= bY : b.y >= a.y && bY <= aY) return a.x - b.x
|
||||||
|
|
||||||
|
return aY - bY
|
||||||
|
},
|
||||||
findFieldInsertIndex (field) {
|
findFieldInsertIndex (field) {
|
||||||
if (!field.areas?.length) return -1
|
if (!field.areas?.length) return -1
|
||||||
|
|
||||||
const area = field.areas[0]
|
const area = field.areas[0]
|
||||||
|
|
||||||
const attachmentUuidsIndex = this.template.schema.reduce((acc, e, index) => {
|
|
||||||
acc[e.attachment_uuid] = index
|
|
||||||
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
|
|
||||||
const compareAreas = (a, b) => {
|
|
||||||
const aAttIdx = attachmentUuidsIndex[a.attachment_uuid]
|
|
||||||
const bAttIdx = attachmentUuidsIndex[b.attachment_uuid]
|
|
||||||
|
|
||||||
if (aAttIdx !== bAttIdx) return aAttIdx - bAttIdx
|
|
||||||
if (a.page !== b.page) return a.page - b.page
|
|
||||||
|
|
||||||
const aY = a.y + a.h
|
|
||||||
const bY = b.y + b.h
|
|
||||||
|
|
||||||
if (Math.abs(aY - bY) < 0.01) return a.x - b.x
|
|
||||||
if (a.h < b.h ? a.y >= b.y && aY <= bY : b.y >= a.y && bY <= aY) return a.x - b.x
|
|
||||||
|
|
||||||
return aY - bY
|
|
||||||
}
|
|
||||||
|
|
||||||
let closestBeforeIndex = -1
|
let closestBeforeIndex = -1
|
||||||
let closestBeforeArea = null
|
let closestBeforeArea = null
|
||||||
let closestAfterIndex = -1
|
let closestAfterIndex = -1
|
||||||
@@ -1451,15 +1759,15 @@ export default {
|
|||||||
this.template.fields.forEach((f, index) => {
|
this.template.fields.forEach((f, index) => {
|
||||||
if (f.submitter_uuid === field.submitter_uuid) {
|
if (f.submitter_uuid === field.submitter_uuid) {
|
||||||
(f.areas || []).forEach((a) => {
|
(f.areas || []).forEach((a) => {
|
||||||
const cmp = compareAreas(a, area)
|
const cmp = this.compareAreas(a, area)
|
||||||
|
|
||||||
if (cmp < 0) {
|
if (cmp < 0) {
|
||||||
if (!closestBeforeArea || (compareAreas(a, closestBeforeArea) > 0 && closestBeforeIndex < index)) {
|
if (!closestBeforeArea || (this.compareAreas(a, closestBeforeArea) > 0 && closestBeforeIndex < index)) {
|
||||||
closestBeforeIndex = index
|
closestBeforeIndex = index
|
||||||
closestBeforeArea = a
|
closestBeforeArea = a
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!closestAfterArea || (compareAreas(a, closestAfterArea) < 0 && closestAfterIndex > index)) {
|
if (!closestAfterArea || (this.compareAreas(a, closestAfterArea) < 0 && closestAfterIndex > index)) {
|
||||||
closestAfterIndex = index
|
closestAfterIndex = index
|
||||||
closestAfterArea = a
|
closestAfterArea = a
|
||||||
}
|
}
|
||||||
@@ -1482,9 +1790,111 @@ export default {
|
|||||||
this.template.fields.push(field)
|
this.template.fields.push(field)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
insertArea (field, area) {
|
||||||
|
field.areas ||= []
|
||||||
|
|
||||||
|
const insertIndex = field.areas.findIndex((a) => this.compareAreas(a, area) > 0)
|
||||||
|
|
||||||
|
if (insertIndex === -1) {
|
||||||
|
field.areas.push(area)
|
||||||
|
} else {
|
||||||
|
field.areas.splice(insertIndex, 0, area)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
insertDetectedField (field) {
|
||||||
|
if (!this.withDetectExistingFields || !field.name) {
|
||||||
|
this.insertField(field)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const role = this.template.submitters.find((s) => s.uuid === field.submitter_uuid)?.name
|
||||||
|
const nameKey = field.name.toLowerCase()
|
||||||
|
const indexKey = [field.name, role].filter(Boolean).join(':').toLowerCase()
|
||||||
|
|
||||||
|
const existingField = this.detectFieldsIndex[indexKey]
|
||||||
|
|
||||||
|
if (existingField) {
|
||||||
|
existingField.areas = existingField.areas || []
|
||||||
|
field.areas.forEach((area) => this.insertArea(existingField, area))
|
||||||
|
} else {
|
||||||
|
const customField = this.detectCustomFieldsIndex[indexKey] || this.detectCustomFieldsIndex[nameKey]
|
||||||
|
|
||||||
|
if (customField) this.applyCustomFieldAttributes(field, customField)
|
||||||
|
|
||||||
|
this.insertField(field)
|
||||||
|
}
|
||||||
|
},
|
||||||
closeDropdown () {
|
closeDropdown () {
|
||||||
document.activeElement.blur()
|
document.activeElement.blur()
|
||||||
},
|
},
|
||||||
|
preloadRevisions () {
|
||||||
|
this.loadRevisionsPromise ||= this.baseFetch(`/templates/${this.template.id}/versions`)
|
||||||
|
},
|
||||||
|
openRevisionsModal () {
|
||||||
|
this.closeDropdown()
|
||||||
|
|
||||||
|
this.loadRevisionsPromise ||= this.baseFetch(`/templates/${this.template.id}/versions`)
|
||||||
|
|
||||||
|
this.loadRevisionsPromise.then(async (resp) => {
|
||||||
|
this.revisions = await resp.json()
|
||||||
|
|
||||||
|
this.isRevisionsModalOpen = true
|
||||||
|
}).finally(() => {
|
||||||
|
this.loadRevisionsPromise = null
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onRevisionApply (revision) {
|
||||||
|
this.beforeRevisionSnapshot = {
|
||||||
|
template: JSON.parse(JSON.stringify(this.template)),
|
||||||
|
dynamicDocuments: JSON.parse(JSON.stringify(this.dynamicDocuments)),
|
||||||
|
revision
|
||||||
|
}
|
||||||
|
|
||||||
|
const { dynamic_documents: nextDynamicDocs = [], ...nextTemplate } = revision.data
|
||||||
|
|
||||||
|
Object.assign(this.template, nextTemplate)
|
||||||
|
|
||||||
|
this.dynamicDocuments.splice(0, this.dynamicDocuments.length, ...nextDynamicDocs)
|
||||||
|
|
||||||
|
this.$nextTick(() => this.reloadDynamicDocumentContent())
|
||||||
|
|
||||||
|
this.isRevisionsModalOpen = false
|
||||||
|
},
|
||||||
|
cancelRevision () {
|
||||||
|
Object.assign(this.template, this.beforeRevisionSnapshot.template)
|
||||||
|
|
||||||
|
this.dynamicDocuments.splice(0, this.dynamicDocuments.length, ...this.beforeRevisionSnapshot.dynamicDocuments)
|
||||||
|
|
||||||
|
this.beforeRevisionSnapshot = null
|
||||||
|
|
||||||
|
this.$nextTick(() => this.reloadDynamicDocumentContent())
|
||||||
|
},
|
||||||
|
applyRevision () {
|
||||||
|
this.beforeRevisionSnapshot = null
|
||||||
|
|
||||||
|
const dynamicDocumentRefs = this.documentRefs.filter((ref) => ref.isDynamic)
|
||||||
|
|
||||||
|
dynamicDocumentRefs.forEach((ref) => ref.update())
|
||||||
|
|
||||||
|
this.rebuildVariablesSchema({ disable: false })
|
||||||
|
|
||||||
|
return Promise.all([this.save({ force: true }), ...dynamicDocumentRefs.map((ref) => ref.saveBody())])
|
||||||
|
},
|
||||||
|
reloadDynamicDocumentContent () {
|
||||||
|
this.documentRefs.forEach((ref) => {
|
||||||
|
if (ref.isDynamic) ref.reloadContent()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
formatRevisionTime (string) {
|
||||||
|
return new Date(string).toLocaleString(this.locale || undefined, {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'numeric',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit'
|
||||||
|
})
|
||||||
|
},
|
||||||
t (key) {
|
t (key) {
|
||||||
return this.i18n[key] || i18n[this.language]?.[key] || i18n.en[key] || key
|
return this.i18n[key] || i18n[this.language]?.[key] || i18n.en[key] || key
|
||||||
},
|
},
|
||||||
@@ -1978,7 +2388,7 @@ export default {
|
|||||||
|
|
||||||
fieldUuidIndex[field.uuid] = newField
|
fieldUuidIndex[field.uuid] = newField
|
||||||
|
|
||||||
newField.areas.push(newArea)
|
this.insertArea(newField, newArea)
|
||||||
newAreas.push(newArea)
|
newAreas.push(newArea)
|
||||||
|
|
||||||
if (['radio', 'multiple'].includes(field.type) && field.options?.length) {
|
if (['radio', 'multiple'].includes(field.type) && field.options?.length) {
|
||||||
@@ -2091,17 +2501,7 @@ export default {
|
|||||||
area.y -= area.h / 2
|
area.y -= area.h / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
this.drawField.areas ||= []
|
this.insertArea(this.drawField, area)
|
||||||
|
|
||||||
const insertBeforeAreaIndex = this.drawField.areas.findIndex((a) => {
|
|
||||||
return a.attachment_uuid === area.attachment_uuid && a.page > area.page
|
|
||||||
})
|
|
||||||
|
|
||||||
if (insertBeforeAreaIndex !== -1) {
|
|
||||||
this.drawField.areas.splice(insertBeforeAreaIndex, 0, area)
|
|
||||||
} else {
|
|
||||||
this.drawField.areas.push(area)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.template.fields.indexOf(this.drawField) === -1) {
|
if (this.template.fields.indexOf(this.drawField) === -1) {
|
||||||
this.insertField(this.drawField)
|
this.insertField(this.drawField)
|
||||||
@@ -2242,9 +2642,7 @@ export default {
|
|||||||
delete field.height
|
delete field.height
|
||||||
}
|
}
|
||||||
|
|
||||||
field.areas ||= []
|
this.insertArea(field, fieldArea)
|
||||||
|
|
||||||
field.areas.push(fieldArea)
|
|
||||||
|
|
||||||
if (this.selectedAreasRef.value.length < 2) {
|
if (this.selectedAreasRef.value.length < 2) {
|
||||||
this.selectedAreasRef.value = [fieldArea]
|
this.selectedAreasRef.value = [fieldArea]
|
||||||
@@ -2314,7 +2712,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
field.areas.push(fieldArea)
|
this.insertArea(field, fieldArea)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const fieldArea = {
|
const fieldArea = {
|
||||||
@@ -2743,7 +3141,7 @@ export default {
|
|||||||
|
|
||||||
const dynamicDocumentSaves = dynamicDocumentRefs.map((ref) => ref.saveBody())
|
const dynamicDocumentSaves = dynamicDocumentRefs.map((ref) => ref.saveBody())
|
||||||
|
|
||||||
Promise.all([this.save(), ...dynamicDocumentSaves]).then(() => {
|
Promise.all([this.save({ force: true, revision: this.withRevisions }), ...dynamicDocumentSaves]).then(() => {
|
||||||
window.Turbo.visit(`/templates/${this.template.id}`)
|
window.Turbo.visit(`/templates/${this.template.id}`)
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.isSaving = false
|
this.isSaving = false
|
||||||
@@ -2773,6 +3171,12 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
detectFieldsForPage ({ page, attachmentUuid }) {
|
detectFieldsForPage ({ page, attachmentUuid }) {
|
||||||
|
if (this.isDetectingPageFields) {
|
||||||
|
this.detectFieldsQueue.push({ page, attachmentUuid })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.isDetectingPageFields = true
|
this.isDetectingPageFields = true
|
||||||
this.detectingAnalyzingProgress = null
|
this.detectingAnalyzingProgress = null
|
||||||
this.detectingFieldsAddedCount = null
|
this.detectingFieldsAddedCount = null
|
||||||
@@ -2811,7 +3215,11 @@ export default {
|
|||||||
this.baseFetch(`/templates/${this.template.id}/detect_fields`, {
|
this.baseFetch(`/templates/${this.template.id}/detect_fields`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ attachment_uuid: attachmentUuid, page })
|
body: JSON.stringify({
|
||||||
|
attachment_uuid: attachmentUuid,
|
||||||
|
page,
|
||||||
|
...(this.withDetectExistingFields ? { fields: this.buildExistingFields() } : {})
|
||||||
|
})
|
||||||
}).then(async (response) => {
|
}).then(async (response) => {
|
||||||
const reader = response.body.getReader()
|
const reader = response.body.getReader()
|
||||||
const decoder = new TextDecoder('utf-8')
|
const decoder = new TextDecoder('utf-8')
|
||||||
@@ -2840,7 +3248,7 @@ export default {
|
|||||||
if (!f.submitter_uuid) {
|
if (!f.submitter_uuid) {
|
||||||
f.submitter_uuid = this.template.submitters[0].uuid
|
f.submitter_uuid = this.template.submitters[0].uuid
|
||||||
}
|
}
|
||||||
this.insertField(f)
|
this.insertDetectedField(f)
|
||||||
})
|
})
|
||||||
|
|
||||||
totalFieldsAdded += errorFields.length
|
totalFieldsAdded += errorFields.length
|
||||||
@@ -2869,7 +3277,7 @@ export default {
|
|||||||
|
|
||||||
const nonOverlappingFields = filterNonOverlappingFields(finalFields)
|
const nonOverlappingFields = filterNonOverlappingFields(finalFields)
|
||||||
|
|
||||||
nonOverlappingFields.forEach((f) => this.insertField(f))
|
nonOverlappingFields.forEach((f) => this.insertDetectedField(f))
|
||||||
totalFieldsAdded += nonOverlappingFields.length
|
totalFieldsAdded += nonOverlappingFields.length
|
||||||
|
|
||||||
if (nonOverlappingFields.length) {
|
if (nonOverlappingFields.length) {
|
||||||
@@ -2907,7 +3315,7 @@ export default {
|
|||||||
|
|
||||||
const nonOverlappingFields = filterNonOverlappingFields(finalFields)
|
const nonOverlappingFields = filterNonOverlappingFields(finalFields)
|
||||||
|
|
||||||
nonOverlappingFields.forEach((f) => this.insertField(f))
|
nonOverlappingFields.forEach((f) => this.insertDetectedField(f))
|
||||||
totalFieldsAdded += nonOverlappingFields.length
|
totalFieldsAdded += nonOverlappingFields.length
|
||||||
|
|
||||||
if (nonOverlappingFields.length) {
|
if (nonOverlappingFields.length) {
|
||||||
@@ -2925,7 +3333,7 @@ export default {
|
|||||||
|
|
||||||
const nonOverlappingFields = filterNonOverlappingFields(finalFields)
|
const nonOverlappingFields = filterNonOverlappingFields(finalFields)
|
||||||
|
|
||||||
nonOverlappingFields.forEach((f) => this.insertField(f))
|
nonOverlappingFields.forEach((f) => this.insertDetectedField(f))
|
||||||
totalFieldsAdded += nonOverlappingFields.length
|
totalFieldsAdded += nonOverlappingFields.length
|
||||||
|
|
||||||
if (nonOverlappingFields.length) {
|
if (nonOverlappingFields.length) {
|
||||||
@@ -2958,11 +3366,19 @@ export default {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.detectingFieldsAddedCount = null
|
this.detectingFieldsAddedCount = null
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
|
if (this.detectFieldsQueue.length) {
|
||||||
|
this.detectFieldsForPage(this.detectFieldsQueue.shift())
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
save ({ force } = { force: false }) {
|
save ({ force = false, revision = false } = {}) {
|
||||||
this.pendingFieldAttachmentUuids = []
|
this.pendingFieldAttachmentUuids = []
|
||||||
|
|
||||||
|
if (this.beforeRevisionSnapshot) {
|
||||||
|
this.beforeRevisionSnapshot = null
|
||||||
|
}
|
||||||
|
|
||||||
if (this.onChange) {
|
if (this.onChange) {
|
||||||
this.onChange(this.template)
|
this.onChange(this.template)
|
||||||
}
|
}
|
||||||
@@ -2988,7 +3404,8 @@ export default {
|
|||||||
submitters: this.template.submitters,
|
submitters: this.template.submitters,
|
||||||
fields: this.template.fields,
|
fields: this.template.fields,
|
||||||
variables_schema: this.template.variables_schema
|
variables_schema: this.template.variables_schema
|
||||||
}
|
},
|
||||||
|
...(revision ? { revision: true } : {})
|
||||||
}),
|
}),
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
class="absolute top-0 bottom-0 right-0 left-0"
|
class="absolute top-0 bottom-0 right-0 left-0"
|
||||||
@click.prevent="$emit('close')"
|
@click.prevent="$emit('close')"
|
||||||
/>
|
/>
|
||||||
<div class="modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl">
|
<div class="modal-box pt-4 pb-6 px-6 mt-20 w-full max-w-xl">
|
||||||
<div class="flex justify-between items-center border-b pb-2 mb-2 font-medium">
|
<div class="flex justify-between items-center border-b pb-2 mb-2 font-medium">
|
||||||
<span class="modal-title">
|
<span class="modal-title">
|
||||||
{{ t('condition') }} - {{ (defaultField ? (defaultField.title || item.title || item.name) : item.name) || buildDefaultName(item) }}
|
{{ t('condition') }} - {{ (defaultField ? (defaultField.title || item.title || item.name) : item.name) || buildDefaultName(item) }}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
:key="image.id"
|
:key="image.id"
|
||||||
:ref="setPageRefs"
|
:ref="setPageRefs"
|
||||||
:input-mode="inputMode"
|
:input-mode="inputMode"
|
||||||
|
:conditional-field-index="conditionalFieldIndex"
|
||||||
|
:formula-values-index="formulaValuesIndex"
|
||||||
:number="index"
|
:number="index"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
:data-page="index"
|
:data-page="index"
|
||||||
@@ -64,6 +66,16 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
conditionalFieldIndex: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
formulaValuesIndex: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
areasIndex: {
|
areasIndex: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -138,6 +150,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
pagePreviewFormat: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '.jpg'
|
||||||
|
},
|
||||||
withFieldsDetection: {
|
withFieldsDetection: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -168,7 +185,7 @@ export default {
|
|||||||
return this.previewImagesIndex[i] || reactive({
|
return this.previewImagesIndex[i] || reactive({
|
||||||
metadata: { ...lazyloadMetadata },
|
metadata: { ...lazyloadMetadata },
|
||||||
id: Math.random().toString(),
|
id: Math.random().toString(),
|
||||||
url: this.basePreviewUrl + `/preview/${this.document.signed_key || this.document.signed_uuid || this.document.uuid}/${i}.jpg`
|
url: this.basePreviewUrl + `/preview/${this.document.signed_key || this.document.signed_uuid || this.document.uuid}/${i}${this.pagePreviewFormat}`
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -183,6 +183,9 @@ export default {
|
|||||||
this.sectionRefs.push(ref)
|
this.sectionRefs.push(ref)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
reloadContent () {
|
||||||
|
this.sectionRefs.forEach((ref) => ref.reloadContent())
|
||||||
|
},
|
||||||
onBeforeUnload (event) {
|
onBeforeUnload (event) {
|
||||||
if (this.saveTimer) {
|
if (this.saveTimer) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|||||||
@@ -25,6 +25,13 @@ tiptapStylesheet.replaceSync(
|
|||||||
-webkit-font-variant-ligatures: none;
|
-webkit-font-variant-ligatures: none;
|
||||||
font-variant-ligatures: none;
|
font-variant-ligatures: none;
|
||||||
font-feature-settings: "liga" 0;
|
font-feature-settings: "liga" 0;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
min-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ProseMirror > article {
|
||||||
|
margin-bottom: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ProseMirror [contenteditable="false"] {
|
.ProseMirror [contenteditable="false"] {
|
||||||
@@ -188,6 +195,8 @@ const CustomHeading = Node.create({
|
|||||||
|
|
||||||
const SectionNode = createBlockNode('section', 'section')
|
const SectionNode = createBlockNode('section', 'section')
|
||||||
const ArticleNode = createBlockNode('article', 'article')
|
const ArticleNode = createBlockNode('article', 'article')
|
||||||
|
const HeaderNode = createBlockNode('header', 'header')
|
||||||
|
const FooterNode = createBlockNode('footer', 'footer')
|
||||||
const DivNode = createBlockNode('div', 'div')
|
const DivNode = createBlockNode('div', 'div')
|
||||||
const BlockquoteNode = createBlockNode('blockquote', 'blockquote')
|
const BlockquoteNode = createBlockNode('blockquote', 'blockquote')
|
||||||
const PreNode = createBlockNode('pre', 'pre')
|
const PreNode = createBlockNode('pre', 'pre')
|
||||||
@@ -738,14 +747,12 @@ export function buildEditor ({ dynamicAreaProps, attachmentsIndex, renderHtmlFor
|
|||||||
History,
|
History,
|
||||||
Gapcursor,
|
Gapcursor,
|
||||||
Dropcursor,
|
Dropcursor,
|
||||||
CustomBold,
|
|
||||||
CustomItalic,
|
|
||||||
CustomUnderline,
|
|
||||||
CustomStrike,
|
|
||||||
CustomParagraph,
|
CustomParagraph,
|
||||||
CustomHeading,
|
CustomHeading,
|
||||||
SectionNode,
|
SectionNode,
|
||||||
ArticleNode,
|
ArticleNode,
|
||||||
|
HeaderNode,
|
||||||
|
FooterNode,
|
||||||
DivNode,
|
DivNode,
|
||||||
BlockquoteNode,
|
BlockquoteNode,
|
||||||
PreNode,
|
PreNode,
|
||||||
@@ -764,6 +771,10 @@ export function buildEditor ({ dynamicAreaProps, attachmentsIndex, renderHtmlFor
|
|||||||
EmptySpanNode,
|
EmptySpanNode,
|
||||||
LinkMark,
|
LinkMark,
|
||||||
SpanMark,
|
SpanMark,
|
||||||
|
CustomBold,
|
||||||
|
CustomItalic,
|
||||||
|
CustomUnderline,
|
||||||
|
CustomStrike,
|
||||||
SubscriptMark,
|
SubscriptMark,
|
||||||
SuperscriptMark,
|
SuperscriptMark,
|
||||||
VariableHighlight,
|
VariableHighlight,
|
||||||
|
|||||||
@@ -281,6 +281,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
reloadContent () {
|
||||||
|
this.editor.commands.setContent(this.section.innerHTML, { emitUpdate: false })
|
||||||
|
},
|
||||||
findAreaNodePos (areaUuid) {
|
findAreaNodePos (areaUuid) {
|
||||||
const el = this.editor.view.dom.querySelector(`[data-area-uuid="${areaUuid}"]`)
|
const el = this.editor.view.dom.querySelector(`[data-area-uuid="${areaUuid}"]`)
|
||||||
|
|
||||||
|
|||||||
@@ -119,7 +119,7 @@
|
|||||||
@change="[schema.format = $event.target.value, save()]"
|
@change="[schema.format = $event.target.value, save()]"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
v-for="format in dateFormats"
|
v-for="format in availableDateFormats"
|
||||||
:key="format"
|
:key="format"
|
||||||
:value="format"
|
:value="format"
|
||||||
>{{ formatDate(new Date(), format) }}</option>
|
>{{ formatDate(new Date(), format) }}</option>
|
||||||
@@ -248,7 +248,7 @@ export default {
|
|||||||
FieldType,
|
FieldType,
|
||||||
IconSettings
|
IconSettings
|
||||||
},
|
},
|
||||||
inject: ['t', 'save', 'backgroundColor'],
|
inject: ['t', 'save', 'backgroundColor', 'dateFormats'],
|
||||||
provide () {
|
provide () {
|
||||||
return {
|
return {
|
||||||
fieldTypes: ['text', 'number', 'date', 'checkbox', 'radio', 'select']
|
fieldTypes: ['text', 'number', 'date', 'checkbox', 'radio', 'select']
|
||||||
@@ -318,20 +318,23 @@ export default {
|
|||||||
'space'
|
'space'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
dateFormats () {
|
availableDateFormats () {
|
||||||
const formats = [
|
const formats = this.dateFormats.length
|
||||||
'MM/DD/YYYY',
|
? [...this.dateFormats]
|
||||||
'DD/MM/YYYY',
|
: [
|
||||||
'YYYY-MM-DD',
|
'MM/DD/YYYY',
|
||||||
'DD-MM-YYYY',
|
'DD/MM/YYYY',
|
||||||
'DD.MM.YYYY',
|
'YYYY-MM-DD',
|
||||||
'MMM D, YYYY',
|
'DD-MM-YYYY',
|
||||||
'MMMM D, YYYY',
|
'DD.MM.YYYY',
|
||||||
'D MMM YYYY',
|
'MMM D, YYYY',
|
||||||
'D MMMM YYYY'
|
'MMMM D, YYYY',
|
||||||
]
|
'MMMM YYYY',
|
||||||
|
'D MMM YYYY',
|
||||||
|
'D MMMM YYYY'
|
||||||
|
]
|
||||||
|
|
||||||
if (Intl.DateTimeFormat().resolvedOptions().timeZone?.includes('Seoul') || navigator.language?.startsWith('ko')) {
|
if (!this.dateFormats.length && (Intl.DateTimeFormat().resolvedOptions().timeZone?.includes('Seoul') || navigator.language?.startsWith('ko'))) {
|
||||||
formats.push('YYYY년 MM월 DD일')
|
formats.push('YYYY년 MM월 DD일')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,18 +404,47 @@ export default {
|
|||||||
formatDate (date, format) {
|
formatDate (date, format) {
|
||||||
const monthFormats = { M: 'numeric', MM: '2-digit', MMM: 'short', MMMM: 'long' }
|
const monthFormats = { M: 'numeric', MM: '2-digit', MMM: 'short', MMMM: 'long' }
|
||||||
const dayFormats = { D: 'numeric', DD: '2-digit' }
|
const dayFormats = { D: 'numeric', DD: '2-digit' }
|
||||||
const yearFormats = { YYYY: 'numeric', YY: '2-digit' }
|
const yearFormats = { YYYY: 'numeric', YYY: 'numeric', YY: '2-digit' }
|
||||||
|
const hourFormats = { H: 'numeric', HH: '2-digit', h: 'numeric', hh: '2-digit' }
|
||||||
|
const minuteFormats = { m: 'numeric', mm: '2-digit' }
|
||||||
|
const secondFormats = { s: 'numeric', ss: '2-digit' }
|
||||||
|
|
||||||
const parts = new Intl.DateTimeFormat([], {
|
const opts = {
|
||||||
day: dayFormats[format.match(/D+/)],
|
day: dayFormats[format.match(/D+/)],
|
||||||
month: monthFormats[format.match(/M+/)],
|
month: monthFormats[format.match(/M+/)],
|
||||||
year: yearFormats[format.match(/Y+/)]
|
year: yearFormats[format.match(/Y+/)]
|
||||||
}).formatToParts(date)
|
}
|
||||||
|
|
||||||
return format
|
if (format.match(/H+/)) { opts.hour = hourFormats[format.match(/H+/)[0]]; opts.hour12 = false }
|
||||||
.replace(/D+/, parts.find((p) => p.type === 'day').value)
|
if (format.match(/h+/)) { opts.hour = hourFormats[format.match(/h+/)[0]]; opts.hour12 = true }
|
||||||
.replace(/M+/, parts.find((p) => p.type === 'month').value)
|
if (/[Aa]/.test(format) && opts.hour12 === undefined) opts.hour12 = true
|
||||||
.replace(/Y+/, parts.find((p) => p.type === 'year').value)
|
if (format.match(/m+/)) opts.minute = minuteFormats[format.match(/m+/)[0]]
|
||||||
|
if (format.match(/s+/)) opts.second = secondFormats[format.match(/s+/)[0]]
|
||||||
|
if (/z/.test(format)) opts.timeZoneName = 'short'
|
||||||
|
|
||||||
|
const partTypes = {
|
||||||
|
M: 'month',
|
||||||
|
D: 'day',
|
||||||
|
Y: 'year',
|
||||||
|
H: 'hour',
|
||||||
|
h: 'hour',
|
||||||
|
m: 'minute',
|
||||||
|
s: 'second',
|
||||||
|
z: 'timeZoneName',
|
||||||
|
A: 'dayPeriod',
|
||||||
|
a: 'dayPeriod'
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = new Intl.DateTimeFormat([], opts).formatToParts(date)
|
||||||
|
|
||||||
|
return format.replace(/MMMM|MMM|MM|M|DD|D|YYYY|YYY|YY|HH|hh|H|h|mm|m|ss|s|A|a|z/g, (token) => {
|
||||||
|
const value = parts.find((p) => p.type === partTypes[token[0]])?.value
|
||||||
|
|
||||||
|
if (token === 'A') return (value || '').toUpperCase()
|
||||||
|
if (token === 'a') return (value || '').toLowerCase()
|
||||||
|
|
||||||
|
return value
|
||||||
|
})
|
||||||
},
|
},
|
||||||
closeDropdown () {
|
closeDropdown () {
|
||||||
this.$el.getRootNode().activeElement.blur()
|
this.$el.getRootNode().activeElement.blur()
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ export default {
|
|||||||
IconMathFunction,
|
IconMathFunction,
|
||||||
FieldType
|
FieldType
|
||||||
},
|
},
|
||||||
inject: ['template', 'backgroundColor', 'selectedAreasRef', 't', 'locale', 'getFieldTypeIndex'],
|
inject: ['template', 'backgroundColor', 'selectedAreasRef', 't', 'locale', 'getFieldTypeIndex', 'dateFormats'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -428,7 +428,8 @@ export default {
|
|||||||
|
|
||||||
if (this.field.type === 'date') {
|
if (this.field.type === 'date') {
|
||||||
this.field.preferences.format ||=
|
this.field.preferences.format ||=
|
||||||
({ 'de-DE': 'DD.MM.YYYY' }[this.locale] || ((Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US') || new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).format(new Date()).match(/\s(?:CST|CDT|PST|PDT|EST|EDT)$/)) ? 'MM/DD/YYYY' : 'DD/MM/YYYY'))
|
this.dateFormats[0] ||
|
||||||
|
({ 'de-DE': 'DD.MM.YYYY' }[this.locale] || ((Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US') || new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).format(new Date()).match(/\s(?:CST|CDT|PST|PDT|EST|EDT)$/)) ? 'MM/DD/YYYY' : 'DD/MM/YYYY'))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -514,7 +514,7 @@ export default {
|
|||||||
ContextSubmenu,
|
ContextSubmenu,
|
||||||
ContextModal
|
ContextModal
|
||||||
},
|
},
|
||||||
inject: ['t', 'getFieldTypeIndex', 'template', 'withCustomFields', 'currencies'],
|
inject: ['t', 'getFieldTypeIndex', 'template', 'withCustomFields', 'currencies', 'dateFormats'],
|
||||||
props: {
|
props: {
|
||||||
contextMenu: {
|
contextMenu: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -580,7 +580,7 @@ export default {
|
|||||||
fieldNames: FieldType.computed.fieldNames,
|
fieldNames: FieldType.computed.fieldNames,
|
||||||
fieldLabels: FieldType.computed.fieldLabels,
|
fieldLabels: FieldType.computed.fieldLabels,
|
||||||
validationOptions: FieldSettings.computed.validations,
|
validationOptions: FieldSettings.computed.validations,
|
||||||
dateFormats: FieldSettings.computed.dateFormats,
|
availableDateFormats: FieldSettings.computed.availableDateFormats,
|
||||||
numberFormats: FieldSettings.computed.numberFormats,
|
numberFormats: FieldSettings.computed.numberFormats,
|
||||||
prefillableFieldTypes: FieldSettings.computed.prefillableFieldTypes,
|
prefillableFieldTypes: FieldSettings.computed.prefillableFieldTypes,
|
||||||
verificationMethods: FieldSettings.computed.verificationMethods,
|
verificationMethods: FieldSettings.computed.verificationMethods,
|
||||||
@@ -686,7 +686,7 @@ export default {
|
|||||||
},
|
},
|
||||||
formatOptions () {
|
formatOptions () {
|
||||||
switch (this.field.type) {
|
switch (this.field.type) {
|
||||||
case 'date': return this.dateFormats.map(f => ({ value: f, label: this.formatDate(new Date(), f) }))
|
case 'date': return this.availableDateFormats.map(f => ({ value: f, label: this.formatDate(new Date(), f) }))
|
||||||
case 'number': return this.numberFormats.map(f => ({ value: f, label: this.formatNumber(123456789.567, f) }))
|
case 'number': return this.numberFormats.map(f => ({ value: f, label: this.formatNumber(123456789.567, f) }))
|
||||||
case 'signature': return this.signatureFormats.map(f => ({ value: f, label: this.t(f) }))
|
case 'signature': return this.signatureFormats.map(f => ({ value: f, label: this.t(f) }))
|
||||||
default: return []
|
default: return []
|
||||||
|
|||||||
@@ -290,7 +290,7 @@
|
|||||||
@change="$emit('save')"
|
@change="$emit('save')"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
v-for="format in dateFormats"
|
v-for="format in availableDateFormats"
|
||||||
:key="format"
|
:key="format"
|
||||||
:value="format"
|
:value="format"
|
||||||
>
|
>
|
||||||
@@ -610,7 +610,7 @@ export default {
|
|||||||
IconTypography,
|
IconTypography,
|
||||||
IconX
|
IconX
|
||||||
},
|
},
|
||||||
inject: ['template', 't'],
|
inject: ['template', 't', 'dateFormats'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -701,20 +701,23 @@ export default {
|
|||||||
'space'
|
'space'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
dateFormats () {
|
availableDateFormats () {
|
||||||
const formats = [
|
const formats = this.dateFormats.length
|
||||||
'MM/DD/YYYY',
|
? [...this.dateFormats]
|
||||||
'DD/MM/YYYY',
|
: [
|
||||||
'YYYY-MM-DD',
|
'MM/DD/YYYY',
|
||||||
'DD-MM-YYYY',
|
'DD/MM/YYYY',
|
||||||
'DD.MM.YYYY',
|
'YYYY-MM-DD',
|
||||||
'MMM D, YYYY',
|
'DD-MM-YYYY',
|
||||||
'MMMM D, YYYY',
|
'DD.MM.YYYY',
|
||||||
'D MMM YYYY',
|
'MMM D, YYYY',
|
||||||
'D MMMM YYYY'
|
'MMMM D, YYYY',
|
||||||
]
|
'MMMM YYYY',
|
||||||
|
'D MMM YYYY',
|
||||||
|
'D MMMM YYYY'
|
||||||
|
]
|
||||||
|
|
||||||
if (Intl.DateTimeFormat().resolvedOptions().timeZone?.includes('Seoul') || navigator.language?.startsWith('ko')) {
|
if (!this.dateFormats.length && (Intl.DateTimeFormat().resolvedOptions().timeZone?.includes('Seoul') || navigator.language?.startsWith('ko'))) {
|
||||||
formats.push('YYYY년 MM월 DD일')
|
formats.push('YYYY년 MM월 DD일')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -783,17 +786,27 @@ export default {
|
|||||||
},
|
},
|
||||||
copyToAllPages (field) {
|
copyToAllPages (field) {
|
||||||
const areaString = JSON.stringify(field.areas[0])
|
const areaString = JSON.stringify(field.areas[0])
|
||||||
|
const newAreas = []
|
||||||
|
const existingAreasIndex = field.areas.reduce((acc, area) => {
|
||||||
|
acc[`${area.attachment_uuid}-${area.page}`] = area
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
this.template.schema.forEach((item) => {
|
||||||
|
const attachment = this.template.documents.find((d) => d.uuid === item.attachment_uuid)
|
||||||
|
|
||||||
this.template.documents.forEach((attachment) => {
|
|
||||||
const numberOfPages = attachment.metadata?.pdf?.number_of_pages || attachment.preview_images.length
|
const numberOfPages = attachment.metadata?.pdf?.number_of_pages || attachment.preview_images.length
|
||||||
|
|
||||||
for (let page = 0; page <= numberOfPages - 1; page++) {
|
for (let page = 0; page <= numberOfPages - 1; page++) {
|
||||||
if (!field.areas.find((area) => area.attachment_uuid === attachment.uuid && area.page === page)) {
|
const existing = existingAreasIndex[`${attachment.uuid}-${page}`]
|
||||||
field.areas.push({ ...JSON.parse(areaString), attachment_uuid: attachment.uuid, page })
|
|
||||||
}
|
newAreas.push(existing || { ...JSON.parse(areaString), attachment_uuid: attachment.uuid, page })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
field.areas = newAreas
|
||||||
|
|
||||||
this.$emit('scroll-to', this.field.areas[this.field.areas.length - 1])
|
this.$emit('scroll-to', this.field.areas[this.field.areas.length - 1])
|
||||||
|
|
||||||
this.$emit('save')
|
this.$emit('save')
|
||||||
@@ -819,33 +832,49 @@ export default {
|
|||||||
return pattern?.match(/^\.{(?<min>\d+),(?<max>\d+)?}$/)?.groups || null
|
return pattern?.match(/^\.{(?<min>\d+),(?<max>\d+)?}$/)?.groups || null
|
||||||
},
|
},
|
||||||
formatDate (date, format) {
|
formatDate (date, format) {
|
||||||
const monthFormats = {
|
const monthFormats = { M: 'numeric', MM: '2-digit', MMM: 'short', MMMM: 'long' }
|
||||||
M: 'numeric',
|
const dayFormats = { D: 'numeric', DD: '2-digit' }
|
||||||
MM: '2-digit',
|
const yearFormats = { YYYY: 'numeric', YYY: 'numeric', YY: '2-digit' }
|
||||||
MMM: 'short',
|
const hourFormats = { H: 'numeric', HH: '2-digit', h: 'numeric', hh: '2-digit' }
|
||||||
MMMM: 'long'
|
const minuteFormats = { m: 'numeric', mm: '2-digit' }
|
||||||
}
|
const secondFormats = { s: 'numeric', ss: '2-digit' }
|
||||||
|
|
||||||
const dayFormats = {
|
const opts = {
|
||||||
D: 'numeric',
|
|
||||||
DD: '2-digit'
|
|
||||||
}
|
|
||||||
|
|
||||||
const yearFormats = {
|
|
||||||
YYYY: 'numeric',
|
|
||||||
YY: '2-digit'
|
|
||||||
}
|
|
||||||
|
|
||||||
const parts = new Intl.DateTimeFormat([], {
|
|
||||||
day: dayFormats[format.match(/D+/)],
|
day: dayFormats[format.match(/D+/)],
|
||||||
month: monthFormats[format.match(/M+/)],
|
month: monthFormats[format.match(/M+/)],
|
||||||
year: yearFormats[format.match(/Y+/)]
|
year: yearFormats[format.match(/Y+/)]
|
||||||
}).formatToParts(date)
|
}
|
||||||
|
|
||||||
return format
|
if (format.match(/H+/)) { opts.hour = hourFormats[format.match(/H+/)[0]]; opts.hour12 = false }
|
||||||
.replace(/D+/, parts.find((p) => p.type === 'day').value)
|
if (format.match(/h+/)) { opts.hour = hourFormats[format.match(/h+/)[0]]; opts.hour12 = true }
|
||||||
.replace(/M+/, parts.find((p) => p.type === 'month').value)
|
if (/[Aa]/.test(format) && opts.hour12 === undefined) opts.hour12 = true
|
||||||
.replace(/Y+/, parts.find((p) => p.type === 'year').value)
|
if (format.match(/m+/)) opts.minute = minuteFormats[format.match(/m+/)[0]]
|
||||||
|
if (format.match(/s+/)) opts.second = secondFormats[format.match(/s+/)[0]]
|
||||||
|
if (/z/.test(format)) opts.timeZoneName = 'short'
|
||||||
|
|
||||||
|
const partTypes = {
|
||||||
|
M: 'month',
|
||||||
|
D: 'day',
|
||||||
|
Y: 'year',
|
||||||
|
H: 'hour',
|
||||||
|
h: 'hour',
|
||||||
|
m: 'minute',
|
||||||
|
s: 'second',
|
||||||
|
z: 'timeZoneName',
|
||||||
|
A: 'dayPeriod',
|
||||||
|
a: 'dayPeriod'
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = new Intl.DateTimeFormat([], opts).formatToParts(date)
|
||||||
|
|
||||||
|
return format.replace(/MMMM|MMM|MM|M|DD|D|YYYY|YYY|YY|HH|hh|H|h|mm|m|ss|s|A|a|z/g, (token) => {
|
||||||
|
const value = parts.find((p) => p.type === partTypes[token[0]])?.value
|
||||||
|
|
||||||
|
if (token === 'A') return (value || '').toUpperCase()
|
||||||
|
if (token === 'a') return (value || '').toLowerCase()
|
||||||
|
|
||||||
|
return value
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,7 +147,7 @@
|
|||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="flex px-2 group justify-between items-center"
|
class="flex px-2 text-sm group justify-between items-center"
|
||||||
:class="{ 'active': submitter === selectedSubmitter, 'py-0.5': submitters.length > 8 }"
|
:class="{ 'active': submitter === selectedSubmitter, 'py-0.5': submitters.length > 8 }"
|
||||||
@click.prevent="selectSubmitter(submitter)"
|
@click.prevent="selectSubmitter(submitter)"
|
||||||
>
|
>
|
||||||
@@ -198,7 +198,7 @@
|
|||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="flex px-2"
|
class="flex px-2 text-sm"
|
||||||
@click.prevent="addSubmitter"
|
@click.prevent="addSubmitter"
|
||||||
>
|
>
|
||||||
<IconUserPlus
|
<IconUserPlus
|
||||||
|
|||||||
@@ -65,13 +65,33 @@
|
|||||||
@set-draw="$emit('set-draw', $event)"
|
@set-draw="$emit('set-draw', $event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!isShowVariables && submitterDefaultFields.length && editable">
|
<div
|
||||||
<hr class="mb-2">
|
v-if="editable && withCustomFieldsTab"
|
||||||
|
class="tabs w-full mb-1.5 custom-fields-tabs"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="tab tab-bordered w-1/2 border-base-300 custom-fields-tab"
|
||||||
|
:class="{ 'tab-active': !showCustomTab }"
|
||||||
|
:style="{ '--tab-border': showCustomTab ? '0px' : '0.5px' }"
|
||||||
|
@click="setFieldsTab('default')"
|
||||||
|
>{{ t('default') }}</a>
|
||||||
|
<a
|
||||||
|
class="tab tab-bordered w-1/2 border-base-300 custom-fields-tab"
|
||||||
|
:class="{ 'tab-active': showCustomTab }"
|
||||||
|
:style="{ '--tab-border': showCustomTab ? '0.5px' : '0px' }"
|
||||||
|
@click="setFieldsTab('custom')"
|
||||||
|
>{{ t('custom') }}</a>
|
||||||
|
</div>
|
||||||
|
<div v-if="!isShowVariables && submitterDefaultFields.length && editable && (!withCustomFieldsTab || showCustomTab)">
|
||||||
|
<hr
|
||||||
|
v-if="!withCustomFieldsTab"
|
||||||
|
class="mb-1.5"
|
||||||
|
>
|
||||||
<template v-if="isShowFieldSearch">
|
<template v-if="isShowFieldSearch">
|
||||||
<input
|
<input
|
||||||
v-model="defaultFieldsSearch"
|
v-model="defaultFieldsSearch"
|
||||||
:placeholder="t('search_field')"
|
:placeholder="t('search_field')"
|
||||||
class="input input-ghost input-xs px-0 text-base mb-2 !outline-0 !rounded bg-transparent w-full"
|
class="input input-ghost input-xs px-0 text-base mb-1.5 !outline-0 !rounded bg-transparent w-full"
|
||||||
>
|
>
|
||||||
<hr class="mb-2">
|
<hr class="mb-2">
|
||||||
</template>
|
</template>
|
||||||
@@ -218,7 +238,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="!isShowVariables && editable && !onlyDefinedFields && (!showCustomTab || (!customFields.length && !newCustomField))"
|
v-if="!isShowVariables && editable && !onlyDefinedFields && (!showCustomTab || (!customFields.length && !newCustomField)) && (!withCustomFieldsTab || !showCustomTab)"
|
||||||
id="field-types-grid"
|
id="field-types-grid"
|
||||||
class="grid grid-cols-3 gap-1 pb-2 fields-grid"
|
class="grid grid-cols-3 gap-1 pb-2 fields-grid"
|
||||||
>
|
>
|
||||||
@@ -363,7 +383,7 @@
|
|||||||
class="rounded py-2 px-4 w-full border border-dashed border-base-300"
|
class="rounded py-2 px-4 w-full border border-dashed border-base-300"
|
||||||
>
|
>
|
||||||
<div class="text-center text-sm">
|
<div class="text-center text-sm">
|
||||||
{{ t('start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document') }}
|
{{ t('start_a_quick_tour_to_learn_how_to_create_and_send_your_first_document') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<label
|
<label
|
||||||
@@ -418,6 +438,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
withCustomFieldsTab: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
withFieldsSearch: {
|
withFieldsSearch: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -428,6 +453,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
withDetectExistingFields: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
withSignatureId: {
|
withSignatureId: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -495,6 +525,11 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
detectCustomFieldsIndex: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
showTourStartForm: {
|
showTourStartForm: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -557,7 +592,8 @@ export default {
|
|||||||
},
|
},
|
||||||
submitterDefaultFields () {
|
submitterDefaultFields () {
|
||||||
return this.defaultFields.filter((f) => {
|
return this.defaultFields.filter((f) => {
|
||||||
return !this.submitterFields.find((field) => field.name === f.name) && (!f.role || f.role === this.selectedSubmitter.name)
|
return (this.withCustomFieldsTab ? true : !this.submitterFields.find((field) => field.name === f.name)) &&
|
||||||
|
(!f.role || f.role === this.selectedSubmitter.name)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
filteredSubmitterDefaultFields () {
|
filteredSubmitterDefaultFields () {
|
||||||
@@ -656,6 +692,78 @@ export default {
|
|||||||
this.customFields.splice(0, this.customFields.length, ...fields)
|
this.customFields.splice(0, this.customFields.length, ...fields)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
buildExistingFields () {
|
||||||
|
const existing = []
|
||||||
|
const seen = new Set()
|
||||||
|
|
||||||
|
const submittersByUuid = this.template.submitters.reduce((acc, s) => {
|
||||||
|
acc[s.uuid] = s
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
const add = (field, role) => {
|
||||||
|
if (!field?.name) return
|
||||||
|
|
||||||
|
const key = field.name.toLowerCase() + ':' + (role || '').toLowerCase()
|
||||||
|
|
||||||
|
if (seen.has(key)) return
|
||||||
|
|
||||||
|
seen.add(key)
|
||||||
|
|
||||||
|
const item = { name: field.name, type: field.type || 'text' }
|
||||||
|
|
||||||
|
if (role) item.role = role
|
||||||
|
|
||||||
|
const optionValues = Array.isArray(field.options)
|
||||||
|
? field.options.map((o) => (typeof o === 'string' ? o : o?.value)).filter(Boolean)
|
||||||
|
: []
|
||||||
|
|
||||||
|
if (optionValues.length) item.options = optionValues
|
||||||
|
|
||||||
|
existing.push(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.template.fields.forEach((f) => add(f, submittersByUuid[f.submitter_uuid]?.name))
|
||||||
|
this.defaultRequiredFields.forEach((f) => add(f, f.role))
|
||||||
|
this.defaultFields.forEach((f) => add(f, f.role))
|
||||||
|
this.customFields.forEach((f) => add(f, f.role))
|
||||||
|
|
||||||
|
return existing
|
||||||
|
},
|
||||||
|
enrichDetectedField (field) {
|
||||||
|
if (!this.withDetectExistingFields || !field.name) return field
|
||||||
|
|
||||||
|
const role = this.template.submitters.find((s) => s.uuid === field.submitter_uuid)?.name
|
||||||
|
const nameKey = field.name.toLowerCase()
|
||||||
|
const indexKey = [field.name, role].filter(Boolean).join(':').toLowerCase()
|
||||||
|
|
||||||
|
const customField = this.detectCustomFieldsIndex[indexKey] || this.detectCustomFieldsIndex[nameKey]
|
||||||
|
|
||||||
|
if (customField) this.applyCustomFieldAttributes(field, customField)
|
||||||
|
|
||||||
|
return field
|
||||||
|
},
|
||||||
|
applyCustomFieldAttributes (field, customField) {
|
||||||
|
const skipKeys = new Set(['uuid', 'areas', 'submitter_uuid', 'conditions', 'prefillable', 'role'])
|
||||||
|
|
||||||
|
Object.entries(customField).forEach(([key, value]) => {
|
||||||
|
if (skipKeys.has(key)) return
|
||||||
|
if (value === null || value === undefined) return
|
||||||
|
|
||||||
|
if (key === 'options') {
|
||||||
|
if (Array.isArray(value) && !Array.isArray(field.options)) {
|
||||||
|
field.options = value.map((o) => (
|
||||||
|
typeof o === 'string' ? { value: o, uuid: v4() } : { ...o, uuid: v4() }
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||||
|
field[key] = JSON.parse(JSON.stringify(value))
|
||||||
|
} else {
|
||||||
|
field[key] = value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
detectFields () {
|
detectFields () {
|
||||||
const fields = []
|
const fields = []
|
||||||
|
|
||||||
@@ -665,7 +773,10 @@ export default {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
},
|
||||||
|
...(this.withDetectExistingFields
|
||||||
|
? { body: JSON.stringify({ fields: this.buildExistingFields() }) }
|
||||||
|
: {})
|
||||||
}).then(async (response) => {
|
}).then(async (response) => {
|
||||||
const reader = response.body.getReader()
|
const reader = response.body.getReader()
|
||||||
const decoder = new TextDecoder('utf-8')
|
const decoder = new TextDecoder('utf-8')
|
||||||
@@ -687,7 +798,7 @@ export default {
|
|||||||
|
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
if ((data.fields || fields).length) {
|
if ((data.fields || fields).length) {
|
||||||
this.template.fields = data.fields || fields
|
this.template.fields = (data.fields || fields).map((f) => this.enrichDetectedField(f))
|
||||||
|
|
||||||
this.save()
|
this.save()
|
||||||
} else {
|
} else {
|
||||||
@@ -705,7 +816,7 @@ export default {
|
|||||||
this.$emit('select-submitter', this.template.submitters[0])
|
this.$emit('select-submitter', this.template.submitters[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
this.template.fields = data.fields || fields
|
this.template.fields = (data.fields || fields).map((f) => this.enrichDetectedField(f))
|
||||||
|
|
||||||
this.save()
|
this.save()
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ const en = {
|
|||||||
sign_yourself: 'Sign Yourself',
|
sign_yourself: 'Sign Yourself',
|
||||||
set_signing_date: 'Set signing date',
|
set_signing_date: 'Set signing date',
|
||||||
signing_date: 'Signing Date',
|
signing_date: 'Signing Date',
|
||||||
|
signing_date_and_time: 'Signing Date and Time',
|
||||||
send: 'Send',
|
send: 'Send',
|
||||||
remove: 'Remove',
|
remove: 'Remove',
|
||||||
edit: 'Edit',
|
edit: 'Edit',
|
||||||
@@ -199,7 +200,7 @@ const en = {
|
|||||||
learn_more: 'Learn more',
|
learn_more: 'Learn more',
|
||||||
and: 'and',
|
and: 'and',
|
||||||
or: 'or',
|
or: 'or',
|
||||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Start a quick tour to learn how to create and send your first document',
|
start_a_quick_tour_to_learn_how_to_create_and_send_your_first_document: 'Start a quick tour to learn how to create and send your first document',
|
||||||
start_tour: 'Start Tour',
|
start_tour: 'Start Tour',
|
||||||
or_add_from: 'Or add from',
|
or_add_from: 'Or add from',
|
||||||
sync: 'Sync',
|
sync: 'Sync',
|
||||||
@@ -214,7 +215,11 @@ const en = {
|
|||||||
align_bottom: 'Align Bottom',
|
align_bottom: 'Align Bottom',
|
||||||
fields_selected: '{count} Fields Selected',
|
fields_selected: '{count} Fields Selected',
|
||||||
field_added: '{count} Field Added',
|
field_added: '{count} Field Added',
|
||||||
fields_added: '{count} Fields Added'
|
fields_added: '{count} Fields Added',
|
||||||
|
revisions: 'Revisions',
|
||||||
|
apply: 'Apply',
|
||||||
|
no_revisions_yet: 'No revisions yet',
|
||||||
|
viewing_revision_from: 'Viewing revision from {date}'
|
||||||
}
|
}
|
||||||
|
|
||||||
const es = {
|
const es = {
|
||||||
@@ -238,7 +243,7 @@ const es = {
|
|||||||
min: 'Mín',
|
min: 'Mín',
|
||||||
max: 'Máx',
|
max: 'Máx',
|
||||||
date_signed: 'Fecha actual',
|
date_signed: 'Fecha actual',
|
||||||
fuente: 'Fuente',
|
font: 'Fuente',
|
||||||
party: 'Parte',
|
party: 'Parte',
|
||||||
method: 'Método',
|
method: 'Método',
|
||||||
reorder_fields: 'Reordenar campos',
|
reorder_fields: 'Reordenar campos',
|
||||||
@@ -271,6 +276,7 @@ const es = {
|
|||||||
with_logo: 'Con logotipo',
|
with_logo: 'Con logotipo',
|
||||||
description: 'Descripción',
|
description: 'Descripción',
|
||||||
signing_date: 'Fecha de Firma',
|
signing_date: 'Fecha de Firma',
|
||||||
|
signing_date_and_time: 'Fecha y Hora de Firma',
|
||||||
display_title: 'Título de visualización',
|
display_title: 'Título de visualización',
|
||||||
unchecked: 'No marcado',
|
unchecked: 'No marcado',
|
||||||
price: 'Precio',
|
price: 'Precio',
|
||||||
@@ -296,7 +302,7 @@ const es = {
|
|||||||
set_signing_date: 'Establecer fecha de firma',
|
set_signing_date: 'Establecer fecha de firma',
|
||||||
are_you_sure_: '¿Estás seguro?',
|
are_you_sure_: '¿Estás seguro?',
|
||||||
sign_yourself: 'Firma tú mismo',
|
sign_yourself: 'Firma tú mismo',
|
||||||
checked: 'Seleccionado',
|
checked: 'Marcado',
|
||||||
current_date: 'Fecha actual',
|
current_date: 'Fecha actual',
|
||||||
send: 'Enviar',
|
send: 'Enviar',
|
||||||
remove: 'Eliminar',
|
remove: 'Eliminar',
|
||||||
@@ -334,16 +340,16 @@ const es = {
|
|||||||
eighth_party: 'Octava Parte',
|
eighth_party: 'Octava Parte',
|
||||||
ninth_party: 'Novena Parte',
|
ninth_party: 'Novena Parte',
|
||||||
tenth_party: 'Décima Parte',
|
tenth_party: 'Décima Parte',
|
||||||
eleventh_party: 'Undécimo Partido',
|
eleventh_party: 'Undécima Parte',
|
||||||
twelfth_party: 'Duodécimo Partido',
|
twelfth_party: 'Duodécima Parte',
|
||||||
thirteenth_party: 'Decimotercer Partido',
|
thirteenth_party: 'Decimotercera Parte',
|
||||||
fourteenth_party: 'Catorceavo Partido',
|
fourteenth_party: 'Decimocuarta Parte',
|
||||||
fifteenth_party: 'Quinceavo Partido',
|
fifteenth_party: 'Decimoquinta Parte',
|
||||||
sixteenth_party: 'Dieciséisavo Partido',
|
sixteenth_party: 'Decimosexta Parte',
|
||||||
seventeenth_party: 'Diecisieteavo Partido',
|
seventeenth_party: 'Decimoséptima Parte',
|
||||||
eighteenth_party: 'Dieciochoavo Partido',
|
eighteenth_party: 'Decimoctava Parte',
|
||||||
nineteenth_party: 'Decimonovena Fiesta',
|
nineteenth_party: 'Decimonovena Parte',
|
||||||
twentieth_party: 'Vigésima Fiesta',
|
twentieth_party: 'Vigésima Parte',
|
||||||
draw: 'Dibujar',
|
draw: 'Dibujar',
|
||||||
add: 'Agregar',
|
add: 'Agregar',
|
||||||
text: 'Texto',
|
text: 'Texto',
|
||||||
@@ -418,7 +424,7 @@ const es = {
|
|||||||
learn_more: 'Aprende más',
|
learn_more: 'Aprende más',
|
||||||
and: 'y',
|
and: 'y',
|
||||||
or: 'o',
|
or: 'o',
|
||||||
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_and_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',
|
||||||
or_add_from: 'O agregar desde',
|
or_add_from: 'O agregar desde',
|
||||||
sync: 'Sincronizar',
|
sync: 'Sincronizar',
|
||||||
@@ -433,7 +439,11 @@ const es = {
|
|||||||
align_bottom: 'Alinear abajo',
|
align_bottom: 'Alinear abajo',
|
||||||
fields_selected: '{count} Campos Seleccionados',
|
fields_selected: '{count} Campos Seleccionados',
|
||||||
field_added: '{count} Campo Añadido',
|
field_added: '{count} Campo Añadido',
|
||||||
fields_added: '{count} Campos Añadidos'
|
fields_added: '{count} Campos Añadidos',
|
||||||
|
revisions: 'Revisiones',
|
||||||
|
apply: 'Aplicar',
|
||||||
|
no_revisions_yet: 'Aún no hay revisiones',
|
||||||
|
viewing_revision_from: 'Viendo revisión del {date}'
|
||||||
}
|
}
|
||||||
|
|
||||||
const it = {
|
const it = {
|
||||||
@@ -456,15 +466,15 @@ const it = {
|
|||||||
length: 'Lunghezza',
|
length: 'Lunghezza',
|
||||||
min: 'Min',
|
min: 'Min',
|
||||||
max: 'Max',
|
max: 'Max',
|
||||||
date_signed: 'Data attuale',
|
date_signed: 'Data corrente',
|
||||||
font: 'Carattere',
|
font: 'Carattere',
|
||||||
party: 'Parte',
|
party: 'Parte',
|
||||||
method: 'Metodo',
|
method: 'Metodo',
|
||||||
reorder_fields: 'Riordina i campi',
|
reorder_fields: 'Riordina i campi',
|
||||||
verify_id: 'Verifica ID',
|
verify_id: 'Verifica ID',
|
||||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Ottieni una firma elettronica qualificata (QeS) con il fornitore di fiducia. Clicca per saperne di più.',
|
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Ottieni una firma elettronica qualificata (QeS) con il fornitore di fiducia. Clicca per saperne di più.',
|
||||||
ricorrente: 'Ricorrente',
|
recurrent: 'Ricorrente',
|
||||||
una_volta: 'Una volta',
|
one_off: 'Una tantum',
|
||||||
editable: 'Modificabile',
|
editable: 'Modificabile',
|
||||||
search_field: 'Campo di ricerca',
|
search_field: 'Campo di ricerca',
|
||||||
field_not_found: 'Campo non trovato',
|
field_not_found: 'Campo non trovato',
|
||||||
@@ -508,6 +518,7 @@ const it = {
|
|||||||
sign_yourself: 'Firma te stesso',
|
sign_yourself: 'Firma te stesso',
|
||||||
set_signing_date: 'Imposta data di firma',
|
set_signing_date: 'Imposta data di firma',
|
||||||
signing_date: 'Data di firma',
|
signing_date: 'Data di firma',
|
||||||
|
signing_date_and_time: 'Data e ora di firma',
|
||||||
send: 'Invia',
|
send: 'Invia',
|
||||||
remove: 'Rimuovi',
|
remove: 'Rimuovi',
|
||||||
edit: 'Modifica',
|
edit: 'Modifica',
|
||||||
@@ -522,7 +533,7 @@ const it = {
|
|||||||
drawn: 'Disegnato',
|
drawn: 'Disegnato',
|
||||||
drawn_or_typed: 'Disegnato o Digitato',
|
drawn_or_typed: 'Disegnato o Digitato',
|
||||||
drawn_or_upload: 'Disegnato o Caricato',
|
drawn_or_upload: 'Disegnato o Caricato',
|
||||||
upload: 'Caricare',
|
upload: 'Carica',
|
||||||
formula: 'Formula',
|
formula: 'Formula',
|
||||||
typed: 'Digitato',
|
typed: 'Digitato',
|
||||||
draw_field_on_the_document: 'Disegnare un campo sul documento',
|
draw_field_on_the_document: 'Disegnare un campo sul documento',
|
||||||
@@ -637,7 +648,7 @@ const it = {
|
|||||||
learn_more: 'Scopri di più',
|
learn_more: 'Scopri di più',
|
||||||
and: 'e',
|
and: 'e',
|
||||||
or: 'o',
|
or: 'o',
|
||||||
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_and_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',
|
||||||
or_add_from: 'O aggiungi da',
|
or_add_from: 'O aggiungi da',
|
||||||
sync: 'Sincronizza',
|
sync: 'Sincronizza',
|
||||||
@@ -652,7 +663,11 @@ const it = {
|
|||||||
align_bottom: 'Allinea in basso',
|
align_bottom: 'Allinea in basso',
|
||||||
fields_selected: '{count} Campi Selezionati',
|
fields_selected: '{count} Campi Selezionati',
|
||||||
field_added: '{count} Campo Aggiunto',
|
field_added: '{count} Campo Aggiunto',
|
||||||
fields_added: '{count} Campi Aggiunti'
|
fields_added: '{count} Campi Aggiunti',
|
||||||
|
revisions: 'Revisioni',
|
||||||
|
apply: 'Applica',
|
||||||
|
no_revisions_yet: 'Nessuna revisione ancora',
|
||||||
|
viewing_revision_from: 'Visualizzazione revisione del {date}'
|
||||||
}
|
}
|
||||||
|
|
||||||
const pt = {
|
const pt = {
|
||||||
@@ -676,13 +691,13 @@ const pt = {
|
|||||||
min: 'Mín',
|
min: 'Mín',
|
||||||
max: 'Máx',
|
max: 'Máx',
|
||||||
date_signed: 'Data atual',
|
date_signed: 'Data atual',
|
||||||
fonte: 'Fonte',
|
font: 'Fonte',
|
||||||
party: 'Parte',
|
party: 'Parte',
|
||||||
method: 'Método',
|
method: 'Método',
|
||||||
reorder_fields: 'Reorganizar campos',
|
reorder_fields: 'Reorganizar campos',
|
||||||
verify_id: 'Verificar ID',
|
verify_id: 'Verificar ID',
|
||||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtenha a assinatura eletrônica qualificada (QeS) com o provedor confiável. Clique para saber mais.',
|
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtenha a assinatura eletrônica qualificada (QeS) com o provedor confiável. Clique para saber mais.',
|
||||||
recurrent: 'Recurrente',
|
recurrent: 'Recorrente',
|
||||||
one_off: 'Único',
|
one_off: 'Único',
|
||||||
editable: 'Editável',
|
editable: 'Editável',
|
||||||
search_field: 'Campo de busca',
|
search_field: 'Campo de busca',
|
||||||
@@ -710,6 +725,7 @@ const pt = {
|
|||||||
description: 'Descrição',
|
description: 'Descrição',
|
||||||
display_title: 'Título de exibição',
|
display_title: 'Título de exibição',
|
||||||
signing_date: 'Data da Assinatura',
|
signing_date: 'Data da Assinatura',
|
||||||
|
signing_date_and_time: 'Data e Hora da Assinatura',
|
||||||
unchecked: 'Não marcado',
|
unchecked: 'Não marcado',
|
||||||
price: 'Preço',
|
price: 'Preço',
|
||||||
equal: 'Igual',
|
equal: 'Igual',
|
||||||
@@ -748,7 +764,7 @@ const pt = {
|
|||||||
processing_: 'Processando...',
|
processing_: 'Processando...',
|
||||||
add_pdf_documents_or_images: 'Adicionar documentos PDF ou imagens',
|
add_pdf_documents_or_images: 'Adicionar documentos PDF ou imagens',
|
||||||
add_documents_or_images: 'Adicionar documentos ou imagens',
|
add_documents_or_images: 'Adicionar documentos ou imagens',
|
||||||
add_a_new_document: 'Ajouter un nouveau document',
|
add_a_new_document: 'Adicionar um novo documento',
|
||||||
replace_existing_document: 'Substituir documento existente',
|
replace_existing_document: 'Substituir documento existente',
|
||||||
clone_and_replace_documents: 'Clonar e substituir documentos',
|
clone_and_replace_documents: 'Clonar e substituir documentos',
|
||||||
required: 'Obrigatório',
|
required: 'Obrigatório',
|
||||||
@@ -856,7 +872,7 @@ const pt = {
|
|||||||
learn_more: 'Saiba mais',
|
learn_more: 'Saiba mais',
|
||||||
and: 'e',
|
and: 'e',
|
||||||
or: 'ou',
|
or: 'ou',
|
||||||
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_and_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',
|
||||||
or_add_from: 'Ou adicionar de',
|
or_add_from: 'Ou adicionar de',
|
||||||
sync: 'Sincronizar',
|
sync: 'Sincronizar',
|
||||||
@@ -871,7 +887,11 @@ const pt = {
|
|||||||
align_bottom: 'Alinhar à parte inferior',
|
align_bottom: 'Alinhar à parte inferior',
|
||||||
fields_selected: '{count} Campos Selecionados',
|
fields_selected: '{count} Campos Selecionados',
|
||||||
field_added: '{count} Campo Adicionado',
|
field_added: '{count} Campo Adicionado',
|
||||||
fields_added: '{count} Campos Adicionados'
|
fields_added: '{count} Campos Adicionados',
|
||||||
|
revisions: 'Revisões',
|
||||||
|
apply: 'Aplicar',
|
||||||
|
no_revisions_yet: 'Nenhuma revisão ainda',
|
||||||
|
viewing_revision_from: 'Visualizando revisão de {date}'
|
||||||
}
|
}
|
||||||
|
|
||||||
const fr = {
|
const fr = {
|
||||||
@@ -896,7 +916,7 @@ const fr = {
|
|||||||
max: 'Max',
|
max: 'Max',
|
||||||
font: 'Police',
|
font: 'Police',
|
||||||
party: 'Partie',
|
party: 'Partie',
|
||||||
date_signed: 'Date de signature',
|
date_signed: 'Date du jour',
|
||||||
method: 'Méthode',
|
method: 'Méthode',
|
||||||
reorder_fields: 'Réorganiser les champs',
|
reorder_fields: 'Réorganiser les champs',
|
||||||
verify_id: "Vérifier l'ID",
|
verify_id: "Vérifier l'ID",
|
||||||
@@ -946,6 +966,7 @@ const fr = {
|
|||||||
sign_yourself: 'Signer vous-même',
|
sign_yourself: 'Signer vous-même',
|
||||||
set_signing_date: 'Définir la date de signature',
|
set_signing_date: 'Définir la date de signature',
|
||||||
signing_date: 'Date de signature',
|
signing_date: 'Date de signature',
|
||||||
|
signing_date_and_time: 'Date et heure de signature',
|
||||||
send: 'Envoyer',
|
send: 'Envoyer',
|
||||||
remove: 'Supprimer',
|
remove: 'Supprimer',
|
||||||
edit: 'Modifier',
|
edit: 'Modifier',
|
||||||
@@ -1075,7 +1096,7 @@ const fr = {
|
|||||||
learn_more: 'En savoir plus',
|
learn_more: 'En savoir plus',
|
||||||
and: 'et',
|
and: 'et',
|
||||||
or: 'ou',
|
or: 'ou',
|
||||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Commencez une visite rapide pour apprendre à créer et à envoyer votre premier document',
|
start_a_quick_tour_to_learn_how_to_create_and_send_your_first_document: 'Commencez une visite rapide pour apprendre à créer et à envoyer votre premier document',
|
||||||
start_tour: 'Démarrer',
|
start_tour: 'Démarrer',
|
||||||
or_add_from: 'Ou ajouter depuis',
|
or_add_from: 'Ou ajouter depuis',
|
||||||
sync: 'Synchroniser',
|
sync: 'Synchroniser',
|
||||||
@@ -1090,7 +1111,11 @@ const fr = {
|
|||||||
align_bottom: 'Aligner en bas',
|
align_bottom: 'Aligner en bas',
|
||||||
fields_selected: '{count} Champs Sélectionnés',
|
fields_selected: '{count} Champs Sélectionnés',
|
||||||
field_added: '{count} Champ Ajouté',
|
field_added: '{count} Champ Ajouté',
|
||||||
fields_added: '{count} Champs Ajoutés'
|
fields_added: '{count} Champs Ajoutés',
|
||||||
|
revisions: 'Révisions',
|
||||||
|
apply: 'Appliquer',
|
||||||
|
no_revisions_yet: 'Aucune révision pour le moment',
|
||||||
|
viewing_revision_from: 'Affichage de la révision du {date}'
|
||||||
}
|
}
|
||||||
|
|
||||||
const de = {
|
const de = {
|
||||||
@@ -1115,7 +1140,7 @@ const de = {
|
|||||||
max: 'Max.',
|
max: 'Max.',
|
||||||
font: 'Schriftart',
|
font: 'Schriftart',
|
||||||
party: 'Partei',
|
party: 'Partei',
|
||||||
date_signed: 'Unterzeichnungsdatum',
|
date_signed: 'Aktuelles Datum',
|
||||||
method: 'Methode',
|
method: 'Methode',
|
||||||
reorder_fields: 'Felder neu anordnen',
|
reorder_fields: 'Felder neu anordnen',
|
||||||
verify_id: 'ID überprüfen',
|
verify_id: 'ID überprüfen',
|
||||||
@@ -1165,6 +1190,7 @@ const de = {
|
|||||||
sign_yourself: 'Selbst unterschreiben',
|
sign_yourself: 'Selbst unterschreiben',
|
||||||
set_signing_date: 'Unterzeichnungsdatum festlegen',
|
set_signing_date: 'Unterzeichnungsdatum festlegen',
|
||||||
signing_date: 'Unterzeichnungsdatum',
|
signing_date: 'Unterzeichnungsdatum',
|
||||||
|
signing_date_and_time: 'Unterzeichnungsdatum und -uhrzeit',
|
||||||
send: 'Senden',
|
send: 'Senden',
|
||||||
remove: 'Entfernen',
|
remove: 'Entfernen',
|
||||||
edit: 'Bearbeiten',
|
edit: 'Bearbeiten',
|
||||||
@@ -1294,7 +1320,7 @@ const de = {
|
|||||||
learn_more: 'Mehr erfahren',
|
learn_more: 'Mehr erfahren',
|
||||||
and: 'und',
|
and: 'und',
|
||||||
or: 'oder',
|
or: 'oder',
|
||||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Starten Sie eine Kurztour, um zu lernen, wie Sie Ihr erstes Dokument erstellen und versenden.',
|
start_a_quick_tour_to_learn_how_to_create_and_send_your_first_document: 'Starten Sie eine Kurztour, um zu lernen, wie Sie Ihr erstes Dokument erstellen und versenden.',
|
||||||
start_tour: 'Tour starten',
|
start_tour: 'Tour starten',
|
||||||
or_add_from: 'Oder hinzufügen aus',
|
or_add_from: 'Oder hinzufügen aus',
|
||||||
sync: 'Synchronisieren',
|
sync: 'Synchronisieren',
|
||||||
@@ -1309,7 +1335,11 @@ const de = {
|
|||||||
align_bottom: 'Unten ausrichten',
|
align_bottom: 'Unten ausrichten',
|
||||||
fields_selected: '{count} Felder Ausgewählt',
|
fields_selected: '{count} Felder Ausgewählt',
|
||||||
field_added: '{count} Feld Hinzugefügt',
|
field_added: '{count} Feld Hinzugefügt',
|
||||||
fields_added: '{count} Felder Hinzugefügt'
|
fields_added: '{count} Felder Hinzugefügt',
|
||||||
|
revisions: 'Revisionen',
|
||||||
|
apply: 'Anwenden',
|
||||||
|
no_revisions_yet: 'Noch keine Revisionen',
|
||||||
|
viewing_revision_from: 'Ansicht der Revision vom {date}'
|
||||||
}
|
}
|
||||||
|
|
||||||
const nl = {
|
const nl = {
|
||||||
@@ -1334,7 +1364,7 @@ const nl = {
|
|||||||
max: 'Max',
|
max: 'Max',
|
||||||
font: 'Lettertype',
|
font: 'Lettertype',
|
||||||
party: 'Partij',
|
party: 'Partij',
|
||||||
date_signed: 'Datum ondertekend',
|
date_signed: 'Huidige datum',
|
||||||
method: 'Methode',
|
method: 'Methode',
|
||||||
reorder_fields: 'Velden herschikken',
|
reorder_fields: 'Velden herschikken',
|
||||||
verify_id: 'ID verifiëren',
|
verify_id: 'ID verifiëren',
|
||||||
@@ -1384,6 +1414,7 @@ const nl = {
|
|||||||
sign_yourself: 'Zelf ondertekenen',
|
sign_yourself: 'Zelf ondertekenen',
|
||||||
set_signing_date: 'Ondertekeningsdatum instellen',
|
set_signing_date: 'Ondertekeningsdatum instellen',
|
||||||
signing_date: 'Ondertekeningsdatum',
|
signing_date: 'Ondertekeningsdatum',
|
||||||
|
signing_date_and_time: 'Ondertekeningsdatum en -tijd',
|
||||||
send: 'Verzenden',
|
send: 'Verzenden',
|
||||||
remove: 'Verwijderen',
|
remove: 'Verwijderen',
|
||||||
edit: 'Bewerken',
|
edit: 'Bewerken',
|
||||||
@@ -1513,7 +1544,7 @@ const nl = {
|
|||||||
learn_more: 'Meer informatie',
|
learn_more: 'Meer informatie',
|
||||||
and: 'en',
|
and: 'en',
|
||||||
or: 'of',
|
or: 'of',
|
||||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Start een korte rondleiding om te leren hoe u uw eerste document maakt en verzendt',
|
start_a_quick_tour_to_learn_how_to_create_and_send_your_first_document: 'Start een korte rondleiding om te leren hoe u uw eerste document maakt en verzendt',
|
||||||
start_tour: 'Rondleiding starten',
|
start_tour: 'Rondleiding starten',
|
||||||
or_add_from: 'Of toevoegen van',
|
or_add_from: 'Of toevoegen van',
|
||||||
sync: 'Synchroniseren',
|
sync: 'Synchroniseren',
|
||||||
@@ -1528,7 +1559,11 @@ const nl = {
|
|||||||
align_bottom: 'Onder uitlijnen',
|
align_bottom: 'Onder uitlijnen',
|
||||||
fields_selected: '{count} Velden Geselecteerd',
|
fields_selected: '{count} Velden Geselecteerd',
|
||||||
field_added: '{count} Veld Toegevoegd',
|
field_added: '{count} Veld Toegevoegd',
|
||||||
fields_added: '{count} Velden Toegevoegd'
|
fields_added: '{count} Velden Toegevoegd',
|
||||||
|
revisions: 'Revisies',
|
||||||
|
apply: 'Toepassen',
|
||||||
|
no_revisions_yet: 'Nog geen revisies',
|
||||||
|
viewing_revision_from: 'Revisie van {date} bekijken'
|
||||||
}
|
}
|
||||||
|
|
||||||
export { en, es, it, pt, fr, de, nl }
|
export { en, es, it, pt, fr, de, nl }
|
||||||
@@ -37,6 +37,8 @@
|
|||||||
:ref="setAreaRefs"
|
:ref="setAreaRefs"
|
||||||
:area="item.area"
|
:area="item.area"
|
||||||
:input-mode="inputMode"
|
:input-mode="inputMode"
|
||||||
|
:conditional-field-index="conditionalFieldIndex"
|
||||||
|
:formula-values-index="formulaValuesIndex"
|
||||||
:page-width="width"
|
:page-width="width"
|
||||||
:page-height="height"
|
:page-height="height"
|
||||||
:field="item.field"
|
:field="item.field"
|
||||||
@@ -55,6 +57,7 @@
|
|||||||
@scroll-to="$emit('scroll-to', $event)"
|
@scroll-to="$emit('scroll-to', $event)"
|
||||||
@add-custom-field="$emit('add-custom-field', $event)"
|
@add-custom-field="$emit('add-custom-field', $event)"
|
||||||
@contextmenu="openAreaContextMenu($event, item.area, item.field)"
|
@contextmenu="openAreaContextMenu($event, item.area, item.field)"
|
||||||
|
@click-title="closeContextMenu"
|
||||||
/>
|
/>
|
||||||
<FieldArea
|
<FieldArea
|
||||||
v-for="(area, index) in newAreas"
|
v-for="(area, index) in newAreas"
|
||||||
@@ -180,6 +183,16 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
conditionalFieldIndex: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
formulaValuesIndex: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
defaultFields: {
|
defaultFields: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -143,8 +143,8 @@
|
|||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
name="state"
|
name="oauth_data"
|
||||||
:value="oauthState"
|
:value="oauthData"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
@@ -334,7 +334,7 @@ export default {
|
|||||||
authenticityToken () {
|
authenticityToken () {
|
||||||
return document.querySelector('meta[name="csrf-token"]')?.content
|
return document.querySelector('meta[name="csrf-token"]')?.content
|
||||||
},
|
},
|
||||||
oauthState () {
|
oauthData () {
|
||||||
const params = new URLSearchParams('')
|
const params = new URLSearchParams('')
|
||||||
|
|
||||||
params.set('redir', document.location.href)
|
params.set('redir', document.location.href)
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
<template>
|
||||||
|
<div class="modal modal-open items-start !animate-none overflow-y-auto">
|
||||||
|
<div
|
||||||
|
class="absolute top-0 bottom-0 right-0 left-0"
|
||||||
|
@click.prevent="$emit('close')"
|
||||||
|
/>
|
||||||
|
<div class="modal-box pt-4 pb-6 mt-20 w-full">
|
||||||
|
<div class="flex justify-between items-center border-b pb-2 mb-3 font-medium">
|
||||||
|
<span>{{ t('revisions') }}</span>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="text-xl"
|
||||||
|
@click.prevent="$emit('close')"
|
||||||
|
>×</a>
|
||||||
|
</div>
|
||||||
|
<ul class="space-y-1.5">
|
||||||
|
<li
|
||||||
|
v-for="revision in revisions"
|
||||||
|
:key="revision.id"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="w-full text-left rounded-lg p-3 border border-dashed border-base-200 transition-colors disabled:cursor-default hover:bg-base-200 hover:border-base-200"
|
||||||
|
:disabled="loadingId !== null"
|
||||||
|
@click="viewRevision(revision.id)"
|
||||||
|
>
|
||||||
|
<div class="flex justify-between items-center gap-2">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span>{{ formatDate(revision.created_at) }}</span>
|
||||||
|
<span class="-ml-0.5 flex items-center space-x-1 text-xs text-base-content/60 mt-0.5">
|
||||||
|
<IconUser class="w-3.5 h-3.5 flex-shrink-0" />
|
||||||
|
<span class="truncate">{{ revision.author.full_name || revision.author.email }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span class="btn btn-sm btn-neutral text-white pointer-events-none flex-shrink-0">
|
||||||
|
<IconInnerShadowTop
|
||||||
|
v-if="loadingId === revision.id"
|
||||||
|
class="w-4 h-4 animate-spin"
|
||||||
|
/>
|
||||||
|
<span v-else>{{ t('view') }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
v-if="!revisions.length"
|
||||||
|
class="py-4 text-center text-base-content/60"
|
||||||
|
>
|
||||||
|
{{ t('no_revisions_yet') }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { IconUser, IconInnerShadowTop } from '@tabler/icons-vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'RevisionsModal',
|
||||||
|
components: { IconUser, IconInnerShadowTop },
|
||||||
|
inject: ['t', 'baseFetch'],
|
||||||
|
props: {
|
||||||
|
template: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
revisions: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
locale: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['close', 'apply'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
loadingId: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
viewRevision (id) {
|
||||||
|
if (this.loadingId !== null) return
|
||||||
|
|
||||||
|
this.loadingId = id
|
||||||
|
|
||||||
|
this.baseFetch(`/templates/${this.template.id}/versions/${id}`)
|
||||||
|
.then((r) => r.json())
|
||||||
|
.then((revision) => { this.$emit('apply', revision) })
|
||||||
|
.finally(() => { this.loadingId = null })
|
||||||
|
},
|
||||||
|
formatDate (string) {
|
||||||
|
return new Date(string).toLocaleString(this.locale || undefined, {
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -228,7 +228,7 @@ export default {
|
|||||||
'https://www.googleapis.com/auth/userinfo.email',
|
'https://www.googleapis.com/auth/userinfo.email',
|
||||||
'https://www.googleapis.com/auth/drive.file'
|
'https://www.googleapis.com/auth/drive.file'
|
||||||
].join(' '),
|
].join(' '),
|
||||||
state: new URLSearchParams({
|
oauth_data: new URLSearchParams({
|
||||||
redir: `/templates/${this.templateId}/edit?google_drive_open=1`
|
redir: `/templates/${this.templateId}/edit?google_drive_open=1`
|
||||||
}).toString()
|
}).toString()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,13 +30,12 @@ class SendFormCompletedWebhookRequestJob
|
|||||||
attempt:,
|
attempt:,
|
||||||
data: Submitters::SerializeForWebhook.call(submitter))
|
data: Submitters::SerializeForWebhook.call(submitter))
|
||||||
|
|
||||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
return if attempt > MAX_ATTEMPTS || (resp && resp.status.to_i < 400)
|
||||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
|
||||||
SendFormCompletedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendFormCompletedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
**params,
|
**params,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -28,13 +28,12 @@ class SendFormDeclinedWebhookRequestJob
|
|||||||
attempt:,
|
attempt:,
|
||||||
data: Submitters::SerializeForWebhook.call(submitter))
|
data: Submitters::SerializeForWebhook.call(submitter))
|
||||||
|
|
||||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
return if attempt > MAX_ATTEMPTS || (resp && resp.status.to_i < 400)
|
||||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
|
||||||
SendFormDeclinedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendFormDeclinedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
**params,
|
**params,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -28,13 +28,12 @@ class SendFormStartedWebhookRequestJob
|
|||||||
attempt:,
|
attempt:,
|
||||||
data: Submitters::SerializeForWebhook.call(submitter))
|
data: Submitters::SerializeForWebhook.call(submitter))
|
||||||
|
|
||||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
return if attempt > MAX_ATTEMPTS || (resp && resp.status.to_i < 400)
|
||||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
|
||||||
SendFormStartedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendFormStartedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
**params,
|
**params,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -28,13 +28,12 @@ class SendFormViewedWebhookRequestJob
|
|||||||
attempt:,
|
attempt:,
|
||||||
data: Submitters::SerializeForWebhook.call(submitter))
|
data: Submitters::SerializeForWebhook.call(submitter))
|
||||||
|
|
||||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
return if attempt > MAX_ATTEMPTS || (resp && resp.status.to_i < 400)
|
||||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
|
||||||
SendFormViewedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendFormViewedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
**params,
|
**params,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -26,13 +26,12 @@ class SendSubmissionArchivedWebhookRequestJob
|
|||||||
attempt:,
|
attempt:,
|
||||||
data: submission.as_json(only: %i[id archived_at]))
|
data: submission.as_json(only: %i[id archived_at]))
|
||||||
|
|
||||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
return if attempt > MAX_ATTEMPTS || (resp && resp.status.to_i < 400)
|
||||||
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
|
||||||
SendSubmissionArchivedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendSubmissionArchivedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
**params,
|
**params,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -26,13 +26,12 @@ class SendSubmissionCompletedWebhookRequestJob
|
|||||||
attempt:,
|
attempt:,
|
||||||
data: Submissions::SerializeForApi.call(submission))
|
data: Submissions::SerializeForApi.call(submission))
|
||||||
|
|
||||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
return if attempt > MAX_ATTEMPTS || (resp && resp.status.to_i < 400)
|
||||||
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
|
||||||
SendSubmissionCompletedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendSubmissionCompletedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
**params,
|
**params,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -26,13 +26,12 @@ class SendSubmissionCreatedWebhookRequestJob
|
|||||||
attempt:,
|
attempt:,
|
||||||
data: Submissions::SerializeForApi.call(submission))
|
data: Submissions::SerializeForApi.call(submission))
|
||||||
|
|
||||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
return if attempt > MAX_ATTEMPTS || (resp && resp.status.to_i < 400)
|
||||||
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
|
||||||
SendSubmissionCreatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendSubmissionCreatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
**params,
|
**params,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -26,13 +26,12 @@ class SendSubmissionExpiredWebhookRequestJob
|
|||||||
attempt:,
|
attempt:,
|
||||||
data: Submissions::SerializeForApi.call(submission))
|
data: Submissions::SerializeForApi.call(submission))
|
||||||
|
|
||||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
return if attempt > MAX_ATTEMPTS || (resp && resp.status.to_i < 400)
|
||||||
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
|
||||||
SendSubmissionExpiredWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendSubmissionExpiredWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
**params,
|
**params,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -26,13 +26,12 @@ class SendTemplateArchivedWebhookRequestJob
|
|||||||
attempt:,
|
attempt:,
|
||||||
data: template.as_json(only: %i[id archived_at]))
|
data: template.as_json(only: %i[id archived_at]))
|
||||||
|
|
||||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
return if attempt > MAX_ATTEMPTS || (resp && resp.status.to_i < 400)
|
||||||
(!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan))
|
|
||||||
SendTemplateArchivedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendTemplateArchivedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
**params,
|
**params,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -26,13 +26,12 @@ class SendTemplateCreatedWebhookRequestJob
|
|||||||
attempt:,
|
attempt:,
|
||||||
data: Templates::SerializeForApi.call(template))
|
data: Templates::SerializeForApi.call(template))
|
||||||
|
|
||||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
return if attempt > MAX_ATTEMPTS || (resp && resp.status.to_i < 400)
|
||||||
(!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan))
|
|
||||||
SendTemplateCreatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendTemplateCreatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
**params,
|
**params,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -26,13 +26,12 @@ class SendTemplateUpdatedWebhookRequestJob
|
|||||||
attempt:,
|
attempt:,
|
||||||
data: Templates::SerializeForApi.call(template))
|
data: Templates::SerializeForApi.call(template))
|
||||||
|
|
||||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
return if attempt > MAX_ATTEMPTS || (resp && resp.status.to_i < 400)
|
||||||
(!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan))
|
|
||||||
SendTemplateUpdatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendTemplateUpdatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
**params,
|
**params,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -32,6 +32,7 @@ class Account < ApplicationRecord
|
|||||||
has_many :submitters, dependent: :destroy
|
has_many :submitters, dependent: :destroy
|
||||||
has_many :account_linked_accounts, dependent: :destroy
|
has_many :account_linked_accounts, dependent: :destroy
|
||||||
has_many :email_events, dependent: :destroy
|
has_many :email_events, dependent: :destroy
|
||||||
|
has_many :document_metadata, class_name: 'DocumentMetadata', dependent: :destroy
|
||||||
has_many :webhook_urls, dependent: :destroy
|
has_many :webhook_urls, dependent: :destroy
|
||||||
has_many :webhook_events, dependent: nil
|
has_many :webhook_events, dependent: nil
|
||||||
has_many :account_accesses, dependent: :destroy
|
has_many :account_accesses, dependent: :destroy
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class AccountConfig < ApplicationRecord
|
|||||||
DOWNLOAD_LINKS_EXPIRE_KEY = 'download_links_expire'
|
DOWNLOAD_LINKS_EXPIRE_KEY = 'download_links_expire'
|
||||||
FORCE_SSO_AUTH_KEY = 'force_sso_auth'
|
FORCE_SSO_AUTH_KEY = 'force_sso_auth'
|
||||||
FLATTEN_RESULT_PDF_KEY = 'flatten_result_pdf'
|
FLATTEN_RESULT_PDF_KEY = 'flatten_result_pdf'
|
||||||
|
ROTATE_INCREMENTAL_PDF_KEY = 'rotate_incremental_pdf'
|
||||||
WITH_SIGNATURE_ID = 'with_signature_id'
|
WITH_SIGNATURE_ID = 'with_signature_id'
|
||||||
WITH_FILE_LINKS_KEY = 'with_file_links'
|
WITH_FILE_LINKS_KEY = 'with_file_links'
|
||||||
WITH_SIGNATURE_ID_REASON_KEY = 'with_signature_id_reason'
|
WITH_SIGNATURE_ID_REASON_KEY = 'with_signature_id_reason'
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: document_metadata
|
||||||
|
#
|
||||||
|
# id :bigint not null, primary key
|
||||||
|
# blob_checksum :string not null
|
||||||
|
# text_runs :text not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# account_id :bigint not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_document_metadata_on_account_id_and_blob_checksum (account_id,blob_checksum) UNIQUE
|
||||||
|
#
|
||||||
|
# Foreign Keys
|
||||||
|
#
|
||||||
|
# fk_rails_... (account_id => accounts.id)
|
||||||
|
#
|
||||||
|
class DocumentMetadata < ApplicationRecord
|
||||||
|
belongs_to :account
|
||||||
|
|
||||||
|
attribute :text_runs, :string, default: -> { {} }
|
||||||
|
|
||||||
|
serialize :text_runs, coder: JSON
|
||||||
|
end
|
||||||
@@ -101,13 +101,18 @@ class Submission < ApplicationRecord
|
|||||||
where(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
|
where(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
|
||||||
.and(Submitter.arel_table[:declined_at].not_eq(nil))).select(1).arel.exists)
|
.and(Submitter.arel_table[:declined_at].not_eq(nil))).select(1).arel.exists)
|
||||||
}
|
}
|
||||||
scope :expired, -> { pending.where(expire_at: ..Time.current) }
|
scope :expired, lambda {
|
||||||
|
where(expire_at: ..Time.current)
|
||||||
|
.where(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
|
||||||
|
.and(Submitter.arel_table[:completed_at].eq(nil))).select(1).arel.exists)
|
||||||
|
}
|
||||||
|
|
||||||
enum :source, {
|
enum :source, {
|
||||||
invite: 'invite',
|
invite: 'invite',
|
||||||
bulk: 'bulk',
|
bulk: 'bulk',
|
||||||
api: 'api',
|
api: 'api',
|
||||||
embed: 'embed',
|
embed: 'embed',
|
||||||
|
mcp: 'mcp',
|
||||||
link: 'link'
|
link: 'link'
|
||||||
}, scope: false, prefix: true
|
}, scope: false, prefix: true
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ class Template < ApplicationRecord
|
|||||||
has_many :submissions, dependent: :destroy
|
has_many :submissions, dependent: :destroy
|
||||||
has_many :template_sharings, dependent: :destroy
|
has_many :template_sharings, dependent: :destroy
|
||||||
has_many :template_accesses, dependent: :destroy
|
has_many :template_accesses, dependent: :destroy
|
||||||
|
has_many :template_versions, dependent: :destroy
|
||||||
has_many :dynamic_documents, dependent: :destroy
|
has_many :dynamic_documents, dependent: :destroy
|
||||||
has_many :dynamic_document_versions, through: :dynamic_documents, source: :versions
|
has_many :dynamic_document_versions, through: :dynamic_documents, source: :versions
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: template_versions
|
||||||
|
#
|
||||||
|
# id :bigint not null, primary key
|
||||||
|
# data :text not null
|
||||||
|
# sha1 :string not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# account_id :bigint not null
|
||||||
|
# author_id :bigint not null
|
||||||
|
# template_id :bigint not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_template_versions_on_account_id (account_id)
|
||||||
|
# index_template_versions_on_author_id (author_id)
|
||||||
|
# index_template_versions_on_template_id_and_sha1 (template_id,sha1) UNIQUE
|
||||||
|
#
|
||||||
|
# Foreign Keys
|
||||||
|
#
|
||||||
|
# fk_rails_... (account_id => accounts.id)
|
||||||
|
# fk_rails_... (author_id => users.id)
|
||||||
|
# fk_rails_... (template_id => templates.id)
|
||||||
|
#
|
||||||
|
class TemplateVersion < ApplicationRecord
|
||||||
|
belongs_to :template
|
||||||
|
belongs_to :account
|
||||||
|
belongs_to :author, class_name: 'User'
|
||||||
|
|
||||||
|
attribute :data, :string, default: -> { {} }
|
||||||
|
|
||||||
|
serialize :data, coder: JSON
|
||||||
|
|
||||||
|
before_validation :set_account, on: :create
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_account
|
||||||
|
self.account ||= template.account
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -4,14 +4,15 @@
|
|||||||
#
|
#
|
||||||
# Table name: webhook_urls
|
# Table name: webhook_urls
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# events :text not null
|
# events :text not null
|
||||||
# secret :text not null
|
# hmac_secret :text not null
|
||||||
# sha1 :string not null
|
# secret :text not null
|
||||||
# url :text not null
|
# sha1 :string not null
|
||||||
# created_at :datetime not null
|
# url :text not null
|
||||||
# updated_at :datetime not null
|
# created_at :datetime not null
|
||||||
# account_id :bigint not null
|
# updated_at :datetime not null
|
||||||
|
# account_id :bigint not null
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
@@ -47,10 +48,15 @@ class WebhookUrl < ApplicationRecord
|
|||||||
serialize :secret, coder: JSON
|
serialize :secret, coder: JSON
|
||||||
|
|
||||||
before_validation :set_sha1
|
before_validation :set_sha1
|
||||||
|
before_validation :set_hmac_secret
|
||||||
|
|
||||||
encrypts :url, :secret
|
encrypts :url, :secret, :hmac_secret
|
||||||
|
|
||||||
def set_sha1
|
def set_sha1
|
||||||
self.sha1 = Digest::SHA1.hexdigest(url)
|
self.sha1 = Digest::SHA1.hexdigest(url)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_hmac_secret
|
||||||
|
self.hmac_secret ||= WebhookUrls::Signatures.generate_secret
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -18,24 +18,7 @@
|
|||||||
<%= f.button button_title(title: t(:sign_in), disabled_with: t(:signing_in)), class: 'base-button' %>
|
<%= f.button button_title(title: t(:sign_in), disabled_with: t(:signing_in)), class: 'base-button' %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if devise_mapping.omniauthable? %>
|
<%= render 'omniauthable' %>
|
||||||
<div class="space-y-4">
|
|
||||||
<% if User.omniauth_providers.include?(:google_oauth2) %>
|
|
||||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :google_oauth2), data: { turbo: false }, method: :post do |f| %>
|
|
||||||
<set-timezone data-input-id="state" data-params="true"></set-timezone>
|
|
||||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query %>
|
|
||||||
<%= f.button button_title(title: t('sign_in_with_google'), icon: svg_icon('brand_google', class: 'w-6 h-6')), class: 'white-button w-full mt-4' %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
<% if User.omniauth_providers.include?(:microsoft_office365) %>
|
|
||||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :microsoft_office365), data: { turbo: false }, method: :post do |f| %>
|
|
||||||
<set-timezone data-input-id="state_microsoft" data-params="true"></set-timezone>
|
|
||||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query, id: 'state_microsoft' %>
|
|
||||||
<%= f.button button_title(title: t('sign_in_with_microsoft'), icon: svg_icon('brand_microsoft', class: 'w-6 h-6')), class: 'white-button w-full' %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<%= render 'extra_links' %>
|
<%= render 'extra_links' %>
|
||||||
<%= render 'devise/shared/links' %>
|
<%= render 'devise/shared/links' %>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0"></path>
|
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0"></path>
|
||||||
<path d="M9 12l2 2l4 -4"></path>
|
<path d="M9 12l2 2l4 -4"></path>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 389 B After Width: | Height: | Size: 408 B |
@@ -1,4 +1,4 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-2" />
|
<path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-2" />
|
||||||
<path d="M7 11l5 5l5 -5" />
|
<path d="M7 11l5 5l5 -5" />
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 397 B After Width: | Height: | Size: 416 B |
@@ -1,4 +1,4 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0"></path>
|
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0"></path>
|
||||||
<path d="M12 9h.01"></path>
|
<path d="M12 9h.01"></path>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 412 B After Width: | Height: | Size: 431 B |
@@ -1,4 +1,4 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<path d="M12 3a9 9 0 1 0 9 9" />
|
<path d="M12 3a9 9 0 1 0 9 9" />
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 318 B After Width: | Height: | Size: 337 B |
@@ -1,4 +1,4 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<path d="M12 18h-7a2 2 0 0 1 -2 -2v-10a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v7.5" />
|
<path d="M12 18h-7a2 2 0 0 1 -2 -2v-10a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v7.5" />
|
||||||
<path d="M3 6l9 6l9 -6" />
|
<path d="M3 6l9 6l9 -6" />
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 448 B After Width: | Height: | Size: 467 B |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="<%= local_assigns[:class] %>">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M17 17h2a2 2 0 0 0 2 -2v-4a2 2 0 0 0 -2 -2h-14a2 2 0 0 0 -2 2v4a2 2 0 0 0 2 2h2" />
|
||||||
|
<path d="M17 9v-4a2 2 0 0 0 -2 -2h-6a2 2 0 0 0 -2 2v4" />
|
||||||
|
<path d="M7 15a2 2 0 0 1 2 -2h6a2 2 0 0 1 2 2v4a2 2 0 0 1 -2 2h-6a2 2 0 0 1 -2 -2l0 -4" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 529 B |
@@ -0,0 +1,15 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="<%= local_assigns[:class] %>">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M4 5a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1l0 -4" />
|
||||||
|
<path d="M7 17l0 .01" />
|
||||||
|
<path d="M14 5a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1l0 -4" />
|
||||||
|
<path d="M7 7l0 .01" />
|
||||||
|
<path d="M4 15a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1l0 -4" />
|
||||||
|
<path d="M17 7l0 .01" />
|
||||||
|
<path d="M14 14l3 0" />
|
||||||
|
<path d="M20 14l0 .01" />
|
||||||
|
<path d="M14 14l0 3" />
|
||||||
|
<path d="M14 20l3 0" />
|
||||||
|
<path d="M17 17l3 0" />
|
||||||
|
<path d="M20 17l0 3" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 797 B |
@@ -1,4 +1,4 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
|
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
<path d="M19.933 13.041a8 8 0 1 1 -9.925 -8.788c3.899 -1 7.935 1.007 9.425 4.747"></path>
|
<path d="M19.933 13.041a8 8 0 1 1 -9.925 -8.788c3.899 -1 7.935 1.007 9.425 4.747"></path>
|
||||||
<path d="M20 4v5h-5"></path>
|
<path d="M20 4v5h-5"></path>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 409 B After Width: | Height: | Size: 428 B |
@@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="<%= local_assigns[:class] %>"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0" /><path d="M6 21v-2a4 4 0 0 1 4 -4h3" /><path d="M16 22l5 -5" /><path d="M21 21.5v-4.5h-4.5" /></svg>
|
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="<%= local_assigns[:class] %>"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0" /><path d="M6 21v-2a4 4 0 0 1 4 -4h3" /><path d="M16 22l5 -5" /><path d="M21 21.5v-4.5h-4.5" /></svg>
|
||||||
|
Before Width: | Height: | Size: 416 B After Width: | Height: | Size: 435 B |
@@ -1,4 +1,4 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
<path d="M3 19c3.333 -2 5 -4 5 -6c0 -3 -1 -3 -2 -3s-2.032 1.085 -2 3c.034 2.048 1.658 2.877 2.5 4c1.5 2 2.5 2.5 3.5 1c.667 -1 1.167 -1.833 1.5 -2.5c1 2.333 2.333 3.5 4 3.5h2.5"></path>
|
<path d="M3 19c3.333 -2 5 -4 5 -6c0 -3 -1 -3 -2 -3s-2.032 1.085 -2 3c.034 2.048 1.658 2.877 2.5 4c1.5 2 2.5 2.5 3.5 1c.667 -1 1.167 -1.833 1.5 -2.5c1 2.333 2.333 3.5 4 3.5h2.5"></path>
|
||||||
<path d="M20 17v-12c0 -1.121 -.879 -2 -2 -2s-2 .879 -2 2v12l2 2l2 -2z"></path>
|
<path d="M20 17v-12c0 -1.121 -.879 -2 -2 -2s-2 .879 -2 2v12l2 2l2 -2z"></path>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 584 B After Width: | Height: | Size: 603 B |
@@ -1,4 +1,4 @@
|
|||||||
<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">
|
<svg aria-hidden="true" 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="M18 6l-12 12"></path>
|
<path d="M18 6l-12 12"></path>
|
||||||
<path d="M6 6l12 12"></path>
|
<path d="M6 6l12 12"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 290 B After Width: | Height: | Size: 309 B |
@@ -20,9 +20,10 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<% if params[:modal].present? %>
|
<% if params[:modal].present? %>
|
||||||
<% url_params = Rails.application.routes.recognize_path(params[:modal], method: :get) %>
|
<% modal_uri = Addressable::URI.parse(params[:modal]) %>
|
||||||
|
<% url_params = Rails.application.routes.recognize_path(modal_uri.path, method: :get) %>
|
||||||
<% if url_params[:action] == 'new' %>
|
<% if url_params[:action] == 'new' %>
|
||||||
<open-modal src="<%= url_for(url_params) %>"></open-modal>
|
<open-modal src="<%= url_for(**url_params, params: modal_uri.query_values) %>"></open-modal>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<turbo-frame id="modal"></turbo-frame>
|
<turbo-frame id="modal"></turbo-frame>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<a target="_blank" href="<%= Docuseal::GITHUB_URL %>" rel="noopener noreferrer nofollow" class="relative flex items-center rounded-full px-2 py-0.5 text-xs leading-4 mt-1 text-base-content border border-base-300 tooltip tooltip-bottom" data-tip="Give a star on GitHub">
|
<a target="_blank" href="<%= Docuseal::GITHUB_URL %>" rel="noopener noreferrer nofollow" class="relative flex items-center rounded-full px-2 py-0.5 text-xs leading-4 mt-1 text-base-content border border-base-300 tooltip tooltip-bottom" data-tip="Give a star on GitHub">
|
||||||
<span class="flex items-center justify-between space-x-0.5 font-medium">
|
<span class="flex items-center justify-between space-x-0.5 font-medium">
|
||||||
<%= svg_icon('start', class: 'h-3 w-3') %>
|
<%= svg_icon('start', class: 'h-3 w-3') %>
|
||||||
<span>11k</span>
|
<span>16k</span>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -1,18 +1,22 @@
|
|||||||
<% uuid = local_assigns[:uuid] || SecureRandom.uuid %>
|
<% uuid = local_assigns[:uuid] || SecureRandom.uuid %>
|
||||||
<input type="checkbox" id="<%= uuid %>" class="modal-toggle">
|
<% title_id = "#{uuid}-title" %>
|
||||||
<div id="<%= local_assigns[:id] %>" class="modal items-start !animate-none overflow-y-auto">
|
<%= tag.dialog id: uuid, class: 'modal items-start overflow-y-auto', inert: true, 'aria-labelledby': (title_id if local_assigns[:title]) do %>
|
||||||
<div class="modal-box pt-4 pb-6 px-6 mt-20 max-h-none">
|
<div class="modal-box pt-4 pb-6 px-6 mt-20 max-h-none">
|
||||||
<% if local_assigns[:title] %>
|
<% if local_assigns[:title] %>
|
||||||
<div class="flex justify-between items-center border-b pb-2 mb-2 font-medium">
|
<div class="flex justify-between items-center border-b pb-2 mb-2 font-medium">
|
||||||
<span>
|
<span id="<%= title_id %>">
|
||||||
<%= local_assigns[:title] %>
|
<%= local_assigns[:title] %>
|
||||||
</span>
|
</span>
|
||||||
<label for="<%= uuid %>" class="text-xl">×</label>
|
<form method="dialog">
|
||||||
|
<button class="text-xl cursor-pointer w-6 h-6" aria-label="<%= t('close') %>">×</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div>
|
<div>
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<label class="modal-backdrop" for="<%= uuid %>"></label>
|
<form method="dialog" class="modal-backdrop">
|
||||||
</div>
|
<button aria-label="<%= t('close') %>"></button>
|
||||||
|
</form>
|
||||||
|
<% end %>
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% 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 : :post, 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">
|
||||||
<submit-form data-on="change" class="flex">
|
<submit-form data-on="change" class="flex">
|
||||||
<%= f.check_box :testing_toggle, class: 'toggle', checked: current_account.testing?, style: 'height: 0.885rem; width: 1.35rem; --handleoffset: 0.395rem; margin-left: -2px; margin-right: 8px' %>
|
<%= f.check_box :testing_toggle, class: 'toggle', checked: current_account.testing?, style: 'height: 0.885rem; width: 1.35rem; --handleoffset: 0.395rem; margin-left: -2px; margin-right: 8px' %>
|
||||||
|
|||||||
@@ -97,7 +97,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<%= render 'shared/settings_nav_extra2' %>
|
<%= render 'shared/settings_nav_extra2' %>
|
||||||
<% 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' } do |f| %>
|
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :post, html: { class: 'w-full' } do |f| %>
|
||||||
<li>
|
<li>
|
||||||
<label class="flex items-center text-base hover:bg-base-300 w-full justify-between" for="testing_toggle">
|
<label class="flex items-center text-base hover:bg-base-300 w-full justify-between" for="testing_toggle">
|
||||||
<span class="mr-2 w-full">
|
<span class="mr-2 w-full">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<% if can?(:manage, EncryptedConfig) || (current_user != true_user && current_account.testing?) %>
|
<% if can?(:manage, EncryptedConfig) || (current_user != true_user && current_account.testing?) %>
|
||||||
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'flex' } do |f| %>
|
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :post, html: { class: 'flex' } do |f| %>
|
||||||
<label class="flex items-center justify-between" for="testing_toggle">
|
<label class="flex items-center justify-between" for="testing_toggle">
|
||||||
<span class="mr-2 text-lg">
|
<span class="mr-2 text-lg">
|
||||||
<%= t('test_mode') %>
|
<%= t('test_mode') %>
|
||||||
|
|||||||