mirror of
https://github.com/docusealco/docuseal
synced 2026-11-05 06:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef05f030de | ||
|
|
b8c36ff09c | ||
|
|
1ba8a9a374 | ||
|
|
661e9b69fb | ||
|
|
a1aeabccff | ||
|
|
0e5e0e7772 | ||
|
|
2a74fdd095 | ||
|
|
ac426e55b2 | ||
|
|
ad2b35673a | ||
|
|
af1e8fa6f2 | ||
|
|
5800cb6c5a | ||
|
|
64d3360e82 | ||
|
|
3ae98bf03e | ||
|
|
65e81d90f4 | ||
|
|
4fb9637e2d | ||
|
|
6b3726dbd5 | ||
|
|
c5b0f2c76c | ||
|
|
ea18f9e28a | ||
|
|
d229c75978 | ||
|
|
ecbe3c3e4f | ||
|
|
38a5cb968b | ||
|
|
a30ee9e961 | ||
|
|
eba27004dc | ||
|
|
09849acd2f | ||
|
|
19e9047a39 | ||
|
|
82b6bdd9ad | ||
|
|
5764b4dac1 | ||
|
|
3affd67d24 | ||
|
|
d5a98a0f9e | ||
|
|
9629cc7226 | ||
|
|
06415ad869 | ||
|
|
3f872da435 | ||
|
|
bc1d5a9825 | ||
|
|
72c8ed9a00 | ||
|
|
3fec433846 | ||
|
|
3b572702d7 | ||
|
|
febdc0b48d | ||
|
|
8773bb2f62 | ||
|
|
55ad70fabc | ||
|
|
23dcd62672 | ||
|
|
42371d7ce3 | ||
|
|
4116746be3 | ||
|
|
b2b2856ac0 | ||
|
|
b65bca213f | ||
|
|
7a72e2cbb7 | ||
|
|
3a73398a1e | ||
|
|
cce20d9230 | ||
|
|
8b72c91951 | ||
|
|
b0d8e47a75 | ||
|
|
85824faa2b | ||
|
|
8a507339af | ||
|
|
4636849118 | ||
|
|
cc52cae9fc | ||
|
|
b6bb4d2c8b | ||
|
|
5032676bd2 | ||
|
|
146b37f8cd | ||
|
|
47b3a2d09e | ||
|
|
f2306b334b | ||
|
|
3affa65571 | ||
|
|
6f3be16826 | ||
|
|
07ce76537b | ||
|
|
a1fb38b8cb |
No files matched your search
@@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
:position: before
|
||||||
|
:position_in_additional_file_patterns: before
|
||||||
|
:position_in_class: before
|
||||||
|
:position_in_factory: before
|
||||||
|
:position_in_fixture: before
|
||||||
|
:position_in_routes: before
|
||||||
|
:position_in_serializer: before
|
||||||
|
:position_in_test: before
|
||||||
|
:classified_sort: true
|
||||||
|
:exclude_controllers: true
|
||||||
|
:exclude_factories: true
|
||||||
|
:exclude_fixtures: false
|
||||||
|
:exclude_helpers: true
|
||||||
|
:exclude_scaffolds: true
|
||||||
|
:exclude_serializers: false
|
||||||
|
:exclude_sti_subclasses: false
|
||||||
|
:exclude_tests: false
|
||||||
|
:force: false
|
||||||
|
:format_markdown: false
|
||||||
|
:format_rdoc: false
|
||||||
|
:format_yard: false
|
||||||
|
:frozen: false
|
||||||
|
:ignore_model_sub_dir: false
|
||||||
|
:ignore_unknown_models: false
|
||||||
|
:include_version: false
|
||||||
|
:show_check_constraints: false
|
||||||
|
:show_complete_foreign_keys: false
|
||||||
|
:show_foreign_keys: true
|
||||||
|
:show_indexes: true
|
||||||
|
:simple_indexes: false
|
||||||
|
:sort: false
|
||||||
|
:timestamp: false
|
||||||
|
:trace: false
|
||||||
|
:with_comment: true
|
||||||
|
:with_column_comments: true
|
||||||
|
:with_table_comments: true
|
||||||
|
:active_admin: false
|
||||||
|
:command:
|
||||||
|
:debug: false
|
||||||
|
:hide_default_column_types: ''
|
||||||
|
:hide_limit_column_types: ''
|
||||||
|
:ignore_columns:
|
||||||
|
:ignore_routes:
|
||||||
|
:models: true
|
||||||
|
:routes: false
|
||||||
|
:skip_on_db_migrate: false
|
||||||
|
:target_action: :do_annotations
|
||||||
|
:wrapper:
|
||||||
|
:wrapper_close:
|
||||||
|
:wrapper_open:
|
||||||
|
:classes_default_to_s: []
|
||||||
|
:additional_file_patterns: []
|
||||||
|
:model_dir:
|
||||||
|
- app/models
|
||||||
|
:require: []
|
||||||
|
:root_dir:
|
||||||
|
- ''
|
||||||
@@ -77,6 +77,33 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
./node_modules/eslint/bin/eslint.js "app/javascript/**/*.js"
|
./node_modules/eslint/bin/eslint.js "app/javascript/**/*.js"
|
||||||
|
|
||||||
|
brakeman:
|
||||||
|
name: Brakeman
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install Ruby
|
||||||
|
uses: ruby/setup-ruby@v1
|
||||||
|
with:
|
||||||
|
ruby-version: 3.4.1
|
||||||
|
- name: Cache gems
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: vendor/bundle
|
||||||
|
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-gem-
|
||||||
|
- name: Install gems
|
||||||
|
run: |
|
||||||
|
gem install bundler
|
||||||
|
bundle config path vendor/bundle
|
||||||
|
bundle install --jobs 4 --retry 4
|
||||||
|
yarn install
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install libvips
|
||||||
|
- name: Run Brakeman
|
||||||
|
run: bundle exec brakeman -q --exit-on-warn
|
||||||
|
|
||||||
rspec:
|
rspec:
|
||||||
name: RSpec
|
name: RSpec
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04-arm
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
|||||||
+3
-3
@@ -1,4 +1,4 @@
|
|||||||
FROM ruby:3.4.1-alpine as fonts
|
FROM ruby:3.4.1-alpine AS fonts
|
||||||
|
|
||||||
WORKDIR /fonts
|
WORKDIR /fonts
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@ RUN apk --no-cache add fontforge wget && wget https://github.com/satbyy/go-noto-
|
|||||||
|
|
||||||
RUN fontforge -lang=py -c 'font1 = fontforge.open("FreeSans.ttf"); font2 = fontforge.open("NotoSansSymbols2-Regular.ttf"); font1.mergeFonts(font2); font1.generate("FreeSans.ttf")'
|
RUN fontforge -lang=py -c 'font1 = fontforge.open("FreeSans.ttf"); font2 = fontforge.open("NotoSansSymbols2-Regular.ttf"); font1.mergeFonts(font2); font1.generate("FreeSans.ttf")'
|
||||||
|
|
||||||
FROM ruby:3.4.1-alpine as webpack
|
FROM ruby:3.4.1-alpine AS webpack
|
||||||
|
|
||||||
ENV RAILS_ENV=production
|
ENV RAILS_ENV=production
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
@@ -32,7 +32,7 @@ COPY ./app/views ./app/views
|
|||||||
|
|
||||||
RUN echo "gem 'shakapacker'" > Gemfile && ./bin/shakapacker
|
RUN echo "gem 'shakapacker'" > Gemfile && ./bin/shakapacker
|
||||||
|
|
||||||
FROM ruby:3.4.1-alpine as app
|
FROM ruby:3.4.1-alpine AS app
|
||||||
|
|
||||||
ENV RAILS_ENV=production
|
ENV RAILS_ENV=production
|
||||||
ENV BUNDLE_WITHOUT="development:test"
|
ENV BUNDLE_WITHOUT="development:test"
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ gem 'twitter_cldr', require: false
|
|||||||
gem 'tzinfo-data'
|
gem 'tzinfo-data'
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
gem 'annotate'
|
|
||||||
gem 'better_html'
|
gem 'better_html'
|
||||||
gem 'bullet'
|
gem 'bullet'
|
||||||
gem 'debug'
|
gem 'debug'
|
||||||
@@ -63,6 +62,9 @@ group :development, :test do
|
|||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
|
gem 'annotaterb'
|
||||||
|
gem 'brakeman', require: false
|
||||||
|
gem 'foreman', require: false
|
||||||
gem 'letter_opener_web'
|
gem 'letter_opener_web'
|
||||||
gem 'web-console'
|
gem 'web-console'
|
||||||
end
|
end
|
||||||
|
|||||||
+15
-12
@@ -74,9 +74,7 @@ GEM
|
|||||||
uri (>= 0.13.1)
|
uri (>= 0.13.1)
|
||||||
addressable (2.8.7)
|
addressable (2.8.7)
|
||||||
public_suffix (>= 2.0.2, < 7.0)
|
public_suffix (>= 2.0.2, < 7.0)
|
||||||
annotate (2.6.5)
|
annotaterb (4.14.0)
|
||||||
activerecord (>= 2.3.0)
|
|
||||||
rake (>= 0.8.7)
|
|
||||||
arabic-letter-connector (0.1.1)
|
arabic-letter-connector (0.1.1)
|
||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
aws-eventstream (1.3.0)
|
aws-eventstream (1.3.0)
|
||||||
@@ -120,6 +118,8 @@ GEM
|
|||||||
bindex (0.8.1)
|
bindex (0.8.1)
|
||||||
bootsnap (1.18.4)
|
bootsnap (1.18.4)
|
||||||
msgpack (~> 1.2)
|
msgpack (~> 1.2)
|
||||||
|
brakeman (7.0.0)
|
||||||
|
racc
|
||||||
builder (3.3.0)
|
builder (3.3.0)
|
||||||
bullet (8.0.0)
|
bullet (8.0.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
@@ -229,6 +229,7 @@ GEM
|
|||||||
ffi (1.17.1-arm64-darwin)
|
ffi (1.17.1-arm64-darwin)
|
||||||
ffi (1.17.1-x86_64-linux-gnu)
|
ffi (1.17.1-x86_64-linux-gnu)
|
||||||
ffi (1.17.1-x86_64-linux-musl)
|
ffi (1.17.1-x86_64-linux-musl)
|
||||||
|
foreman (0.88.1)
|
||||||
geom2d (0.4.1)
|
geom2d (0.4.1)
|
||||||
globalid (1.2.1)
|
globalid (1.2.1)
|
||||||
activesupport (>= 6.1)
|
activesupport (>= 6.1)
|
||||||
@@ -337,18 +338,18 @@ GEM
|
|||||||
net-smtp (0.5.0)
|
net-smtp (0.5.0)
|
||||||
net-protocol
|
net-protocol
|
||||||
nio4r (2.7.4)
|
nio4r (2.7.4)
|
||||||
nokogiri (1.18.2)
|
nokogiri (1.18.5)
|
||||||
mini_portile2 (~> 2.8.2)
|
mini_portile2 (~> 2.8.2)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.18.2-aarch64-linux-gnu)
|
nokogiri (1.18.5-aarch64-linux-gnu)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.18.2-aarch64-linux-musl)
|
nokogiri (1.18.5-aarch64-linux-musl)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.18.2-arm64-darwin)
|
nokogiri (1.18.5-arm64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.18.2-x86_64-linux-gnu)
|
nokogiri (1.18.5-x86_64-linux-gnu)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.18.2-x86_64-linux-musl)
|
nokogiri (1.18.5-x86_64-linux-musl)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
oj (3.16.8)
|
oj (3.16.8)
|
||||||
bigdecimal (>= 3.0)
|
bigdecimal (>= 3.0)
|
||||||
@@ -386,7 +387,7 @@ GEM
|
|||||||
puma (6.5.0)
|
puma (6.5.0)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
racc (1.8.1)
|
racc (1.8.1)
|
||||||
rack (3.1.10)
|
rack (3.1.12)
|
||||||
rack-proxy (0.7.7)
|
rack-proxy (0.7.7)
|
||||||
rack
|
rack
|
||||||
rack-session (2.0.0)
|
rack-session (2.0.0)
|
||||||
@@ -558,7 +559,7 @@ GEM
|
|||||||
unicode-emoji (~> 4.0, >= 4.0.4)
|
unicode-emoji (~> 4.0, >= 4.0.4)
|
||||||
unicode-emoji (4.0.4)
|
unicode-emoji (4.0.4)
|
||||||
uniform_notifier (1.16.0)
|
uniform_notifier (1.16.0)
|
||||||
uri (1.0.2)
|
uri (1.0.3)
|
||||||
useragent (0.16.11)
|
useragent (0.16.11)
|
||||||
warden (1.2.9)
|
warden (1.2.9)
|
||||||
rack (>= 2.0.9)
|
rack (>= 2.0.9)
|
||||||
@@ -588,13 +589,14 @@ PLATFORMS
|
|||||||
x86_64-linux-musl
|
x86_64-linux-musl
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
annotate
|
annotaterb
|
||||||
arabic-letter-connector
|
arabic-letter-connector
|
||||||
aws-sdk-s3
|
aws-sdk-s3
|
||||||
aws-sdk-secretsmanager
|
aws-sdk-secretsmanager
|
||||||
azure-storage-blob
|
azure-storage-blob
|
||||||
better_html
|
better_html
|
||||||
bootsnap
|
bootsnap
|
||||||
|
brakeman
|
||||||
bullet
|
bullet
|
||||||
cancancan
|
cancancan
|
||||||
capybara
|
capybara
|
||||||
@@ -610,6 +612,7 @@ DEPENDENCIES
|
|||||||
faker
|
faker
|
||||||
faraday
|
faraday
|
||||||
faraday-follow_redirects
|
faraday-follow_redirects
|
||||||
|
foreman
|
||||||
google-cloud-storage
|
google-cloud-storage
|
||||||
hexapdf
|
hexapdf
|
||||||
image_processing
|
image_processing
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class AccountConfigsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def account_config_params
|
def account_config_params
|
||||||
params.required(:account_config).permit!.tap do |attrs|
|
params.required(:account_config).permit(:key, :value, { value: {} }, { value: [] }).tap do |attrs|
|
||||||
attrs[:value] = attrs[:value] == '1' if attrs[:value].in?(%w[1 0])
|
attrs[:value] = attrs[:value] == '1' if attrs[:value].in?(%w[1 0])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ module Api
|
|||||||
message: %i[subject body],
|
message: %i[subject body],
|
||||||
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
|
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
|
||||||
:completed, :phone, :application_key, :external_id, :reply_to, :go_to_last,
|
:completed, :phone, :application_key, :external_id, :reply_to, :go_to_last,
|
||||||
{ metadata: {}, values: {}, readonly_fields: [], message: %i[subject body],
|
{ metadata: {}, values: {}, roles: [], readonly_fields: [], message: %i[subject body],
|
||||||
fields: [:name, :uuid, :default_value, :value, :title, :description,
|
fields: [:name, :uuid, :default_value, :value, :title, :description,
|
||||||
:readonly, :validation_pattern, :invalid_message,
|
:readonly, :validation_pattern, :invalid_message,
|
||||||
{ default_value: [], value: [], preferences: {} }] }]]
|
{ default_value: [], value: [], preferences: {} }] }]]
|
||||||
|
|||||||
@@ -146,9 +146,15 @@ module Api
|
|||||||
|
|
||||||
if attrs[:completed]
|
if attrs[:completed]
|
||||||
submitter.values = Submitters::SubmitValues.merge_default_values(submitter)
|
submitter.values = Submitters::SubmitValues.merge_default_values(submitter)
|
||||||
submitter.values = Submitters::SubmitValues.merge_formula_values(submitter)
|
|
||||||
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
|
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
|
||||||
|
|
||||||
|
formula_values = Submitters::SubmitValues.build_formula_values(submitter)
|
||||||
|
|
||||||
|
if formula_values.present?
|
||||||
|
submitter.values = submitter.values.merge(formula_values)
|
||||||
|
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
|
||||||
|
end
|
||||||
|
|
||||||
submitter.values = submitter.values.transform_values do |v|
|
submitter.values = submitter.values.transform_values do |v|
|
||||||
v == '{{date}}' ? Time.current.in_time_zone(submitter.account.timezone).to_date.to_s : v
|
v == '{{date}}' ? Time.current.in_time_zone(submitter.account.timezone).to_date.to_s : v
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class NotificationsSettingsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def email_config_params
|
def email_config_params
|
||||||
params.require(:account_config).permit!.tap do |attrs|
|
params.require(:account_config).permit(:key, :value, { value: {} }, { value: [] }).tap do |attrs|
|
||||||
attrs[:key] = nil unless attrs[:key].in?([AccountConfig::BCC_EMAILS, AccountConfig::SUBMITTER_REMINDERS])
|
attrs[:key] = nil unless attrs[:key].in?([AccountConfig::BCC_EMAILS, AccountConfig::SUBMITTER_REMINDERS])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class PersonalizationSettingsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def account_config_params
|
def account_config_params
|
||||||
attrs = params.require(:account_config).permit!
|
attrs = params.require(:account_config).permit(:key, :value, { value: {} }, { value: [] })
|
||||||
|
|
||||||
return attrs if attrs[:value].is_a?(String)
|
return attrs if attrs[:value].is_a?(String)
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class StartFormController < ApplicationController
|
|||||||
redirect_to start_form_completed_path(@template.slug, email: submitter_params[:email])
|
redirect_to start_form_completed_path(@template.slug, email: submitter_params[:email])
|
||||||
else
|
else
|
||||||
if filter_undefined_submitters(@template).size > 1 && @submitter.new_record?
|
if filter_undefined_submitters(@template).size > 1 && @submitter.new_record?
|
||||||
@error_message = I18n.t('not_found')
|
@error_message = multiple_submitters_error_message
|
||||||
|
|
||||||
return render :show
|
return render :show
|
||||||
end
|
end
|
||||||
@@ -117,4 +117,12 @@ class StartFormController < ApplicationController
|
|||||||
|
|
||||||
@template = Template.find_by!(slug:)
|
@template = Template.find_by!(slug:)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def multiple_submitters_error_message
|
||||||
|
if current_user&.account_id == @template.account_id
|
||||||
|
helpers.t('this_submission_has_multiple_signers_which_prevents_the_use_of_a_sharing_link_html')
|
||||||
|
else
|
||||||
|
I18n.t('not_found')
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -20,7 +20,7 @@ class SubmissionsPreviewController < ApplicationController
|
|||||||
|
|
||||||
@submission ||= Submission.find_by!(slug: params[:slug])
|
@submission ||= Submission.find_by!(slug: params[:slug])
|
||||||
|
|
||||||
raise ActionController::RoutingError if @submission.account.archived_at?
|
raise ActionController::RoutingError, I18n.t('not_found') if @submission.account.archived_at?
|
||||||
|
|
||||||
if !@submission.submitters.all?(&:completed_at?) && !signature_valid &&
|
if !@submission.submitters.all?(&:completed_at?) && !signature_valid &&
|
||||||
(!current_user || !current_ability.can?(:read, @submission))
|
(!current_user || !current_ability.can?(:read, @submission))
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ class TemplatesDebugController < ApplicationController
|
|||||||
def show
|
def show
|
||||||
attachment = @template.documents.first
|
attachment = @template.documents.first
|
||||||
|
|
||||||
pdf = HexaPDF::Document.new(io: StringIO.new(attachment.download))
|
data = attachment.download
|
||||||
|
pdf = HexaPDF::Document.new(io: StringIO.new(data))
|
||||||
|
|
||||||
fields = Templates::FindAcroFields.call(pdf, attachment)
|
fields = Templates::FindAcroFields.call(pdf, attachment, data)
|
||||||
|
|
||||||
attachment.metadata['pdf'] ||= {}
|
attachment.metadata['pdf'] ||= {}
|
||||||
attachment.metadata['pdf']['fields'] = fields
|
attachment.metadata['pdf']['fields'] = fields
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class UserConfigsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def user_config_params
|
def user_config_params
|
||||||
params.required(:user_config).permit!.tap do |attrs|
|
params.required(:user_config).permit(:key, :value, { value: {} }, { value: [] }).tap do |attrs|
|
||||||
attrs[:value] = attrs[:value] == '1' if attrs[:value].in?(%w[1 0])
|
attrs[:value] = attrs[:value] == '1' if attrs[:value].in?(%w[1 0])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -45,17 +45,16 @@ class UsersController < ApplicationController
|
|||||||
return redirect_to settings_users_path, notice: I18n.t('unable_to_update_user') if Docuseal.demo?
|
return redirect_to settings_users_path, notice: I18n.t('unable_to_update_user') if Docuseal.demo?
|
||||||
|
|
||||||
attrs = user_params.compact_blank.merge(user_params.slice(:archived_at))
|
attrs = user_params.compact_blank.merge(user_params.slice(:archived_at))
|
||||||
attrs.delete(:role) if !role_valid?(attrs[:role]) || current_user == @user
|
|
||||||
|
|
||||||
if params.dig(:user, :account_id).present?
|
if params.dig(:user, :account_id).present?
|
||||||
account = Account.accessible_by(current_ability).find(params[:user][:account_id])
|
account = Account.accessible_by(current_ability).find(params.dig(:user, :account_id))
|
||||||
|
|
||||||
authorize!(:manage, account)
|
authorize!(:manage, account)
|
||||||
|
|
||||||
@user.account = account
|
@user.account = account
|
||||||
end
|
end
|
||||||
|
|
||||||
if @user.update(attrs)
|
if @user.update(attrs.except(current_user == @user ? :role : nil))
|
||||||
redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_updated')
|
redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_updated')
|
||||||
else
|
else
|
||||||
render turbo_stream: turbo_stream.replace(:modal, template: 'users/edit'), status: :unprocessable_entity
|
render turbo_stream: turbo_stream.replace(:modal, template: 'users/edit'), status: :unprocessable_entity
|
||||||
@@ -84,8 +83,11 @@ class UsersController < ApplicationController
|
|||||||
|
|
||||||
def user_params
|
def user_params
|
||||||
if params.key?(:user)
|
if params.key?(:user)
|
||||||
params.require(:user).permit(:email, :first_name, :last_name, :password,
|
permitted_params = %i[email first_name last_name password archived_at]
|
||||||
:role, :archived_at, :account_id)
|
|
||||||
|
permitted_params << :role if role_valid?(params.dig(:user, :role))
|
||||||
|
|
||||||
|
params.require(:user).permit(permitted_params)
|
||||||
else
|
else
|
||||||
{}
|
{}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="field-area flex absolute lg:text-base -outline-offset-1"
|
class="flex absolute lg:text-base -outline-offset-1 field-area"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
:style="computedStyle"
|
:style="computedStyle"
|
||||||
:class="{ 'font-mono': field.preferences?.font === 'Courier', 'font-serif': field.preferences?.font === 'Times', 'text-[1.6vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars, '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, 'field-area-active outline-red-500 outline-dashed outline-2 z-10': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
|
:class="{ 'text-[1.6vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars, '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 }"
|
||||||
>
|
>
|
||||||
<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"
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
<div
|
<div
|
||||||
v-else-if="field.type === 'cells'"
|
v-else-if="field.type === 'cells'"
|
||||||
class="w-full flex items-center"
|
class="w-full flex items-center"
|
||||||
:class="{ 'justify-end': field.preferences?.align === 'right' }"
|
:class="{ 'justify-end': field.preferences?.align === 'right', ...fontClasses }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(char, index) in modelValue"
|
v-for="(char, index) in modelValue"
|
||||||
@@ -184,7 +184,7 @@
|
|||||||
ref="textContainer"
|
ref="textContainer"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="flex items-center px-0.5 w-full"
|
class="flex items-center px-0.5 w-full"
|
||||||
:class="alignClasses[field.preferences?.align]"
|
:class="{ ...alignClasses, ...fontClasses }"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="field && field.name && withFieldPlaceholder && !modelValue && modelValue !== 0"
|
v-if="field && field.name && withFieldPlaceholder && !modelValue && modelValue !== 0"
|
||||||
@@ -326,10 +326,26 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
alignClasses () {
|
alignClasses () {
|
||||||
|
if (!this.field.preferences) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
center: 'text-center',
|
'text-center': this.field.preferences.align === 'center',
|
||||||
left: 'text-left',
|
'text-left': this.field.preferences.align === 'left',
|
||||||
right: 'text-right'
|
'text-right': this.field.preferences.align === 'right'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fontClasses () {
|
||||||
|
if (!this.field.preferences) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'font-mono': this.field.preferences.font === 'Courier',
|
||||||
|
'font-serif': this.field.preferences.font === 'Times',
|
||||||
|
'font-bold': ['bold_italic', 'bold'].includes(this.field.preferences.font_type),
|
||||||
|
italic: ['bold_italic', 'italic'].includes(this.field.preferences.font_type)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
option () {
|
option () {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
v-if="val"
|
v-if="val"
|
||||||
class="flex items-center space-x-1.5 w-full"
|
class="flex items-center space-x-1.5 w-full attachment-file-name"
|
||||||
:href="attachmentsIndex[val].url"
|
:href="attachmentsIndex[val].url"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
@@ -26,7 +26,10 @@
|
|||||||
{{ attachmentsIndex[val].filename }}
|
{{ attachmentsIndex[val].filename }}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<button @click.prevent="removeAttachment(val)">
|
<button
|
||||||
|
class="remove-attachment-button"
|
||||||
|
@click.prevent="removeAttachment(val)"
|
||||||
|
>
|
||||||
<IconTrashX
|
<IconTrashX
|
||||||
:width="18"
|
:width="18"
|
||||||
:heigh="19"
|
:heigh="19"
|
||||||
@@ -44,7 +47,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="field.description && !modelValue.length"
|
v-if="field.description && !modelValue.length"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="field.description" />
|
<MarkdownContent :string="field.description" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
id="form_completed"
|
id="form_completed"
|
||||||
class="mx-auto max-w-md flex flex-col"
|
class="mx-auto max-w-md flex flex-col completed-form"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
>
|
>
|
||||||
<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">
|
||||||
@@ -10,13 +10,13 @@
|
|||||||
:width="30"
|
:width="30"
|
||||||
:height="30"
|
:height="30"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span class="completed-form-message-title">
|
||||||
{{ completedMessage.title || (hasSignatureFields ? (hasMultipleDocuments ? t('documents_have_been_signed') : t('document_has_been_signed')) : t('form_has_been_completed')) }}
|
{{ completedMessage.title || (hasSignatureFields ? (hasMultipleDocuments ? t('documents_have_been_signed') : t('document_has_been_signed')) : t('form_has_been_completed')) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="completedMessage.body"
|
v-if="completedMessage.body"
|
||||||
class="mt-2"
|
class="mt-2 completed-form-message-body"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
:string="completedMessage.body"
|
:string="completedMessage.body"
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
v-if="completedButton.url"
|
v-if="completedButton.url"
|
||||||
:href="sanitizeHref(completedButton.url)"
|
:href="sanitizeHref(completedButton.url)"
|
||||||
rel="noopener noreferrer nofollow"
|
rel="noopener noreferrer nofollow"
|
||||||
class="white-button flex items-center w-full"
|
class="white-button flex items-center w-full completed-form-completed-button"
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
{{ completedButton.title || 'Back to Website' }}
|
{{ completedButton.title || 'Back to Website' }}
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
v-if="canSendEmail && !isDemo && withSendCopyButton"
|
v-if="canSendEmail && !isDemo && withSendCopyButton"
|
||||||
class="white-button !h-auto flex items-center space-x-1 w-full"
|
class="white-button !h-auto flex items-center space-x-1 w-full completed-form-send-copy-button"
|
||||||
:disabled="isSendingCopy"
|
:disabled="isSendingCopy"
|
||||||
@click.prevent="sendCopyToEmail"
|
@click.prevent="sendCopyToEmail"
|
||||||
>
|
>
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="!isWebView && withDownloadButton"
|
v-if="!isWebView && withDownloadButton"
|
||||||
class="base-button flex items-center space-x-1 w-full"
|
class="base-button flex items-center space-x-1 w-full completed-form-download-button"
|
||||||
:disabled="isDownloading"
|
:disabled="isDownloading"
|
||||||
@click.prevent="download"
|
@click.prevent="download"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<label
|
<label
|
||||||
v-if="showFieldNames"
|
v-if="showFieldNames"
|
||||||
:for="field.uuid"
|
:for="field.uuid"
|
||||||
class="label text-xl sm:text-2xl py-0"
|
class="label text-xl sm:text-2xl py-0 field-name-label"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
v-if="field.title"
|
v-if="field.title"
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
class="btn btn-outline btn-sm !normal-case font-normal"
|
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" />
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="field.description" />
|
<MarkdownContent :string="field.description" />
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
:for="inputId"
|
:for="inputId"
|
||||||
class="w-full relative bg-base-300 hover:bg-base-200 rounded-md border border-base-content border-dashed"
|
class="w-full relative bg-base-300 hover:bg-base-200 rounded-md border border-base-content border-dashed file-dropzone"
|
||||||
:class="{ 'opacity-50': isLoading }"
|
:class="{ 'opacity-50': isLoading }"
|
||||||
>
|
>
|
||||||
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
|
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
<button
|
<button
|
||||||
v-if="!isFormVisible"
|
v-if="!isFormVisible"
|
||||||
id="expand_form_button"
|
id="expand_form_button"
|
||||||
class="btn btn-neutral flex text-white absolute bottom-0 w-full mb-3"
|
class="btn btn-neutral flex text-white absolute bottom-0 w-full mb-3 expand-form-button"
|
||||||
style="width: 96%; margin-left: 2%"
|
style="width: 96%; margin-left: 2%"
|
||||||
@click.prevent="[isFormVisible = true, scrollIntoField(currentField)]"
|
@click.prevent="[isFormVisible = true, scrollIntoField(currentField)]"
|
||||||
>
|
>
|
||||||
@@ -93,14 +93,14 @@
|
|||||||
<div
|
<div
|
||||||
v-show="isFormVisible"
|
v-show="isFormVisible"
|
||||||
id="form_container"
|
id="form_container"
|
||||||
class="shadow-md bg-base-100 absolute bottom-0 w-full border-base-200 border p-4 rounded"
|
class="shadow-md bg-base-100 absolute bottom-0 w-full border-base-200 border p-4 rounded form-container"
|
||||||
:class="{ 'md:bottom-4': isBreakpointMd }"
|
:class="{ 'md:bottom-4': isBreakpointMd }"
|
||||||
:style="{ backgroundColor: backgroundColor }"
|
:style="{ backgroundColor: backgroundColor }"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-if="!isCompleted"
|
v-if="!isCompleted"
|
||||||
id="minimize_form_button"
|
id="minimize_form_button"
|
||||||
class="absolute right-0 top-0"
|
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')"
|
||||||
@click.prevent="minimizeForm"
|
@click.prevent="minimizeForm"
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
ref="form"
|
ref="form"
|
||||||
:action="submitPath"
|
:action="submitPath"
|
||||||
method="post"
|
method="post"
|
||||||
class="mx-auto"
|
class="mx-auto steps-form"
|
||||||
:style="{ maxWidth: isBreakpointMd ? '582px' : '' }"
|
:style="{ maxWidth: isBreakpointMd ? '582px' : '' }"
|
||||||
@submit.prevent="submitStep"
|
@submit.prevent="submitStep"
|
||||||
>
|
>
|
||||||
@@ -165,7 +165,7 @@
|
|||||||
v-if="showFieldNames && (currentField.name || currentField.title)"
|
v-if="showFieldNames && (currentField.name || currentField.title)"
|
||||||
:for="currentField.uuid"
|
:for="currentField.uuid"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5"
|
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
||||||
:class="{ 'mb-2': !currentField.description }"
|
:class="{ 'mb-2': !currentField.description }"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="currentField.description"
|
v-if="currentField.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="currentField.description" />
|
<MarkdownContent :string="currentField.description" />
|
||||||
</div>
|
</div>
|
||||||
@@ -226,7 +226,7 @@
|
|||||||
v-if="showFieldNames && (currentField.name || currentField.title)"
|
v-if="showFieldNames && (currentField.name || currentField.title)"
|
||||||
:for="currentField.uuid"
|
:for="currentField.uuid"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5"
|
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
||||||
:class="{ 'mb-2': !currentField.description }"
|
:class="{ 'mb-2': !currentField.description }"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
@@ -245,7 +245,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="currentField.description"
|
v-if="currentField.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="currentField.description" />
|
<MarkdownContent :string="currentField.description" />
|
||||||
</div>
|
</div>
|
||||||
@@ -270,7 +270,7 @@
|
|||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
:for="option.uuid"
|
:for="option.uuid"
|
||||||
class="flex items-center space-x-3"
|
class="flex items-center space-x-3 radio-label"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
:id="option.uuid"
|
:id="option.uuid"
|
||||||
@@ -304,7 +304,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="currentField.description"
|
v-if="currentField.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="currentField.description" />
|
<MarkdownContent :string="currentField.description" />
|
||||||
</div>
|
</div>
|
||||||
@@ -338,7 +338,7 @@
|
|||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
:for="field.uuid"
|
:for="field.uuid"
|
||||||
class="flex items-center space-x-3"
|
class="flex items-center space-x-3 checkbox-label"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
@@ -481,7 +481,7 @@
|
|||||||
id="submit_form_button"
|
id="submit_form_button"
|
||||||
ref="submitButton"
|
ref="submitButton"
|
||||||
type="submit"
|
type="submit"
|
||||||
class="base-button w-full flex justify-center"
|
class="base-button w-full flex justify-center submit-form-button"
|
||||||
:disabled="isButtonDisabled"
|
:disabled="isButtonDisabled"
|
||||||
>
|
>
|
||||||
<span class="flex">
|
<span class="flex">
|
||||||
@@ -533,13 +533,13 @@
|
|||||||
v-if="stepFields.length < 80"
|
v-if="stepFields.length < 80"
|
||||||
class="flex justify-center mt-3 sm:mt-4 mb-0 sm:mb-1"
|
class="flex justify-center mt-3 sm:mt-4 mb-0 sm:mb-1"
|
||||||
>
|
>
|
||||||
<div class="flex items-center flex-wrap">
|
<div class="flex items-center flex-wrap steps-progress">
|
||||||
<a
|
<a
|
||||||
v-for="(step, index) in stepFields"
|
v-for="(step, index) in stepFields"
|
||||||
:key="step[0].uuid"
|
:key="step[0].uuid"
|
||||||
href="#"
|
href="#"
|
||||||
class="inline border border-base-300 h-3 w-3 rounded-full mx-1 mt-1"
|
class="inline border border-base-300 h-3 w-3 rounded-full mx-1 mt-1"
|
||||||
:class="{ 'bg-base-300': 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.prevent="isCompleted ? '' : [saveStep(), goToStep(index, true)]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -1037,7 +1037,7 @@ export default {
|
|||||||
}, [])
|
}, [])
|
||||||
},
|
},
|
||||||
formulaFields () {
|
formulaFields () {
|
||||||
return this.fields.filter((f) => f.preferences?.formula && f.type !== 'payment')
|
return this.fields.filter((f) => f.preferences?.formula && f.type !== 'payment' && this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f))
|
||||||
},
|
},
|
||||||
attachmentsIndex () {
|
attachmentsIndex () {
|
||||||
return this.attachments.reduce((acc, a) => {
|
return this.attachments.reduce((acc, a) => {
|
||||||
@@ -1191,22 +1191,24 @@ export default {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultValue = !field || isEmpty(field.default_value) ? null : field.default_value
|
||||||
|
|
||||||
if (['empty', 'unchecked'].includes(condition.action)) {
|
if (['empty', 'unchecked'].includes(condition.action)) {
|
||||||
return isEmpty(this.values[condition.field_uuid])
|
return isEmpty(this.values[condition.field_uuid] ?? defaultValue)
|
||||||
} else if (['not_empty', 'checked'].includes(condition.action)) {
|
} else if (['not_empty', 'checked'].includes(condition.action)) {
|
||||||
return !isEmpty(this.values[condition.field_uuid])
|
return !isEmpty(this.values[condition.field_uuid] ?? defaultValue)
|
||||||
} else if (['equal', 'contains'].includes(condition.action) && field) {
|
} else if (['equal', 'contains'].includes(condition.action) && field) {
|
||||||
if (field.options) {
|
if (field.options) {
|
||||||
const option = field.options.find((o) => o.uuid === condition.value)
|
const option = field.options.find((o) => o.uuid === condition.value)
|
||||||
const values = [this.values[condition.field_uuid]].flat()
|
const values = [this.values[condition.field_uuid] ?? defaultValue].flat()
|
||||||
|
|
||||||
return values.includes(this.optionValue(option, field.options.indexOf(option)))
|
return values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||||
} else {
|
} else {
|
||||||
return [this.values[condition.field_uuid]].flat().includes(condition.value)
|
return [this.values[condition.field_uuid] ?? defaultValue].flat().includes(condition.value)
|
||||||
}
|
}
|
||||||
} else if (['not_equal', 'does_not_contain'].includes(condition.action) && field) {
|
} else if (['not_equal', 'does_not_contain'].includes(condition.action) && field) {
|
||||||
const option = field.options.find((o) => o.uuid === condition.value)
|
const option = field.options.find((o) => o.uuid === condition.value)
|
||||||
const values = [this.values[condition.field_uuid]].flat()
|
const values = [this.values[condition.field_uuid] ?? defaultValue].flat()
|
||||||
|
|
||||||
return !values.includes(this.optionValue(option, field.options.indexOf(option)))
|
return !values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||||
} else {
|
} else {
|
||||||
@@ -1299,7 +1301,9 @@ export default {
|
|||||||
|
|
||||||
if (!this.isCompleted) {
|
if (!this.isCompleted) {
|
||||||
if (scrollToArea) {
|
if (scrollToArea) {
|
||||||
this.scrollIntoField(this.currentField)
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => this.scrollIntoField(this.currentField), 1)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.enableScrollIntoField = false
|
this.enableScrollIntoField = false
|
||||||
@@ -1316,7 +1320,7 @@ export default {
|
|||||||
const currentFieldUuids = this.currentStepFields.map((f) => f.uuid)
|
const currentFieldUuids = this.currentStepFields.map((f) => f.uuid)
|
||||||
const currentFieldType = this.currentField.type
|
const currentFieldType = this.currentField.type
|
||||||
|
|
||||||
if (!formData && !this.$refs.form.checkValidity()) {
|
if (!formData && !this.$refs.form.checkValidity() && currentFieldUuids.every((fieldUuid) => isEmpty(this.submittedValues[fieldUuid]) || !isEmpty(this.values[fieldUuid]))) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<label
|
<label
|
||||||
v-if="showFieldNames"
|
v-if="showFieldNames"
|
||||||
:for="field.uuid"
|
:for="field.uuid"
|
||||||
class="label text-xl sm:text-2xl py-0"
|
class="label text-xl sm:text-2xl py-0 field-name-label"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
v-if="field.title"
|
v-if="field.title"
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
class="btn btn-outline btn-sm"
|
class="btn btn-outline btn-sm reupload-button"
|
||||||
@click.prevent="remove"
|
@click.prevent="remove"
|
||||||
>
|
>
|
||||||
<IconReload :width="16" />
|
<IconReload :width="16" />
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<img
|
<img
|
||||||
:src="attachmentsIndex[modelValue].url"
|
:src="attachmentsIndex[modelValue].url"
|
||||||
class="h-52 border border-base-300 rounded mx-auto"
|
class="h-52 border border-base-300 rounded mx-auto uploaded-image-preview"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="field.description" />
|
<MarkdownContent :string="field.description" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
v-if="showFieldNames"
|
v-if="showFieldNames"
|
||||||
class="label text-xl sm:text-2xl py-0"
|
class="label text-xl sm:text-2xl py-0 field-name-label"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
v-if="field.title"
|
v-if="field.title"
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<a
|
<a
|
||||||
id="type_text_button"
|
id="type_text_button"
|
||||||
href="#"
|
href="#"
|
||||||
class="btn btn-outline font-medium btn-sm"
|
class="btn btn-outline font-medium btn-sm type-text-button"
|
||||||
@click.prevent="toggleTextInput"
|
@click.prevent="toggleTextInput"
|
||||||
>
|
>
|
||||||
<IconTextSize :width="16" />
|
<IconTextSize :width="16" />
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
<a
|
<a
|
||||||
id="type_text_button"
|
id="type_text_button"
|
||||||
href="#"
|
href="#"
|
||||||
class="btn btn-outline font-medium btn-sm"
|
class="btn btn-outline font-medium btn-sm type-text-button"
|
||||||
@click.prevent="toggleTextInput"
|
@click.prevent="toggleTextInput"
|
||||||
>
|
>
|
||||||
<IconSignature :width="16" />
|
<IconSignature :width="16" />
|
||||||
@@ -55,9 +55,7 @@
|
|||||||
class="tooltip"
|
class="tooltip"
|
||||||
:data-tip="t('click_to_upload')"
|
:data-tip="t('click_to_upload')"
|
||||||
>
|
>
|
||||||
<label
|
<label class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap upload-image-button">
|
||||||
class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap"
|
|
||||||
>
|
|
||||||
<IconUpload :width="16" />
|
<IconUpload :width="16" />
|
||||||
<input
|
<input
|
||||||
:key="uploadImageInputKey"
|
:key="uploadImageInputKey"
|
||||||
@@ -74,7 +72,7 @@
|
|||||||
<a
|
<a
|
||||||
v-if="modelValue || computedPreviousValue"
|
v-if="modelValue || computedPreviousValue"
|
||||||
href="#"
|
href="#"
|
||||||
class="btn font-medium btn-outline btn-sm"
|
class="btn font-medium btn-outline btn-sm clear-canvas-button"
|
||||||
@click.prevent="remove"
|
@click.prevent="remove"
|
||||||
>
|
>
|
||||||
<IconReload :width="16" />
|
<IconReload :width="16" />
|
||||||
@@ -83,7 +81,7 @@
|
|||||||
<a
|
<a
|
||||||
v-else
|
v-else
|
||||||
href="#"
|
href="#"
|
||||||
class="btn font-medium btn-outline btn-sm"
|
class="btn font-medium btn-outline btn-sm clear-canvas-button"
|
||||||
@click.prevent="clear"
|
@click.prevent="clear"
|
||||||
>
|
>
|
||||||
<IconReload :width="16" />
|
<IconReload :width="16" />
|
||||||
@@ -105,7 +103,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="field.description" />
|
<MarkdownContent :string="field.description" />
|
||||||
</div>
|
</div>
|
||||||
@@ -123,7 +121,7 @@
|
|||||||
<canvas
|
<canvas
|
||||||
v-show="!modelValue && !computedPreviousValue"
|
v-show="!modelValue && !computedPreviousValue"
|
||||||
ref="canvas"
|
ref="canvas"
|
||||||
class="bg-white border border-base-300 rounded-2xl w-full"
|
class="bg-white border border-base-300 rounded-2xl w-full draw-canvas"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
v-if="!isDrawInitials && !modelValue && !computedPreviousValue"
|
v-if="!isDrawInitials && !modelValue && !computedPreviousValue"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
v-if="showFieldNames && (field.name || field.title)"
|
v-if="showFieldNames && (field.name || field.title)"
|
||||||
:for="field.uuid"
|
:for="field.uuid"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5"
|
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
||||||
:class="{ 'mb-2': !field.description }"
|
:class="{ 'mb-2': !field.description }"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="field.description" />
|
<MarkdownContent :string="field.description" />
|
||||||
</div>
|
</div>
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
:for="option.uuid"
|
:for="option.uuid"
|
||||||
class="flex items-center space-x-3"
|
class="flex items-center space-x-3 checkbox-label"
|
||||||
@click="scrollIntoField(field)"
|
@click="scrollIntoField(field)"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
v-if="showFieldNames && (field.name || field.title)"
|
v-if="showFieldNames && (field.name || field.title)"
|
||||||
:for="field.uuid"
|
:for="field.uuid"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5"
|
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
||||||
:class="{ 'mb-2': !field.description }"
|
:class="{ 'mb-2': !field.description }"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="field.description" />
|
<MarkdownContent :string="field.description" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<label
|
<label
|
||||||
v-if="!modelValue && !sessionId"
|
v-if="!modelValue && !sessionId"
|
||||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5"
|
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
v-if="field.title"
|
v-if="field.title"
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
<button
|
<button
|
||||||
v-if="sessionId"
|
v-if="sessionId"
|
||||||
disabled
|
disabled
|
||||||
class="base-button w-full"
|
class="base-button w-full modal-save-button"
|
||||||
>
|
>
|
||||||
<IconLoader
|
<IconLoader
|
||||||
width="22"
|
width="22"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<label
|
<label
|
||||||
v-if="showFieldNames"
|
v-if="showFieldNames"
|
||||||
:for="isCodeSent ? 'one_time_code' : field.uuid"
|
:for="isCodeSent ? 'one_time_code' : field.uuid"
|
||||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5"
|
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
||||||
:class="{ 'mb-2': !field.description }"
|
:class="{ 'mb-2': !field.description }"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="field.description" />
|
<MarkdownContent :string="field.description" />
|
||||||
</div>
|
</div>
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
<a
|
<a
|
||||||
v-if="!defaultValue"
|
v-if="!defaultValue"
|
||||||
href="#"
|
href="#"
|
||||||
class="link"
|
class="link change-phone-number-link"
|
||||||
@click.prevent="isCodeSent = false"
|
@click.prevent="isCodeSent = false"
|
||||||
>
|
>
|
||||||
{{ t('change_phone_number') }}
|
{{ t('change_phone_number') }}
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
<a
|
<a
|
||||||
v-else
|
v-else
|
||||||
href="#"
|
href="#"
|
||||||
class="link"
|
class="link resend-code-link"
|
||||||
@click.prevent="resendCode"
|
@click.prevent="resendCode"
|
||||||
>
|
>
|
||||||
{{ isResendLoading ? t('sending') : t('resend_code') }}
|
{{ isResendLoading ? t('sending') : t('resend_code') }}
|
||||||
@@ -68,13 +68,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-show="!isCodeSent"
|
v-show="!isCodeSent"
|
||||||
class="flex w-full rounded-full outline-neutral-content outline-2 outline-offset-2 focus-within:outline"
|
class="flex w-full rounded-full outline-neutral-content outline-2 outline-offset-2 focus-within:outline phone-number-input-container"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
id="country_code"
|
id="country_code"
|
||||||
class="relative inline-block"
|
class="relative inline-block"
|
||||||
>
|
>
|
||||||
<div class="btn bg-base-200 border border-neutral-300 text-2xl whitespace-nowrap font-normal rounded-l-full">
|
<div class="btn bg-base-200 border border-neutral-300 text-2xl whitespace-nowrap font-normal rounded-l-full country-code-select-label">
|
||||||
{{ selectedCountry.flag }} +{{ selectedCountry.dial }}
|
{{ selectedCountry.flag }} +{{ selectedCountry.dial }}
|
||||||
</div>
|
</div>
|
||||||
<select
|
<select
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
v-if="showFieldNames"
|
v-if="showFieldNames"
|
||||||
class="label text-xl sm:text-2xl py-0"
|
class="label text-xl sm:text-2xl py-0 field-name-label"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
v-if="field.title"
|
v-if="field.title"
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<a
|
<a
|
||||||
id="type_text_button"
|
id="type_text_button"
|
||||||
href="#"
|
href="#"
|
||||||
class="btn btn-outline btn-sm font-medium"
|
class="btn btn-outline btn-sm font-medium type-text-button"
|
||||||
@click.prevent="[toggleTextInput(), hideQr()]"
|
@click.prevent="[toggleTextInput(), hideQr()]"
|
||||||
>
|
>
|
||||||
<IconSignature :width="16" />
|
<IconSignature :width="16" />
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
<a
|
<a
|
||||||
id="type_text_button"
|
id="type_text_button"
|
||||||
href="#"
|
href="#"
|
||||||
class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap"
|
class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap type-text-button"
|
||||||
@click.prevent="[toggleTextInput(), hideQr()]"
|
@click.prevent="[toggleTextInput(), hideQr()]"
|
||||||
>
|
>
|
||||||
<IconTextSize :width="16" />
|
<IconTextSize :width="16" />
|
||||||
@@ -61,9 +61,7 @@
|
|||||||
:class="{ 'hidden sm:inline': modelValue || computedPreviousValue }"
|
:class="{ 'hidden sm:inline': modelValue || computedPreviousValue }"
|
||||||
:data-tip="t('take_photo')"
|
:data-tip="t('take_photo')"
|
||||||
>
|
>
|
||||||
<label
|
<label class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap upload-image-button">
|
||||||
class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap"
|
|
||||||
>
|
|
||||||
<IconCamera :width="16" />
|
<IconCamera :width="16" />
|
||||||
<input
|
<input
|
||||||
:key="uploadImageInputKey"
|
:key="uploadImageInputKey"
|
||||||
@@ -80,7 +78,7 @@
|
|||||||
<a
|
<a
|
||||||
v-if="modelValue || computedPreviousValue"
|
v-if="modelValue || computedPreviousValue"
|
||||||
href="#"
|
href="#"
|
||||||
class="btn btn-outline btn-sm font-medium"
|
class="btn btn-outline btn-sm font-medium reupload-button"
|
||||||
@click.prevent="remove"
|
@click.prevent="remove"
|
||||||
>
|
>
|
||||||
<IconReload :width="16" />
|
<IconReload :width="16" />
|
||||||
@@ -119,7 +117,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="field.description" />
|
<MarkdownContent :string="field.description" />
|
||||||
</div>
|
</div>
|
||||||
@@ -167,7 +165,7 @@
|
|||||||
v-show="!modelValue && !computedPreviousValue"
|
v-show="!modelValue && !computedPreviousValue"
|
||||||
ref="canvas"
|
ref="canvas"
|
||||||
style="padding: 1px; 0"
|
style="padding: 1px; 0"
|
||||||
class="bg-white border border-base-300 rounded-2xl w-full"
|
class="bg-white border border-base-300 rounded-2xl w-full draw-canvas"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="isShowQr"
|
v-if="isShowQr"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
v-if="showFieldNames && (field.name || field.title)"
|
v-if="showFieldNames && (field.name || field.title)"
|
||||||
:for="field.uuid"
|
:for="field.uuid"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5"
|
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
||||||
:class="{ 'mb-2': !field.description }"
|
:class="{ 'mb-2': !field.description }"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="field.description" />
|
<MarkdownContent :string="field.description" />
|
||||||
</div>
|
</div>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="btn btn-ghost btn-circle btn-sm"
|
class="btn btn-ghost btn-circle btn-sm toggle-multiline-text-button"
|
||||||
@click.prevent="toggleTextArea"
|
@click.prevent="toggleTextArea"
|
||||||
>
|
>
|
||||||
<IconAlignBoxLeftTop />
|
<IconAlignBoxLeftTop />
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<label
|
<label class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label">
|
||||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5"
|
|
||||||
>
|
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
v-if="field.title"
|
v-if="field.title"
|
||||||
:string="field.title"
|
:string="field.title"
|
||||||
@@ -11,13 +9,13 @@
|
|||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-4 px-1"
|
class="mb-4 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="field.description" />
|
<MarkdownContent :string="field.description" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="emptyValueRequiredStep && emptyValueRequiredStep[0] !== field"
|
v-if="emptyValueRequiredStep && emptyValueRequiredStep[0] !== field"
|
||||||
class="px-1"
|
class="px-1 field-description-text"
|
||||||
>
|
>
|
||||||
{{ t('complete_all_required_fields_to_proceed_with_identity_verification') }}
|
{{ t('complete_all_required_fields_to_proceed_with_identity_verification') }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="absolute overflow-visible group"
|
class="absolute overflow-visible group field-area-container"
|
||||||
:style="positionStyle"
|
:style="positionStyle"
|
||||||
:class="{ 'z-[1]': isMoved || isDragged }"
|
:class="{ 'z-[1]': isMoved || isDragged }"
|
||||||
@pointerdown.stop
|
@pointerdown.stop
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="field?.type && (isSelected || isNameFocus)"
|
v-if="field?.type && (isSelected || isNameFocus)"
|
||||||
class="absolute bg-white rounded-t border overflow-visible whitespace-nowrap flex z-10"
|
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
|
||||||
@pointerdown.stop
|
@pointerdown.stop
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
<FieldSubmitter
|
<FieldSubmitter
|
||||||
v-if="field.type != 'heading'"
|
v-if="field.type != 'heading'"
|
||||||
v-model="field.submitter_uuid"
|
v-model="field.submitter_uuid"
|
||||||
class="border-r"
|
class="border-r roles-dropdown"
|
||||||
:compact="true"
|
:compact="true"
|
||||||
:editable="editable && (!defaultField || defaultField.role !== submitter?.name)"
|
:editable="editable && (!defaultField || defaultField.role !== submitter?.name)"
|
||||||
:allow-add-new="!defaultSubmitters.length"
|
:allow-add-new="!defaultSubmitters.length"
|
||||||
@@ -108,7 +108,7 @@
|
|||||||
>{{ t('editable') }}</label>
|
>{{ t('editable') }}</label>
|
||||||
<span
|
<span
|
||||||
v-if="field.type !== 'payment' && !isValueInput"
|
v-if="field.type !== 'payment' && !isValueInput"
|
||||||
class="dropdown dropdown-end"
|
class="dropdown dropdown-end field-area-settings-dropdown"
|
||||||
@mouseenter="renderDropdown = true"
|
@mouseenter="renderDropdown = true"
|
||||||
@touchstart="renderDropdown = true"
|
@touchstart="renderDropdown = true"
|
||||||
>
|
>
|
||||||
@@ -141,6 +141,7 @@
|
|||||||
:with-required="false"
|
:with-required="false"
|
||||||
:with-areas="false"
|
:with-areas="false"
|
||||||
@click-formula="isShowFormulaModal = true"
|
@click-formula="isShowFormulaModal = true"
|
||||||
|
@click-font="isShowFontModal = true"
|
||||||
@click-description="isShowDescriptionModal = true"
|
@click-description="isShowDescriptionModal = true"
|
||||||
@click-condition="isShowConditionsModal = true"
|
@click-condition="isShowConditionsModal = true"
|
||||||
@scroll-to="[selectedAreaRef.value = $event, $emit('scroll-to', $event)]"
|
@scroll-to="[selectedAreaRef.value = $event, $emit('scroll-to', $event)]"
|
||||||
@@ -159,9 +160,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
ref="touchValueTarget"
|
ref="touchValueTarget"
|
||||||
class="flex items-center h-full w-full"
|
class="flex items-center h-full w-full field-area"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
:class="[isValueInput ? 'bg-opacity-50' : 'bg-opacity-80', field.type === 'heading' ? 'bg-gray-50' : bgColors[submitterIndex % bgColors.length], isDefaultValuePresent || isValueInput || (withFieldPlaceholder && field.areas) ? (alignClasses[field.preferences?.align] || '') : 'justify-center']"
|
:class="[isValueInput ? 'bg-opacity-50' : 'bg-opacity-80', field.type === 'heading' ? 'bg-gray-50' : bgColors[submitterIndex % bgColors.length], isDefaultValuePresent || isValueInput || (withFieldPlaceholder && field.areas) ? fontClasses : 'justify-center']"
|
||||||
@click="focusValueInput"
|
@click="focusValueInput"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -253,6 +254,17 @@
|
|||||||
@close="isShowFormulaModal = false"
|
@close="isShowFormulaModal = false"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
|
<Teleport
|
||||||
|
v-if="isShowFontModal"
|
||||||
|
:to="modalContainerEl"
|
||||||
|
>
|
||||||
|
<FontModal
|
||||||
|
:field="field"
|
||||||
|
:editable="editable && !defaultField"
|
||||||
|
:build-default-name="buildDefaultName"
|
||||||
|
@close="isShowFontModal = false"
|
||||||
|
/>
|
||||||
|
</Teleport>
|
||||||
<Teleport
|
<Teleport
|
||||||
v-if="isShowConditionsModal"
|
v-if="isShowConditionsModal"
|
||||||
:to="modalContainerEl"
|
:to="modalContainerEl"
|
||||||
@@ -283,6 +295,7 @@ import FieldType from './field_type'
|
|||||||
import Field from './field'
|
import Field from './field'
|
||||||
import FieldSettings from './field_settings'
|
import FieldSettings from './field_settings'
|
||||||
import FormulaModal from './formula_modal'
|
import FormulaModal from './formula_modal'
|
||||||
|
import FontModal from './font_modal'
|
||||||
import ConditionsModal from './conditions_modal'
|
import ConditionsModal from './conditions_modal'
|
||||||
import DescriptionModal from './description_modal'
|
import DescriptionModal from './description_modal'
|
||||||
import { IconX, IconCheck, IconDotsVertical } from '@tabler/icons-vue'
|
import { IconX, IconCheck, IconDotsVertical } from '@tabler/icons-vue'
|
||||||
@@ -295,6 +308,7 @@ export default {
|
|||||||
IconCheck,
|
IconCheck,
|
||||||
FieldSettings,
|
FieldSettings,
|
||||||
FormulaModal,
|
FormulaModal,
|
||||||
|
FontModal,
|
||||||
IconDotsVertical,
|
IconDotsVertical,
|
||||||
DescriptionModal,
|
DescriptionModal,
|
||||||
ConditionsModal,
|
ConditionsModal,
|
||||||
@@ -352,6 +366,7 @@ export default {
|
|||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isShowFormulaModal: false,
|
isShowFormulaModal: false,
|
||||||
|
isShowFontModal: false,
|
||||||
isShowConditionsModal: false,
|
isShowConditionsModal: false,
|
||||||
isContenteditable: false,
|
isContenteditable: false,
|
||||||
isSettingsFocus: false,
|
isSettingsFocus: false,
|
||||||
@@ -386,11 +401,19 @@ export default {
|
|||||||
defaultName () {
|
defaultName () {
|
||||||
return this.buildDefaultName(this.field, this.template.fields)
|
return this.buildDefaultName(this.field, this.template.fields)
|
||||||
},
|
},
|
||||||
alignClasses () {
|
fontClasses () {
|
||||||
|
if (!this.field.preferences) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
center: 'justify-center',
|
'justify-center': this.field.preferences.align === 'center',
|
||||||
left: 'justify-start',
|
'justify-start': this.field.preferences.align === 'left',
|
||||||
right: 'justify-end'
|
'justify-end': this.field.preferences.align === 'right',
|
||||||
|
'font-mono': this.field.preferences.font === 'Courier',
|
||||||
|
'font-serif': this.field.preferences.font === 'Times',
|
||||||
|
'font-bold': ['bold_italic', 'bold'].includes(this.field.preferences.font_type),
|
||||||
|
italic: ['bold_italic', 'italic'].includes(this.field.preferences.font_type)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
optionIndexText () {
|
optionIndexText () {
|
||||||
@@ -483,6 +506,10 @@ export default {
|
|||||||
this.$el.getRootNode().activeElement.blur()
|
this.$el.getRootNode().activeElement.blur()
|
||||||
},
|
},
|
||||||
maybeToggleDefaultValue () {
|
maybeToggleDefaultValue () {
|
||||||
|
if (!this.editable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (['text', 'number'].includes(this.field.type)) {
|
if (['text', 'number'].includes(this.field.type)) {
|
||||||
this.isContenteditable = true
|
this.isContenteditable = true
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="$slots.buttons || withTitle"
|
v-if="$slots.buttons || withTitle"
|
||||||
id="title_container"
|
id="title_container"
|
||||||
class="flex justify-between py-1.5 items-center pr-4 top-0 z-10"
|
class="flex justify-between py-1.5 items-center pr-4 top-0 z-10 title-container"
|
||||||
:class="{ sticky: withStickySubmitters || isBreakpointLg }"
|
:class="{ sticky: withStickySubmitters || isBreakpointLg }"
|
||||||
:style="{ backgroundColor }"
|
:style="{ backgroundColor }"
|
||||||
>
|
>
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
v-if="withTitle"
|
v-if="withTitle"
|
||||||
:model-value="template.name"
|
:model-value="template.name"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
class="text-xl md:text-3xl font-semibold focus:text-clip"
|
class="text-xl md:text-3xl font-semibold focus:text-clip template-name"
|
||||||
:icon-stroke-width="2.3"
|
:icon-stroke-width="2.3"
|
||||||
@update:model-value="updateName"
|
@update:model-value="updateName"
|
||||||
/>
|
/>
|
||||||
@@ -163,7 +163,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="main_container"
|
id="main_container"
|
||||||
class="flex"
|
class="flex main-container"
|
||||||
:class="$slots.buttons || withTitle ? (isMobile ? 'max-h-[calc(100%_-_60px)]' : 'md:max-h-[calc(100%_-_60px)]') : (isMobile ? 'max-h-[100%]' : 'md:max-h-[100%]')"
|
:class="$slots.buttons || withTitle ? (isMobile ? 'max-h-[calc(100%_-_60px)]' : 'md:max-h-[calc(100%_-_60px)]') : (isMobile ? 'max-h-[100%]' : 'md:max-h-[100%]')"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -205,7 +205,7 @@
|
|||||||
<button
|
<button
|
||||||
v-if="sortedDocuments.length && editable && withAddPageButton"
|
v-if="sortedDocuments.length && editable && withAddPageButton"
|
||||||
id="add_blank_page_button"
|
id="add_blank_page_button"
|
||||||
class="btn btn-outline w-full"
|
class="btn btn-outline w-full add-blank-page-button"
|
||||||
@click.prevent="addBlankPage"
|
@click.prevent="addBlankPage"
|
||||||
>
|
>
|
||||||
<IconInnerShadowTop
|
<IconInnerShadowTop
|
||||||
@@ -239,7 +239,7 @@
|
|||||||
<button
|
<button
|
||||||
v-if="withAddPageButton"
|
v-if="withAddPageButton"
|
||||||
id="add_blank_page_button"
|
id="add_blank_page_button"
|
||||||
class="btn btn-outline w-full mt-4"
|
class="btn btn-outline w-full mt-4 add-blank-page-button"
|
||||||
@click.prevent="addBlankPage"
|
@click.prevent="addBlankPage"
|
||||||
>
|
>
|
||||||
<IconInnerShadowTop
|
<IconInnerShadowTop
|
||||||
@@ -266,7 +266,7 @@
|
|||||||
:is-drag="!!dragField"
|
:is-drag="!!dragField"
|
||||||
:input-mode="inputMode"
|
:input-mode="inputMode"
|
||||||
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
||||||
:allow-draw="!onlyDefinedFields"
|
:allow-draw="!onlyDefinedFields || drawField"
|
||||||
:data-document-uuid="document.uuid"
|
:data-document-uuid="document.uuid"
|
||||||
:default-submitters="defaultSubmitters"
|
:default-submitters="defaultSubmitters"
|
||||||
:with-field-placeholder="withFieldPlaceholder"
|
:with-field-placeholder="withFieldPlaceholder"
|
||||||
@@ -307,7 +307,7 @@
|
|||||||
<button
|
<button
|
||||||
v-if="withAddPageButton"
|
v-if="withAddPageButton"
|
||||||
id="add_blank_page_button"
|
id="add_blank_page_button"
|
||||||
class="btn btn-outline w-full mt-4"
|
class="btn btn-outline w-full mt-4 add-blank-page-button"
|
||||||
@click.prevent="addBlankPage"
|
@click.prevent="addBlankPage"
|
||||||
>
|
>
|
||||||
<IconInnerShadowTop
|
<IconInnerShadowTop
|
||||||
@@ -327,7 +327,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="withFieldsList && !isMobile"
|
v-if="withFieldsList && !isMobile"
|
||||||
id="fields_list_container"
|
id="fields_list_container"
|
||||||
class="relative w-80 flex-none mt-1 pr-4 pl-0.5 hidden md:block"
|
class="relative w-80 flex-none mt-1 pr-4 pl-0.5 hidden md:block fields-list-container"
|
||||||
:class="drawField ? 'overflow-hidden' : 'overflow-y-auto overflow-x-hidden'"
|
:class="drawField ? 'overflow-hidden' : 'overflow-y-auto overflow-x-hidden'"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -335,13 +335,13 @@
|
|||||||
class="sticky inset-0 h-full z-20"
|
class="sticky inset-0 h-full z-20"
|
||||||
:style="{ backgroundColor }"
|
:style="{ backgroundColor }"
|
||||||
>
|
>
|
||||||
<div class="bg-base-200 rounded-lg p-5 text-center space-y-4">
|
<div class="bg-base-200 rounded-lg p-5 text-center space-y-4 draw-field-container">
|
||||||
<p>
|
<p>
|
||||||
{{ t('draw_field_on_the_document') }}
|
{{ t('draw_field_on_the_document') }}
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
class="base-button"
|
class="base-button cancel-draw-button"
|
||||||
@click="clearDrawField"
|
@click="clearDrawField"
|
||||||
>
|
>
|
||||||
{{ t('cancel') }}
|
{{ t('cancel') }}
|
||||||
@@ -408,7 +408,10 @@
|
|||||||
@select="startFieldDraw($event)"
|
@select="startFieldDraw($event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div id="docuseal_modal_container" />
|
<div
|
||||||
|
id="docuseal_modal_container"
|
||||||
|
class="modal-container"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -1316,6 +1319,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDropfield (area) {
|
onDropfield (area) {
|
||||||
|
if (!this.editable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const field = this.fieldsDragFieldRef.value || {
|
const field = this.fieldsDragFieldRef.value || {
|
||||||
name: '',
|
name: '',
|
||||||
uuid: v4(),
|
uuid: v4(),
|
||||||
@@ -1338,9 +1345,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === 'date') {
|
if (field.type === 'date') {
|
||||||
field.preferences = {
|
field.preferences ||= {}
|
||||||
format: this.defaultDateFormat
|
field.preferences.format ||= this.defaultDateFormat
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,12 @@
|
|||||||
/>
|
/>
|
||||||
<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 max-h-none 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>
|
<span class="modal-title">
|
||||||
{{ t('condition') }} - {{ item.name || buildDefaultName(item, template.fields) }}
|
{{ t('condition') }} - {{ item.name || buildDefaultName(item, template.fields) }}
|
||||||
</span>
|
</span>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="text-xl"
|
class="text-xl modal-close-button"
|
||||||
@click.prevent="$emit('close')"
|
@click.prevent="$emit('close')"
|
||||||
>×</a>
|
>×</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,9 +50,9 @@
|
|||||||
v-if="conditions.length > 1"
|
v-if="conditions.length > 1"
|
||||||
class="flex justify-between mx-1"
|
class="flex justify-between mx-1"
|
||||||
>
|
>
|
||||||
<span class="text-sm">
|
<label class="text-sm">
|
||||||
{{ t('condition') }} {{ cindex + 1 }}
|
{{ t('condition') }} {{ cindex + 1 }}
|
||||||
</span>
|
</label>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="link text-sm"
|
class="link text-sm"
|
||||||
@@ -131,9 +131,7 @@
|
|||||||
class="inline float-right link text-right mb-3 px-2"
|
class="inline float-right link text-right mb-3 px-2"
|
||||||
@click.prevent="conditions.push({})"
|
@click.prevent="conditions.push({})"
|
||||||
> + {{ t('add_condition') }}</a>
|
> + {{ t('add_condition') }}</a>
|
||||||
<button
|
<button class="base-button w-full mt-2 modal-save-button">
|
||||||
class="base-button w-full mt-2"
|
|
||||||
>
|
|
||||||
{{ t('save') }}
|
{{ t('save') }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex space-x-2">
|
<div class="flex space-x-2">
|
||||||
<Contenteditable
|
<Contenteditable
|
||||||
class="w-full block mr-6"
|
class="w-full block mr-6 document-preview-name"
|
||||||
:model-value="item.name"
|
:model-value="item.name"
|
||||||
:icon-width="16"
|
:icon-width="16"
|
||||||
@update:model-value="onUpdateName"
|
@update:model-value="onUpdateName"
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
v-if="withArrows"
|
v-if="withArrows"
|
||||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors document-control-button"
|
||||||
style="width: 24px; height: 24px"
|
style="width: 24px; height: 24px"
|
||||||
@click.stop="$emit('up', item)"
|
@click.stop="$emit('up', item)"
|
||||||
>
|
>
|
||||||
@@ -23,14 +23,14 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="withArrows"
|
v-if="withArrows"
|
||||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors document-control-button"
|
||||||
style="width: 24px; height: 24px"
|
style="width: 24px; height: 24px"
|
||||||
@click.stop="$emit('down', item)"
|
@click.stop="$emit('down', item)"
|
||||||
>
|
>
|
||||||
↓
|
↓
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors document-control-button"
|
||||||
style="width: 24px; height: 24px"
|
style="width: 24px; height: 24px"
|
||||||
@click.stop="$emit('remove', item)"
|
@click.stop="$emit('remove', item)"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -8,12 +8,12 @@
|
|||||||
/>
|
/>
|
||||||
<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 max-h-none 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>
|
<span class="modal-title">
|
||||||
{{ field.name || buildDefaultName(field, template.fields) }}
|
{{ field.name || buildDefaultName(field, template.fields) }}
|
||||||
</span>
|
</span>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="text-xl"
|
class="text-xl modal-close-button"
|
||||||
@click.prevent="$emit('close')"
|
@click.prevent="$emit('close')"
|
||||||
>×</a>
|
>×</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -55,9 +55,7 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button class="base-button w-full mt-4 modal-save-button">
|
||||||
class="base-button w-full mt-4"
|
|
||||||
>
|
|
||||||
{{ t('save') }}
|
{{ t('save') }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
class="list-field group mb-2"
|
class="list-field group mb-2"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="border border-base-300 rounded rounded-tr-none relative group"
|
class="border border-base-300 rounded rounded-tr-none relative group fields-list-item"
|
||||||
:style="{ backgroundColor: backgroundColor }"
|
:style="{ backgroundColor: backgroundColor }"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between relative group/contenteditable-container">
|
<div class="flex items-center justify-between relative group/contenteditable-container">
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
v-else-if="field.type !== 'heading'"
|
v-else-if="field.type !== 'heading'"
|
||||||
class="dropdown dropdown-end"
|
class="dropdown dropdown-end field-settings-dropdown"
|
||||||
@mouseenter="renderDropdown = true"
|
@mouseenter="renderDropdown = true"
|
||||||
@touchstart="renderDropdown = true"
|
@touchstart="renderDropdown = true"
|
||||||
>
|
>
|
||||||
@@ -127,6 +127,7 @@
|
|||||||
:editable="editable"
|
:editable="editable"
|
||||||
:background-color="dropdownBgColor"
|
:background-color="dropdownBgColor"
|
||||||
@click-formula="isShowFormulaModal = true"
|
@click-formula="isShowFormulaModal = true"
|
||||||
|
@click-font="isShowFontModal = true"
|
||||||
@click-description="isShowDescriptionModal = true"
|
@click-description="isShowDescriptionModal = true"
|
||||||
@click-condition="isShowConditionsModal = true"
|
@click-condition="isShowConditionsModal = true"
|
||||||
@set-draw="$emit('set-draw', $event)"
|
@set-draw="$emit('set-draw', $event)"
|
||||||
@@ -136,7 +137,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
class="relative text-transparent group-hover:text-base-content pr-1"
|
class="relative text-transparent group-hover:text-base-content pr-1 field-remove-button"
|
||||||
:title="t('remove')"
|
:title="t('remove')"
|
||||||
@click="$emit('remove', field)"
|
@click="$emit('remove', field)"
|
||||||
>
|
>
|
||||||
@@ -231,6 +232,17 @@
|
|||||||
@close="isShowFormulaModal = false"
|
@close="isShowFormulaModal = false"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
|
<Teleport
|
||||||
|
v-if="isShowFontModal"
|
||||||
|
:to="modalContainerEl"
|
||||||
|
>
|
||||||
|
<FontModal
|
||||||
|
:field="field"
|
||||||
|
:editable="editable && !defaultField"
|
||||||
|
:build-default-name="buildDefaultName"
|
||||||
|
@close="isShowFontModal = false"
|
||||||
|
/>
|
||||||
|
</Teleport>
|
||||||
<Teleport
|
<Teleport
|
||||||
v-if="isShowConditionsModal"
|
v-if="isShowConditionsModal"
|
||||||
:to="modalContainerEl"
|
:to="modalContainerEl"
|
||||||
@@ -261,6 +273,7 @@ import FieldType from './field_type'
|
|||||||
import PaymentSettings from './payment_settings'
|
import PaymentSettings from './payment_settings'
|
||||||
import FieldSettings from './field_settings'
|
import FieldSettings from './field_settings'
|
||||||
import FormulaModal from './formula_modal'
|
import FormulaModal from './formula_modal'
|
||||||
|
import FontModal from './font_modal'
|
||||||
import ConditionsModal from './conditions_modal'
|
import ConditionsModal from './conditions_modal'
|
||||||
import DescriptionModal from './description_modal'
|
import DescriptionModal from './description_modal'
|
||||||
import { IconRouteAltLeft, IconMathFunction, IconNewSection, IconTrashX, IconSettings } from '@tabler/icons-vue'
|
import { IconRouteAltLeft, IconMathFunction, IconNewSection, IconTrashX, IconSettings } from '@tabler/icons-vue'
|
||||||
@@ -275,6 +288,7 @@ export default {
|
|||||||
PaymentSettings,
|
PaymentSettings,
|
||||||
IconNewSection,
|
IconNewSection,
|
||||||
FormulaModal,
|
FormulaModal,
|
||||||
|
FontModal,
|
||||||
DescriptionModal,
|
DescriptionModal,
|
||||||
ConditionsModal,
|
ConditionsModal,
|
||||||
IconRouteAltLeft,
|
IconRouteAltLeft,
|
||||||
@@ -305,6 +319,7 @@ export default {
|
|||||||
isNameFocus: false,
|
isNameFocus: false,
|
||||||
showPaymentModal: false,
|
showPaymentModal: false,
|
||||||
isShowFormulaModal: false,
|
isShowFormulaModal: false,
|
||||||
|
isShowFontModal: false,
|
||||||
isShowConditionsModal: false,
|
isShowConditionsModal: false,
|
||||||
isShowDescriptionModal: false,
|
isShowDescriptionModal: false,
|
||||||
renderDropdown: false
|
renderDropdown: false
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="['number', 'cells'].includes(field.type)"
|
v-if="['cells'].includes(field.type)"
|
||||||
class="py-1.5 px-1 relative"
|
class="py-1.5 px-1 relative"
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
@@ -330,6 +330,19 @@
|
|||||||
v-if="field.type != 'stamp'"
|
v-if="field.type != 'stamp'"
|
||||||
class="pb-0.5 mt-0.5"
|
class="pb-0.5 mt-0.5"
|
||||||
>
|
>
|
||||||
|
<li v-if="['text', 'number', 'date'].includes(field.type)">
|
||||||
|
<label
|
||||||
|
class="label-text cursor-pointer text-center w-full flex items-center"
|
||||||
|
@click="$emit('click-font')"
|
||||||
|
>
|
||||||
|
<IconTypography
|
||||||
|
width="18"
|
||||||
|
/>
|
||||||
|
<span class="text-sm">
|
||||||
|
{{ t('font') }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="field.type != 'stamp'"
|
v-if="field.type != 'stamp'"
|
||||||
>
|
>
|
||||||
@@ -427,7 +440,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconRouteAltLeft, IconShape, IconX, IconMathFunction, IconNewSection, IconInfoCircle, IconCopy } from '@tabler/icons-vue'
|
import { IconRouteAltLeft, IconTypography, IconShape, IconX, IconMathFunction, IconNewSection, IconInfoCircle, IconCopy } from '@tabler/icons-vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FieldSettings',
|
name: 'FieldSettings',
|
||||||
@@ -438,6 +451,7 @@ export default {
|
|||||||
IconRouteAltLeft,
|
IconRouteAltLeft,
|
||||||
IconCopy,
|
IconCopy,
|
||||||
IconNewSection,
|
IconNewSection,
|
||||||
|
IconTypography,
|
||||||
IconX
|
IconX
|
||||||
},
|
},
|
||||||
inject: ['template', 'save', 't'],
|
inject: ['template', 'save', 't'],
|
||||||
@@ -472,7 +486,7 @@ export default {
|
|||||||
default: true
|
default: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['set-draw', 'scroll-to', 'click-formula', 'click-description', 'click-condition', 'remove-area'],
|
emits: ['set-draw', 'scroll-to', 'click-formula', 'click-description', 'click-condition', 'click-font', 'remove-area'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
@touchstart="renderDropdown = true"
|
@touchstart="renderDropdown = true"
|
||||||
>
|
>
|
||||||
<div class="flex space-x-2 items-end">
|
<div class="flex space-x-2 items-end">
|
||||||
<div class="group/contenteditable-container bg-base-100 rounded-md p-2 border border-base-300 w-full flex justify-between items-end">
|
<div class="group/contenteditable-container bg-base-100 rounded-md p-2 border border-base-300 w-full flex justify-between items-end roles-dropdown-label-mobile">
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<span
|
<span
|
||||||
class="w-3 h-3 flex-shrink-0 rounded-full"
|
class="w-3 h-3 flex-shrink-0 rounded-full"
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown dropdown-top dropdown-end">
|
<div class="dropdown dropdown-top dropdown-end roles-dropdown-mobile">
|
||||||
<label
|
<label
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="bg-base-100 cursor-pointer rounded-md p-2 border border-base-300 w-full flex justify-center"
|
class="bg-base-100 cursor-pointer rounded-md p-2 border border-base-300 w-full flex justify-center"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<span
|
<span
|
||||||
class="dropdown"
|
class="dropdown field-types-dropdown"
|
||||||
@mouseenter="renderDropdown = true"
|
@mouseenter="renderDropdown = true"
|
||||||
@touchstart="renderDropdown = true"
|
@touchstart="renderDropdown = true"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div :class="withStickySubmitters ? 'sticky top-0 z-[1]' : ''">
|
<div :class="withStickySubmitters ? 'sticky top-0 z-[1]' : ''">
|
||||||
<FieldSubmitter
|
<FieldSubmitter
|
||||||
:model-value="selectedSubmitter.uuid"
|
:model-value="selectedSubmitter.uuid"
|
||||||
class="roles-dropdown w-full rounded-lg"
|
class="roles-dropdown w-full rounded-lg roles-dropdown"
|
||||||
:style="withStickySubmitters ? { backgroundColor } : {}"
|
:style="withStickySubmitters ? { backgroundColor } : {}"
|
||||||
:submitters="submitters"
|
:submitters="submitters"
|
||||||
:menu-style="{ overflow: 'auto', display: 'flex', flexDirection: 'row', maxHeight: 'calc(100vh - 120px)', backgroundColor: ['', null, 'transparent'].includes(backgroundColor) ? 'white' : backgroundColor }"
|
:menu-style="{ overflow: 'auto', display: 'flex', flexDirection: 'row', maxHeight: 'calc(100vh - 120px)', backgroundColor: ['', null, 'transparent'].includes(backgroundColor) ? 'white' : backgroundColor }"
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
<div
|
<div
|
||||||
:style="{ backgroundColor }"
|
:style="{ backgroundColor }"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
class="default-field border border-base-300 rounded rounded-tr-none relative group mb-2"
|
class="border border-base-300 rounded rounded-tr-none relative group mb-2 default-field fields-list-item"
|
||||||
@dragstart="onDragstart({ type: 'text', ...field })"
|
@dragstart="onDragstart({ type: 'text', ...field })"
|
||||||
@dragend="$emit('drag-end')"
|
@dragend="$emit('drag-end')"
|
||||||
>
|
>
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="editable && !onlyDefinedFields"
|
v-if="editable && !onlyDefinedFields"
|
||||||
class="grid grid-cols-3 gap-1 pb-2"
|
class="grid grid-cols-3 gap-1 pb-2 fields-grid"
|
||||||
>
|
>
|
||||||
<template
|
<template
|
||||||
v-for="(icon, type) in fieldIconsSorted"
|
v-for="(icon, type) in fieldIconsSorted"
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
<button
|
<button
|
||||||
v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment') && (withVerification || type != 'verification')"
|
v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment') && (withVerification || type != 'verification')"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
class="field-type-button group flex items-center justify-center border border-dashed w-full rounded relative"
|
class="field-type-button group flex items-center justify-center border border-dashed w-full rounded relative fields-grid-item"
|
||||||
:style="{ backgroundColor }"
|
:style="{ backgroundColor }"
|
||||||
:class="drawFieldType === type ? 'border-base-content/40' : 'border-base-300 hover:border-base-content/20'"
|
:class="drawFieldType === type ? 'border-base-content/40' : 'border-base-300 hover:border-base-content/20'"
|
||||||
@dragstart="onDragstart({ type: type })"
|
@dragstart="onDragstart({ type: type })"
|
||||||
@@ -142,7 +142,7 @@
|
|||||||
<a
|
<a
|
||||||
href="https://www.docuseal.com/pricing"
|
href="https://www.docuseal.com/pricing"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative"
|
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative fields-grid-item"
|
||||||
:style="{ backgroundColor }"
|
:style="{ backgroundColor }"
|
||||||
>
|
>
|
||||||
<div class="w-0 absolute left-0">
|
<div class="w-0 absolute left-0">
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
<a
|
<a
|
||||||
href="https://www.docuseal.com/contact"
|
href="https://www.docuseal.com/contact"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative"
|
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative fields-grid-item"
|
||||||
:style="{ backgroundColor }"
|
:style="{ backgroundColor }"
|
||||||
>
|
>
|
||||||
<div class="w-0 absolute left-0">
|
<div class="w-0 absolute left-0">
|
||||||
|
|||||||
@@ -0,0 +1,290 @@
|
|||||||
|
<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 px-6 mt-20 max-h-none w-full max-w-xl">
|
||||||
|
<div class="flex justify-between items-center border-b pb-2 mb-2 font-medium">
|
||||||
|
<span class="modal-title">
|
||||||
|
{{ t('font') }} - {{ field.name || buildDefaultName(field, template.fields) }}
|
||||||
|
</span>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="text-xl modal-close-button"
|
||||||
|
@click.prevent="$emit('close')"
|
||||||
|
>×</a>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center space-x-1.5">
|
||||||
|
<span>
|
||||||
|
<div class="dropdown modal-field-font-dropdown">
|
||||||
|
<label
|
||||||
|
tabindex="0"
|
||||||
|
class="base-input flex items-center justify-between"
|
||||||
|
style="height: 32px; padding-right: 0; width: 120px"
|
||||||
|
:class="fonts.find((f) => f.value === preferences.font)?.class"
|
||||||
|
>
|
||||||
|
<span style="margin-top: 1px">
|
||||||
|
{{ preferences.font || 'Default' }}
|
||||||
|
</span>
|
||||||
|
<IconChevronDown
|
||||||
|
class="ml-2 mr-2 mt-0.5"
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
tabindex="0"
|
||||||
|
class="dropdown-content p-0 mt-1 block z-10 menu shadow bg-white border border-base-300 rounded-md w-52"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(font, index) in fonts"
|
||||||
|
:key="index"
|
||||||
|
:value="font.value"
|
||||||
|
:class="{ 'bg-base-300': preferences.font == font.value, [font.class]: true }"
|
||||||
|
class="hover:bg-base-300 px-2 py-1.5 cursor-pointer"
|
||||||
|
@click="[font.value ? preferences.font = font.value : delete preferences.font, closeDropdown()]"
|
||||||
|
>
|
||||||
|
{{ font.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span class="relative">
|
||||||
|
<select
|
||||||
|
class="select input-bordered bg-white select-sm text-center pl-2"
|
||||||
|
style="font-size: 16px; width: 86px; text-align-last: center;"
|
||||||
|
@change="$event.target.value ? preferences.font_size = parseInt($event.target.value) : delete preferences.font_size"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
:selected="!preferences.font_size"
|
||||||
|
value=""
|
||||||
|
>
|
||||||
|
Auto
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
v-for="size in sizes"
|
||||||
|
:key="size"
|
||||||
|
:value="size"
|
||||||
|
:selected="size === preferences.font_size"
|
||||||
|
>
|
||||||
|
{{ size }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<span
|
||||||
|
class="border-l pl-1.5 absolute bg-white bottom-0 pointer-events-none text-sm h-5"
|
||||||
|
style="right: 13px; top: 7px"
|
||||||
|
>
|
||||||
|
pt
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<div
|
||||||
|
class="join"
|
||||||
|
style="height: 32px"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-for="(type, index) in types"
|
||||||
|
:key="index"
|
||||||
|
class="btn btn-sm join-item bg-white input-bordered hover:border-base-content/20 hover:bg-base-200/50 px-2"
|
||||||
|
:class="{ '!bg-base-300': preferences.font_type?.includes(type.value) }"
|
||||||
|
@click="setFontType(type.value)"
|
||||||
|
>
|
||||||
|
<component :is="type.icon" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<div
|
||||||
|
class="join"
|
||||||
|
style="height: 32px"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-for="(align, index) in aligns"
|
||||||
|
:key="index"
|
||||||
|
class="btn btn-sm join-item bg-white input-bordered hover:border-base-content/20 hover:bg-base-200/50 px-2"
|
||||||
|
:class="{ '!bg-base-300': preferences.align === align.value }"
|
||||||
|
@click="align.value && preferences.align != align.value ? preferences.align = align.value : delete preferences.align"
|
||||||
|
>
|
||||||
|
<component :is="align.icon" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<select
|
||||||
|
class="input input-bordered bg-white input-sm text-lg rounded-md"
|
||||||
|
style="-webkit-appearance: none; -moz-appearance: none; text-indent: 0px; text-overflow: ''; padding: 0px 6px; height: 32px"
|
||||||
|
@change="$event.target.value ? preferences.color = $event.target.value : delete preferences.color"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="(color, index) in colors"
|
||||||
|
:key="index"
|
||||||
|
:value="color.value"
|
||||||
|
:selected="color.value == preferences.color"
|
||||||
|
>
|
||||||
|
{{ color.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<div
|
||||||
|
class="flex items-center border border-base-content/20 rounded-xl bg-white px-4 h-16 modal-field-font-preview"
|
||||||
|
:style="{
|
||||||
|
color: preferences.color || 'black',
|
||||||
|
fontSize: (preferences.font_size || 12) + 'pt',
|
||||||
|
}"
|
||||||
|
:class="textClasses"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
contenteditable="true"
|
||||||
|
class="outline-none"
|
||||||
|
>
|
||||||
|
{{ field.default_value || field.name || buildDefaultName(field, template.fields) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<button
|
||||||
|
class="base-button w-full modal-save-button"
|
||||||
|
@click.prevent="saveAndClose"
|
||||||
|
>
|
||||||
|
{{ t('save') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { IconChevronDown, IconBold, IconItalic, IconAlignLeft, IconAlignRight, IconAlignCenter } from '@tabler/icons-vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FontModal',
|
||||||
|
components: {
|
||||||
|
IconChevronDown
|
||||||
|
},
|
||||||
|
inject: ['t', 'save', 'template'],
|
||||||
|
props: {
|
||||||
|
field: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
editable: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
buildDefaultName: {
|
||||||
|
type: Function,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['close'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
preferences: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
fonts () {
|
||||||
|
return [
|
||||||
|
{ value: null, label: 'Default' },
|
||||||
|
{ value: 'Times', label: 'Times', class: 'font-serif' },
|
||||||
|
{ value: 'Courier', label: 'Courier', class: 'font-mono' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
types () {
|
||||||
|
return [
|
||||||
|
{ icon: IconBold, value: 'bold' },
|
||||||
|
{ icon: IconItalic, value: 'italic' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
aligns () {
|
||||||
|
return [
|
||||||
|
{ icon: IconAlignLeft, value: 'left' },
|
||||||
|
{ icon: IconAlignCenter, value: 'center' },
|
||||||
|
{ icon: IconAlignRight, value: 'right' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sizes () {
|
||||||
|
return [...Array(23).keys()].map(i => i + 6)
|
||||||
|
},
|
||||||
|
colors () {
|
||||||
|
return [
|
||||||
|
{ label: '⬛', value: 'black' },
|
||||||
|
{ label: '🟦', value: 'blue' },
|
||||||
|
{ label: '🟥', value: 'red' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
textClasses () {
|
||||||
|
return {
|
||||||
|
'font-mono': this.preferences.font === 'Courier',
|
||||||
|
'font-serif': this.preferences.font === 'Times',
|
||||||
|
'justify-center': this.preferences.align === 'center',
|
||||||
|
'justify-start': this.preferences.align === 'left',
|
||||||
|
'justify-end': this.preferences.align === 'right',
|
||||||
|
'font-bold': ['bold_italic', 'bold'].includes(this.preferences.font_type),
|
||||||
|
italic: ['bold_italic', 'italic'].includes(this.preferences.font_type)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
keys () {
|
||||||
|
return ['font_type', 'font_size', 'color', 'align', 'font']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.preferences = this.keys.reduce((acc, key) => {
|
||||||
|
acc[key] = this.field.preferences?.[key]
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
closeDropdown () {
|
||||||
|
this.$el.getRootNode().activeElement.blur()
|
||||||
|
},
|
||||||
|
setFontType (value) {
|
||||||
|
if (value === 'bold') {
|
||||||
|
if (this.preferences.font_type === 'bold') {
|
||||||
|
delete this.preferences.font_type
|
||||||
|
} else if (this.preferences.font_type === 'italic') {
|
||||||
|
this.preferences.font_type = 'bold_italic'
|
||||||
|
} else if (this.preferences.font_type === 'bold_italic') {
|
||||||
|
this.preferences.font_type = 'italic'
|
||||||
|
} else {
|
||||||
|
this.preferences.font_type = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === 'italic') {
|
||||||
|
if (this.preferences.font_type === 'italic') {
|
||||||
|
delete this.preferences.font_type
|
||||||
|
} else if (this.preferences.font_type === 'bold') {
|
||||||
|
this.preferences.font_type = 'bold_italic'
|
||||||
|
} else if (this.preferences.font_type === 'bold_italic') {
|
||||||
|
this.preferences.font_type = 'bold'
|
||||||
|
} else {
|
||||||
|
this.preferences.font_type = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
saveAndClose () {
|
||||||
|
this.field.preferences ||= {}
|
||||||
|
|
||||||
|
this.keys.forEach((key) => delete this.field.preferences[key])
|
||||||
|
|
||||||
|
Object.assign(this.field.preferences, this.preferences)
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
|
||||||
|
this.$emit('close')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -8,12 +8,12 @@
|
|||||||
/>
|
/>
|
||||||
<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 max-h-none 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>
|
<span class="modal-title">
|
||||||
{{ t('formula') }} - {{ field.name || buildDefaultName(field, template.fields) }}
|
{{ t('formula') }} - {{ field.name || buildDefaultName(field, template.fields) }}
|
||||||
</span>
|
</span>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="text-xl"
|
class="text-xl modal-close-button"
|
||||||
@click.prevent="$emit('close')"
|
@click.prevent="$emit('close')"
|
||||||
>×</a>
|
>×</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
class="base-button w-full"
|
class="base-button w-full modal-save-button"
|
||||||
@click.prevent="validateSaveAndClose"
|
@click.prevent="validateSaveAndClose"
|
||||||
>
|
>
|
||||||
{{ t('save') }}
|
{{ t('save') }}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const en = {
|
const en = {
|
||||||
|
font: 'Font',
|
||||||
party: 'Party',
|
party: 'Party',
|
||||||
method: 'Method',
|
method: 'Method',
|
||||||
reorder_fields: 'Reorder fields',
|
reorder_fields: 'Reorder fields',
|
||||||
@@ -161,6 +162,7 @@ const en = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const es = {
|
const es = {
|
||||||
|
fuente: 'Fuente',
|
||||||
party: 'Parte',
|
party: 'Parte',
|
||||||
method: 'Método',
|
method: 'Método',
|
||||||
reorder_fields: 'Reordenar campos',
|
reorder_fields: 'Reordenar campos',
|
||||||
@@ -323,6 +325,7 @@ const es = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const it = {
|
const it = {
|
||||||
|
font: 'Carattere',
|
||||||
party: 'Parte',
|
party: 'Parte',
|
||||||
method: 'Metodo',
|
method: 'Metodo',
|
||||||
reorder_fields: 'Riordina i campi',
|
reorder_fields: 'Riordina i campi',
|
||||||
@@ -485,6 +488,7 @@ const it = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pt = {
|
const pt = {
|
||||||
|
fonte: 'Fonte',
|
||||||
party: 'Parte',
|
party: 'Parte',
|
||||||
method: 'Método',
|
method: 'Método',
|
||||||
reorder_fields: 'Reorganizar campos',
|
reorder_fields: 'Reorganizar campos',
|
||||||
@@ -647,6 +651,7 @@ const pt = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fr = {
|
const fr = {
|
||||||
|
font: 'Police',
|
||||||
party: 'Partie',
|
party: 'Partie',
|
||||||
method: 'Méthode',
|
method: 'Méthode',
|
||||||
reorder_fields: 'Réorganiser les champs',
|
reorder_fields: 'Réorganiser les champs',
|
||||||
@@ -809,6 +814,7 @@ const fr = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const de = {
|
const de = {
|
||||||
|
font: 'Schriftart',
|
||||||
party: 'Partei',
|
party: 'Partei',
|
||||||
method: 'Verfahren',
|
method: 'Verfahren',
|
||||||
reorder_fields: 'Felder neu anordnen',
|
reorder_fields: 'Felder neu anordnen',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="absolute text-center w-full bottom-0 pr-3 mb-4">
|
<div class="absolute text-center w-full bottom-0 pr-3 mb-4">
|
||||||
<span class="w-full bg-base-200 px-4 py-2 rounded-md inline-flex space-x-2 mx-auto items-center justify-between mb-2 z-20">
|
<span class="w-full bg-base-200 px-4 py-2 rounded-md inline-flex space-x-2 mx-auto items-center justify-between mb-2 z-20 draw-field-container-mobile">
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<component
|
<component
|
||||||
:is="fieldIcons[drawField.type]"
|
:is="fieldIcons[drawField.type]"
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<span
|
<span class="dropdown dropdown-top dropdown-end absolute bottom-4 right-4 z-10 fields-dropdown-mobile">
|
||||||
class="dropdown dropdown-top dropdown-end absolute bottom-4 right-4 z-10"
|
|
||||||
>
|
|
||||||
<label
|
<label
|
||||||
class="btn btn-neutral text-white btn-circle btn-lg group"
|
class="btn btn-neutral text-white btn-circle btn-lg group"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<span
|
<span
|
||||||
class="dropdown dropdown-end"
|
class="dropdown dropdown-end field-settings-dropdown"
|
||||||
:class="{ 'dropdown-open': ((!field.preferences?.price && !field.preferences?.formula) || !isConnected) && !isLoading }"
|
:class="{ 'dropdown-open': ((!field.preferences?.price && !field.preferences?.formula) || !isConnected) && !isLoading }"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors p-0"
|
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors p-0 document-control-button"
|
||||||
@click.stop="isShowConditionsModal = true"
|
@click.stop="isShowConditionsModal = true"
|
||||||
>
|
>
|
||||||
<IconRouteAltLeft
|
<IconRouteAltLeft
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors document-control-button"
|
||||||
style="width: 24px; height: 24px"
|
style="width: 24px; height: 24px"
|
||||||
@click.stop="$emit('remove', item)"
|
@click.stop="$emit('remove', item)"
|
||||||
>
|
>
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
class="tooltip tooltip-left before:text-xs"
|
class="tooltip tooltip-left before:text-xs"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors p-0"
|
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors p-0 document-control-button"
|
||||||
@click.stop="$emit('reorder', item)"
|
@click.stop="$emit('reorder', item)"
|
||||||
>
|
>
|
||||||
<IconSortDescending2
|
<IconSortDescending2
|
||||||
@@ -75,14 +75,14 @@
|
|||||||
</span>
|
</span>
|
||||||
<template v-if="withArrows">
|
<template v-if="withArrows">
|
||||||
<button
|
<button
|
||||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors document-control-button"
|
||||||
style="width: 24px; height: 24px"
|
style="width: 24px; height: 24px"
|
||||||
@click.stop="$emit('up', item)"
|
@click.stop="$emit('up', item)"
|
||||||
>
|
>
|
||||||
↑
|
↑
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors document-control-button"
|
||||||
style="width: 24px; height: 24px"
|
style="width: 24px; height: 24px"
|
||||||
@click.stop="$emit('down', item)"
|
@click.stop="$emit('down', item)"
|
||||||
>
|
>
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex pb-2 pt-1.5">
|
<div class="flex pb-2 pt-1.5 document-preview-name">
|
||||||
<Contenteditable
|
<Contenteditable
|
||||||
:model-value="item.name"
|
:model-value="item.name"
|
||||||
:icon-width="16"
|
:icon-width="16"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<label
|
<label
|
||||||
:for="inputId"
|
:for="inputId"
|
||||||
class="btn btn-neutral btn-xs text-white transition-none"
|
class="btn btn-neutral btn-xs text-white transition-none replace-document-button"
|
||||||
:class="{ 'opacity-100': isLoading || isProcessing }"
|
:class="{ 'opacity-100': isLoading || isProcessing }"
|
||||||
>
|
>
|
||||||
{{ message }}
|
{{ message }}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<label
|
<label
|
||||||
id="add_document_button"
|
id="add_document_button"
|
||||||
:for="inputId"
|
:for="inputId"
|
||||||
class="btn btn-outline w-full"
|
class="btn btn-outline w-full add-document-button"
|
||||||
:class="{ 'btn-disabled': isLoading || isProcessing }"
|
:class="{ 'btn-disabled': isLoading || isProcessing }"
|
||||||
>
|
>
|
||||||
<IconInnerShadowTop
|
<IconInnerShadowTop
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ProcessSubmissionExpiredJob
|
||||||
|
include Sidekiq::Job
|
||||||
|
|
||||||
|
def perform(params = {})
|
||||||
|
submission = Submission.find(params['submission_id'])
|
||||||
|
|
||||||
|
return if submission.archived_at?
|
||||||
|
return if submission.template.archived_at?
|
||||||
|
return if submission.submitters.where.not(declined_at: nil).exists?
|
||||||
|
return unless submission.submitters.exists?(completed_at: nil)
|
||||||
|
|
||||||
|
WebhookUrls.for_account_id(submission.account_id, %w[submission.expired]).each do |webhook|
|
||||||
|
SendSubmissionExpiredWebhookRequestJob.perform_async('submission_id' => submission.id,
|
||||||
|
'webhook_url_id' => webhook.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -5,7 +5,7 @@ class SendFormCompletedWebhookRequestJob
|
|||||||
|
|
||||||
sidekiq_options queue: :webhooks
|
sidekiq_options queue: :webhooks
|
||||||
|
|
||||||
MAX_ATTEMPTS = 20
|
MAX_ATTEMPTS = 12
|
||||||
|
|
||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
submitter = Submitter.find(params['submitter_id'])
|
submitter = Submitter.find(params['submitter_id'])
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SendSubmissionExpiredWebhookRequestJob
|
||||||
|
include Sidekiq::Job
|
||||||
|
|
||||||
|
sidekiq_options queue: :webhooks
|
||||||
|
|
||||||
|
MAX_ATTEMPTS = 10
|
||||||
|
|
||||||
|
def perform(params = {})
|
||||||
|
submission = Submission.find(params['submission_id'])
|
||||||
|
webhook_url = WebhookUrl.find(params['webhook_url_id'])
|
||||||
|
|
||||||
|
attempt = params['attempt'].to_i
|
||||||
|
|
||||||
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.expired')
|
||||||
|
|
||||||
|
resp = SendWebhookRequest.call(webhook_url, event_type: 'submission.expired',
|
||||||
|
data: Submissions::SerializeForApi.call(submission))
|
||||||
|
|
||||||
|
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
||||||
|
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
||||||
|
SendSubmissionExpiredWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
|
'submission_id' => submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
|
'attempt' => attempt + 1,
|
||||||
|
'last_status' => resp&.status.to_i
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -55,6 +55,7 @@ class Submitter < ApplicationRecord
|
|||||||
has_many_attached :attachments
|
has_many_attached :attachments
|
||||||
has_many_attached :preview_documents
|
has_many_attached :preview_documents
|
||||||
has_many :template_accesses, through: :template
|
has_many :template_accesses, through: :template
|
||||||
|
has_many :email_events, as: :emailable, dependent: (Docuseal.multitenant? ? nil : :destroy)
|
||||||
|
|
||||||
has_many :document_generation_events, dependent: :destroy
|
has_many :document_generation_events, dependent: :destroy
|
||||||
has_many :submission_events, dependent: :destroy
|
has_many :submission_events, dependent: :destroy
|
||||||
@@ -63,6 +64,8 @@ class Submitter < ApplicationRecord
|
|||||||
|
|
||||||
scope :completed, -> { where.not(completed_at: nil) }
|
scope :completed, -> { where.not(completed_at: nil) }
|
||||||
|
|
||||||
|
after_destroy :anonymize_email_events, if: -> { Docuseal.multitenant? }
|
||||||
|
|
||||||
def status
|
def status
|
||||||
if declined_at?
|
if declined_at?
|
||||||
'declined'
|
'declined'
|
||||||
@@ -108,4 +111,12 @@ class Submitter < ApplicationRecord
|
|||||||
fields.any? { |f| f['submitter_uuid'] == uuid && signature_field_types.include?(f['type']) }
|
fields.any? { |f| f['submitter_uuid'] == uuid && signature_field_types.include?(f['type']) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def anonymize_email_events
|
||||||
|
email_events.each do |event|
|
||||||
|
event.update!(email: Digest::MD5.base64digest(event.email))
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -28,10 +28,12 @@ class WebhookUrl < ApplicationRecord
|
|||||||
form.started
|
form.started
|
||||||
form.completed
|
form.completed
|
||||||
form.declined
|
form.declined
|
||||||
|
submission.created
|
||||||
|
submission.completed
|
||||||
|
submission.expired
|
||||||
|
submission.archived
|
||||||
template.created
|
template.created
|
||||||
template.updated
|
template.updated
|
||||||
submission.created
|
|
||||||
submission.archived
|
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
<span>
|
<span>
|
||||||
<%= t('add_signature_id_to_the_documents') %>
|
<%= t('add_signature_id_to_the_documents') %>
|
||||||
</span>
|
</span>
|
||||||
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %>
|
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()', disabled: can?(:manage, :cfr) %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
<span>
|
<span>
|
||||||
<%= t('require_signing_reason') %>
|
<%= t('require_signing_reason') %>
|
||||||
</span>
|
</span>
|
||||||
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %>
|
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()', disabled: can?(:manage, :cfr) %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -30,7 +30,9 @@
|
|||||||
<div dir="auto" class="form-control !mt-0">
|
<div dir="auto" class="form-control !mt-0">
|
||||||
<%= f.label :email, t('email'), class: 'label' %>
|
<%= f.label :email, t('email'), class: 'label' %>
|
||||||
<%= f.email_field :email, value: current_user&.email || params[:email] || @submitter.email, required: true, class: 'base-input', placeholder: t('provide_your_email_to_start') %>
|
<%= f.email_field :email, value: current_user&.email || params[:email] || @submitter.email, required: true, class: 'base-input', placeholder: t('provide_your_email_to_start') %>
|
||||||
<%= @error_message %>
|
<% if @error_message %>
|
||||||
|
<span class="label-text-alt text-red-400 mt-1"><%= @error_message %></span>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<toggle-submit dir="auto" class="form-control">
|
<toggle-submit dir="auto" class="form-control">
|
||||||
<%= f.button button_title(title: t('start'), disabled_with: t('starting')), class: 'base-button' %>
|
<%= f.button button_title(title: t('start'), disabled_with: t('starting')), class: 'base-button' %>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<% align = field.dig('preferences', 'align') %>
|
<% align = field.dig('preferences', 'align') %>
|
||||||
<% color = field.dig('preferences', 'color') %>
|
<% color = field.dig('preferences', 'color') %>
|
||||||
<% font = field.dig('preferences', 'font') %>
|
<% font = field.dig('preferences', 'font') %>
|
||||||
<field-value dir="auto" class="flex absolute text-[1.6vw] lg:text-base <%= 'font-mono' if font == 'Courier' %> <%= 'font-serif' if font == 'Times' %> <%= align == 'right' ? 'justify-end' : (align == 'center' ? 'justify-center' : '') %>" style="<%= "color: #{color}; " if color.present? %>width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; <%= "font-size: clamp(4pt, 1.6vw, #{field['preferences']['font_size'].to_i * 1.23}pt); line-height: `clamp(6pt, 2.0vw, #{(field['preferences']['font_size'].to_i * 1.23) + 3}pt)`" if field.dig('preferences', 'font_size') %>">
|
<% font_type = field.dig('preferences', 'font_type') %>
|
||||||
|
<field-value dir="auto" class="flex absolute text-[1.6vw] lg:text-base <%= 'font-mono' if font == 'Courier' %> <%= 'font-serif' if font == 'Times' %> <%= 'font-bold' if font_type == 'bold' || font_type == 'bold_italic' %> <%= 'italic' if font_type == 'italic' || font_type == 'bold_italic' %> <%= align == 'right' ? 'justify-end' : (align == 'center' ? 'justify-center' : '') %>" style="<%= "color: #{color}; " if color.present? %>width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; <%= "font-size: clamp(4pt, 1.6vw, #{field['preferences']['font_size'].to_i * 1.23}pt); line-height: `clamp(6pt, 2.0vw, #{(field['preferences']['font_size'].to_i * 1.23) + 3}pt)`" if field.dig('preferences', 'font_size') %>">
|
||||||
<% if field['type'] == 'signature' %>
|
<% if field['type'] == 'signature' %>
|
||||||
<div class="flex flex-col justify-between h-full overflow-hidden">
|
<div class="flex flex-col justify-between h-full overflow-hidden">
|
||||||
<div class="flex-grow flex overflow-hidden" style="min-height: 50%">
|
<div class="flex-grow flex overflow-hidden" style="min-height: 50%">
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<%= svg_icon('external_link', class: 'w-6 h-6') %>
|
<%= svg_icon('external_link', class: 'w-6 h-6') %>
|
||||||
<span class="hidden md:inline"><%= t('audit_log') %></span>
|
<span class="hidden md:inline"><%= t('audit_log') %></span>
|
||||||
</a>
|
</a>
|
||||||
<% else %>
|
<% elsif signed_in? %>
|
||||||
<%= link_to submission_events_path(@submission), class: 'white-button', data: { turbo_frame: :modal } do %>
|
<%= link_to submission_events_path(@submission), class: 'white-button', data: { turbo_frame: :modal } do %>
|
||||||
<%= svg_icon('logs', class: 'w-6 h-6') %>
|
<%= svg_icon('logs', class: 'w-6 h-6') %>
|
||||||
<span class="hidden md:block"><%= t('event_log') %></span>
|
<span class="hidden md:block"><%= t('event_log') %></span>
|
||||||
@@ -105,7 +105,18 @@
|
|||||||
<% value = values[field['uuid']] %>
|
<% value = values[field['uuid']] %>
|
||||||
<% value ||= field['default_value'] if field['type'] == 'heading' %>
|
<% value ||= field['default_value'] if field['type'] == 'heading' %>
|
||||||
<% next if value.blank? %>
|
<% next if value.blank? %>
|
||||||
<%= render 'submissions/value', area:, field:, attachments_index:, value:, locale: @submission.account.locale, timezone: @submission.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: %>
|
<% if (mask = field.dig('preferences', 'mask').presence) && signed_in? && can?(:read, @submission) %>
|
||||||
|
<span class="group">
|
||||||
|
<span class="hidden group-hover:inline">
|
||||||
|
<%= render 'submissions/value', area:, field:, attachments_index:, value:, locale: @submission.account.locale, timezone: @submission.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: %>
|
||||||
|
</span>
|
||||||
|
<span class="group-hover:hidden">
|
||||||
|
<%= render 'submissions/value', area:, field:, attachments_index:, value: Array.wrap(value).map { |e| TextUtils.mask_value(e, mask) }.join(', '), locale: @submission.account.locale, timezone: @submission.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: %>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<% else %>
|
||||||
|
<%= render 'submissions/value', area:, field:, attachments_index:, value: mask.present? ? Array.wrap(value).map { |e| TextUtils.mask_value(e, mask) }.join(', ') : value, locale: @submission.account.locale, timezone: @submission.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: %>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -232,12 +243,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<% elsif field['type'] == 'checkbox' %>
|
<% elsif field['type'] == 'checkbox' %>
|
||||||
<%= svg_icon('check', class: 'w-6 h-6') %>
|
<%= svg_icon('check', class: 'w-6 h-6') %>
|
||||||
<% elsif field['type'] == 'number' %>
|
|
||||||
<%= NumberUtils.format_number(value, field.dig('preferences', 'format')) %>
|
|
||||||
<% elsif field['type'] == 'date' %>
|
|
||||||
<%= TimeUtils.format_date_string(value, field.dig('preferences', 'format'), @submission.account.locale) %>
|
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="whitespace-pre-wrap"><%= Array.wrap(value).join(', ') %></div>
|
<% if field['type'] == 'number' %>
|
||||||
|
<% value = NumberUtils.format_number(value, field.dig('preferences', 'format')) %>
|
||||||
|
<% elsif field['type'] == 'date' %>
|
||||||
|
<% value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), @submission.account.locale) %>
|
||||||
|
<% end %>
|
||||||
|
<% if (mask = field.dig('preferences', 'mask').presence) %>
|
||||||
|
<% if signed_in? && can?(:read, @submission) %>
|
||||||
|
<div class="[&:not(:hover)]:after:content-[attr(data-value)] block whitespace-pre-wrap group" data-value="<%= Array.wrap(value).map { |e| TextUtils.mask_value(e, mask) }.join(', ') %>"><span class="hidden group-hover:block"><%= Array.wrap(value).join(', ') %></span></div>
|
||||||
|
<% else %>
|
||||||
|
<div class="whitespace-pre-wrap"><%= Array.wrap(value).map { |e| TextUtils.mask_value(e, mask) }.join(', ') %></div>
|
||||||
|
<% end %>
|
||||||
|
<% else %>
|
||||||
|
<div class="whitespace-pre-wrap"><%= Array.wrap(value).join(', ') %></div>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
<% next if field['conditions'].present? && values[field['uuid']].blank? && field['submitter_uuid'] != @submitter.uuid %>
|
<% next if field['conditions'].present? && values[field['uuid']].blank? && field['submitter_uuid'] != @submitter.uuid %>
|
||||||
<% next if field['conditions'].present? && field['submitter_uuid'] == @submitter.uuid %>
|
<% next if field['conditions'].present? && field['submitter_uuid'] == @submitter.uuid %>
|
||||||
<% next if field.dig('preferences', 'formula').present? && field['submitter_uuid'] == @submitter.uuid %>
|
<% next if field.dig('preferences', 'formula').present? && field['submitter_uuid'] == @submitter.uuid %>
|
||||||
<%= render 'submissions/value', area:, field:, attachments_index: @attachments_index, value:, locale: @submitter.account.locale, timezone: @submitter.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: @form_configs[:with_signature_id] %>
|
<%= render 'submissions/value', area:, field:, attachments_index: @attachments_index, value: field.dig('preferences', 'mask').present? ? TextUtils.mask_value(value, field.dig('preferences', 'mask')) : value, locale: @submitter.account.locale, timezone: @submitter.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: @form_configs[:with_signature_id] %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -285,12 +285,12 @@
|
|||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.text_field :name, class: 'w-full outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1 peer mb-2', autocomplete: 'off', placeholder: "#{index + 1}#{(index + 1).ordinal} Party", required: true %>
|
<%= ff.text_field :name, class: 'w-full outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1 peer mb-2', autocomplete: 'off', placeholder: "#{index + 1}#{(index + 1).ordinal} Party", required: true %>
|
||||||
<% if @template.submitters.size == 2 %>
|
<% if @template.submitters.size == 2 %>
|
||||||
<%= ff.email_field :email, class: 'base-input', autocomplete: 'off', placeholder: t('default_email'), disabled: ff.object.is_requester || ff.object.invite_by_uuid.present? || ff.object.optional_invite_by_uuid.present?, id: field_uuid = SecureRandom.uuid %>
|
<%= tag.input name: ff.field_name(:email), value: ff.object.email, type: :email, class: 'base-input', multiple: true, autocomplete: 'off', placeholder: t('default_email'), disabled: ff.object.is_requester || ff.object.invite_by_uuid.present? || ff.object.optional_invite_by_uuid.present?, id: field_uuid = SecureRandom.uuid %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<toggle-attribute data-target-id="<%= email_field_uuid = SecureRandom.uuid %>" data-class-name="hidden" data-value="email">
|
<toggle-attribute data-target-id="<%= email_field_uuid = SecureRandom.uuid %>" data-class-name="hidden" data-value="email">
|
||||||
<%= ff.select :option, [[t('not_specified'), 'not_set'], [t('submission_requester'), 'is_requester'], [t('specified_email'), 'email'], *(@template.submitters - [submitter]).flat_map { |e| [[t('invite_by_name', name: e['name']), "invite_by_#{e['uuid']}"], [t('invite_by_name', name: e['name']) + " (#{t(:optional).capitalize})", "optional_invite_by_#{e['uuid']}"]] }, *(@template.submitters - [submitter]).map { |e| [t('same_as_name', name: e['name']), "linked_to_#{e['uuid']}"] }], {}, class: 'base-select mb-3' %>
|
<%= ff.select :option, [[t('not_specified'), 'not_set'], [t('submission_requester'), 'is_requester'], [t('specified_email'), 'email'], *(@template.submitters - [submitter]).flat_map { |e| [[t('invite_by_name', name: e['name']), "invite_by_#{e['uuid']}"], [t('invite_by_name', name: e['name']) + " (#{t(:optional).capitalize})", "optional_invite_by_#{e['uuid']}"]] }, *(@template.submitters - [submitter]).map { |e| [t('same_as_name', name: e['name']), "linked_to_#{e['uuid']}"] }], {}, class: 'base-select mb-3' %>
|
||||||
</toggle-attribute>
|
</toggle-attribute>
|
||||||
<%= ff.email_field :email, class: "base-input #{'hidden' if item.option != 'email'}", autocomplete: 'off', placeholder: t('default_email'), id: email_field_uuid %>
|
<%= tag.input name: ff.field_name(:email), type: :email, value: ff.object.email, multiple: true, class: "base-input #{'hidden' if item.option != 'email'}", autocomplete: 'off', placeholder: t('default_email'), id: email_field_uuid %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% if @template.submitters.size == 2 %>
|
<% if @template.submitters.size == 2 %>
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"ignored_warnings": [
|
||||||
|
{
|
||||||
|
"fingerprint": "25f4ce5fee1e1180fa1919dc4ee78db3ab3457a956e4679503aa745771a43836",
|
||||||
|
"note": "Permitted parameters are necessary for creating submitters via API"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -49,7 +49,7 @@ Rails.application.configure do
|
|||||||
end
|
end
|
||||||
|
|
||||||
config.active_storage.resolve_model_to_route = :rails_storage_proxy if ENV['ACTIVE_STORAGE_PUBLIC'] != 'true'
|
config.active_storage.resolve_model_to_route = :rails_storage_proxy if ENV['ACTIVE_STORAGE_PUBLIC'] != 'true'
|
||||||
config.active_storage.service_urls_expire_in = 4.hours
|
config.active_storage.service_urls_expire_in = ENV.fetch('PRESIGNED_URLS_EXPIRE_MINUTES', '240').to_i.minutes
|
||||||
|
|
||||||
# Mount Action Cable outside main process or domain.
|
# Mount Action Cable outside main process or domain.
|
||||||
# config.action_cable.mount_path = nil
|
# config.action_cable.mount_path = nil
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ Devise.setup do |config|
|
|||||||
|
|
||||||
# ==> Configuration for :rememberable
|
# ==> Configuration for :rememberable
|
||||||
# The time the user will be remembered without asking for credentials again.
|
# The time the user will be remembered without asking for credentials again.
|
||||||
config.remember_for = 2.years
|
config.remember_for = ENV.fetch('SESSION_REMEMBER_DAYS', '730').to_i.days
|
||||||
|
|
||||||
# Invalidates all the remember me tokens when the user signs out.
|
# Invalidates all the remember me tokens when the user signs out.
|
||||||
config.expire_all_remember_me_on_sign_out = true
|
config.expire_all_remember_me_on_sign_out = true
|
||||||
|
|||||||
@@ -700,6 +700,7 @@ en: &en
|
|||||||
submission_created_via_source_html: '<b>Submission created</b> via %{source}'
|
submission_created_via_source_html: '<b>Submission created</b> via %{source}'
|
||||||
pro_user_seats_used: Pro user seats used
|
pro_user_seats_used: Pro user seats used
|
||||||
manage_plan: Manage plan
|
manage_plan: Manage plan
|
||||||
|
this_submission_has_multiple_signers_which_prevents_the_use_of_a_sharing_link_html: This submission has multiple signers, which prevents the use of a sharing link as it's unclear which signer is responsible for specific fields. To resolve this, follow this <a href="https://www.docuseal.com/resources/pre-filling-recipients" class="link font-bold" rel="noopener noreferrer nofollow" target="_blank">guide</a> to define the default signer details.
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Bulk Send
|
bulk: Bulk Send
|
||||||
@@ -1426,6 +1427,7 @@ es: &es
|
|||||||
submission_created_via_source_html: '<b>Envío creado</b> a través de %{source}'
|
submission_created_via_source_html: '<b>Envío creado</b> a través de %{source}'
|
||||||
pro_user_seats_used: Plazas de usuario Pro en uso
|
pro_user_seats_used: Plazas de usuario Pro en uso
|
||||||
manage_plan: Gestionar plan
|
manage_plan: Gestionar plan
|
||||||
|
this_submission_has_multiple_signers_which_prevents_the_use_of_a_sharing_link_html: 'Este envío tiene múltiples firmantes, lo que impide el uso de un enlace para compartir, ya que no está claro qué firmante es responsable de los campos específicos. Para resolver esto, sigue esta <a href="https://www.docuseal.com/resources/pre-filling-recipients" class="link font-bold" rel="noopener noreferrer nofollow" target="_blank">guía</a> para definir los detalles predeterminados del firmante.'
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Envío masivo
|
bulk: Envío masivo
|
||||||
@@ -2151,6 +2153,7 @@ it: &it
|
|||||||
submission_created_via_source_html: '<b>Invio creato</b> tramite %{source}'
|
submission_created_via_source_html: '<b>Invio creato</b> tramite %{source}'
|
||||||
pro_user_seats_used: Posti utente Pro in uso
|
pro_user_seats_used: Posti utente Pro in uso
|
||||||
manage_plan: Gestisci piano
|
manage_plan: Gestisci piano
|
||||||
|
this_submission_has_multiple_signers_which_prevents_the_use_of_a_sharing_link_html: "Questa sottomissione ha più firmatari, il che impedisce l'uso di un link di condivisione poiché non è chiaro quale firmatario sia responsabile di specifici campi. Per risolvere questo problema, segui questa <a href=\"https://www.docuseal.com/resources/pre-filling-recipients\" class=\"link font-bold\" rel=\"noopener noreferrer nofollow\" target=\"_blank\">guida</a> per definire i dettagli predefiniti del firmatario."
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Invio massivo
|
bulk: Invio massivo
|
||||||
@@ -2878,6 +2881,7 @@ fr: &fr
|
|||||||
submission_created_via_source_html: '<b>Soumission créée</b> via %{source}'
|
submission_created_via_source_html: '<b>Soumission créée</b> via %{source}'
|
||||||
pro_user_seats_used: Places utilisateur Pro en cours d'utilisation
|
pro_user_seats_used: Places utilisateur Pro en cours d'utilisation
|
||||||
manage_plan: Gérer le plan
|
manage_plan: Gérer le plan
|
||||||
|
this_submission_has_multiple_signers_which_prevents_the_use_of_a_sharing_link_html: 'Cette soumission comporte plusieurs signataires, ce qui empêche l’utilisation d’un lien de partage, car il n’est pas clair quel signataire est responsable de quels champs. Pour résoudre ce problème, suivez ce <a href="https://www.docuseal.com/resources/pre-filling-recipients" class="link font-bold" rel="noopener noreferrer nofollow" target="_blank">guide</a> pour définir les détails du signataire par défaut.'
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Envoi en masse
|
bulk: Envoi en masse
|
||||||
@@ -3604,6 +3608,7 @@ pt: &pt
|
|||||||
submission_created_via_source_html: '<b>Envio criado</b> via %{source}'
|
submission_created_via_source_html: '<b>Envio criado</b> via %{source}'
|
||||||
pro_user_seats_used: Lugares de usuário Pro em uso
|
pro_user_seats_used: Lugares de usuário Pro em uso
|
||||||
manage_plan: Gerenciar plano
|
manage_plan: Gerenciar plano
|
||||||
|
this_submission_has_multiple_signers_which_prevents_the_use_of_a_sharing_link_html: 'Este envio tem vários signatários, o que impede o uso de um link de compartilhamento, pois não está claro qual signatário é responsável por quais campos. Para resolver isso, siga este <a href="https://www.docuseal.com/resources/pre-filling-recipients" class="link font-bold" rel="noopener noreferrer nofollow" target="_blank">guia</a> para definir os detalhes padrão do signatário.'
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Envio em massa
|
bulk: Envio em massa
|
||||||
@@ -4330,6 +4335,7 @@ de: &de
|
|||||||
submission_created_via_source_html: '<b>Übermittlung erstellt</b> über %{source}'
|
submission_created_via_source_html: '<b>Übermittlung erstellt</b> über %{source}'
|
||||||
pro_user_seats_used: Verwendete Pro-Benutzerplätze
|
pro_user_seats_used: Verwendete Pro-Benutzerplätze
|
||||||
manage_plan: Plan verwalten
|
manage_plan: Plan verwalten
|
||||||
|
this_submission_has_multiple_signers_which_prevents_the_use_of_a_sharing_link_html: 'Diese Übermittlung hat mehrere Unterzeichner, was die Nutzung eines Freigabelinks verhindert, da unklar ist, welcher Unterzeichner für welche Felder verantwortlich ist. Um dies zu lösen, folgen Sie dieser <a href="https://www.docuseal.com/resources/pre-filling-recipients" class="link font-bold" rel="noopener noreferrer nofollow" target="_blank">Anleitung</a>, um die Standarddetails des Unterzeichners festzulegen.'
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Massenversand
|
bulk: Massenversand
|
||||||
|
|||||||
+8
-2
@@ -33,8 +33,14 @@ environment ENV.fetch('RAILS_ENV', 'development')
|
|||||||
# the concurrency of the application would be max `threads` * `workers`.
|
# the concurrency of the application would be max `threads` * `workers`.
|
||||||
# Workers do not work on JRuby or Windows (both of which do not support
|
# Workers do not work on JRuby or Windows (both of which do not support
|
||||||
# processes).
|
# processes).
|
||||||
#
|
|
||||||
workers ENV.fetch('WEB_CONCURRENCY', 0)
|
if ENV['WEB_CONCURRENCY_AUTO'] == 'true'
|
||||||
|
require 'etc'
|
||||||
|
|
||||||
|
workers Etc.nprocessors
|
||||||
|
else
|
||||||
|
workers ENV.fetch('WEB_CONCURRENCY', 0)
|
||||||
|
end
|
||||||
|
|
||||||
# Use the `preload_app!` method when specifying a `workers` number.
|
# Use the `preload_app!` method when specifying a `workers` number.
|
||||||
# This directive tells Puma to first boot the application and load code
|
# This directive tells Puma to first boot the application and load code
|
||||||
|
|||||||
@@ -122,6 +122,8 @@ Rails.application.routes.draw do
|
|||||||
get '/disk/:encoded_key/*filename' => 'active_storage/disk#show', as: :rails_disk_service
|
get '/disk/:encoded_key/*filename' => 'active_storage/disk#show', as: :rails_disk_service
|
||||||
put '/disk/:encoded_token' => 'active_storage/disk#update', as: :update_rails_disk_service
|
put '/disk/:encoded_token' => 'active_storage/disk#update', as: :update_rails_disk_service
|
||||||
post '/direct_uploads' => 'active_storage/direct_uploads#create', as: :rails_direct_uploads
|
post '/direct_uploads' => 'active_storage/direct_uploads#create', as: :rails_direct_uploads
|
||||||
|
|
||||||
|
ActiveSupport.run_load_hooks(:multitenant_routes, self)
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :start_form, only: %i[show update], path: 'd', param: 'slug' do
|
resources :start_form, only: %i[show update], path: 'd', param: 'slug' do
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CreateConsole1984Tables < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
create_table :console1984_sessions do |t|
|
||||||
|
t.text :reason
|
||||||
|
t.references :user, null: false, index: false
|
||||||
|
t.timestamps
|
||||||
|
|
||||||
|
t.index :created_at
|
||||||
|
t.index %i[user_id created_at]
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table :console1984_users do |t|
|
||||||
|
t.string :username, null: false
|
||||||
|
t.timestamps
|
||||||
|
|
||||||
|
t.index [:username]
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table :console1984_commands do |t|
|
||||||
|
t.text :statements
|
||||||
|
t.references :sensitive_access
|
||||||
|
t.references :session, null: false, index: false
|
||||||
|
t.timestamps
|
||||||
|
|
||||||
|
t.index %i[session_id created_at sensitive_access_id], name: 'on_session_and_sensitive_chronologically'
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table :console1984_sensitive_accesses do |t|
|
||||||
|
t.text :justification
|
||||||
|
t.references :session, null: false
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
+35
-1
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.2].define(version: 2024_12_07_172237) do
|
ActiveRecord::Schema[8.0].define(version: 2025_02_25_111255) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
||||||
@@ -112,6 +112,40 @@ ActiveRecord::Schema[7.2].define(version: 2024_12_07_172237) do
|
|||||||
t.index ["submitter_id"], name: "index_completed_submitters_on_submitter_id", unique: true
|
t.index ["submitter_id"], name: "index_completed_submitters_on_submitter_id", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "console1984_commands", force: :cascade do |t|
|
||||||
|
t.text "statements"
|
||||||
|
t.bigint "sensitive_access_id"
|
||||||
|
t.bigint "session_id", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["sensitive_access_id"], name: "index_console1984_commands_on_sensitive_access_id"
|
||||||
|
t.index ["session_id", "created_at", "sensitive_access_id"], name: "on_session_and_sensitive_chronologically"
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table "console1984_sensitive_accesses", force: :cascade do |t|
|
||||||
|
t.text "justification"
|
||||||
|
t.bigint "session_id", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["session_id"], name: "index_console1984_sensitive_accesses_on_session_id"
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table "console1984_sessions", force: :cascade do |t|
|
||||||
|
t.text "reason"
|
||||||
|
t.bigint "user_id", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["created_at"], name: "index_console1984_sessions_on_created_at"
|
||||||
|
t.index ["user_id", "created_at"], name: "index_console1984_sessions_on_user_id_and_created_at"
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table "console1984_users", force: :cascade do |t|
|
||||||
|
t.string "username", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["username"], name: "index_console1984_users_on_username"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "document_generation_events", force: :cascade do |t|
|
create_table "document_generation_events", force: :cascade do |t|
|
||||||
t.bigint "submitter_id", null: false
|
t.bigint "submitter_id", null: false
|
||||||
t.string "event_name", null: false
|
t.string "event_name", null: false
|
||||||
|
|||||||
+11
-10
@@ -8,19 +8,20 @@ module DownloadUtils
|
|||||||
module_function
|
module_function
|
||||||
|
|
||||||
def call(url)
|
def call(url)
|
||||||
uri = Addressable::URI.parse(url)
|
uri = begin
|
||||||
|
URI(url)
|
||||||
if Docuseal.multitenant?
|
rescue URI::Error
|
||||||
raise UnableToDownload, "Error loading: #{uri.display_uri}. Only HTTPS is allowed." if uri.scheme != 'https'
|
Addressable::URI.parse(url).normalize
|
||||||
|
|
||||||
if uri.host.in?(LOCALHOSTS)
|
|
||||||
raise UnableToDownload, "Error loading: #{uri.display_uri}. Can't download from localhost."
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
resp = conn.get(uri.display_uri.to_s)
|
if Docuseal.multitenant?
|
||||||
|
raise UnableToDownload, "Error loading: #{uri}. Only HTTPS is allowed." if uri.scheme != 'https'
|
||||||
|
raise UnableToDownload, "Error loading: #{uri}. Can't download from localhost." if uri.host.in?(LOCALHOSTS)
|
||||||
|
end
|
||||||
|
|
||||||
raise UnableToDownload, "Error loading: #{uri.display_uri}" if resp.status >= 400
|
resp = conn.get(uri)
|
||||||
|
|
||||||
|
raise UnableToDownload, "Error loading: #{uri}" if resp.status >= 400
|
||||||
|
|
||||||
resp
|
resp
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ module ReplaceEmailVariables
|
|||||||
SUBMITTER_SLUG = /\{+submitter\.slug\}+/i
|
SUBMITTER_SLUG = /\{+submitter\.slug\}+/i
|
||||||
SUBMISSION_LINK = /\{+submission\.link\}+/i
|
SUBMISSION_LINK = /\{+submission\.link\}+/i
|
||||||
SUBMISSION_ID = /\{+submission\.id\}+/i
|
SUBMISSION_ID = /\{+submission\.id\}+/i
|
||||||
SUBMISSION_SUBMITTERS = /\{+submission\.submitters\}+/i
|
SUBMITTERS = /\{+(?:submission\.)?submitters\}+/i
|
||||||
|
SUBMITTERS_N_EMAIL = /\{+submitters\[(?<index>\d+)\]\.email\}+/i
|
||||||
|
SUBMITTERS_N_NAME = /\{+submitters\[(?<index>\d+)\]\.name\}+/i
|
||||||
|
SUBMITTERS_N_FIRST_NAME = /\{+submitters\[(?<index>\d+)\]\.first_name\}+/i
|
||||||
DOCUMENTS_LINKS = /\{+documents\.links\}+/i
|
DOCUMENTS_LINKS = /\{+documents\.links\}+/i
|
||||||
DOCUMENTS_LINK = /\{+documents\.link\}+/i
|
DOCUMENTS_LINK = /\{+documents\.link\}+/i
|
||||||
|
|
||||||
@@ -37,13 +40,25 @@ module ReplaceEmailVariables
|
|||||||
text = replace(text, SUBMISSION_LINK, html_escape:) do
|
text = replace(text, SUBMISSION_LINK, html_escape:) do
|
||||||
submitter.submission ? build_submission_link(submitter.submission) : ''
|
submitter.submission ? build_submission_link(submitter.submission) : ''
|
||||||
end
|
end
|
||||||
text = replace(text, SUBMISSION_SUBMITTERS, html_escape:) { build_submission_submitters(submitter.submission) }
|
text = replace(text, SUBMITTERS, html_escape:) { build_submission_submitters(submitter.submission) }
|
||||||
text = replace(text, DOCUMENTS_LINKS, html_escape:) { build_documents_links_text(submitter, sig) }
|
text = replace(text, DOCUMENTS_LINKS, html_escape:) { build_documents_links_text(submitter, sig) }
|
||||||
text = replace(text, DOCUMENTS_LINK, html_escape:) { build_documents_links_text(submitter, sig) }
|
text = replace(text, DOCUMENTS_LINK, html_escape:) { build_documents_links_text(submitter, sig) }
|
||||||
text = replace(text, ACCOUNT_NAME, html_escape:) { submitter.submission.account.name }
|
text = replace(text, ACCOUNT_NAME, html_escape:) { submitter.submission.account.name }
|
||||||
text = replace(text, SENDER_NAME, html_escape:) { submitter.submission.created_by_user&.full_name }
|
text = replace(text, SENDER_NAME, html_escape:) { submitter.submission.created_by_user&.full_name }
|
||||||
text = replace(text, SENDER_FIRST_NAME, html_escape:) { submitter.submission.created_by_user&.first_name }
|
text = replace(text, SENDER_FIRST_NAME, html_escape:) { submitter.submission.created_by_user&.first_name }
|
||||||
|
|
||||||
|
text = replace(text, SUBMITTERS_N_NAME, html_escape:) do |match|
|
||||||
|
build_submitters_n_field(submitter.submission, match[:index].to_i - 1, :name)
|
||||||
|
end
|
||||||
|
|
||||||
|
text = replace(text, SUBMITTERS_N_EMAIL, html_escape:) do |match|
|
||||||
|
build_submitters_n_field(submitter.submission, match[:index].to_i - 1, :email)
|
||||||
|
end
|
||||||
|
|
||||||
|
text = replace(text, SUBMITTERS_N_FIRST_NAME, html_escape:) do |match|
|
||||||
|
build_submitters_n_field(submitter.submission, match[:index].to_i - 1, :first_name)
|
||||||
|
end
|
||||||
|
|
||||||
replace(text, SENDER_EMAIL, html_escape:) { submitter.submission.created_by_user&.email.to_s.sub(/\+\w+@/, '@') }
|
replace(text, SENDER_EMAIL, html_escape:) { submitter.submission.created_by_user&.email.to_s.sub(/\+\w+@/, '@') }
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics
|
# rubocop:enable Metrics
|
||||||
@@ -54,12 +69,18 @@ module ReplaceEmailVariables
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def build_submitters_n_field(submission, index, field_name)
|
||||||
|
uuid = (submission.template_submitters || submission.template.submitters).dig(index, 'uuid')
|
||||||
|
|
||||||
|
submission.submitters.find { |s| s.uuid == uuid }.try(field_name)
|
||||||
|
end
|
||||||
|
|
||||||
def replace(text, var, html_escape: false)
|
def replace(text, var, html_escape: false)
|
||||||
text.gsub(var) do
|
text.gsub(var) do
|
||||||
if html_escape
|
if html_escape
|
||||||
ERB::Util.html_escape(yield)
|
ERB::Util.html_escape(yield(Regexp.last_match))
|
||||||
else
|
else
|
||||||
yield
|
yield(Regexp.last_match)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,10 +3,28 @@
|
|||||||
module SendWebhookRequest
|
module SendWebhookRequest
|
||||||
USER_AGENT = 'DocuSeal.com Webhook'
|
USER_AGENT = 'DocuSeal.com Webhook'
|
||||||
|
|
||||||
|
LOCALHOSTS = %w[0.0.0.0 127.0.0.1 localhost].freeze
|
||||||
|
|
||||||
|
HttpsError = Class.new(StandardError)
|
||||||
|
LocalhostError = Class.new(StandardError)
|
||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
def call(webhook_url, event_type:, data:)
|
def call(webhook_url, event_type:, data:)
|
||||||
Faraday.post(webhook_url.url) do |req|
|
uri = begin
|
||||||
|
URI(webhook_url.url)
|
||||||
|
rescue URI::Error
|
||||||
|
Addressable::URI.parse(webhook_url.url).normalize
|
||||||
|
end
|
||||||
|
|
||||||
|
if Docuseal.multitenant?
|
||||||
|
raise HttpsError, 'Only HTTPS is allowed.' if uri.scheme != 'https' &&
|
||||||
|
!AccountConfig.exists?(key: :allow_http,
|
||||||
|
account_id: webhook_url.account_id)
|
||||||
|
raise LocalhostError, "Can't send to localhost." if uri.host.in?(LOCALHOSTS)
|
||||||
|
end
|
||||||
|
|
||||||
|
Faraday.post(uri) do |req|
|
||||||
req.headers['Content-Type'] = 'application/json'
|
req.headers['Content-Type'] = 'application/json'
|
||||||
req.headers['User-Agent'] = USER_AGENT
|
req.headers['User-Agent'] = USER_AGENT
|
||||||
req.headers.merge!(webhook_url.secret.to_h) if webhook_url.secret.present?
|
req.headers.merge!(webhook_url.secret.to_h) if webhook_url.secret.present?
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ module Submissions
|
|||||||
|
|
||||||
def normalize_email(email)
|
def normalize_email(email)
|
||||||
return if email.blank?
|
return if email.blank?
|
||||||
|
return if email.is_a?(Numeric)
|
||||||
|
|
||||||
return email.downcase if email.to_s.include?(',') ||
|
return email.downcase if email.to_s.include?(',') ||
|
||||||
email.to_s.match?(/\.(?:gob|om|mm|cm|et|mo|nz|za|ie)\z/) ||
|
email.to_s.match?(/\.(?:gob|om|mm|cm|et|mo|nz|za|ie)\z/) ||
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ module Submissions
|
|||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
# rubocop:disable Metrics/BlockLength
|
# rubocop:disable Metrics
|
||||||
def call(template:, user:, submissions_attrs:, source:, submitters_order:, params: {})
|
def call(template:, user:, submissions_attrs:, source:, submitters_order:, params: {})
|
||||||
preferences = Submitters.normalize_preferences(user.account, user, params)
|
preferences = Submitters.normalize_preferences(user.account, user, params)
|
||||||
|
|
||||||
Array.wrap(submissions_attrs).filter_map do |attrs|
|
submissions = Array.wrap(submissions_attrs).filter_map do |attrs|
|
||||||
submission_preferences = Submitters.normalize_preferences(user.account, user, attrs)
|
submission_preferences = Submitters.normalize_preferences(user.account, user, attrs)
|
||||||
submission_preferences = preferences.merge(submission_preferences)
|
submission_preferences = preferences.merge(submission_preferences)
|
||||||
|
|
||||||
@@ -23,15 +23,33 @@ module Submissions
|
|||||||
expire_at: attrs[:expire_at],
|
expire_at: attrs[:expire_at],
|
||||||
template_submitters: [], submitters_order:)
|
template_submitters: [], submitters_order:)
|
||||||
|
|
||||||
maybe_set_template_fields(submission, attrs[:submitters])
|
template_submitters = template.submitters.deep_dup
|
||||||
|
|
||||||
attrs[:submitters].each_with_index do |submitter_attrs, index|
|
attrs[:submitters].each_with_index do |submitter_attrs, index|
|
||||||
uuid = find_submitter_uuid(template, submitter_attrs, index)
|
if submitter_attrs[:roles].present? && submitter_attrs[:roles].size > 1
|
||||||
|
template_submitter, template_submitters, submission.template_fields =
|
||||||
|
merge_submitters_and_fields(submitter_attrs, template_submitters,
|
||||||
|
submission.template_fields || submission.template.fields)
|
||||||
|
|
||||||
next if uuid.blank?
|
submission.template_schema = submission.template.schema if submission.template_schema.blank?
|
||||||
next if submitter_attrs.slice('email', 'phone', 'name').compact_blank.blank?
|
|
||||||
|
uuid = template_submitter['uuid']
|
||||||
|
else
|
||||||
|
if submitter_attrs[:roles].present? && submitter_attrs[:roles].size == 1
|
||||||
|
submitter_attrs[:role] = submitter_attrs[:roles].first
|
||||||
|
end
|
||||||
|
|
||||||
|
uuid = find_submitter_uuid(template_submitters, submitter_attrs, index)
|
||||||
|
|
||||||
|
next if uuid.blank?
|
||||||
|
next if submitter_attrs.slice('email', 'phone', 'name').compact_blank.blank?
|
||||||
|
|
||||||
|
submission.template_fields = submission.template.fields if submitter_attrs[:completed].present? &&
|
||||||
|
submission.template_fields.blank?
|
||||||
|
|
||||||
|
template_submitter = template_submitters.find { |e| e['uuid'] == uuid }
|
||||||
|
end
|
||||||
|
|
||||||
template_submitter = template.submitters.find { |e| e['uuid'] == uuid }
|
|
||||||
submission.template_submitters << template_submitter.except('optional_invite_by_uuid', 'invite_by_uuid')
|
submission.template_submitters << template_submitter.except('optional_invite_by_uuid', 'invite_by_uuid')
|
||||||
|
|
||||||
is_order_sent = submitters_order == 'random' || index.zero?
|
is_order_sent = submitters_order == 'random' || index.zero?
|
||||||
@@ -41,6 +59,8 @@ module Submissions
|
|||||||
preferences: preferences.merge(submission_preferences))
|
preferences: preferences.merge(submission_preferences))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
maybe_set_template_fields(submission, attrs[:submitters])
|
||||||
|
|
||||||
if submission.submitters.size > template.submitters.size
|
if submission.submitters.size > template.submitters.size
|
||||||
raise BaseError, 'Defined more signing parties than in template'
|
raise BaseError, 'Defined more signing parties than in template'
|
||||||
end
|
end
|
||||||
@@ -51,8 +71,20 @@ module Submissions
|
|||||||
|
|
||||||
submission.tap(&:save!)
|
submission.tap(&:save!)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
maybe_enqueue_expire_at(submissions)
|
||||||
|
|
||||||
|
submissions
|
||||||
|
end
|
||||||
|
# rubocop:enable Metrics
|
||||||
|
|
||||||
|
def maybe_enqueue_expire_at(submissions)
|
||||||
|
submissions.each do |submission|
|
||||||
|
next unless submission.expire_at?
|
||||||
|
|
||||||
|
ProcessSubmissionExpiredJob.perform_at(submission.expire_at, 'submission_id' => submission.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics/BlockLength
|
|
||||||
|
|
||||||
def maybe_add_invite_submitters(submission, template)
|
def maybe_add_invite_submitters(submission, template)
|
||||||
template.submitters.each_with_index do |item, index|
|
template.submitters.each_with_index do |item, index|
|
||||||
@@ -82,8 +114,10 @@ module Submissions
|
|||||||
def maybe_set_template_fields(submission, submitters_attrs, default_submitter_uuid: nil)
|
def maybe_set_template_fields(submission, submitters_attrs, default_submitter_uuid: nil)
|
||||||
template_fields = (submission.template_fields || submission.template.fields).deep_dup
|
template_fields = (submission.template_fields || submission.template.fields).deep_dup
|
||||||
|
|
||||||
|
submitters = submission.template_submitters || submission.template.submitters
|
||||||
|
|
||||||
submitters_attrs.each_with_index do |submitter_attrs, index|
|
submitters_attrs.each_with_index do |submitter_attrs, index|
|
||||||
submitter_uuid = default_submitter_uuid || find_submitter_uuid(submission.template, submitter_attrs, index)
|
submitter_uuid = default_submitter_uuid || find_submitter_uuid(submitters, submitter_attrs, index)
|
||||||
|
|
||||||
process_readonly_fields_param(submitter_attrs[:readonly_fields], template_fields, submitter_uuid)
|
process_readonly_fields_param(submitter_attrs[:readonly_fields], template_fields, submitter_uuid)
|
||||||
process_field_values_param(submitter_attrs[:values], template_fields, submitter_uuid)
|
process_field_values_param(submitter_attrs[:values], template_fields, submitter_uuid)
|
||||||
@@ -100,6 +134,65 @@ module Submissions
|
|||||||
submission
|
submission
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def merge_submitters_and_fields(submitter_attrs, template_submitters, template_fields)
|
||||||
|
selected_submitters = submitter_attrs[:roles].map do |role|
|
||||||
|
template_submitters.find { |e| e['name'].to_s.casecmp(role).zero? } ||
|
||||||
|
raise(BaseError, "#{role} role doesn't exist")
|
||||||
|
end
|
||||||
|
|
||||||
|
merge_role_uuids = selected_submitters.pluck('uuid')
|
||||||
|
old_role_uuids = template_submitters.pluck('uuid')
|
||||||
|
name = submitter_attrs[:role].presence || selected_submitters.pluck('name').join(' / ')
|
||||||
|
|
||||||
|
merged_submitter, template_submitters =
|
||||||
|
build_merged_submitter(template_submitters, role_uuids: merge_role_uuids, name:)
|
||||||
|
|
||||||
|
field_names_index = {}
|
||||||
|
|
||||||
|
sorted_fields = template_fields.sort_by { |e| old_role_uuids.index(e['submitter_uuid']) }
|
||||||
|
|
||||||
|
sorted_fields.each do |field|
|
||||||
|
next unless merge_role_uuids.include?(field['submitter_uuid'])
|
||||||
|
|
||||||
|
if (existing_field = field_names_index[field['name']])
|
||||||
|
existing_field['areas'] ||= []
|
||||||
|
existing_field['areas'].push(*field['areas'])
|
||||||
|
template_fields.delete(field)
|
||||||
|
else
|
||||||
|
field['submitter_uuid'] = merged_submitter['uuid']
|
||||||
|
field_names_index[field['name']] = field if field['name'].present?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
[merged_submitter, template_submitters, template_fields]
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_merged_submitter(submitters, role_uuids:, name:)
|
||||||
|
new_uuid = Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, role_uuids.sort.join(':'))
|
||||||
|
|
||||||
|
merged_submitter = nil
|
||||||
|
|
||||||
|
submitters =
|
||||||
|
submitters.filter_map do |submitter|
|
||||||
|
submitter['optional_invite_by_uuid'] = new_uuid if role_uuids.include?(submitter['optional_invite_by_uuid'])
|
||||||
|
submitter['invite_by_uuid'] = new_uuid if role_uuids.include?(submitter['invite_by_uuid'])
|
||||||
|
submitter['linked_to_uuid'] = new_uuid if role_uuids.include?(submitter['linked_to_uuid'])
|
||||||
|
|
||||||
|
if role_uuids.include?(submitter['uuid'])
|
||||||
|
next if merged_submitter
|
||||||
|
|
||||||
|
merged_submitter = submitter.deep_dup
|
||||||
|
merged_submitter['uuid'] = new_uuid
|
||||||
|
merged_submitter['name'] = name
|
||||||
|
merged_submitter.delete('linked_to_uuid')
|
||||||
|
end
|
||||||
|
|
||||||
|
submitter
|
||||||
|
end
|
||||||
|
|
||||||
|
[merged_submitter, submitters]
|
||||||
|
end
|
||||||
|
|
||||||
def process_readonly_fields_param(readonly_fields, template_fields, submitter_uuid)
|
def process_readonly_fields_param(readonly_fields, template_fields, submitter_uuid)
|
||||||
return if readonly_fields.blank?
|
return if readonly_fields.blank?
|
||||||
|
|
||||||
@@ -178,11 +271,11 @@ module Submissions
|
|||||||
field
|
field
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_submitter_uuid(template, attrs, index)
|
def find_submitter_uuid(submitters, attrs, index)
|
||||||
uuid = attrs[:uuid].presence
|
uuid = attrs[:uuid].presence
|
||||||
uuid ||= template.submitters.find { |e| e['name'].to_s.casecmp(attrs[:role].to_s).zero? }&.dig('uuid')
|
uuid ||= submitters.find { |e| e['name'].to_s.casecmp(attrs[:role].to_s).zero? }&.dig('uuid')
|
||||||
|
|
||||||
uuid || template.submitters[index]&.dig('uuid')
|
uuid || submitters[index]&.dig('uuid')
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_submitter(submission:, attrs:, uuid:, is_order_sent:, user:, preferences:, params:)
|
def build_submitter(submission:, attrs:, uuid:, is_order_sent:, user:, preferences:, params:)
|
||||||
@@ -225,9 +318,15 @@ module Submissions
|
|||||||
|
|
||||||
def assign_completed_attributes(submitter)
|
def assign_completed_attributes(submitter)
|
||||||
submitter.values = Submitters::SubmitValues.merge_default_values(submitter)
|
submitter.values = Submitters::SubmitValues.merge_default_values(submitter)
|
||||||
submitter.values = Submitters::SubmitValues.merge_formula_values(submitter)
|
|
||||||
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
|
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
|
||||||
|
|
||||||
|
formula_values = Submitters::SubmitValues.build_formula_values(submitter)
|
||||||
|
|
||||||
|
if formula_values.present?
|
||||||
|
submitter.values = submitter.values.merge(formula_values)
|
||||||
|
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
|
||||||
|
end
|
||||||
|
|
||||||
submitter.values = submitter.values.transform_values do |v|
|
submitter.values = submitter.values.transform_values do |v|
|
||||||
v == '{{date}}' ? Time.current.in_time_zone(submitter.submission.account.timezone).to_date.to_s : v
|
v == '{{date}}' ? Time.current.in_time_zone(submitter.submission.account.timezone).to_date.to_s : v
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -359,13 +359,14 @@ module Submissions
|
|||||||
value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), account.locale)
|
value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), account.locale)
|
||||||
end
|
end
|
||||||
|
|
||||||
if field['type'] == 'number'
|
value = NumberUtils.format_number(value, field.dig('preferences', 'format')) if field['type'] == 'number'
|
||||||
value = NumberUtils.format_number(value,
|
|
||||||
field.dig('preferences', 'format'))
|
|
||||||
end
|
|
||||||
|
|
||||||
value = value.join(', ') if value.is_a?(Array)
|
value = value.join(', ') if value.is_a?(Array)
|
||||||
|
|
||||||
|
if (mask = field.dig('preferences', 'mask').presence)
|
||||||
|
value = TextUtils.mask_value(value, mask)
|
||||||
|
end
|
||||||
|
|
||||||
composer.formatted_text_box([{ text: TextUtils.maybe_rtl_reverse(value.to_s.presence || 'n/a') }],
|
composer.formatted_text_box([{ text: TextUtils.maybe_rtl_reverse(value.to_s.presence || 'n/a') }],
|
||||||
text_align: value.to_s.match?(RTL_REGEXP) ? :right : :left,
|
text_align: value.to_s.match?(RTL_REGEXP) ? :right : :left,
|
||||||
padding: [0, 0, 10, 0])
|
padding: [0, 0, 10, 0])
|
||||||
|
|||||||
@@ -23,15 +23,9 @@ module Submissions
|
|||||||
**Submissions::GenerateResultAttachments.build_signing_params(submitter, pkcs, tsa_url)
|
**Submissions::GenerateResultAttachments.build_signing_params(submitter, pkcs, tsa_url)
|
||||||
}
|
}
|
||||||
|
|
||||||
begin
|
sign_pdf(io, pdf, sign_params)
|
||||||
pdf.sign(io, **sign_params)
|
|
||||||
rescue HexaPDF::MalformedPDFError => e
|
|
||||||
Rollbar.error(e) if defined?(Rollbar)
|
|
||||||
|
|
||||||
pdf.sign(io, write_options: { incremental: false }, **sign_params)
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
pdf.write(io, incremental: true, validate: false)
|
pdf.write(io, incremental: true, validate: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
Submissions::GenerateResultAttachments.maybe_enable_ltv(io, sign_params)
|
Submissions::GenerateResultAttachments.maybe_enable_ltv(io, sign_params)
|
||||||
@@ -45,6 +39,20 @@ module Submissions
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def sign_pdf(io, pdf, sign_params)
|
||||||
|
pdf.sign(io, **sign_params)
|
||||||
|
rescue HexaPDF::MalformedPDFError => e
|
||||||
|
Rollbar.error(e) if defined?(Rollbar)
|
||||||
|
|
||||||
|
pdf.sign(io, write_options: { incremental: false }, **sign_params)
|
||||||
|
rescue HexaPDF::Error => e
|
||||||
|
Rollbar.error(e) if defined?(Rollbar)
|
||||||
|
|
||||||
|
pdf.validate(auto_correct: true)
|
||||||
|
|
||||||
|
pdf.sign(io, write_options: { validate: false }, **sign_params)
|
||||||
|
end
|
||||||
|
|
||||||
def build_combined_pdf(submitter)
|
def build_combined_pdf(submitter)
|
||||||
pdfs_index = Submissions::GenerateResultAttachments.generate_pdfs(submitter)
|
pdfs_index = Submissions::GenerateResultAttachments.generate_pdfs(submitter)
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,29 @@ module Submissions
|
|||||||
module GenerateResultAttachments
|
module GenerateResultAttachments
|
||||||
FONT_SIZE = 11
|
FONT_SIZE = 11
|
||||||
FONT_PATH = '/fonts/GoNotoKurrent-Regular.ttf'
|
FONT_PATH = '/fonts/GoNotoKurrent-Regular.ttf'
|
||||||
|
FONT_BOLD_PATH = '/fonts/GoNotoKurrent-Bold.ttf'
|
||||||
FONT_NAME = if File.exist?(FONT_PATH)
|
FONT_NAME = if File.exist?(FONT_PATH)
|
||||||
FONT_PATH
|
FONT_PATH
|
||||||
else
|
else
|
||||||
'Helvetica'
|
'Helvetica'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
FONT_BOLD_NAME = if File.exist?(FONT_BOLD_PATH)
|
||||||
|
FONT_BOLD_PATH
|
||||||
|
else
|
||||||
|
'Helvetica'
|
||||||
|
end
|
||||||
|
|
||||||
|
FONT_ITALIC_NAME = 'Helvetica'
|
||||||
|
FONT_BOLD_ITALIC_NAME = 'Helvetica'
|
||||||
|
|
||||||
|
FONT_VARIANS = {
|
||||||
|
none: FONT_NAME,
|
||||||
|
bold: FONT_BOLD_NAME,
|
||||||
|
italic: FONT_ITALIC_NAME,
|
||||||
|
bold_italic: FONT_BOLD_ITALIC_NAME
|
||||||
|
}.freeze
|
||||||
|
|
||||||
SIGN_REASON = 'Signed by %<name>s with DocuSeal.com'
|
SIGN_REASON = 'Signed by %<name>s with DocuSeal.com'
|
||||||
|
|
||||||
RTL_REGEXP = TextUtils::RTL_REGEXP
|
RTL_REGEXP = TextUtils::RTL_REGEXP
|
||||||
@@ -18,12 +35,15 @@ module Submissions
|
|||||||
TEXT_TOP_MARGIN = 1
|
TEXT_TOP_MARGIN = 1
|
||||||
MAX_PAGE_ROTATE = 20
|
MAX_PAGE_ROTATE = 20
|
||||||
|
|
||||||
COURIER_FONT = 'Courier'
|
|
||||||
|
|
||||||
A4_SIZE = [595, 842].freeze
|
A4_SIZE = [595, 842].freeze
|
||||||
|
|
||||||
TESTING_FOOTER = 'Testing Document - NOT LEGALLY BINDING'
|
TESTING_FOOTER = 'Testing Document - NOT LEGALLY BINDING'
|
||||||
DEFAULT_FONTS = %w[Times Helvetica Courier].freeze
|
DEFAULT_FONTS = %w[Times Helvetica Courier].freeze
|
||||||
|
FONTS_LINE_HEIGHT = {
|
||||||
|
'Times' => 1.4,
|
||||||
|
'Helvetica' => 1.4,
|
||||||
|
'Courier' => 1.6
|
||||||
|
}.freeze
|
||||||
|
|
||||||
MISSING_GLYPH_REPLACE = {
|
MISSING_GLYPH_REPLACE = {
|
||||||
'▪' => '-',
|
'▪' => '-',
|
||||||
@@ -192,8 +212,16 @@ module Submissions
|
|||||||
fill_color = field.dig('preferences', 'color').presence
|
fill_color = field.dig('preferences', 'color').presence
|
||||||
|
|
||||||
font_name = field.dig('preferences', 'font')
|
font_name = field.dig('preferences', 'font')
|
||||||
|
font_variant = (field.dig('preferences', 'font_type').presence || 'none').to_sym
|
||||||
|
|
||||||
font_name = FONT_NAME unless font_name.in?(DEFAULT_FONTS)
|
font_name = FONT_NAME unless font_name.in?(DEFAULT_FONTS)
|
||||||
font = pdf.fonts.add(font_name)
|
|
||||||
|
if font_variant != :none && font_name == FONT_NAME
|
||||||
|
font_name = FONT_VARIANS[font_variant]
|
||||||
|
font_variant = nil unless font_name.in?(DEFAULT_FONTS)
|
||||||
|
end
|
||||||
|
|
||||||
|
font = pdf.fonts.add(font_name, variant: font_variant)
|
||||||
|
|
||||||
value = submitter.values[field['uuid']]
|
value = submitter.values[field['uuid']]
|
||||||
value = field['default_value'] if field['type'] == 'heading'
|
value = field['default_value'] if field['type'] == 'heading'
|
||||||
@@ -391,6 +419,10 @@ module Submissions
|
|||||||
when ->(type) { type == 'cells' && !area['cell_w'].to_f.zero? }
|
when ->(type) { type == 'cells' && !area['cell_w'].to_f.zero? }
|
||||||
cell_width = area['cell_w'] * width
|
cell_width = area['cell_w'] * width
|
||||||
|
|
||||||
|
if (mask = field.dig('preferences', 'mask').presence)
|
||||||
|
value = TextUtils.mask_value(value, mask)
|
||||||
|
end
|
||||||
|
|
||||||
chars = TextUtils.maybe_rtl_reverse(value).chars
|
chars = TextUtils.maybe_rtl_reverse(value).chars
|
||||||
chars = chars.reverse if field.dig('preferences', 'align') == 'right'
|
chars = chars.reverse if field.dig('preferences', 'align') == 'right'
|
||||||
|
|
||||||
@@ -440,8 +472,12 @@ module Submissions
|
|||||||
|
|
||||||
value = TextUtils.maybe_rtl_reverse(Array.wrap(value).join(', '))
|
value = TextUtils.maybe_rtl_reverse(Array.wrap(value).join(', '))
|
||||||
|
|
||||||
|
if (mask = field.dig('preferences', 'mask').presence)
|
||||||
|
value = TextUtils.mask_value(value, mask)
|
||||||
|
end
|
||||||
|
|
||||||
text_params = { font:, fill_color:, font_size: }
|
text_params = { font:, fill_color:, font_size: }
|
||||||
text_params[:line_height] = text_params[:font_size] * 1.6 if font_name == COURIER_FONT
|
text_params[:line_height] = text_params[:font_size] * (FONTS_LINE_HEIGHT[font_name] || 1)
|
||||||
|
|
||||||
text = HexaPDF::Layout::TextFragment.create(value, **text_params)
|
text = HexaPDF::Layout::TextFragment.create(value, **text_params)
|
||||||
|
|
||||||
@@ -450,7 +486,7 @@ module Submissions
|
|||||||
|
|
||||||
if preferences_font_size.blank? && box_height > (area['h'] * height) + 1
|
if preferences_font_size.blank? && box_height > (area['h'] * height) + 1
|
||||||
text_params[:font_size] = (font_size / 1.4).to_i
|
text_params[:font_size] = (font_size / 1.4).to_i
|
||||||
text_params[:line_height] = text_params[:font_size] * 1.6 if font_name == COURIER_FONT
|
text_params[:line_height] = text_params[:font_size] * (FONTS_LINE_HEIGHT[font_name] || 1)
|
||||||
|
|
||||||
text = HexaPDF::Layout::TextFragment.create(value, **text_params)
|
text = HexaPDF::Layout::TextFragment.create(value, **text_params)
|
||||||
|
|
||||||
@@ -461,7 +497,7 @@ module Submissions
|
|||||||
|
|
||||||
if preferences_font_size.blank? && box_height > (area['h'] * height) + 1
|
if preferences_font_size.blank? && box_height > (area['h'] * height) + 1
|
||||||
text_params[:font_size] = (font_size / 1.9).to_i
|
text_params[:font_size] = (font_size / 1.9).to_i
|
||||||
text_params[:line_height] = text_params[:font_size] * 1.6 if font_name == COURIER_FONT
|
text_params[:line_height] = text_params[:font_size] * (FONTS_LINE_HEIGHT[font_name] || 1)
|
||||||
|
|
||||||
text = HexaPDF::Layout::TextFragment.create(value, **text_params)
|
text = HexaPDF::Layout::TextFragment.create(value, **text_params)
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ module Submissions
|
|||||||
end
|
end
|
||||||
|
|
||||||
def normalize_submitter_params!(submitter_params, template, index = nil, for_submitter: nil)
|
def normalize_submitter_params!(submitter_params, template, index = nil, for_submitter: nil)
|
||||||
default_values = submitter_params[:values] || {}
|
with_values = submitter_params[:values].present?
|
||||||
|
|
||||||
|
default_values = with_values ? submitter_params[:values] : {}
|
||||||
|
|
||||||
submitter_params[:fields]&.each do |f|
|
submitter_params[:fields]&.each do |f|
|
||||||
default_values[f[:name].presence || f[:uuid]] = f[:default_value] if f.key?(:default_value)
|
default_values[f[:name].presence || f[:uuid]] = f[:default_value] if f.key?(:default_value)
|
||||||
@@ -33,8 +35,9 @@ module Submissions
|
|||||||
default_values,
|
default_values,
|
||||||
submitter_name: submitter_params[:role] ||
|
submitter_name: submitter_params[:role] ||
|
||||||
template.submitters.dig(index, 'name'),
|
template.submitters.dig(index, 'name'),
|
||||||
|
role_names: submitter_params[:roles],
|
||||||
for_submitter:,
|
for_submitter:,
|
||||||
throw_errors: true)
|
throw_errors: !with_values)
|
||||||
|
|
||||||
submitter_params[:values] = values
|
submitter_params[:values] = values
|
||||||
|
|
||||||
|
|||||||
@@ -17,38 +17,47 @@ module Submitters
|
|||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
def call(template, values, submitter_name: nil, for_submitter: nil, throw_errors: false)
|
def call(template, values, submitter_name: nil, role_names: nil, for_submitter: nil, throw_errors: false)
|
||||||
fields = fetch_fields(template, submitter_name:, for_submitter:)
|
fields =
|
||||||
|
if role_names.present?
|
||||||
|
fetch_roles_fields(template, roles: role_names)
|
||||||
|
else
|
||||||
|
fetch_fields(template, submitter_name:, for_submitter:)
|
||||||
|
end
|
||||||
|
|
||||||
fields_uuid_index = fields.index_by { |e| e['uuid'] }
|
fields_uuid_index = fields.index_by { |e| e['uuid'] }
|
||||||
fields_name_index = build_fields_index(fields)
|
fields_name_index = build_fields_index(fields)
|
||||||
|
|
||||||
attachments = []
|
attachments = []
|
||||||
|
|
||||||
normalized_values = values.to_h.filter_map do |key, value|
|
normalized_values = values.to_h.each_with_object({}) do |(key, value), acc|
|
||||||
if fields_uuid_index[key].blank?
|
|
||||||
original_key = key
|
|
||||||
|
|
||||||
key = fields_name_index[key]&.dig('uuid') || fields_name_index[key.to_s.downcase]&.dig('uuid')
|
|
||||||
|
|
||||||
raise(UnknownFieldName, "Unknown field: #{original_key}") if key.blank? && throw_errors
|
|
||||||
end
|
|
||||||
|
|
||||||
next if key.blank?
|
next if key.blank?
|
||||||
|
|
||||||
field = fields_uuid_index[key]
|
uuid_field = fields_uuid_index[key]
|
||||||
|
|
||||||
if field['type'].in?(%w[initials signature image file stamp]) && value.present?
|
value_fields = [uuid_field] if uuid_field
|
||||||
new_value, new_attachments =
|
|
||||||
normalize_attachment_value(value, field, template.account, attachments, for_submitter)
|
|
||||||
|
|
||||||
attachments.push(*new_attachments)
|
if value_fields.blank?
|
||||||
|
value_fields = fields_name_index[key].presence || fields_name_index[key.to_s.downcase]
|
||||||
|
|
||||||
value = new_value
|
raise(UnknownFieldName, "Unknown field: #{key}") if value_fields.blank? && throw_errors
|
||||||
end
|
end
|
||||||
|
|
||||||
[key, normalize_value(field, value)]
|
next if value_fields.blank?
|
||||||
end.to_h
|
|
||||||
|
value_fields.each do |field|
|
||||||
|
if field['type'].in?(%w[initials signature image file stamp]) && value.present?
|
||||||
|
new_value, new_attachments =
|
||||||
|
normalize_attachment_value(value, field, template.account, attachments, for_submitter)
|
||||||
|
|
||||||
|
attachments.push(*new_attachments)
|
||||||
|
|
||||||
|
value = new_value
|
||||||
|
end
|
||||||
|
|
||||||
|
acc[field['uuid']] = normalize_value(field, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
[normalized_values, attachments]
|
[normalized_values, attachments]
|
||||||
end
|
end
|
||||||
@@ -103,10 +112,24 @@ module Submitters
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fetch_roles_fields(template, roles:)
|
||||||
|
submitters = roles.map do |submitter_name|
|
||||||
|
template.submitters.find { |e| e['name'] == submitter_name } ||
|
||||||
|
raise(UnknownSubmitterName,
|
||||||
|
"Unknown submitter role: #{submitter_name}. Template defines #{template.submitters.pluck('name')}")
|
||||||
|
end
|
||||||
|
|
||||||
|
role_uuids = submitters.pluck('uuid')
|
||||||
|
|
||||||
|
template.fields.select do |e|
|
||||||
|
role_uuids.include?(e['submitter_uuid'])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def build_fields_index(fields)
|
def build_fields_index(fields)
|
||||||
fields.index_by { |e| e['name'] }
|
fields.group_by { |e| e['name'] }
|
||||||
.merge(fields.index_by { |e| e['name'].to_s.parameterize.underscore })
|
.merge(fields.group_by { |e| e['name'].to_s.parameterize.underscore })
|
||||||
.merge(fields.index_by { |e| e['name'].to_s.downcase })
|
.merge(fields.group_by { |e| e['name'].to_s.downcase })
|
||||||
end
|
end
|
||||||
|
|
||||||
def normalize_attachment_value(value, field, account, attachments, for_submitter = nil)
|
def normalize_attachment_value(value, field, account, attachments, for_submitter = nil)
|
||||||
|
|||||||
@@ -53,9 +53,17 @@ module Submitters
|
|||||||
submitter.completed_at = Time.current
|
submitter.completed_at = Time.current
|
||||||
submitter.ip = request.remote_ip
|
submitter.ip = request.remote_ip
|
||||||
submitter.ua = request.user_agent
|
submitter.ua = request.user_agent
|
||||||
|
|
||||||
submitter.values = merge_default_values(submitter)
|
submitter.values = merge_default_values(submitter)
|
||||||
submitter.values = maybe_remove_condition_values(submitter)
|
submitter.values = maybe_remove_condition_values(submitter)
|
||||||
submitter.values = merge_formula_values(submitter)
|
|
||||||
|
formula_values = build_formula_values(submitter)
|
||||||
|
|
||||||
|
if formula_values.present?
|
||||||
|
submitter.values = submitter.values.merge(formula_values)
|
||||||
|
submitter.values = maybe_remove_condition_values(submitter)
|
||||||
|
end
|
||||||
|
|
||||||
submitter.values = submitter.values.transform_values do |v|
|
submitter.values = submitter.values.transform_values do |v|
|
||||||
v == '{{date}}' ? Time.current.in_time_zone(submitter.account.timezone).to_date.to_s : v
|
v == '{{date}}' ? Time.current.in_time_zone(submitter.account.timezone).to_date.to_s : v
|
||||||
end
|
end
|
||||||
@@ -149,7 +157,7 @@ module Submitters
|
|||||||
default_values.compact_blank.merge(submitter.values)
|
default_values.compact_blank.merge(submitter.values)
|
||||||
end
|
end
|
||||||
|
|
||||||
def merge_formula_values(submitter)
|
def build_formula_values(submitter)
|
||||||
computed_values = submitter.submission.template_fields.each_with_object({}) do |field, acc|
|
computed_values = submitter.submission.template_fields.each_with_object({}) do |field, acc|
|
||||||
next if field['submitter_uuid'] != submitter.uuid
|
next if field['submitter_uuid'] != submitter.uuid
|
||||||
next if field['type'] == 'payment'
|
next if field['type'] == 'payment'
|
||||||
@@ -161,7 +169,7 @@ module Submitters
|
|||||||
acc[field['uuid']] = calculate_formula_value(formula, submitter.values.merge(acc.compact_blank))
|
acc[field['uuid']] = calculate_formula_value(formula, submitter.values.merge(acc.compact_blank))
|
||||||
end
|
end
|
||||||
|
|
||||||
submitter.values.merge(computed_values.compact_blank)
|
computed_values.compact_blank
|
||||||
end
|
end
|
||||||
|
|
||||||
def calculate_formula_value(_formula, _values)
|
def calculate_formula_value(_formula, _values)
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# This rake task was added by annotate_rb gem.
|
||||||
|
|
||||||
|
# Can set `ANNOTATERB_SKIP_ON_DB_TASKS` to be anything to skip this
|
||||||
|
if Rails.env.development? && ENV['ANNOTATERB_SKIP_ON_DB_TASKS'].nil?
|
||||||
|
require 'annotate_rb'
|
||||||
|
|
||||||
|
AnnotateRb::Core.load_rake_tasks
|
||||||
|
end
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
if Rails.env.development?
|
|
||||||
require 'annotate'
|
|
||||||
|
|
||||||
task set_annotation_options: :environment do
|
|
||||||
Annotate.set_defaults(
|
|
||||||
'active_admin' => 'false',
|
|
||||||
'additional_file_patterns' => [],
|
|
||||||
'routes' => 'false',
|
|
||||||
'models' => 'true',
|
|
||||||
'position_in_routes' => 'before',
|
|
||||||
'position_in_class' => 'before',
|
|
||||||
'position_in_test' => 'before',
|
|
||||||
'position_in_fixture' => 'before',
|
|
||||||
'position_in_factory' => 'before',
|
|
||||||
'position_in_serializer' => 'before',
|
|
||||||
'show_foreign_keys' => 'true',
|
|
||||||
'show_complete_foreign_keys' => 'false',
|
|
||||||
'show_indexes' => 'true',
|
|
||||||
'simple_indexes' => 'false',
|
|
||||||
'model_dir' => 'app/models',
|
|
||||||
'root_dir' => '',
|
|
||||||
'include_version' => 'false',
|
|
||||||
'require' => '',
|
|
||||||
'exclude_tests' => 'true',
|
|
||||||
'exclude_fixtures' => 'true',
|
|
||||||
'exclude_factories' => 'true',
|
|
||||||
'exclude_serializers' => 'false',
|
|
||||||
'exclude_scaffolds' => 'true',
|
|
||||||
'exclude_controllers' => 'true',
|
|
||||||
'exclude_helpers' => 'true',
|
|
||||||
'exclude_sti_subclasses' => 'false',
|
|
||||||
'ignore_model_sub_dir' => 'false',
|
|
||||||
'ignore_columns' => nil,
|
|
||||||
'ignore_routes' => nil,
|
|
||||||
'ignore_unknown_models' => 'false',
|
|
||||||
'hide_limit_column_types' => 'integer,bigint,boolean',
|
|
||||||
'hide_default_column_types' => 'json,jsonb,hstore',
|
|
||||||
'skip_on_db_migrate' => 'false',
|
|
||||||
'format_bare' => 'true',
|
|
||||||
'format_rdoc' => 'false',
|
|
||||||
'format_yard' => 'false',
|
|
||||||
'format_markdown' => 'false',
|
|
||||||
'sort' => 'false',
|
|
||||||
'force' => 'false',
|
|
||||||
'frozen' => 'false',
|
|
||||||
'classified_sort' => 'true',
|
|
||||||
'trace' => 'false',
|
|
||||||
'wrapper_open' => nil,
|
|
||||||
'wrapper_close' => nil,
|
|
||||||
'with_comment' => 'true'
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
Annotate.load_tasks
|
|
||||||
end
|
|
||||||
@@ -9,7 +9,6 @@ module Templates
|
|||||||
|
|
||||||
template.external_id = external_id
|
template.external_id = external_id
|
||||||
template.author = author
|
template.author = author
|
||||||
template.preferences = original_template.preferences.deep_dup
|
|
||||||
template.name = name.presence || "#{original_template.name} (#{I18n.t('clone')})"
|
template.name = name.presence || "#{original_template.name} (#{I18n.t('clone')})"
|
||||||
|
|
||||||
if folder_name.present?
|
if folder_name.present?
|
||||||
@@ -18,10 +17,11 @@ module Templates
|
|||||||
template.folder_id = original_template.folder_id
|
template.folder_id = original_template.folder_id
|
||||||
end
|
end
|
||||||
|
|
||||||
template.submitters, template.fields, template.schema =
|
template.submitters, template.fields, template.schema, template.preferences =
|
||||||
update_submitters_and_fields_and_schema(original_template.submitters.deep_dup,
|
update_submitters_and_fields_and_schema(original_template.submitters.deep_dup,
|
||||||
original_template.fields.deep_dup,
|
original_template.fields.deep_dup,
|
||||||
original_template.schema.deep_dup)
|
original_template.schema.deep_dup,
|
||||||
|
original_template.preferences.deep_dup)
|
||||||
|
|
||||||
if name.present? && template.schema.size == 1 &&
|
if name.present? && template.schema.size == 1 &&
|
||||||
original_template.schema.first['name'] == original_template.name &&
|
original_template.schema.first['name'] == original_template.name &&
|
||||||
@@ -33,7 +33,7 @@ module Templates
|
|||||||
end
|
end
|
||||||
|
|
||||||
# rubocop:disable Metrics, Style/CombinableLoops
|
# rubocop:disable Metrics, Style/CombinableLoops
|
||||||
def update_submitters_and_fields_and_schema(cloned_submitters, cloned_fields, cloned_schema)
|
def update_submitters_and_fields_and_schema(cloned_submitters, cloned_fields, cloned_schema, cloned_preferences)
|
||||||
submitter_uuids_replacements = {}
|
submitter_uuids_replacements = {}
|
||||||
field_uuids_replacements = {}
|
field_uuids_replacements = {}
|
||||||
|
|
||||||
@@ -58,6 +58,10 @@ module Templates
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
cloned_preferences['submitters'].to_a.each do |submitter|
|
||||||
|
submitter['uuid'] = submitter_uuids_replacements[submitter['uuid']]
|
||||||
|
end
|
||||||
|
|
||||||
cloned_fields.each do |field|
|
cloned_fields.each do |field|
|
||||||
new_field_uuid = SecureRandom.uuid
|
new_field_uuid = SecureRandom.uuid
|
||||||
|
|
||||||
@@ -88,7 +92,7 @@ module Templates
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
[cloned_submitters, cloned_fields, cloned_schema]
|
[cloned_submitters, cloned_fields, cloned_schema, cloned_preferences]
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics, Style/CombinableLoops
|
# rubocop:enable Metrics, Style/CombinableLoops
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ module Templates
|
|||||||
Escolher
|
Escolher
|
||||||
)\b/ix
|
)\b/ix
|
||||||
|
|
||||||
|
DATE_FORMAT_REGEXP = %r{[myd]{2,4}[-\\/\s.][myd]{2,4}[-\\/\s.][myd]{2,4}}i
|
||||||
|
|
||||||
FIELD_ALIGNMENT = {
|
FIELD_ALIGNMENT = {
|
||||||
0 => 'left',
|
0 => 'left',
|
||||||
1 => 'center',
|
1 => 'center',
|
||||||
@@ -30,8 +32,8 @@ module Templates
|
|||||||
module_function
|
module_function
|
||||||
|
|
||||||
# rubocop:disable Metrics
|
# rubocop:disable Metrics
|
||||||
def call(pdf, attachment)
|
def call(pdf, attachment, data)
|
||||||
return [] unless pdf.acro_form
|
return [] if pdf.acro_form.blank? && data.exclude?('/Form')
|
||||||
|
|
||||||
fields, annots_index = build_fields_with_pages(pdf)
|
fields, annots_index = build_fields_with_pages(pdf)
|
||||||
|
|
||||||
@@ -101,7 +103,7 @@ module Templates
|
|||||||
|
|
||||||
{
|
{
|
||||||
uuid: SecureRandom.uuid,
|
uuid: SecureRandom.uuid,
|
||||||
required: false,
|
required: field.flags.include?(:required),
|
||||||
preferences: {},
|
preferences: {},
|
||||||
areas:,
|
areas:,
|
||||||
**field_properties
|
**field_properties
|
||||||
@@ -147,7 +149,7 @@ module Templates
|
|||||||
options: build_options(field[:Opt], 'radio'),
|
options: build_options(field[:Opt], 'radio'),
|
||||||
default_value: selected_option
|
default_value: selected_option
|
||||||
}
|
}
|
||||||
elsif field.field_type == :Btn && field.concrete_field_type == :check_box &&
|
elsif field.field_type == :Btn && %i[check_box radio_button].include?(field.concrete_field_type) &&
|
||||||
field[:Kids].present? && field[:Kids].size > 1 && field.allowed_values.size > 1
|
field[:Kids].present? && field[:Kids].size > 1 && field.allowed_values.size > 1
|
||||||
selected_option = (field.allowed_values || []).find { |v| v == field.field_value }
|
selected_option = (field.allowed_values || []).find { |v| v == field.field_value }
|
||||||
|
|
||||||
@@ -187,15 +189,29 @@ module Templates
|
|||||||
default_value: field.field_value
|
default_value: field.field_value
|
||||||
}
|
}
|
||||||
elsif field.field_type == :Tx
|
elsif field.field_type == :Tx
|
||||||
{
|
if field[:AA] && ((field[:AA][:F] && field[:AA][:F][:JS].include?('AFDate_')) ||
|
||||||
**attrs,
|
(field[:AA][:K] && field[:AA][:F][:JS].include?('AFDate_')))
|
||||||
type: 'text',
|
if (format = field[:AA][:F][:JS][DATE_FORMAT_REGEXP])
|
||||||
default_value: field.field_value
|
attrs[:preferences] ||= {}
|
||||||
}
|
attrs[:preferences][:format] = format.upcase
|
||||||
|
end
|
||||||
|
|
||||||
|
{
|
||||||
|
**attrs,
|
||||||
|
type: 'date',
|
||||||
|
default_value: field.field_value
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
**attrs,
|
||||||
|
type: 'text',
|
||||||
|
default_value: field.field_value
|
||||||
|
}
|
||||||
|
end
|
||||||
elsif field.field_type == :Sig
|
elsif field.field_type == :Sig
|
||||||
{
|
{
|
||||||
**attrs,
|
**attrs,
|
||||||
type: 'signature'
|
type: field.try(:field_name).to_s.downcase.include?('initials') ? 'initials' : 'signature'
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{}
|
{}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ module Templates
|
|||||||
if extract_fields && data.size < MAX_FLATTEN_FILE_SIZE
|
if extract_fields && data.size < MAX_FLATTEN_FILE_SIZE
|
||||||
pdf = HexaPDF::Document.new(io: StringIO.new(data))
|
pdf = HexaPDF::Document.new(io: StringIO.new(data))
|
||||||
|
|
||||||
fields = Templates::FindAcroFields.call(pdf, attachment)
|
fields = Templates::FindAcroFields.call(pdf, attachment, data)
|
||||||
end
|
end
|
||||||
|
|
||||||
generate_pdf_preview_images(attachment, data, pdf, max_pages:)
|
generate_pdf_preview_images(attachment, data, pdf, max_pages:)
|
||||||
@@ -85,6 +85,8 @@ module Templates
|
|||||||
end
|
end
|
||||||
|
|
||||||
Concurrent::Promise.zip(*promises).value!.each do |blob|
|
Concurrent::Promise.zip(*promises).value!.each do |blob|
|
||||||
|
next unless blob
|
||||||
|
|
||||||
ApplicationRecord.no_touching do
|
ApplicationRecord.no_touching do
|
||||||
ActiveStorage::Attachment.create!(
|
ActiveStorage::Attachment.create!(
|
||||||
blob:,
|
blob:,
|
||||||
@@ -114,6 +116,10 @@ module Templates
|
|||||||
blob.upload(io)
|
blob.upload(io)
|
||||||
|
|
||||||
blob
|
blob
|
||||||
|
rescue Vips::Error => e
|
||||||
|
Rollbar.warning(e) if defined?(Rollbar)
|
||||||
|
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_flatten_form(data, pdf)
|
def maybe_flatten_form(data, pdf)
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
module TextUtils
|
module TextUtils
|
||||||
RTL_REGEXP = /[\p{Hebrew}\p{Arabic}]/
|
RTL_REGEXP = /[\p{Hebrew}\p{Arabic}]/
|
||||||
|
MASK_REGEXP = /[^\s\-_\[\]\(\)\+\?\.\,]/
|
||||||
|
MASK_SYMBOL = 'X'
|
||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
@@ -13,6 +15,24 @@ module TextUtils
|
|||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mask_value(text, unmask_size = 0)
|
||||||
|
if unmask_size.is_a?(Numeric) && !unmask_size.zero? && unmask_size.abs < text.length
|
||||||
|
if unmask_size.negative?
|
||||||
|
[
|
||||||
|
text.first(text.length + unmask_size).gsub(MASK_REGEXP, MASK_SYMBOL),
|
||||||
|
text.last(-unmask_size)
|
||||||
|
].join
|
||||||
|
elsif unmask_size.positive?
|
||||||
|
[
|
||||||
|
text.first(unmask_size),
|
||||||
|
text.last(text.length - unmask_size).gsub(MASK_REGEXP, MASK_SYMBOL)
|
||||||
|
].join
|
||||||
|
end
|
||||||
|
else
|
||||||
|
text.to_s.gsub(MASK_REGEXP, MASK_SYMBOL)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def maybe_rtl_reverse(text)
|
def maybe_rtl_reverse(text)
|
||||||
if text.match?(RTL_REGEXP)
|
if text.match?(RTL_REGEXP)
|
||||||
TwitterCldr::Shared::Bidi
|
TwitterCldr::Shared::Bidi
|
||||||
|
|||||||
@@ -772,6 +772,64 @@ RSpec.describe 'Signing Form', type: :system do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when the masked field' do
|
||||||
|
let(:template) { create(:template, submitter_count: 2, account:, author:, only_field_types: %w[text]) }
|
||||||
|
let(:submission) { create(:submission, template: template) }
|
||||||
|
let!(:first_submitter) { create(:submitter, submission:, uuid: template.submitters[0]['uuid'], account:) }
|
||||||
|
let!(:second_submitter) { create(:submitter, submission:, uuid: template.submitters[1]['uuid'], account:) }
|
||||||
|
|
||||||
|
it 'shows the masked value instead of the real value' do
|
||||||
|
field = submission.template_fields.find do |f|
|
||||||
|
f['name'] == 'First Name' && f['submitter_uuid'] == first_submitter.uuid
|
||||||
|
end
|
||||||
|
field['preferences']['mask'] = true
|
||||||
|
submission.save!
|
||||||
|
|
||||||
|
visit submit_form_path(slug: first_submitter.slug)
|
||||||
|
|
||||||
|
fill_in 'First Name', with: 'Jahn'
|
||||||
|
click_button 'Complete'
|
||||||
|
|
||||||
|
visit submit_form_path(slug: second_submitter.slug)
|
||||||
|
|
||||||
|
expect(page).to have_content('XXXX')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the template requires multiple submitters' do
|
||||||
|
let(:template) { create(:template, submitter_count: 2, account:, author:, only_field_types: %w[text]) }
|
||||||
|
|
||||||
|
context 'when default signer details are not defined' do
|
||||||
|
it 'shows an explanation error message if a logged-in user associated with the template account opens the link' do
|
||||||
|
sign_in author
|
||||||
|
visit start_form_path(slug: template.slug)
|
||||||
|
fill_in 'Email', with: author.email
|
||||||
|
click_button 'Start'
|
||||||
|
|
||||||
|
expect(page).to have_content('This submission has multiple signers, which prevents the use of a sharing link ' \
|
||||||
|
"as it's unclear which signer is responsible for specific fields. " \
|
||||||
|
'To resolve this, follow this guide to define the default signer details.')
|
||||||
|
expect(page).to have_link('guide', href: 'https://www.docuseal.com/resources/pre-filling-recipients')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows a "Not found" error message if a logged-out user associated with the template account opens the link' do
|
||||||
|
visit start_form_path(slug: template.slug)
|
||||||
|
fill_in 'Email', with: author.email
|
||||||
|
click_button 'Start'
|
||||||
|
|
||||||
|
expect(page).to have_content('Not found')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows a "Not found" error message if an unrelated user opens the link' do
|
||||||
|
visit start_form_path(slug: template.slug)
|
||||||
|
fill_in 'Email', with: 'john.doe@example.com'
|
||||||
|
click_button 'Start'
|
||||||
|
|
||||||
|
expect(page).to have_content('Not found')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it 'sends completed email' do
|
it 'sends completed email' do
|
||||||
template = create(:template, account:, author:, only_field_types: %w[text signature])
|
template = create(:template, account:, author:, only_field_types: %w[text signature])
|
||||||
submission = create(:submission, template:)
|
submission = create(:submission, template:)
|
||||||
|
|||||||
Reference in new issue
Block a user