mirror of
https://github.com/docusealco/docuseal.git
synced 2026-08-07 15:25:16 +00:00
Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d0799b8cc1 | |||
| 889fcad859 | |||
| c133fef99d | |||
| cfdc978a2a | |||
| bff1651966 | |||
| b97854f2fe | |||
| 7a4629bf79 | |||
| 2573d8cb19 | |||
| dd559e90f1 | |||
| e5db61e358 | |||
| 69376c0faf | |||
| 1f48be135c | |||
| 1cae12b728 | |||
| 86d2cd93f8 | |||
| b0110d9340 | |||
| 7e70f4fb14 | |||
| 28e3d65e63 | |||
| f1a2b71b59 | |||
| 47c7270911 | |||
| 4e1fa336a2 | |||
| 244bc32136 | |||
| 42c30bdfd0 | |||
| 0abf48ddba | |||
| 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 |
@@ -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:
|
||||
- ''
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.4.1
|
||||
ruby-version: 3.4.2
|
||||
- name: Cache gems
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.4.1
|
||||
ruby-version: 3.4.2
|
||||
- name: Cache gems
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
@@ -77,6 +77,33 @@ jobs:
|
||||
run: |
|
||||
./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.2
|
||||
- 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:
|
||||
name: RSpec
|
||||
runs-on: ubuntu-latest
|
||||
@@ -100,7 +127,7 @@ jobs:
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.4.1
|
||||
ruby-version: 3.4.2
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
FROM ruby:3.4.1-alpine AS fonts
|
||||
FROM ruby:3.4.2-alpine AS 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")'
|
||||
|
||||
FROM ruby:3.4.1-alpine AS webpack
|
||||
FROM ruby:3.4.2-alpine AS webpack
|
||||
|
||||
ENV RAILS_ENV=production
|
||||
ENV NODE_ENV=production
|
||||
@@ -32,7 +32,7 @@ COPY ./app/views ./app/views
|
||||
|
||||
RUN echo "gem 'shakapacker'" > Gemfile && ./bin/shakapacker
|
||||
|
||||
FROM ruby:3.4.1-alpine AS app
|
||||
FROM ruby:3.4.2-alpine AS app
|
||||
|
||||
ENV RAILS_ENV=production
|
||||
ENV BUNDLE_WITHOUT="development:test"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
source 'https://rubygems.org'
|
||||
|
||||
ruby '3.4.1'
|
||||
ruby '3.4.2'
|
||||
|
||||
gem 'arabic-letter-connector', require: 'arabic-letter-connector/logic'
|
||||
gem 'aws-sdk-s3', require: false
|
||||
@@ -46,7 +46,6 @@ gem 'twitter_cldr', require: false
|
||||
gem 'tzinfo-data'
|
||||
|
||||
group :development, :test do
|
||||
gem 'annotate'
|
||||
gem 'better_html'
|
||||
gem 'bullet'
|
||||
gem 'debug'
|
||||
@@ -63,6 +62,9 @@ group :development, :test do
|
||||
end
|
||||
|
||||
group :development do
|
||||
gem 'annotaterb'
|
||||
gem 'brakeman', require: false
|
||||
gem 'foreman', require: false
|
||||
gem 'letter_opener_web'
|
||||
gem 'web-console'
|
||||
end
|
||||
|
||||
+16
-13
@@ -74,9 +74,7 @@ GEM
|
||||
uri (>= 0.13.1)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
annotate (2.6.5)
|
||||
activerecord (>= 2.3.0)
|
||||
rake (>= 0.8.7)
|
||||
annotaterb (4.14.0)
|
||||
arabic-letter-connector (0.1.1)
|
||||
ast (2.4.2)
|
||||
aws-eventstream (1.3.0)
|
||||
@@ -120,6 +118,8 @@ GEM
|
||||
bindex (0.8.1)
|
||||
bootsnap (1.18.4)
|
||||
msgpack (~> 1.2)
|
||||
brakeman (7.0.0)
|
||||
racc
|
||||
builder (3.3.0)
|
||||
bullet (8.0.0)
|
||||
activesupport (>= 3.0.0)
|
||||
@@ -229,6 +229,7 @@ GEM
|
||||
ffi (1.17.1-arm64-darwin)
|
||||
ffi (1.17.1-x86_64-linux-gnu)
|
||||
ffi (1.17.1-x86_64-linux-musl)
|
||||
foreman (0.88.1)
|
||||
geom2d (0.4.1)
|
||||
globalid (1.2.1)
|
||||
activesupport (>= 6.1)
|
||||
@@ -337,18 +338,18 @@ GEM
|
||||
net-smtp (0.5.0)
|
||||
net-protocol
|
||||
nio4r (2.7.4)
|
||||
nokogiri (1.18.3)
|
||||
nokogiri (1.18.5)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.3-aarch64-linux-gnu)
|
||||
nokogiri (1.18.5-aarch64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.3-aarch64-linux-musl)
|
||||
nokogiri (1.18.5-aarch64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.3-arm64-darwin)
|
||||
nokogiri (1.18.5-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.3-x86_64-linux-gnu)
|
||||
nokogiri (1.18.5-x86_64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.3-x86_64-linux-musl)
|
||||
nokogiri (1.18.5-x86_64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
oj (3.16.8)
|
||||
bigdecimal (>= 3.0)
|
||||
@@ -386,7 +387,7 @@ GEM
|
||||
puma (6.5.0)
|
||||
nio4r (~> 2.0)
|
||||
racc (1.8.1)
|
||||
rack (3.1.10)
|
||||
rack (3.1.12)
|
||||
rack-proxy (0.7.7)
|
||||
rack
|
||||
rack-session (2.0.0)
|
||||
@@ -558,7 +559,7 @@ GEM
|
||||
unicode-emoji (~> 4.0, >= 4.0.4)
|
||||
unicode-emoji (4.0.4)
|
||||
uniform_notifier (1.16.0)
|
||||
uri (1.0.2)
|
||||
uri (1.0.3)
|
||||
useragent (0.16.11)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
@@ -588,13 +589,14 @@ PLATFORMS
|
||||
x86_64-linux-musl
|
||||
|
||||
DEPENDENCIES
|
||||
annotate
|
||||
annotaterb
|
||||
arabic-letter-connector
|
||||
aws-sdk-s3
|
||||
aws-sdk-secretsmanager
|
||||
azure-storage-blob
|
||||
better_html
|
||||
bootsnap
|
||||
brakeman
|
||||
bullet
|
||||
cancancan
|
||||
capybara
|
||||
@@ -610,6 +612,7 @@ DEPENDENCIES
|
||||
faker
|
||||
faraday
|
||||
faraday-follow_redirects
|
||||
foreman
|
||||
google-cloud-storage
|
||||
hexapdf
|
||||
image_processing
|
||||
@@ -649,7 +652,7 @@ DEPENDENCIES
|
||||
webmock
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.4.1p0
|
||||
ruby 3.4.2p28
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.3
|
||||
|
||||
@@ -15,6 +15,7 @@ class AccountConfigsController < ApplicationController
|
||||
AccountConfig::DOWNLOAD_LINKS_AUTH_KEY,
|
||||
AccountConfig::FORCE_SSO_AUTH_KEY,
|
||||
AccountConfig::FLATTEN_RESULT_PDF_KEY,
|
||||
AccountConfig::ENFORCE_SIGNING_ORDER_KEY,
|
||||
AccountConfig::WITH_SIGNATURE_ID,
|
||||
AccountConfig::COMBINE_PDF_RESULT_KEY,
|
||||
AccountConfig::REQUIRE_SIGNING_REASON_KEY,
|
||||
@@ -39,7 +40,7 @@ class AccountConfigsController < ApplicationController
|
||||
end
|
||||
|
||||
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])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,6 +17,10 @@ module Api
|
||||
submissions = submissions.joins(template: :folder).where(folder: { name: params[:template_folder] })
|
||||
end
|
||||
|
||||
if params.key?(:archived)
|
||||
submissions = params[:archived].in?(['true', true]) ? submissions.archived : submissions.active
|
||||
end
|
||||
|
||||
submissions = Submissions::Filter.call(submissions, current_user, params)
|
||||
|
||||
submissions = paginate(submissions.preload(:created_by_user, :submitters,
|
||||
@@ -179,7 +183,7 @@ module Api
|
||||
message: %i[subject body],
|
||||
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
|
||||
:completed, :phone, :application_key, :external_id, :reply_to, :go_to_last,
|
||||
{ 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,
|
||||
:readonly, :validation_pattern, :invalid_message,
|
||||
{ default_value: [], value: [], preferences: {} }] }]]
|
||||
|
||||
@@ -39,7 +39,7 @@ class NotificationsSettingsController < ApplicationController
|
||||
end
|
||||
|
||||
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])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -50,7 +50,7 @@ class PersonalizationSettingsController < ApplicationController
|
||||
end
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ class StartFormController < ApplicationController
|
||||
redirect_to start_form_completed_path(@template.slug, email: submitter_params[:email])
|
||||
else
|
||||
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
|
||||
end
|
||||
@@ -117,4 +117,12 @@ class StartFormController < ApplicationController
|
||||
|
||||
@template = Template.find_by!(slug:)
|
||||
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
|
||||
|
||||
@@ -23,9 +23,9 @@ class SubmissionsDownloadController < ApplicationController
|
||||
|
||||
last_submitter = submitter.submission.submitters.where.not(completed_at: nil).order(:completed_at).last
|
||||
|
||||
Submissions::EnsureResultGenerated.call(last_submitter)
|
||||
return head :not_found unless last_submitter
|
||||
|
||||
return head :not_found unless last_submitter.completed_at?
|
||||
Submissions::EnsureResultGenerated.call(last_submitter)
|
||||
|
||||
if last_submitter.completed_at < TTL.ago && !signature_valid && !current_user_submitter?(last_submitter)
|
||||
Rollbar.info("TTL: #{last_submitter.id}") if defined?(Rollbar)
|
||||
|
||||
@@ -20,7 +20,7 @@ class SubmissionsPreviewController < ApplicationController
|
||||
|
||||
@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 &&
|
||||
(!current_user || !current_ability.can?(:read, @submission))
|
||||
|
||||
@@ -20,7 +20,11 @@ class SubmitFormController < ApplicationController
|
||||
@submitter.account.archived_at?
|
||||
return render :expired if submission.expired?
|
||||
return render :declined if @submitter.declined_at?
|
||||
return render :awaiting if submission.template.preferences['submitters_order'] == 'preserved' &&
|
||||
|
||||
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
|
||||
|
||||
return render :awaiting if (@form_configs[:enforce_signing_order] ||
|
||||
submission.template.preferences['submitters_order'] == 'preserved') &&
|
||||
!Submitters.current_submitter_order?(@submitter)
|
||||
|
||||
Submitters.preload_with_pages(@submitter)
|
||||
@@ -29,8 +33,6 @@ class SubmitFormController < ApplicationController
|
||||
|
||||
@attachments_index = build_attachments_index(submission)
|
||||
|
||||
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
|
||||
|
||||
return unless @form_configs[:prefill_signature]
|
||||
|
||||
if (user_signature = UserConfigs.load_signature(current_user))
|
||||
@@ -70,6 +72,10 @@ class SubmitFormController < ApplicationController
|
||||
Submitters::SubmitValues.call(submitter, params, request)
|
||||
|
||||
head :ok
|
||||
rescue Submitters::SubmitValues::RequiredFieldError => e
|
||||
Rollbar.warning("Required field #{submitter.id}: #{e.message}") if defined?(Rollbar)
|
||||
|
||||
render json: { field_uuid: e.message }, status: :unprocessable_entity
|
||||
rescue Submitters::SubmitValues::ValidationError => e
|
||||
render json: { error: e.message }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
@@ -6,9 +6,10 @@ class TemplatesDebugController < ApplicationController
|
||||
def show
|
||||
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']['fields'] = fields
|
||||
|
||||
@@ -5,7 +5,8 @@ class UserConfigsController < ApplicationController
|
||||
authorize_resource :user_config
|
||||
|
||||
ALLOWED_KEYS = [
|
||||
UserConfig::RECEIVE_COMPLETED_EMAIL
|
||||
UserConfig::RECEIVE_COMPLETED_EMAIL,
|
||||
UserConfig::SHOW_APP_TOUR
|
||||
].freeze
|
||||
|
||||
InvalidKey = Class.new(StandardError)
|
||||
@@ -26,8 +27,9 @@ class UserConfigsController < ApplicationController
|
||||
end
|
||||
|
||||
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] == 'true' if attrs[:value].in?(%w[true false])
|
||||
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?
|
||||
|
||||
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?
|
||||
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)
|
||||
|
||||
@user.account = account
|
||||
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')
|
||||
else
|
||||
render turbo_stream: turbo_stream.replace(:modal, template: 'users/edit'), status: :unprocessable_entity
|
||||
@@ -84,8 +83,11 @@ class UsersController < ApplicationController
|
||||
|
||||
def user_params
|
||||
if params.key?(:user)
|
||||
params.require(:user).permit(:email, :first_name, :last_name, :password,
|
||||
:role, :archived_at, :account_id)
|
||||
permitted_params = %i[email first_name last_name password archived_at]
|
||||
|
||||
permitted_params << :role if role_valid?(params.dig(:user, :role))
|
||||
|
||||
params.require(:user).permit(permitted_params)
|
||||
else
|
||||
{}
|
||||
end
|
||||
|
||||
@@ -32,11 +32,10 @@ import CheckboxGroup from './elements/checkbox_group'
|
||||
import MaskedInput from './elements/masked_input'
|
||||
import SetDateButton from './elements/set_date_button'
|
||||
import IndeterminateCheckbox from './elements/indeterminate_checkbox'
|
||||
import AppTour from './elements/app_tour'
|
||||
|
||||
import * as TurboInstantClick from './lib/turbo_instant_click'
|
||||
|
||||
import './images/preview.png'
|
||||
|
||||
TurboInstantClick.start()
|
||||
|
||||
document.addEventListener('turbo:before-cache', () => {
|
||||
@@ -101,6 +100,7 @@ safeRegisterElement('checkbox-group', CheckboxGroup)
|
||||
safeRegisterElement('masked-input', MaskedInput)
|
||||
safeRegisterElement('set-date-button', SetDateButton)
|
||||
safeRegisterElement('indeterminate-checkbox', IndeterminateCheckbox)
|
||||
safeRegisterElement('app-tour', AppTour)
|
||||
|
||||
safeRegisterElement('template-builder', class extends HTMLElement {
|
||||
connectedCallback () {
|
||||
@@ -126,7 +126,8 @@ safeRegisterElement('template-builder', class extends HTMLElement {
|
||||
withSignYourselfButton: this.dataset.withSignYourselfButton !== 'false',
|
||||
withConditions: this.dataset.withConditions === 'true',
|
||||
currencies: (this.dataset.currencies || '').split(',').filter(Boolean),
|
||||
acceptFileTypes: this.dataset.acceptFileTypes
|
||||
acceptFileTypes: this.dataset.acceptFileTypes,
|
||||
showTourStartForm: this.dataset.showTourStartForm === 'true'
|
||||
})
|
||||
|
||||
this.component = this.app.mount(this.appElem)
|
||||
|
||||
+20
-17
@@ -1,5 +1,6 @@
|
||||
import SignaturePad from 'signature_pad'
|
||||
import { cropCanvasAndExportToPNG } from './submission_form/crop_canvas'
|
||||
import { isValidSignatureCanvas } from './submission_form/validate_signature'
|
||||
|
||||
window.customElements.define('draw-signature', class extends HTMLElement {
|
||||
connectedCallback () {
|
||||
@@ -43,6 +44,8 @@ window.customElements.define('draw-signature', class extends HTMLElement {
|
||||
|
||||
return response
|
||||
})
|
||||
}).catch(error => {
|
||||
console.log(error)
|
||||
}).finally(() => {
|
||||
this.submitButton.disabled = false
|
||||
})
|
||||
@@ -65,26 +68,26 @@ window.customElements.define('draw-signature', class extends HTMLElement {
|
||||
}
|
||||
|
||||
async submitImage () {
|
||||
return new Promise((resolve, reject) => {
|
||||
cropCanvasAndExportToPNG(this.canvas, { errorOnTooSmall: true }).then(async (blob) => {
|
||||
const file = new File([blob], 'signature.png', { type: 'image/png' })
|
||||
if (!isValidSignatureCanvas(this.pad.toData())) {
|
||||
alert('Signature is too small or simple. Please redraw.')
|
||||
|
||||
const formData = new FormData()
|
||||
return Promise.reject(new Error('Image too small or simple'))
|
||||
}
|
||||
|
||||
formData.append('file', file)
|
||||
formData.append('submitter_slug', this.dataset.slug)
|
||||
formData.append('name', 'attachments')
|
||||
formData.append('remember_signature', 'true')
|
||||
return cropCanvasAndExportToPNG(this.canvas).then(async (blob) => {
|
||||
const file = new File([blob], 'signature.png', { type: 'image/png' })
|
||||
|
||||
return fetch('/api/attachments', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then((resp) => resp.json()).then((attachment) => {
|
||||
return resolve(attachment)
|
||||
})
|
||||
}).catch((error) => {
|
||||
return reject(error)
|
||||
})
|
||||
const formData = new FormData()
|
||||
|
||||
formData.append('file', file)
|
||||
formData.append('submitter_slug', this.dataset.slug)
|
||||
formData.append('name', 'attachments')
|
||||
formData.append('remember_signature', 'true')
|
||||
|
||||
return fetch('/api/attachments', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(resp => resp.json())
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,338 @@
|
||||
export default class extends HTMLElement {
|
||||
async connectedCallback () {
|
||||
this.tourType = this.dataset.type
|
||||
this.nextPagePath = this.dataset.nextPagePath
|
||||
this.I18n = JSON.parse(this.dataset.i18n || '{}')
|
||||
|
||||
if (this.dataset.showTour === 'true') this.start()
|
||||
}
|
||||
|
||||
async start () {
|
||||
if (window.innerWidth < 768) return
|
||||
|
||||
const [{ driver }] = await Promise.all([
|
||||
import('driver.js'),
|
||||
import('driver.js/dist/driver.css')
|
||||
])
|
||||
|
||||
this.driverObj = driver({
|
||||
showProgress: true,
|
||||
nextBtnText: this.I18n.next,
|
||||
prevBtnText: this.I18n.previous,
|
||||
doneBtnText: this.I18n.done,
|
||||
onDestroyStarted: () => {
|
||||
this.disableAppGuide().finally(() => { this.destroy() })
|
||||
},
|
||||
onHighlightStarted: (element) => {
|
||||
if (element) {
|
||||
const clickHandler = () => {
|
||||
this.disableAppGuide().finally(() => { this.destroy() })
|
||||
element.removeEventListener('click', clickHandler)
|
||||
}
|
||||
|
||||
element.addEventListener('click', clickHandler)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (this.tourType === 'dashboard') {
|
||||
this.showDashboardTour()
|
||||
} else if (this.tourType === 'builder') {
|
||||
this.showTemplateBuilderTour()
|
||||
} else if (this.tourType === 'account') {
|
||||
this.showAccountTour()
|
||||
} else if (this.tourType === 'template') {
|
||||
this.showTemplateTour()
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback () {
|
||||
if (this.driverObj) this.destroy()
|
||||
}
|
||||
|
||||
destroy () {
|
||||
if (this.builderTemplate) this.builderTemplate.fields.shift()
|
||||
if (this.driverObj) this.driverObj.destroy()
|
||||
}
|
||||
|
||||
showTemplateTour () {
|
||||
const steps = [
|
||||
{
|
||||
element: '#share_link_clipboard',
|
||||
popover: {
|
||||
title: this.I18n.copy_and_share_link,
|
||||
description: this.I18n.copy_and_share_link_description,
|
||||
side: 'bottom',
|
||||
align: 'end'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#sign_yourself_button',
|
||||
popover: {
|
||||
title: this.I18n.sign_the_document,
|
||||
description: this.I18n.sign_the_document_description,
|
||||
side: 'top',
|
||||
align: 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#send_to_recipients_button',
|
||||
popover: {
|
||||
title: this.I18n.send_for_signing,
|
||||
description: this.I18n.add_recipients_description,
|
||||
side: 'top',
|
||||
align: 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#add_recipients_button',
|
||||
popover: {
|
||||
title: this.I18n.add_recipients,
|
||||
description: this.I18n.add_recipients_description,
|
||||
side: 'bottom',
|
||||
align: 'end'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#account_settings_button',
|
||||
popover: {
|
||||
title: this.I18n.settings,
|
||||
description: this.I18n.settings_template_description,
|
||||
side: 'right',
|
||||
align: 'start',
|
||||
showButtons: this.nextPagePath ? ['next', 'previous', 'close'] : ['previous', 'close'],
|
||||
onNextClick: () => {
|
||||
if (this.nextPagePath) {
|
||||
window.Turbo.visit(this.nextPagePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
].filter((step) => document.querySelector(step.element))
|
||||
|
||||
this.driverObj.setSteps(steps)
|
||||
this.driverObj.drive()
|
||||
}
|
||||
|
||||
showDashboardTour () {
|
||||
this.driverObj.setSteps([
|
||||
{
|
||||
element: '#templates_submissions_toggle',
|
||||
popover: {
|
||||
title: this.I18n.template_and_submissions,
|
||||
description: this.I18n.template_and_submissions_description,
|
||||
side: 'right',
|
||||
align: 'start'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#templates_upload_button',
|
||||
popover: {
|
||||
title: this.I18n.upload_a_pdf_file,
|
||||
description: this.I18n.upload_a_pdf_file_description,
|
||||
side: 'left',
|
||||
align: 'start',
|
||||
showButtons: this.nextPagePath ? ['next', 'previous', 'close'] : ['previous', 'close'],
|
||||
onNextClick: () => {
|
||||
if (this.nextPagePath) {
|
||||
window.Turbo.visit(this.nextPagePath)
|
||||
}
|
||||
}
|
||||
},
|
||||
onHighlightStarted: () => {}
|
||||
}
|
||||
])
|
||||
|
||||
this.driverObj.drive()
|
||||
}
|
||||
|
||||
showAccountTour () {
|
||||
this.driverObj.setSteps([
|
||||
{
|
||||
element: '#account_settings_menu',
|
||||
popover: {
|
||||
title: this.I18n.settings,
|
||||
description: this.I18n.settings_account_description,
|
||||
side: 'right',
|
||||
align: 'start'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#support_channels',
|
||||
popover: {
|
||||
title: this.I18n.support,
|
||||
description: this.I18n.support_description,
|
||||
side: 'left',
|
||||
align: 'start'
|
||||
}
|
||||
}
|
||||
].filter((step) => document.querySelector(step.element)))
|
||||
|
||||
this.driverObj.drive()
|
||||
}
|
||||
|
||||
showTemplateBuilderTour () {
|
||||
const builderComponent = document.querySelector('template-builder')?.component
|
||||
|
||||
this.builderTemplate = builderComponent?.template
|
||||
|
||||
if (this.builderTemplate) {
|
||||
this.builderTemplate.fields.unshift({
|
||||
uuid: 'b387399b-88dc-4345-9d37-743e97a9b2b3',
|
||||
submitter_uuid: this.builderTemplate.submitters[0].uuid,
|
||||
name: 'First Name',
|
||||
type: 'text'
|
||||
})
|
||||
|
||||
builderComponent.$nextTick(() => {
|
||||
this.driverObj.setSteps([
|
||||
{
|
||||
element: '.roles-dropdown',
|
||||
popover: {
|
||||
title: this.I18n.select_a_signer_party,
|
||||
description: this.I18n.select_a_signer_party_description,
|
||||
side: 'left',
|
||||
align: 'start',
|
||||
onPopoverRender: () => {
|
||||
const rolesDropdown = document.querySelector('.roles-dropdown')
|
||||
|
||||
rolesDropdown.dispatchEvent(new Event('mouseenter', { bubbles: true, cancelable: true }))
|
||||
rolesDropdown.classList.add('dropdown-open')
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '.roles-dropdown .dropdown-content',
|
||||
popover: {
|
||||
title: this.I18n.available_parties,
|
||||
description: this.I18n.available_parties_description,
|
||||
side: 'left',
|
||||
align: 'start',
|
||||
onPopoverRender: () => {
|
||||
document.querySelector('.roles-dropdown .dropdown-content').classList.remove('driver-active-element')
|
||||
},
|
||||
onNextClick: () => {
|
||||
document.querySelector('.roles-dropdown').classList.remove('dropdown-open')
|
||||
this.driverObj.moveNext()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#field-types-grid',
|
||||
popover: {
|
||||
title: this.I18n.available_field_types,
|
||||
description: this.I18n.available_field_types_description,
|
||||
side: 'right',
|
||||
align: 'start',
|
||||
onPrevClick: () => {
|
||||
document.querySelector('.roles-dropdown').classList.add('dropdown-open')
|
||||
this.driverObj.movePrevious()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#text_type_field_button',
|
||||
popover: {
|
||||
title: this.I18n.text_input_field,
|
||||
description: this.I18n.text_input_field_description,
|
||||
side: 'left',
|
||||
align: 'start'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#signature_type_field_button',
|
||||
popover: {
|
||||
title: this.I18n.signature_field,
|
||||
description: this.I18n.signature_field_description,
|
||||
side: 'left',
|
||||
align: 'start'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '.fields',
|
||||
popover: {
|
||||
title: this.I18n.added_fields,
|
||||
description: this.I18n.added_fields_description,
|
||||
side: 'right',
|
||||
align: 'start'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '.list-field label:has(svg.tabler-icon-settings)',
|
||||
popover: {
|
||||
title: this.I18n.open_field_settings,
|
||||
description: this.I18n.open_field_settings_description,
|
||||
side: 'bottom',
|
||||
align: 'end',
|
||||
onPopoverRender: () => {
|
||||
const settingsDropdown = document.querySelector('.list-field div:first-child span:has(svg.tabler-icon-settings)')
|
||||
|
||||
document.querySelectorAll('.list-field div:first-child .text-transparent').forEach((e) => e.classList.remove('text-transparent'))
|
||||
settingsDropdown.dispatchEvent(new Event('mouseenter', { bubbles: true, cancelable: true }))
|
||||
settingsDropdown.classList.add('dropdown-open')
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '.list-field div:first-child span:has(svg.tabler-icon-settings) .dropdown-content',
|
||||
popover: {
|
||||
title: this.I18n.field_settings,
|
||||
description: this.I18n.field_settings_description,
|
||||
side: 'left',
|
||||
align: 'start',
|
||||
onPopoverRender: () => {
|
||||
document.querySelector('.list-field div:first-child span:has(svg.tabler-icon-settings) .dropdown-content').classList.remove('driver-active-element')
|
||||
},
|
||||
onNextClick: () => {
|
||||
document.querySelector('.list-field div:first-child span:has(svg.tabler-icon-settings)').classList.remove('dropdown-open')
|
||||
this.driverObj.moveNext()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#send_button',
|
||||
popover: {
|
||||
title: this.I18n.send_document,
|
||||
description: this.I18n.send_document_description,
|
||||
side: 'bottom',
|
||||
align: 'end',
|
||||
onPrevClick: () => {
|
||||
document.querySelector('.list-field div:first-child span:has(svg.tabler-icon-settings)').classList.add('dropdown-open')
|
||||
this.driverObj.movePrevious()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#sign_yourself_button',
|
||||
popover: {
|
||||
title: this.I18n.sign_yourself,
|
||||
description: this.I18n.sign_yourself_description,
|
||||
side: 'bottom',
|
||||
align: 'end',
|
||||
onNextClick: () => {
|
||||
if (this.nextPagePath) {
|
||||
window.Turbo.visit(this.nextPagePath)
|
||||
} else {
|
||||
this.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
this.driverObj.drive()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async disableAppGuide () {
|
||||
return fetch('/user_configs', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
||||
},
|
||||
body: JSON.stringify({ key: 'show_app_tour', value: false })
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ export default actionable(targetable(class extends HTMLElement {
|
||||
}
|
||||
|
||||
toggleLoading = (e) => {
|
||||
if (e && e.target && !e.target.contains(this)) {
|
||||
if (e && e.target && (!e.target.contains(this) || !e.detail?.formSubmission?.formElement?.contains(this))) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 41 KiB |
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<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"
|
||||
: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
|
||||
v-if="(!withFieldPlaceholder || !field.name || field.type === 'cells') && !isActive && !isValueSet && field.type !== 'checkbox' && submittable && !area.option_uuid"
|
||||
@@ -168,7 +168,7 @@
|
||||
<div
|
||||
v-else-if="field.type === 'cells'"
|
||||
class="w-full flex items-center"
|
||||
:class="{ 'justify-end': field.preferences?.align === 'right' }"
|
||||
:class="{ 'justify-end': field.preferences?.align === 'right', ...fontClasses }"
|
||||
>
|
||||
<div
|
||||
v-for="(char, index) in modelValue"
|
||||
@@ -184,7 +184,7 @@
|
||||
ref="textContainer"
|
||||
dir="auto"
|
||||
class="flex items-center px-0.5 w-full"
|
||||
:class="alignClasses[field.preferences?.align]"
|
||||
:class="{ ...alignClasses, ...fontClasses }"
|
||||
>
|
||||
<span
|
||||
v-if="field && field.name && withFieldPlaceholder && !modelValue && modelValue !== 0"
|
||||
@@ -326,10 +326,26 @@ export default {
|
||||
}
|
||||
},
|
||||
alignClasses () {
|
||||
if (!this.field.preferences) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return {
|
||||
center: 'text-center',
|
||||
left: 'text-left',
|
||||
right: 'text-right'
|
||||
'text-center': this.field.preferences.align === 'center',
|
||||
'text-left': this.field.preferences.align === 'left',
|
||||
'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 () {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
>
|
||||
<a
|
||||
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"
|
||||
target="_blank"
|
||||
>
|
||||
@@ -26,7 +26,10 @@
|
||||
{{ attachmentsIndex[val].filename }}
|
||||
</span>
|
||||
</a>
|
||||
<button @click.prevent="removeAttachment(val)">
|
||||
<button
|
||||
class="remove-attachment-button"
|
||||
@click.prevent="removeAttachment(val)"
|
||||
>
|
||||
<IconTrashX
|
||||
:width="18"
|
||||
:heigh="19"
|
||||
@@ -44,7 +47,7 @@
|
||||
<div
|
||||
v-if="field.description && !modelValue.length"
|
||||
dir="auto"
|
||||
class="mb-3 px-1"
|
||||
class="mb-3 px-1 field-description-text"
|
||||
>
|
||||
<MarkdownContent :string="field.description" />
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
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"
|
||||
>
|
||||
<div class="font-medium text-2xl flex items-center space-x-1.5 mx-auto">
|
||||
@@ -10,13 +10,13 @@
|
||||
:width="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')) }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="completedMessage.body"
|
||||
class="mt-2"
|
||||
class="mt-2 completed-form-message-body"
|
||||
>
|
||||
<MarkdownContent
|
||||
:string="completedMessage.body"
|
||||
@@ -27,7 +27,7 @@
|
||||
v-if="completedButton.url"
|
||||
:href="sanitizeHref(completedButton.url)"
|
||||
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>
|
||||
{{ completedButton.title || 'Back to Website' }}
|
||||
@@ -35,7 +35,7 @@
|
||||
</a>
|
||||
<button
|
||||
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"
|
||||
@click.prevent="sendCopyToEmail"
|
||||
>
|
||||
@@ -50,7 +50,7 @@
|
||||
</button>
|
||||
<button
|
||||
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"
|
||||
@click.prevent="download"
|
||||
>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
function cropCanvasAndExportToPNG (canvas, { errorOnTooSmall } = { errorOnTooSmall: false }) {
|
||||
function cropCanvasAndExportToPNG (canvas) {
|
||||
const ctx = canvas.getContext('2d')
|
||||
|
||||
const width = canvas.width
|
||||
@@ -33,10 +33,6 @@ function cropCanvasAndExportToPNG (canvas, { errorOnTooSmall } = { errorOnTooSma
|
||||
croppedCanvas.height = croppedHeight
|
||||
const croppedCtx = croppedCanvas.getContext('2d')
|
||||
|
||||
if (errorOnTooSmall && (croppedWidth < 20 || croppedHeight < 20)) {
|
||||
return Promise.reject(new Error('Image too small'))
|
||||
}
|
||||
|
||||
croppedCtx.drawImage(canvas, leftmost, topmost, croppedWidth, croppedHeight, 0, 0, croppedWidth, croppedHeight)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<label
|
||||
v-if="showFieldNames"
|
||||
: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
|
||||
v-if="field.title"
|
||||
@@ -26,7 +26,7 @@
|
||||
</template>
|
||||
</label>
|
||||
<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')]"
|
||||
>
|
||||
<IconCalendarCheck :width="16" />
|
||||
@@ -35,7 +35,7 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="field.description"
|
||||
class="mb-3 px-1"
|
||||
class="mb-3 px-1 field-description-text"
|
||||
dir="auto"
|
||||
>
|
||||
<MarkdownContent :string="field.description" />
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
>
|
||||
<label
|
||||
: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 }"
|
||||
>
|
||||
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
<button
|
||||
v-if="!isFormVisible"
|
||||
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%"
|
||||
@click.prevent="[isFormVisible = true, scrollIntoField(currentField)]"
|
||||
>
|
||||
@@ -93,14 +93,14 @@
|
||||
<div
|
||||
v-show="isFormVisible"
|
||||
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 }"
|
||||
:style="{ backgroundColor: backgroundColor }"
|
||||
>
|
||||
<button
|
||||
v-if="!isCompleted"
|
||||
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'"
|
||||
:title="t('minimize')"
|
||||
@click.prevent="minimizeForm"
|
||||
@@ -119,7 +119,7 @@
|
||||
ref="form"
|
||||
:action="submitPath"
|
||||
method="post"
|
||||
class="mx-auto"
|
||||
class="mx-auto steps-form"
|
||||
:style="{ maxWidth: isBreakpointMd ? '582px' : '' }"
|
||||
@submit.prevent="submitStep"
|
||||
>
|
||||
@@ -165,7 +165,7 @@
|
||||
v-if="showFieldNames && (currentField.name || currentField.title)"
|
||||
:for="currentField.uuid"
|
||||
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 }"
|
||||
>
|
||||
<MarkdownContent
|
||||
@@ -188,7 +188,7 @@
|
||||
<div
|
||||
v-if="currentField.description"
|
||||
dir="auto"
|
||||
class="mb-3 px-1"
|
||||
class="mb-3 px-1 field-description-text"
|
||||
>
|
||||
<MarkdownContent :string="currentField.description" />
|
||||
</div>
|
||||
@@ -226,7 +226,7 @@
|
||||
v-if="showFieldNames && (currentField.name || currentField.title)"
|
||||
:for="currentField.uuid"
|
||||
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 }"
|
||||
>
|
||||
<MarkdownContent
|
||||
@@ -245,7 +245,7 @@
|
||||
<div
|
||||
v-if="currentField.description"
|
||||
dir="auto"
|
||||
class="mb-3 px-1"
|
||||
class="mb-3 px-1 field-description-text"
|
||||
>
|
||||
<MarkdownContent :string="currentField.description" />
|
||||
</div>
|
||||
@@ -270,7 +270,7 @@
|
||||
>
|
||||
<label
|
||||
:for="option.uuid"
|
||||
class="flex items-center space-x-3"
|
||||
class="flex items-center space-x-3 radio-label"
|
||||
>
|
||||
<input
|
||||
:id="option.uuid"
|
||||
@@ -304,7 +304,7 @@
|
||||
<div
|
||||
v-if="currentField.description"
|
||||
dir="auto"
|
||||
class="mb-3 px-1"
|
||||
class="mb-3 px-1 field-description-text"
|
||||
>
|
||||
<MarkdownContent :string="currentField.description" />
|
||||
</div>
|
||||
@@ -338,7 +338,7 @@
|
||||
>
|
||||
<label
|
||||
:for="field.uuid"
|
||||
class="flex items-center space-x-3"
|
||||
class="flex items-center space-x-3 checkbox-label"
|
||||
>
|
||||
<input
|
||||
type="hidden"
|
||||
@@ -481,7 +481,7 @@
|
||||
id="submit_form_button"
|
||||
ref="submitButton"
|
||||
type="submit"
|
||||
class="base-button w-full flex justify-center"
|
||||
class="base-button w-full flex justify-center submit-form-button"
|
||||
:disabled="isButtonDisabled"
|
||||
>
|
||||
<span class="flex">
|
||||
@@ -533,13 +533,13 @@
|
||||
v-if="stepFields.length < 80"
|
||||
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
|
||||
v-for="(step, index) in stepFields"
|
||||
:key="step[0].uuid"
|
||||
href="#"
|
||||
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)]"
|
||||
/>
|
||||
</div>
|
||||
@@ -1191,22 +1191,24 @@ export default {
|
||||
return false
|
||||
}
|
||||
|
||||
const defaultValue = !field || isEmpty(field.default_value) ? null : field.default_value
|
||||
|
||||
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)) {
|
||||
return !isEmpty(this.values[condition.field_uuid])
|
||||
return !isEmpty(this.values[condition.field_uuid] ?? defaultValue)
|
||||
} else if (['equal', 'contains'].includes(condition.action) && field) {
|
||||
if (field.options) {
|
||||
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)))
|
||||
} 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) {
|
||||
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)))
|
||||
} else {
|
||||
@@ -1299,7 +1301,9 @@ export default {
|
||||
|
||||
if (!this.isCompleted) {
|
||||
if (scrollToArea) {
|
||||
this.scrollIntoField(this.currentField)
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => this.scrollIntoField(this.currentField), 1)
|
||||
})
|
||||
}
|
||||
|
||||
this.enableScrollIntoField = false
|
||||
@@ -1316,7 +1320,7 @@ export default {
|
||||
const currentFieldUuids = this.currentStepFields.map((f) => f.uuid)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1400,7 +1404,21 @@ export default {
|
||||
if (response.status === 422 || response.status === 500) {
|
||||
const data = await response.json()
|
||||
|
||||
if (data.error) {
|
||||
if (data.field_uuid) {
|
||||
const field = this.fieldsUuidIndex[data.field_uuid]
|
||||
|
||||
if (field) {
|
||||
const step = this.stepFields.findIndex((fields) => fields.includes(field))
|
||||
|
||||
if (step !== -1) {
|
||||
this.goToStep(step, this.autoscrollFields)
|
||||
|
||||
this.showFillAllRequiredFields = true
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('Required field: ' + data.field_uuid))
|
||||
} else if (data.error) {
|
||||
const i18nKey = data.error.replace(/\s+/g, '_').toLowerCase()
|
||||
|
||||
alert(this.t(i18nKey) !== i18nKey ? this.t(i18nKey) : data.error)
|
||||
@@ -1435,11 +1453,7 @@ export default {
|
||||
this.isSubmittingComplete = false
|
||||
})
|
||||
}).catch(error => {
|
||||
if (error?.message === 'Image too small') {
|
||||
alert(this.t('signature_is_too_small_please_redraw'))
|
||||
} else {
|
||||
console.log(error)
|
||||
}
|
||||
console.log(error)
|
||||
}).finally(() => {
|
||||
this.isSubmitting = false
|
||||
this.isSubmittingComplete = false
|
||||
|
||||
@@ -93,7 +93,7 @@ const en = {
|
||||
reupload: 'Reupload',
|
||||
upload: 'Upload',
|
||||
files: 'Files',
|
||||
signature_is_too_small_please_redraw: 'Signature is too small. Please redraw.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'Signature is too small or simple. Please redraw.',
|
||||
wait_countdown_seconds: 'Wait {countdown} seconds'
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ const es = {
|
||||
reupload: 'Volver a subir',
|
||||
upload: 'Subir',
|
||||
files: 'Archivos',
|
||||
signature_is_too_small_please_redraw: 'La firma es demasiado pequeña. Por favor, dibújala de nuevo.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'La firma es demasiado pequeña o simple. Por favor, vuelve a dibujarla.',
|
||||
wait_countdown_seconds: 'Espera {countdown} segundos'
|
||||
}
|
||||
|
||||
@@ -289,7 +289,7 @@ const it = {
|
||||
reupload: 'Ricarica',
|
||||
upload: 'Carica',
|
||||
files: 'File',
|
||||
signature_is_too_small_please_redraw: 'La firma è troppo piccola. Ridisegnala per favore.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'La firma è troppo piccola o semplice. Ridisegnala, per favore.',
|
||||
wait_countdown_seconds: 'Attendi {countdown} secondi'
|
||||
}
|
||||
|
||||
@@ -387,7 +387,7 @@ const de = {
|
||||
reupload: 'Erneut hochladen',
|
||||
upload: 'Hochladen',
|
||||
files: 'Dateien',
|
||||
signature_is_too_small_please_redraw: 'Die Unterschrift ist zu klein. Bitte erneut zeichnen.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'Die Unterschrift ist zu klein oder zu einfach. Bitte erneut zeichnen.',
|
||||
wait_countdown_seconds: 'Warte {countdown} Sekunden'
|
||||
}
|
||||
|
||||
@@ -485,7 +485,7 @@ const fr = {
|
||||
reupload: 'Recharger',
|
||||
upload: 'Télécharger',
|
||||
files: 'Fichiers',
|
||||
signature_is_too_small_please_redraw: 'La signature est trop petite. Veuillez la redessiner.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'La signature est trop petite ou trop simple. Veuillez la redessiner.',
|
||||
wait_countdown_seconds: 'Attendez {countdown} secondes'
|
||||
}
|
||||
|
||||
@@ -583,7 +583,7 @@ const pl = {
|
||||
reupload: 'Ponowne przesłanie',
|
||||
upload: 'Przesyłanie',
|
||||
files: 'Pliki',
|
||||
signature_is_too_small_please_redraw: 'Podpis jest zbyt mały. Proszę narysować go ponownie.'
|
||||
signature_is_too_small_or_simple_please_redraw: 'Podpis jest zbyt mały lub zbyt prosty. Proszę narysować go ponownie.'
|
||||
}
|
||||
|
||||
const uk = {
|
||||
@@ -680,7 +680,7 @@ const uk = {
|
||||
reupload: 'Перезавантажити',
|
||||
upload: 'Завантажити',
|
||||
files: 'Файли',
|
||||
signature_is_too_small_please_redraw: 'Підпис занадто малий. Будь ласка, перемалюйте його.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'Підпис занадто маленький або надто простий. Будь ласка, перемалюйте.',
|
||||
wait_countdown_seconds: 'Зачекайте {countdown} секунд'
|
||||
}
|
||||
|
||||
@@ -778,7 +778,7 @@ const cs = {
|
||||
reupload: 'Znovu nahrát',
|
||||
upload: 'Nahrát',
|
||||
files: 'Soubory',
|
||||
signature_is_too_small_please_redraw: 'Podpis je příliš malý. Prosím, překreslete ho.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'Podpis je příliš malý nebo jednoduchý. Nakreslete jej prosím znovu.',
|
||||
wait_countdown_seconds: 'Počkejte {countdown} sekund'
|
||||
}
|
||||
|
||||
@@ -876,7 +876,7 @@ const pt = {
|
||||
reupload: 'Reenviar',
|
||||
upload: 'Carregar',
|
||||
files: 'Arquivos',
|
||||
signature_is_too_small_please_redraw: 'A assinatura é muito pequena. Por favor, redesenhe-a.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'A assinatura é muito pequena ou simples. Por favor, redesenhe.',
|
||||
wait_countdown_seconds: 'Aguarde {countdown} segundos'
|
||||
}
|
||||
|
||||
@@ -975,7 +975,7 @@ const he = {
|
||||
reupload: 'העלה שוב',
|
||||
upload: 'העלאה',
|
||||
files: 'קבצים',
|
||||
signature_is_too_small_please_redraw: 'החתימה קטנה מדי. אנא צייר מחדש.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'החתימה קטנה או פשוטה מדי. אנא חתום מחדש.',
|
||||
wait_countdown_seconds: 'המתן {countdown} שניות'
|
||||
}
|
||||
|
||||
@@ -1074,7 +1074,7 @@ const nl = {
|
||||
reupload: 'Opnieuw uploaden',
|
||||
upload: 'Uploaden',
|
||||
files: 'Bestanden',
|
||||
signature_is_too_small_please_redraw: 'De handtekening is te klein. Teken deze opnieuw, alstublieft.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'De handtekening is te klein of te eenvoudig. Teken opnieuw.',
|
||||
wait_countdown_seconds: 'Wacht {countdown} seconden'
|
||||
}
|
||||
|
||||
@@ -1172,7 +1172,7 @@ const ar = {
|
||||
reupload: 'إعادة التحميل',
|
||||
upload: 'تحميل',
|
||||
files: 'الملفات',
|
||||
signature_is_too_small_please_redraw: 'التوقيع صغير جدًا. يرجى إعادة الرسم.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'التوقيع صغير جدًا أو بسيط جدًا. يرجى إعادة رسمه.',
|
||||
wait_countdown_seconds: 'انتظر {countdown} ثانية'
|
||||
}
|
||||
|
||||
@@ -1269,7 +1269,7 @@ const ko = {
|
||||
reupload: '다시 업로드',
|
||||
upload: '업로드',
|
||||
files: '파일',
|
||||
signature_is_too_small_please_redraw: '서명이 너무 작습니다. 다시 그려주세요.',
|
||||
signature_is_too_small_or_simple_please_redraw: '서명이 너무 작거나 단순합니다. 다시 그려주세요.',
|
||||
wait_countdown_seconds: '{countdown}초 기다리세요'
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<label
|
||||
v-if="showFieldNames"
|
||||
: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
|
||||
v-if="field.title"
|
||||
@@ -15,7 +15,7 @@
|
||||
</template>
|
||||
</label>
|
||||
<button
|
||||
class="btn btn-outline btn-sm"
|
||||
class="btn btn-outline btn-sm reupload-button"
|
||||
@click.prevent="remove"
|
||||
>
|
||||
<IconReload :width="16" />
|
||||
@@ -25,7 +25,7 @@
|
||||
<div>
|
||||
<img
|
||||
: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>
|
||||
<input
|
||||
@@ -40,7 +40,7 @@
|
||||
<div
|
||||
v-if="field.description"
|
||||
dir="auto"
|
||||
class="mb-3 px-1"
|
||||
class="mb-3 px-1 field-description-text"
|
||||
>
|
||||
<MarkdownContent :string="field.description" />
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
>
|
||||
<label
|
||||
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
|
||||
v-if="field.title"
|
||||
@@ -25,7 +25,7 @@
|
||||
<a
|
||||
id="type_text_button"
|
||||
href="#"
|
||||
class="btn btn-outline font-medium btn-sm"
|
||||
class="btn btn-outline font-medium btn-sm type-text-button"
|
||||
@click.prevent="toggleTextInput"
|
||||
>
|
||||
<IconTextSize :width="16" />
|
||||
@@ -42,7 +42,7 @@
|
||||
<a
|
||||
id="type_text_button"
|
||||
href="#"
|
||||
class="btn btn-outline font-medium btn-sm"
|
||||
class="btn btn-outline font-medium btn-sm type-text-button"
|
||||
@click.prevent="toggleTextInput"
|
||||
>
|
||||
<IconSignature :width="16" />
|
||||
@@ -55,9 +55,7 @@
|
||||
class="tooltip"
|
||||
:data-tip="t('click_to_upload')"
|
||||
>
|
||||
<label
|
||||
class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap"
|
||||
>
|
||||
<label class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap upload-image-button">
|
||||
<IconUpload :width="16" />
|
||||
<input
|
||||
:key="uploadImageInputKey"
|
||||
@@ -74,7 +72,7 @@
|
||||
<a
|
||||
v-if="modelValue || computedPreviousValue"
|
||||
href="#"
|
||||
class="btn font-medium btn-outline btn-sm"
|
||||
class="btn font-medium btn-outline btn-sm clear-canvas-button"
|
||||
@click.prevent="remove"
|
||||
>
|
||||
<IconReload :width="16" />
|
||||
@@ -83,7 +81,7 @@
|
||||
<a
|
||||
v-else
|
||||
href="#"
|
||||
class="btn font-medium btn-outline btn-sm"
|
||||
class="btn font-medium btn-outline btn-sm clear-canvas-button"
|
||||
@click.prevent="clear"
|
||||
>
|
||||
<IconReload :width="16" />
|
||||
@@ -105,7 +103,7 @@
|
||||
<div
|
||||
v-if="field.description"
|
||||
dir="auto"
|
||||
class="mb-3 px-1"
|
||||
class="mb-3 px-1 field-description-text"
|
||||
>
|
||||
<MarkdownContent :string="field.description" />
|
||||
</div>
|
||||
@@ -120,11 +118,17 @@
|
||||
:src="attachmentsIndex[modelValue || computedPreviousValue].url"
|
||||
class="mx-auto bg-white border border-base-300 rounded max-h-44"
|
||||
>
|
||||
<canvas
|
||||
v-show="!modelValue && !computedPreviousValue"
|
||||
ref="canvas"
|
||||
class="bg-white border border-base-300 rounded-2xl w-full"
|
||||
/>
|
||||
<div class="relative">
|
||||
<div
|
||||
v-if="!isDrawInitials"
|
||||
class="absolute top-0 right-0 left-0 bottom-0"
|
||||
/>
|
||||
<canvas
|
||||
v-show="!modelValue && !computedPreviousValue"
|
||||
ref="canvas"
|
||||
class="bg-white border border-base-300 rounded-2xl w-full draw-canvas"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
v-if="!isDrawInitials && !modelValue && !computedPreviousValue"
|
||||
id="initials_text_input"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
v-if="showFieldNames && (field.name || field.title)"
|
||||
:for="field.uuid"
|
||||
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 }"
|
||||
>
|
||||
<MarkdownContent
|
||||
@@ -20,7 +20,7 @@
|
||||
<div
|
||||
v-if="field.description"
|
||||
dir="auto"
|
||||
class="mb-3 px-1"
|
||||
class="mb-3 px-1 field-description-text"
|
||||
>
|
||||
<MarkdownContent :string="field.description" />
|
||||
</div>
|
||||
@@ -50,7 +50,7 @@
|
||||
>
|
||||
<label
|
||||
:for="option.uuid"
|
||||
class="flex items-center space-x-3"
|
||||
class="flex items-center space-x-3 checkbox-label"
|
||||
@click="scrollIntoField(field)"
|
||||
>
|
||||
<input
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
v-if="showFieldNames && (field.name || field.title)"
|
||||
:for="field.uuid"
|
||||
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 }"
|
||||
>
|
||||
<MarkdownContent
|
||||
@@ -24,7 +24,7 @@
|
||||
<div
|
||||
v-if="field.description"
|
||||
dir="auto"
|
||||
class="mb-3 px-1"
|
||||
class="mb-3 px-1 field-description-text"
|
||||
>
|
||||
<MarkdownContent :string="field.description" />
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<label
|
||||
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
|
||||
v-if="field.title"
|
||||
@@ -34,7 +34,7 @@
|
||||
<button
|
||||
v-if="sessionId"
|
||||
disabled
|
||||
class="base-button w-full"
|
||||
class="base-button w-full modal-save-button"
|
||||
>
|
||||
<IconLoader
|
||||
width="22"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<label
|
||||
v-if="showFieldNames"
|
||||
: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 }"
|
||||
>
|
||||
<MarkdownContent
|
||||
@@ -17,7 +17,7 @@
|
||||
<div
|
||||
v-if="field.description"
|
||||
dir="auto"
|
||||
class="mb-3 px-1"
|
||||
class="mb-3 px-1 field-description-text"
|
||||
>
|
||||
<MarkdownContent :string="field.description" />
|
||||
</div>
|
||||
@@ -45,7 +45,7 @@
|
||||
<a
|
||||
v-if="!defaultValue"
|
||||
href="#"
|
||||
class="link"
|
||||
class="link change-phone-number-link"
|
||||
@click.prevent="isCodeSent = false"
|
||||
>
|
||||
{{ t('change_phone_number') }}
|
||||
@@ -59,7 +59,7 @@
|
||||
<a
|
||||
v-else
|
||||
href="#"
|
||||
class="link"
|
||||
class="link resend-code-link"
|
||||
@click.prevent="resendCode"
|
||||
>
|
||||
{{ isResendLoading ? t('sending') : t('resend_code') }}
|
||||
@@ -68,13 +68,13 @@
|
||||
</div>
|
||||
<div
|
||||
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
|
||||
id="country_code"
|
||||
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 }}
|
||||
</div>
|
||||
<select
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
>
|
||||
<label
|
||||
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
|
||||
v-if="field.title"
|
||||
@@ -28,7 +28,7 @@
|
||||
<a
|
||||
id="type_text_button"
|
||||
href="#"
|
||||
class="btn btn-outline btn-sm font-medium"
|
||||
class="btn btn-outline btn-sm font-medium type-text-button"
|
||||
@click.prevent="[toggleTextInput(), hideQr()]"
|
||||
>
|
||||
<IconSignature :width="16" />
|
||||
@@ -46,7 +46,7 @@
|
||||
<a
|
||||
id="type_text_button"
|
||||
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()]"
|
||||
>
|
||||
<IconTextSize :width="16" />
|
||||
@@ -61,9 +61,7 @@
|
||||
:class="{ 'hidden sm:inline': modelValue || computedPreviousValue }"
|
||||
:data-tip="t('take_photo')"
|
||||
>
|
||||
<label
|
||||
class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap"
|
||||
>
|
||||
<label class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap upload-image-button">
|
||||
<IconCamera :width="16" />
|
||||
<input
|
||||
:key="uploadImageInputKey"
|
||||
@@ -80,7 +78,7 @@
|
||||
<a
|
||||
v-if="modelValue || computedPreviousValue"
|
||||
href="#"
|
||||
class="btn btn-outline btn-sm font-medium"
|
||||
class="btn btn-outline btn-sm font-medium reupload-button"
|
||||
@click.prevent="remove"
|
||||
>
|
||||
<IconReload :width="16" />
|
||||
@@ -119,7 +117,7 @@
|
||||
<div
|
||||
v-if="field.description"
|
||||
dir="auto"
|
||||
class="mb-3 px-1"
|
||||
class="mb-3 px-1 field-description-text"
|
||||
>
|
||||
<MarkdownContent :string="field.description" />
|
||||
</div>
|
||||
@@ -167,7 +165,7 @@
|
||||
v-show="!modelValue && !computedPreviousValue"
|
||||
ref="canvas"
|
||||
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
|
||||
v-if="isShowQr"
|
||||
@@ -294,6 +292,7 @@
|
||||
<script>
|
||||
import { IconReload, IconCamera, IconSignature, IconTextSize, IconArrowsDiagonalMinimize2, IconQrcode, IconX } from '@tabler/icons-vue'
|
||||
import { cropCanvasAndExportToPNG } from './crop_canvas'
|
||||
import { isValidSignatureCanvas } from './validate_signature'
|
||||
import SignaturePad from 'signature_pad'
|
||||
import AppearsOn from './appears_on'
|
||||
import FileDropzone from './dropzone'
|
||||
@@ -600,6 +599,7 @@ export default {
|
||||
},
|
||||
drawImage (event) {
|
||||
this.remove()
|
||||
this.clear()
|
||||
this.isSignatureStarted = true
|
||||
|
||||
this.drawOnCanvas(event.target.files[0], this.$refs.canvas)
|
||||
@@ -682,8 +682,14 @@ export default {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
cropCanvasAndExportToPNG(this.$refs.canvas, { errorOnTooSmall: true }).then(async (blob) => {
|
||||
if (this.isSignatureStarted && this.pad.toData().length > 0 && !isValidSignatureCanvas(this.pad.toData())) {
|
||||
alert(this.t('signature_is_too_small_or_simple_please_redraw'))
|
||||
|
||||
return Promise.reject(new Error('Image too small or simple'))
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
cropCanvasAndExportToPNG(this.$refs.canvas).then(async (blob) => {
|
||||
const file = new File([blob], 'signature.png', { type: 'image/png' })
|
||||
|
||||
if (this.dryRun) {
|
||||
@@ -719,12 +725,6 @@ export default {
|
||||
return resolve(attachment)
|
||||
})
|
||||
}
|
||||
}).catch((error) => {
|
||||
if (error.message === 'Image too small' && this.field.required === false) {
|
||||
return resolve({})
|
||||
} else {
|
||||
return reject(error)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
v-if="showFieldNames && (field.name || field.title)"
|
||||
:for="field.uuid"
|
||||
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 }"
|
||||
>
|
||||
<MarkdownContent
|
||||
@@ -24,7 +24,7 @@
|
||||
<div
|
||||
v-if="field.description"
|
||||
dir="auto"
|
||||
class="mb-3 px-1"
|
||||
class="mb-3 px-1 field-description-text"
|
||||
>
|
||||
<MarkdownContent :string="field.description" />
|
||||
</div>
|
||||
@@ -67,7 +67,7 @@
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
class="btn btn-ghost btn-circle btn-sm"
|
||||
class="btn btn-ghost btn-circle btn-sm toggle-multiline-text-button"
|
||||
@click.prevent="toggleTextArea"
|
||||
>
|
||||
<IconAlignBoxLeftTop />
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
function isValidSignatureCanvas (data) {
|
||||
if (data.length === 0) return false
|
||||
|
||||
const strokes = data.filter(stroke => Array.isArray(stroke.points) && stroke.points.length > 2)
|
||||
|
||||
if (strokes.length === 0) return false
|
||||
|
||||
let skippedStraightLine = 0
|
||||
|
||||
const validStrokes = strokes.filter(stroke => {
|
||||
const points = stroke.points
|
||||
const first = points[0]
|
||||
const last = points[points.length - 1]
|
||||
const A = last.y - first.y
|
||||
const B = first.x - last.x
|
||||
const C = last.x * first.y - first.x * last.y
|
||||
const lineLength = Math.sqrt(A * A + B * B)
|
||||
|
||||
const totalDeviation = points.reduce((sum, p) => {
|
||||
const distanceToLine = Math.abs(A * p.x + B * p.y + C) / lineLength
|
||||
return sum + distanceToLine
|
||||
}, 0)
|
||||
|
||||
const avgDeviation = totalDeviation / points.length
|
||||
|
||||
if (avgDeviation < 3 && skippedStraightLine < 2) {
|
||||
skippedStraightLine++
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return validStrokes.length > 0
|
||||
}
|
||||
|
||||
export { isValidSignatureCanvas }
|
||||
@@ -1,7 +1,5 @@
|
||||
<template>
|
||||
<label
|
||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5"
|
||||
>
|
||||
<label class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label">
|
||||
<MarkdownContent
|
||||
v-if="field.title"
|
||||
:string="field.title"
|
||||
@@ -11,13 +9,13 @@
|
||||
<div
|
||||
v-if="field.description"
|
||||
dir="auto"
|
||||
class="mb-4 px-1"
|
||||
class="mb-4 px-1 field-description-text"
|
||||
>
|
||||
<MarkdownContent :string="field.description" />
|
||||
</div>
|
||||
<div
|
||||
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') }}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="absolute overflow-visible group"
|
||||
class="absolute overflow-visible group field-area-container"
|
||||
:style="positionStyle"
|
||||
:class="{ 'z-[1]': isMoved || isDragged }"
|
||||
@pointerdown.stop
|
||||
@@ -33,7 +33,7 @@
|
||||
</div>
|
||||
<div
|
||||
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"
|
||||
@mousedown.stop
|
||||
@pointerdown.stop
|
||||
@@ -41,7 +41,7 @@
|
||||
<FieldSubmitter
|
||||
v-if="field.type != 'heading'"
|
||||
v-model="field.submitter_uuid"
|
||||
class="border-r"
|
||||
class="border-r roles-dropdown"
|
||||
:compact="true"
|
||||
:editable="editable && (!defaultField || defaultField.role !== submitter?.name)"
|
||||
:allow-add-new="!defaultSubmitters.length"
|
||||
@@ -108,7 +108,7 @@
|
||||
>{{ t('editable') }}</label>
|
||||
<span
|
||||
v-if="field.type !== 'payment' && !isValueInput"
|
||||
class="dropdown dropdown-end"
|
||||
class="dropdown dropdown-end field-area-settings-dropdown"
|
||||
@mouseenter="renderDropdown = true"
|
||||
@touchstart="renderDropdown = true"
|
||||
>
|
||||
@@ -141,6 +141,7 @@
|
||||
:with-required="false"
|
||||
:with-areas="false"
|
||||
@click-formula="isShowFormulaModal = true"
|
||||
@click-font="isShowFontModal = true"
|
||||
@click-description="isShowDescriptionModal = true"
|
||||
@click-condition="isShowConditionsModal = true"
|
||||
@scroll-to="[selectedAreaRef.value = $event, $emit('scroll-to', $event)]"
|
||||
@@ -159,9 +160,9 @@
|
||||
</div>
|
||||
<div
|
||||
ref="touchValueTarget"
|
||||
class="flex items-center h-full w-full"
|
||||
class="flex items-center h-full w-full field-area"
|
||||
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"
|
||||
>
|
||||
<span
|
||||
@@ -253,6 +254,17 @@
|
||||
@close="isShowFormulaModal = false"
|
||||
/>
|
||||
</Teleport>
|
||||
<Teleport
|
||||
v-if="isShowFontModal"
|
||||
:to="modalContainerEl"
|
||||
>
|
||||
<FontModal
|
||||
:field="field"
|
||||
:editable="editable && !defaultField"
|
||||
:build-default-name="buildDefaultName"
|
||||
@close="isShowFontModal = false"
|
||||
/>
|
||||
</Teleport>
|
||||
<Teleport
|
||||
v-if="isShowConditionsModal"
|
||||
:to="modalContainerEl"
|
||||
@@ -283,6 +295,7 @@ import FieldType from './field_type'
|
||||
import Field from './field'
|
||||
import FieldSettings from './field_settings'
|
||||
import FormulaModal from './formula_modal'
|
||||
import FontModal from './font_modal'
|
||||
import ConditionsModal from './conditions_modal'
|
||||
import DescriptionModal from './description_modal'
|
||||
import { IconX, IconCheck, IconDotsVertical } from '@tabler/icons-vue'
|
||||
@@ -295,6 +308,7 @@ export default {
|
||||
IconCheck,
|
||||
FieldSettings,
|
||||
FormulaModal,
|
||||
FontModal,
|
||||
IconDotsVertical,
|
||||
DescriptionModal,
|
||||
ConditionsModal,
|
||||
@@ -352,6 +366,7 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
isShowFormulaModal: false,
|
||||
isShowFontModal: false,
|
||||
isShowConditionsModal: false,
|
||||
isContenteditable: false,
|
||||
isSettingsFocus: false,
|
||||
@@ -386,11 +401,19 @@ export default {
|
||||
defaultName () {
|
||||
return this.buildDefaultName(this.field, this.template.fields)
|
||||
},
|
||||
alignClasses () {
|
||||
fontClasses () {
|
||||
if (!this.field.preferences) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return {
|
||||
center: 'justify-center',
|
||||
left: 'justify-start',
|
||||
right: 'justify-end'
|
||||
'justify-center': this.field.preferences.align === 'center',
|
||||
'justify-start': this.field.preferences.align === 'left',
|
||||
'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 () {
|
||||
@@ -483,6 +506,10 @@ export default {
|
||||
this.$el.getRootNode().activeElement.blur()
|
||||
},
|
||||
maybeToggleDefaultValue () {
|
||||
if (!this.editable) {
|
||||
return
|
||||
}
|
||||
|
||||
if (['text', 'number'].includes(this.field.type)) {
|
||||
this.isContenteditable = true
|
||||
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
<template>
|
||||
<div
|
||||
ref="dragContainer"
|
||||
style="max-width: 1600px"
|
||||
class="mx-auto pl-3 h-full"
|
||||
:class="isMobile ? 'pl-4' : 'md:pl-4'"
|
||||
@dragover="onDragover"
|
||||
>
|
||||
<DragPlaceholder
|
||||
ref="dragPlaceholder"
|
||||
:field="fieldsDragFieldRef.value || toRaw(dragField)"
|
||||
:is-field="template.fields.includes(fieldsDragFieldRef.value)"
|
||||
:is-default="defaultFields.includes(toRaw(dragField))"
|
||||
:is-required="defaultRequiredFields.includes(toRaw(dragField))"
|
||||
/>
|
||||
<div
|
||||
v-if="pendingFieldAttachmentUuids.length && editable"
|
||||
class="top-1.5 sticky h-0 z-20 max-w-2xl mx-auto"
|
||||
@@ -32,7 +41,7 @@
|
||||
<div
|
||||
v-if="$slots.buttons || withTitle"
|
||||
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 }"
|
||||
:style="{ backgroundColor }"
|
||||
>
|
||||
@@ -47,7 +56,7 @@
|
||||
v-if="withTitle"
|
||||
:model-value="template.name"
|
||||
: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"
|
||||
@update:model-value="updateName"
|
||||
/>
|
||||
@@ -60,6 +69,7 @@
|
||||
<template v-else>
|
||||
<a
|
||||
v-if="withSignYourselfButton"
|
||||
id="sign_yourself_button"
|
||||
:href="template.submitters.length > 1 ? `/templates/${template.id}/submissions/new?selfsign=true` : `/d/${template.slug}`"
|
||||
class="btn btn-primary btn-ghost text-base hidden md:flex"
|
||||
:target="template.submitters.length > 1 ? '' : '_blank'"
|
||||
@@ -76,6 +86,7 @@
|
||||
</a>
|
||||
<a
|
||||
v-if="withSendButton"
|
||||
id="send_button"
|
||||
:href="`/templates/${template.id}/submissions/new?with_link=true`"
|
||||
data-turbo-frame="modal"
|
||||
class="white-button md:!px-6"
|
||||
@@ -91,6 +102,7 @@
|
||||
</a>
|
||||
<span
|
||||
v-if="editable"
|
||||
id="save_button_container"
|
||||
class="flex"
|
||||
>
|
||||
<button
|
||||
@@ -163,7 +175,7 @@
|
||||
</div>
|
||||
<div
|
||||
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%]')"
|
||||
>
|
||||
<div
|
||||
@@ -205,7 +217,7 @@
|
||||
<button
|
||||
v-if="sortedDocuments.length && editable && withAddPageButton"
|
||||
id="add_blank_page_button"
|
||||
class="btn btn-outline w-full"
|
||||
class="btn btn-outline w-full add-blank-page-button"
|
||||
@click.prevent="addBlankPage"
|
||||
>
|
||||
<IconInnerShadowTop
|
||||
@@ -239,7 +251,7 @@
|
||||
<button
|
||||
v-if="withAddPageButton"
|
||||
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"
|
||||
>
|
||||
<IconInnerShadowTop
|
||||
@@ -266,9 +278,10 @@
|
||||
:is-drag="!!dragField"
|
||||
:input-mode="inputMode"
|
||||
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
||||
:allow-draw="!onlyDefinedFields"
|
||||
:allow-draw="!onlyDefinedFields || drawField"
|
||||
:data-document-uuid="document.uuid"
|
||||
:default-submitters="defaultSubmitters"
|
||||
:drag-field-placeholder="fieldsDragFieldRef.value || dragField"
|
||||
:with-field-placeholder="withFieldPlaceholder"
|
||||
:draw-field="drawField"
|
||||
:draw-field-type="drawFieldType"
|
||||
@@ -307,7 +320,7 @@
|
||||
<button
|
||||
v-if="withAddPageButton"
|
||||
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"
|
||||
>
|
||||
<IconInnerShadowTop
|
||||
@@ -327,7 +340,7 @@
|
||||
<div
|
||||
v-if="withFieldsList && !isMobile"
|
||||
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'"
|
||||
>
|
||||
<div
|
||||
@@ -335,13 +348,13 @@
|
||||
class="sticky inset-0 h-full z-20"
|
||||
: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>
|
||||
{{ t('draw_field_on_the_document') }}
|
||||
</p>
|
||||
<div>
|
||||
<button
|
||||
class="base-button"
|
||||
class="base-button cancel-draw-button"
|
||||
@click="clearDrawField"
|
||||
>
|
||||
{{ t('cancel') }}
|
||||
@@ -374,12 +387,14 @@
|
||||
:with-sticky-submitters="withStickySubmitters"
|
||||
:only-defined-fields="onlyDefinedFields"
|
||||
:editable="editable"
|
||||
:show-tour-start-form="showTourStartForm"
|
||||
@add-field="addField"
|
||||
@set-draw="[drawField = $event.field, drawOption = $event.option]"
|
||||
@set-draw-type="[drawFieldType = $event, showDrawField = true]"
|
||||
@set-drag="dragField = $event"
|
||||
@set-drag-placeholder="$refs.dragPlaceholder.dragPlaceholder = $event"
|
||||
@change-submitter="selectedSubmitter = $event"
|
||||
@drag-end="dragField = null"
|
||||
@drag-end="[dragField = null, $refs.dragPlaceholder.dragPlaceholder = null]"
|
||||
@scroll-to-area="scrollToArea"
|
||||
/>
|
||||
</div>
|
||||
@@ -408,13 +423,17 @@
|
||||
@select="startFieldDraw($event)"
|
||||
/>
|
||||
</div>
|
||||
<div id="docuseal_modal_container" />
|
||||
<div
|
||||
id="docuseal_modal_container"
|
||||
class="modal-container"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Upload from './upload'
|
||||
import Dropzone from './dropzone'
|
||||
import DragPlaceholder from './drag_placeholder'
|
||||
import Fields from './fields'
|
||||
import MobileDrawField from './mobile_draw_field'
|
||||
import Document from './document'
|
||||
@@ -426,13 +445,14 @@ import MobileFields from './mobile_fields'
|
||||
import FieldSubmitter from './field_submitter'
|
||||
import { IconPlus, IconUsersPlus, IconDeviceFloppy, IconChevronDown, IconEye, IconWritingSign, IconInnerShadowTop, IconInfoCircle, IconAdjustments } from '@tabler/icons-vue'
|
||||
import { v4 } from 'uuid'
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, toRaw } from 'vue'
|
||||
import * as i18n from './i18n'
|
||||
|
||||
export default {
|
||||
name: 'TemplateBuilder',
|
||||
components: {
|
||||
Upload,
|
||||
DragPlaceholder,
|
||||
Document,
|
||||
Fields,
|
||||
IconInfoCircle,
|
||||
@@ -457,6 +477,7 @@ export default {
|
||||
template: this.template,
|
||||
save: this.save,
|
||||
t: this.t,
|
||||
assignDropAreaSize: this.assignDropAreaSize,
|
||||
currencies: this.currencies,
|
||||
locale: this.locale,
|
||||
baseFetch: this.baseFetch,
|
||||
@@ -683,6 +704,11 @@ export default {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({ headers: {} })
|
||||
},
|
||||
showTourStartForm: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
@@ -833,6 +859,17 @@ export default {
|
||||
this.documentRefs = []
|
||||
},
|
||||
methods: {
|
||||
toRaw,
|
||||
onDragover (e) {
|
||||
if (this.$refs.dragPlaceholder?.dragPlaceholder) {
|
||||
this.$refs.dragPlaceholder.isMask = e.target.id === 'mask'
|
||||
|
||||
const ref = this.$refs.dragPlaceholder.dragPlaceholder
|
||||
|
||||
ref.x = e.clientX - ref.offsetX
|
||||
ref.y = e.clientY - ref.offsetY
|
||||
}
|
||||
},
|
||||
reorderFields (item) {
|
||||
const itemFields = []
|
||||
const fields = []
|
||||
@@ -1316,6 +1353,14 @@ export default {
|
||||
}
|
||||
},
|
||||
onDropfield (area) {
|
||||
if (this.$refs.dragPlaceholder) {
|
||||
this.$refs.dragPlaceholder.dragPlaceholder = null
|
||||
}
|
||||
|
||||
if (!this.editable) {
|
||||
return
|
||||
}
|
||||
|
||||
const field = this.fieldsDragFieldRef.value || {
|
||||
name: '',
|
||||
uuid: v4(),
|
||||
@@ -1324,6 +1369,10 @@ export default {
|
||||
...this.dragField
|
||||
}
|
||||
|
||||
if (!field.type) {
|
||||
field.type = 'text'
|
||||
}
|
||||
|
||||
if (!this.fieldsDragFieldRef.value) {
|
||||
if (['select', 'multiple', 'radio'].includes(field.type)) {
|
||||
if (this.dragField?.options?.length) {
|
||||
@@ -1338,9 +1387,8 @@ export default {
|
||||
}
|
||||
|
||||
if (field.type === 'date') {
|
||||
field.preferences = {
|
||||
format: this.defaultDateFormat
|
||||
}
|
||||
field.preferences ||= {}
|
||||
field.preferences.format ||= this.defaultDateFormat
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1351,70 +1399,18 @@ export default {
|
||||
attachment_uuid: area.attachment_uuid
|
||||
}
|
||||
|
||||
const previousField = [...this.template.fields].reverse().find((f) => f.type === field.type)
|
||||
|
||||
let baseArea
|
||||
|
||||
if (this.selectedField?.type === field.type) {
|
||||
baseArea = this.selectedAreaRef.value
|
||||
} else if (previousField?.areas?.length) {
|
||||
baseArea = previousField.areas[previousField.areas.length - 1]
|
||||
} else {
|
||||
if (['checkbox'].includes(field.type)) {
|
||||
baseArea = {
|
||||
w: area.maskW / 30 / area.maskW,
|
||||
h: area.maskW / 30 / area.maskW * (area.maskW / area.maskH)
|
||||
}
|
||||
} else if (field.type === 'image') {
|
||||
baseArea = {
|
||||
w: area.maskW / 5 / area.maskW,
|
||||
h: (area.maskW / 5 / area.maskW) * (area.maskW / area.maskH)
|
||||
}
|
||||
} else if (field.type === 'signature' || field.type === 'stamp' || field.type === 'verification') {
|
||||
baseArea = {
|
||||
w: area.maskW / 5 / area.maskW,
|
||||
h: (area.maskW / 5 / area.maskW) * (area.maskW / area.maskH) / 2
|
||||
}
|
||||
} else if (field.type === 'initials') {
|
||||
baseArea = {
|
||||
w: area.maskW / 10 / area.maskW,
|
||||
h: area.maskW / 35 / area.maskW
|
||||
}
|
||||
} else {
|
||||
baseArea = {
|
||||
w: area.maskW / 5 / area.maskW,
|
||||
h: area.maskW / 35 / area.maskW
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fieldArea.w = baseArea.w
|
||||
fieldArea.h = baseArea.h
|
||||
fieldArea.y = fieldArea.y - baseArea.h / 2
|
||||
|
||||
if (field.type === 'cells') {
|
||||
fieldArea.cell_w = baseArea.cell_w || (baseArea.w / 5)
|
||||
}
|
||||
|
||||
field.areas ||= []
|
||||
|
||||
const lastArea = field.areas[field.areas.length - 1]
|
||||
|
||||
if (lastArea) {
|
||||
fieldArea.w = lastArea.w
|
||||
fieldArea.h = lastArea.h
|
||||
}
|
||||
this.assignDropAreaSize(fieldArea, field, area)
|
||||
|
||||
if (field.width) {
|
||||
fieldArea.w = field.width / area.maskW
|
||||
delete field.width
|
||||
}
|
||||
|
||||
if (field.height) {
|
||||
fieldArea.h = field.height / area.maskH
|
||||
delete field.height
|
||||
}
|
||||
|
||||
field.areas ||= []
|
||||
|
||||
field.areas.push(fieldArea)
|
||||
|
||||
this.selectedAreaRef.value = fieldArea
|
||||
@@ -1438,6 +1434,72 @@ export default {
|
||||
})
|
||||
}
|
||||
},
|
||||
assignDropAreaSize (fieldArea, field, area) {
|
||||
const fieldType = field.type || 'text'
|
||||
|
||||
const previousField = [...this.template.fields].reverse().find((f) => f.type === fieldType)
|
||||
|
||||
let baseArea
|
||||
|
||||
if (this.selectedField?.type === fieldType) {
|
||||
baseArea = this.selectedAreaRef.value
|
||||
} else if (previousField?.areas?.length) {
|
||||
baseArea = previousField.areas[previousField.areas.length - 1]
|
||||
} else {
|
||||
if (['checkbox'].includes(fieldType)) {
|
||||
baseArea = {
|
||||
w: area.maskW / 30 / area.maskW,
|
||||
h: area.maskW / 30 / area.maskW * (area.maskW / area.maskH)
|
||||
}
|
||||
} else if (fieldType === 'image') {
|
||||
baseArea = {
|
||||
w: area.maskW / 5 / area.maskW,
|
||||
h: (area.maskW / 5 / area.maskW) * (area.maskW / area.maskH)
|
||||
}
|
||||
} else if (fieldType === 'signature' || fieldType === 'stamp' || fieldType === 'verification') {
|
||||
baseArea = {
|
||||
w: area.maskW / 5 / area.maskW,
|
||||
h: (area.maskW / 5 / area.maskW) * (area.maskW / area.maskH) / 2
|
||||
}
|
||||
} else if (fieldType === 'initials') {
|
||||
baseArea = {
|
||||
w: area.maskW / 10 / area.maskW,
|
||||
h: area.maskW / 35 / area.maskW
|
||||
}
|
||||
} else {
|
||||
baseArea = {
|
||||
w: area.maskW / 5 / area.maskW,
|
||||
h: area.maskW / 35 / area.maskW
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fieldArea.w = baseArea.w
|
||||
fieldArea.h = baseArea.h
|
||||
|
||||
if (fieldType === 'cells') {
|
||||
fieldArea.cell_w = baseArea.cell_w || (baseArea.w / 5)
|
||||
}
|
||||
|
||||
if (field.areas?.length) {
|
||||
const lastArea = field.areas[field.areas.length - 1]
|
||||
|
||||
if (lastArea) {
|
||||
fieldArea.w = lastArea.w
|
||||
fieldArea.h = lastArea.h
|
||||
}
|
||||
}
|
||||
|
||||
if (field.width) {
|
||||
fieldArea.w = field.width / area.maskW
|
||||
}
|
||||
|
||||
if (field.height) {
|
||||
fieldArea.h = field.height / area.maskH
|
||||
}
|
||||
|
||||
fieldArea.y = fieldArea.y - fieldArea.h / 2
|
||||
},
|
||||
addBlankPage () {
|
||||
this.isLoadingBlankPage = true
|
||||
|
||||
|
||||
@@ -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="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) }}
|
||||
</span>
|
||||
<a
|
||||
href="#"
|
||||
class="text-xl"
|
||||
class="text-xl modal-close-button"
|
||||
@click.prevent="$emit('close')"
|
||||
>×</a>
|
||||
</div>
|
||||
@@ -50,9 +50,9 @@
|
||||
v-if="conditions.length > 1"
|
||||
class="flex justify-between mx-1"
|
||||
>
|
||||
<span class="text-sm">
|
||||
<label class="text-sm">
|
||||
{{ t('condition') }} {{ cindex + 1 }}
|
||||
</span>
|
||||
</label>
|
||||
<a
|
||||
href="#"
|
||||
class="link text-sm"
|
||||
@@ -131,9 +131,7 @@
|
||||
class="inline float-right link text-right mb-3 px-2"
|
||||
@click.prevent="conditions.push({})"
|
||||
> + {{ t('add_condition') }}</a>
|
||||
<button
|
||||
class="base-button w-full mt-2"
|
||||
>
|
||||
<button class="base-button w-full mt-2 modal-save-button">
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex space-x-2">
|
||||
<Contenteditable
|
||||
class="w-full block mr-6"
|
||||
class="w-full block mr-6 document-preview-name"
|
||||
:model-value="item.name"
|
||||
:icon-width="16"
|
||||
@update:model-value="onUpdateName"
|
||||
@@ -15,7 +15,7 @@
|
||||
/>
|
||||
<button
|
||||
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"
|
||||
@click.stop="$emit('up', item)"
|
||||
>
|
||||
@@ -23,14 +23,14 @@
|
||||
</button>
|
||||
<button
|
||||
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"
|
||||
@click.stop="$emit('down', item)"
|
||||
>
|
||||
↓
|
||||
</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"
|
||||
@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="flex justify-between items-center border-b pb-2 mb-2 font-medium">
|
||||
<span>
|
||||
<span class="modal-title">
|
||||
{{ field.name || buildDefaultName(field, template.fields) }}
|
||||
</span>
|
||||
<a
|
||||
href="#"
|
||||
class="text-xl"
|
||||
class="text-xl modal-close-button"
|
||||
@click.prevent="$emit('close')"
|
||||
>×</a>
|
||||
</div>
|
||||
@@ -55,9 +55,7 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="base-button w-full mt-4"
|
||||
>
|
||||
<button class="base-button w-full mt-4 modal-save-button">
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
:is-drag="isDrag"
|
||||
:with-field-placeholder="withFieldPlaceholder"
|
||||
:default-fields="defaultFields"
|
||||
:drag-field-placeholder="dragFieldPlaceholder"
|
||||
:default-submitters="defaultSubmitters"
|
||||
:draw-field="drawField"
|
||||
:draw-field-type="drawFieldType"
|
||||
@@ -39,6 +40,11 @@ export default {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
dragFieldPlaceholder: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
inputMode: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<Field
|
||||
v-if="dragPlaceholder && isField && !isMask && field"
|
||||
ref="dragPlaceholder"
|
||||
:style="dragPlaceholderStyle"
|
||||
:field="field"
|
||||
:with-options="false"
|
||||
class="fixed z-20 pointer-events-none"
|
||||
:editable="false"
|
||||
/>
|
||||
<div
|
||||
v-else-if="dragPlaceholder && (isDefault || isRequired) && !isMask && field"
|
||||
ref="dragPlaceholder"
|
||||
:style="[dragPlaceholderStyle, { backgroundColor: backgroundColor }]"
|
||||
class="fixed z-20 border border-base-300 rounded group default-field fields-list-item pointer-events-none"
|
||||
>
|
||||
<div class="flex items-center justify-between relative cursor-grab">
|
||||
<div class="flex items-center p-1 space-x-1">
|
||||
<IconDrag />
|
||||
<component
|
||||
:is="fieldIcons[field.type || 'text']"
|
||||
:stroke-width="1.6"
|
||||
:width="20"
|
||||
/>
|
||||
<span class="block pl-0.5">
|
||||
{{ field.title || field.name }}
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
v-if="isRequired"
|
||||
:data-tip="t('required')"
|
||||
class="text-red-400 text-3xl pr-1.5 tooltip tooltip-left h-8"
|
||||
>
|
||||
*
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
v-else-if="dragPlaceholder && !isMask && field"
|
||||
ref="dragPlaceholder"
|
||||
class="fixed field-type-button z-20 flex items-center justify-center border border-dashed w-full rounded border-base-content/20 opacity-90 pointer-events-none"
|
||||
:style="[dragPlaceholderStyle, { backgroundColor }]"
|
||||
>
|
||||
<div
|
||||
class="flex items-console cursor-grab h-full absolute left-0 bg-base-200/50"
|
||||
>
|
||||
<IconDrag class="my-auto" />
|
||||
</div>
|
||||
<div class="flex items-center flex-col px-2 py-2">
|
||||
<component :is="fieldIcons[field.type || 'text']" />
|
||||
<span class="text-xs mt-1">
|
||||
{{ fieldNames[field.type || 'text'] }}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Field from './field'
|
||||
import IconDrag from './icon_drag'
|
||||
import FieldType from './field_type'
|
||||
|
||||
export default {
|
||||
name: 'DragPlaceholder',
|
||||
components: {
|
||||
Field,
|
||||
IconDrag
|
||||
},
|
||||
inject: ['t', 'backgroundColor'],
|
||||
props: {
|
||||
field: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
isDefault: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
isRequired: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
isField: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isMask: false,
|
||||
dragPlaceholder: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dragPlaceholderStyle () {
|
||||
if (this.dragPlaceholder) {
|
||||
return {
|
||||
left: this.dragPlaceholder.x + 'px',
|
||||
top: this.dragPlaceholder.y + 'px',
|
||||
width: this.dragPlaceholder.w + 'px',
|
||||
height: this.dragPlaceholder.h + 'px'
|
||||
}
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
fieldNames: FieldType.computed.fieldNames,
|
||||
fieldIcons: FieldType.computed.fieldIcons
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -3,7 +3,7 @@
|
||||
class="list-field group mb-2"
|
||||
>
|
||||
<div
|
||||
class="border border-base-300 rounded rounded-tr-none relative group"
|
||||
class="border border-base-300 rounded relative group fields-list-item"
|
||||
:style="{ backgroundColor: backgroundColor }"
|
||||
>
|
||||
<div class="flex items-center justify-between relative group/contenteditable-container">
|
||||
@@ -98,7 +98,7 @@
|
||||
/>
|
||||
<span
|
||||
v-else-if="field.type !== 'heading'"
|
||||
class="dropdown dropdown-end"
|
||||
class="dropdown dropdown-end field-settings-dropdown"
|
||||
@mouseenter="renderDropdown = true"
|
||||
@touchstart="renderDropdown = true"
|
||||
>
|
||||
@@ -127,6 +127,7 @@
|
||||
:editable="editable"
|
||||
:background-color="dropdownBgColor"
|
||||
@click-formula="isShowFormulaModal = true"
|
||||
@click-font="isShowFontModal = true"
|
||||
@click-description="isShowDescriptionModal = true"
|
||||
@click-condition="isShowConditionsModal = true"
|
||||
@set-draw="$emit('set-draw', $event)"
|
||||
@@ -136,7 +137,7 @@
|
||||
</ul>
|
||||
</span>
|
||||
<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')"
|
||||
@click="$emit('remove', field)"
|
||||
>
|
||||
@@ -148,7 +149,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="field.options"
|
||||
v-if="field.options && withOptions"
|
||||
ref="options"
|
||||
class="border-t border-base-300 mx-2 pt-2 space-y-1.5"
|
||||
draggable="true"
|
||||
@@ -231,6 +232,17 @@
|
||||
@close="isShowFormulaModal = false"
|
||||
/>
|
||||
</Teleport>
|
||||
<Teleport
|
||||
v-if="isShowFontModal"
|
||||
:to="modalContainerEl"
|
||||
>
|
||||
<FontModal
|
||||
:field="field"
|
||||
:editable="editable && !defaultField"
|
||||
:build-default-name="buildDefaultName"
|
||||
@close="isShowFontModal = false"
|
||||
/>
|
||||
</Teleport>
|
||||
<Teleport
|
||||
v-if="isShowConditionsModal"
|
||||
:to="modalContainerEl"
|
||||
@@ -261,6 +273,7 @@ import FieldType from './field_type'
|
||||
import PaymentSettings from './payment_settings'
|
||||
import FieldSettings from './field_settings'
|
||||
import FormulaModal from './formula_modal'
|
||||
import FontModal from './font_modal'
|
||||
import ConditionsModal from './conditions_modal'
|
||||
import DescriptionModal from './description_modal'
|
||||
import { IconRouteAltLeft, IconMathFunction, IconNewSection, IconTrashX, IconSettings } from '@tabler/icons-vue'
|
||||
@@ -275,6 +288,7 @@ export default {
|
||||
PaymentSettings,
|
||||
IconNewSection,
|
||||
FormulaModal,
|
||||
FontModal,
|
||||
DescriptionModal,
|
||||
ConditionsModal,
|
||||
IconRouteAltLeft,
|
||||
@@ -288,6 +302,11 @@ export default {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
withOptions: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
defaultField: {
|
||||
type: Object,
|
||||
required: false,
|
||||
@@ -305,6 +324,7 @@ export default {
|
||||
isNameFocus: false,
|
||||
showPaymentModal: false,
|
||||
isShowFormulaModal: false,
|
||||
isShowFontModal: false,
|
||||
isShowConditionsModal: false,
|
||||
isShowDescriptionModal: false,
|
||||
renderDropdown: false
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
v-if="['number', 'cells'].includes(field.type)"
|
||||
v-if="['cells'].includes(field.type)"
|
||||
class="py-1.5 px-1 relative"
|
||||
@click.stop
|
||||
>
|
||||
@@ -330,6 +330,19 @@
|
||||
v-if="field.type != 'stamp'"
|
||||
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
|
||||
v-if="field.type != 'stamp'"
|
||||
>
|
||||
@@ -427,7 +440,7 @@
|
||||
</template>
|
||||
|
||||
<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 {
|
||||
name: 'FieldSettings',
|
||||
@@ -438,6 +451,7 @@ export default {
|
||||
IconRouteAltLeft,
|
||||
IconCopy,
|
||||
IconNewSection,
|
||||
IconTypography,
|
||||
IconX
|
||||
},
|
||||
inject: ['template', 'save', 't'],
|
||||
@@ -472,7 +486,7 @@ export default {
|
||||
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 () {
|
||||
return {
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
@touchstart="renderDropdown = true"
|
||||
>
|
||||
<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">
|
||||
<span
|
||||
class="w-3 h-3 flex-shrink-0 rounded-full"
|
||||
@@ -22,7 +22,7 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown dropdown-top dropdown-end">
|
||||
<div class="dropdown dropdown-top dropdown-end roles-dropdown-mobile">
|
||||
<label
|
||||
tabindex="0"
|
||||
class="bg-base-100 cursor-pointer rounded-md p-2 border border-base-300 w-full flex justify-center"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<span
|
||||
class="dropdown"
|
||||
class="dropdown field-types-dropdown"
|
||||
@mouseenter="renderDropdown = true"
|
||||
@touchstart="renderDropdown = true"
|
||||
>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div :class="withStickySubmitters ? 'sticky top-0 z-[1]' : ''">
|
||||
<FieldSubmitter
|
||||
:model-value="selectedSubmitter.uuid"
|
||||
class="roles-dropdown w-full rounded-lg"
|
||||
class="roles-dropdown w-full rounded-lg roles-dropdown"
|
||||
:style="withStickySubmitters ? { backgroundColor } : {}"
|
||||
:submitters="submitters"
|
||||
:menu-style="{ overflow: 'auto', display: 'flex', flexDirection: 'row', maxHeight: 'calc(100vh - 120px)', backgroundColor: ['', null, 'transparent'].includes(backgroundColor) ? 'white' : backgroundColor }"
|
||||
@@ -25,11 +25,11 @@
|
||||
:data-uuid="field.uuid"
|
||||
:field="field"
|
||||
:type-index="fields.filter((f) => f.type === field.type).indexOf(field)"
|
||||
:editable="editable && (!fieldsDragFieldRef.value || fieldsDragFieldRef.value !== field)"
|
||||
:editable="editable"
|
||||
:default-field="defaultFieldsIndex[field.name]"
|
||||
:draggable="editable"
|
||||
@dragstart="fieldsDragFieldRef.value = field"
|
||||
@dragend="fieldsDragFieldRef.value = null"
|
||||
@dragstart="[fieldsDragFieldRef.value = field, removeDragOverlay($event), setDragPlaceholder($event)]"
|
||||
@dragend="[fieldsDragFieldRef.value = null, $emit('set-drag-placeholder', null)]"
|
||||
@remove="removeField"
|
||||
@scroll-to="$emit('scroll-to-area', $event)"
|
||||
@set-draw="$emit('set-draw', $event)"
|
||||
@@ -74,8 +74,8 @@
|
||||
<div
|
||||
:style="{ backgroundColor }"
|
||||
draggable="true"
|
||||
class="default-field border border-base-300 rounded rounded-tr-none relative group mb-2"
|
||||
@dragstart="onDragstart({ type: 'text', ...field })"
|
||||
class="border border-base-300 rounded relative group mb-2 default-field fields-list-item"
|
||||
@dragstart="onDragstart($event, field)"
|
||||
@dragend="$emit('drag-end')"
|
||||
>
|
||||
<div class="flex items-center justify-between relative cursor-grab">
|
||||
@@ -104,7 +104,8 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="editable && !onlyDefinedFields"
|
||||
class="grid grid-cols-3 gap-1 pb-2"
|
||||
id="field-types-grid"
|
||||
class="grid grid-cols-3 gap-1 pb-2 fields-grid"
|
||||
>
|
||||
<template
|
||||
v-for="(icon, type) in fieldIconsSorted"
|
||||
@@ -112,11 +113,12 @@
|
||||
>
|
||||
<button
|
||||
v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment') && (withVerification || type != 'verification')"
|
||||
:id="`${type}_type_field_button`"
|
||||
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 }"
|
||||
:class="drawFieldType === type ? 'border-base-content/40' : 'border-base-300 hover:border-base-content/20'"
|
||||
@dragstart="onDragstart({ type: type })"
|
||||
@dragstart="onDragstart($event, { type: type })"
|
||||
@dragend="$emit('drag-end')"
|
||||
@click="['file', 'payment', 'verification'].includes(type) ? $emit('add-field', type) : $emit('set-draw-type', type)"
|
||||
>
|
||||
@@ -142,7 +144,7 @@
|
||||
<a
|
||||
href="https://www.docuseal.com/pricing"
|
||||
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 }"
|
||||
>
|
||||
<div class="w-0 absolute left-0">
|
||||
@@ -168,7 +170,7 @@
|
||||
<a
|
||||
href="https://www.docuseal.com/contact"
|
||||
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 }"
|
||||
>
|
||||
<div class="w-0 absolute left-0">
|
||||
@@ -189,7 +191,7 @@
|
||||
</template>
|
||||
</div>
|
||||
<div
|
||||
v-if="fields.length < 4 && editable && withHelp"
|
||||
v-if="fields.length < 4 && editable && withHelp && !showTourStartForm"
|
||||
class="text-xs p-2 border border-base-200 rounded"
|
||||
>
|
||||
<ul class="list-disc list-outside ml-3">
|
||||
@@ -204,6 +206,23 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
v-show="fields.length < 4 && editable && withHelp && showTourStartForm"
|
||||
class="rounded py-2 px-4 w-full border border-dashed border-base-300"
|
||||
>
|
||||
<div class="text-center text-sm">
|
||||
{{ t('start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document') }}
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<label
|
||||
for="start_tour_button"
|
||||
class="btn btn-sm btn-warning w-40 mt-2"
|
||||
@click="startTour"
|
||||
>
|
||||
{{ t('start_tour') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -290,9 +309,14 @@ export default {
|
||||
selectedSubmitter: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
showTourStartForm: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['add-field', 'set-draw', 'set-draw-type', 'set-drag', 'drag-end', 'scroll-to-area', 'change-submitter'],
|
||||
emits: ['add-field', 'set-draw', 'set-draw-type', 'set-drag', 'drag-end', 'scroll-to-area', 'change-submitter', 'set-drag-placeholder'],
|
||||
data () {
|
||||
return {
|
||||
defaultFieldsSearch: ''
|
||||
@@ -343,22 +367,56 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onDragstart (field) {
|
||||
onDragstart (event, field) {
|
||||
this.removeDragOverlay(event)
|
||||
|
||||
this.setDragPlaceholder(event)
|
||||
|
||||
this.$emit('set-drag', field)
|
||||
},
|
||||
setDragPlaceholder (event) {
|
||||
this.$emit('set-drag-placeholder', {
|
||||
offsetX: event.offsetX,
|
||||
offsetY: event.offsetY,
|
||||
x: event.clientX - event.offsetX,
|
||||
y: event.clientY - event.offsetY,
|
||||
w: event.currentTarget.clientWidth + 2,
|
||||
h: event.currentTarget.clientHeight + 2
|
||||
})
|
||||
},
|
||||
removeDragOverlay (event) {
|
||||
const root = this.$el.getRootNode()
|
||||
const hiddenEl = document.createElement('div')
|
||||
|
||||
hiddenEl.style.width = '1px'
|
||||
hiddenEl.style.height = '1px'
|
||||
hiddenEl.style.opacity = '0'
|
||||
hiddenEl.style.position = 'fixed'
|
||||
|
||||
root.querySelector('#docuseal_modal_container').appendChild(hiddenEl)
|
||||
|
||||
event.dataTransfer.setDragImage(hiddenEl, 0, 0)
|
||||
|
||||
setTimeout(() => { hiddenEl.remove() }, 1000)
|
||||
},
|
||||
startTour () {
|
||||
document.querySelector('app-tour').start()
|
||||
},
|
||||
onFieldDragover (e) {
|
||||
const targetField = e.target.closest('[data-uuid]')
|
||||
const dragField = this.$refs.fields.querySelector(`[data-uuid="${this.fieldsDragFieldRef.value.uuid}"]`)
|
||||
if (this.fieldsDragFieldRef.value) {
|
||||
const targetField = e.target.closest('[data-uuid]')
|
||||
const dragField = this.$refs.fields.querySelector(`[data-uuid="${this.fieldsDragFieldRef.value.uuid}"]`)
|
||||
|
||||
if (dragField && targetField && targetField !== dragField) {
|
||||
const fields = Array.from(this.$refs.fields.children)
|
||||
const currentIndex = fields.indexOf(dragField)
|
||||
const targetIndex = fields.indexOf(targetField)
|
||||
if (dragField && targetField && targetField !== dragField) {
|
||||
const fields = Array.from(this.$refs.fields.children)
|
||||
const currentIndex = fields.indexOf(dragField)
|
||||
const targetIndex = fields.indexOf(targetField)
|
||||
|
||||
if (currentIndex < targetIndex) {
|
||||
targetField.after(dragField)
|
||||
} else {
|
||||
targetField.before(dragField)
|
||||
if (currentIndex < targetIndex) {
|
||||
targetField.after(dragField)
|
||||
} else {
|
||||
targetField.before(dragField)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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 whitespace-nowrap truncate"
|
||||
>
|
||||
{{ 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="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) }}
|
||||
</span>
|
||||
<a
|
||||
href="#"
|
||||
class="text-xl"
|
||||
class="text-xl modal-close-button"
|
||||
@click.prevent="$emit('close')"
|
||||
>×</a>
|
||||
</div>
|
||||
@@ -111,7 +111,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="base-button w-full"
|
||||
class="base-button w-full modal-save-button"
|
||||
@click.prevent="validateSaveAndClose"
|
||||
>
|
||||
{{ t('save') }}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const en = {
|
||||
font: 'Font',
|
||||
party: 'Party',
|
||||
method: 'Method',
|
||||
reorder_fields: 'Reorder fields',
|
||||
@@ -157,10 +158,13 @@ const en = {
|
||||
some_fields_are_missing_in_the_formula: 'Some fields are missing in the formula.',
|
||||
learn_more: 'Learn more',
|
||||
and: 'and',
|
||||
or: 'or'
|
||||
or: 'or',
|
||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Start a quick tour to learn how to create an send your first document',
|
||||
start_tour: 'Start Tour'
|
||||
}
|
||||
|
||||
const es = {
|
||||
fuente: 'Fuente',
|
||||
party: 'Parte',
|
||||
method: 'Método',
|
||||
reorder_fields: 'Reordenar campos',
|
||||
@@ -319,10 +323,13 @@ const es = {
|
||||
some_fields_are_missing_in_the_formula: 'Faltan algunos campos en la fórmula.',
|
||||
learn_more: 'Aprende más',
|
||||
and: 'y',
|
||||
or: 'o'
|
||||
or: 'o',
|
||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Inicia una guía rápida para aprender a crear y enviar tu primer documento.',
|
||||
start_tour: 'Iniciar guía'
|
||||
}
|
||||
|
||||
const it = {
|
||||
font: 'Carattere',
|
||||
party: 'Parte',
|
||||
method: 'Metodo',
|
||||
reorder_fields: 'Riordina i campi',
|
||||
@@ -481,10 +488,13 @@ const it = {
|
||||
some_fields_are_missing_in_the_formula: 'Alcuni campi mancano nella formula.',
|
||||
learn_more: 'Scopri di più',
|
||||
and: 'e',
|
||||
or: 'o'
|
||||
or: 'o',
|
||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Inizia un tour rapido per imparare a creare e inviare il tuo primo documento.',
|
||||
start_tour: 'Inizia il tour'
|
||||
}
|
||||
|
||||
const pt = {
|
||||
fonte: 'Fonte',
|
||||
party: 'Parte',
|
||||
method: 'Método',
|
||||
reorder_fields: 'Reorganizar campos',
|
||||
@@ -643,10 +653,13 @@ const pt = {
|
||||
some_fields_are_missing_in_the_formula: 'Faltam alguns campos na fórmula.',
|
||||
learn_more: 'Saiba mais',
|
||||
and: 'e',
|
||||
or: 'ou'
|
||||
or: 'ou',
|
||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Comece um tour rápido para aprender a criar e enviar seu primeiro documento.',
|
||||
start_tour: 'Iniciar tour'
|
||||
}
|
||||
|
||||
const fr = {
|
||||
font: 'Police',
|
||||
party: 'Partie',
|
||||
method: 'Méthode',
|
||||
reorder_fields: 'Réorganiser les champs',
|
||||
@@ -805,10 +818,13 @@ const fr = {
|
||||
some_fields_are_missing_in_the_formula: 'Certains champs manquent dans la formule.',
|
||||
learn_more: 'En savoir plus',
|
||||
and: 'et',
|
||||
or: 'ou'
|
||||
or: 'ou',
|
||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Lancez une visite rapide pour apprendre à créer et envoyer votre premier document.',
|
||||
start_tour: 'Démarrer'
|
||||
}
|
||||
|
||||
const de = {
|
||||
font: 'Schriftart',
|
||||
party: 'Partei',
|
||||
method: 'Verfahren',
|
||||
reorder_fields: 'Felder neu anordnen',
|
||||
@@ -967,7 +983,9 @@ const de = {
|
||||
some_fields_are_missing_in_the_formula: 'Einige Felder fehlen in der Formel.',
|
||||
learn_more: 'Erfahren Sie mehr',
|
||||
and: 'und',
|
||||
or: 'oder'
|
||||
or: 'oder',
|
||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Starte eine kurze Tour, um zu lernen, wie du dein erstes Dokument erstellst und versendest.',
|
||||
start_tour: 'Starten'
|
||||
}
|
||||
|
||||
export { en, es, it, pt, fr, de }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<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">
|
||||
<component
|
||||
:is="fieldIcons[drawField.type]"
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<template>
|
||||
<span
|
||||
class="dropdown dropdown-top dropdown-end absolute bottom-4 right-4 z-10"
|
||||
>
|
||||
<span class="dropdown dropdown-top dropdown-end absolute bottom-4 right-4 z-10 fields-dropdown-mobile">
|
||||
<label
|
||||
class="btn btn-neutral text-white btn-circle btn-lg group"
|
||||
tabindex="0"
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<FieldArea
|
||||
v-if="newArea"
|
||||
:is-draw="true"
|
||||
:field="{ submitter_uuid: selectedSubmitter.uuid, type: drawField?.type || defaultFieldType }"
|
||||
:field="{ submitter_uuid: selectedSubmitter.uuid, type: drawField?.type || dragFieldPlaceholder?.type || defaultFieldType }"
|
||||
:area="newArea"
|
||||
/>
|
||||
</div>
|
||||
@@ -49,7 +49,9 @@
|
||||
:class="{ 'z-10': !isMobile, 'cursor-grab': isDrag, 'cursor-nwse-resize': drawField, [resizeDirectionClasses[resizeDirection]]: !!resizeDirectionClasses }"
|
||||
@pointermove="onPointermove"
|
||||
@pointerdown="onStartDraw"
|
||||
@dragover.prevent
|
||||
@dragover.prevent="onDragover"
|
||||
@dragenter="onDragenter"
|
||||
@dragleave="newArea = null"
|
||||
@drop="onDrop"
|
||||
@pointerup="onPointerup"
|
||||
/>
|
||||
@@ -64,12 +66,17 @@ export default {
|
||||
components: {
|
||||
FieldArea
|
||||
},
|
||||
inject: ['fieldTypes', 'defaultDrawFieldType', 'fieldsDragFieldRef'],
|
||||
inject: ['fieldTypes', 'defaultDrawFieldType', 'fieldsDragFieldRef', 'assignDropAreaSize'],
|
||||
props: {
|
||||
image: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
dragFieldPlaceholder: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
areas: {
|
||||
type: Array,
|
||||
required: false,
|
||||
@@ -192,7 +199,24 @@ export default {
|
||||
this.areaRefs.push(el)
|
||||
}
|
||||
},
|
||||
onDragenter (e) {
|
||||
this.newArea = {}
|
||||
|
||||
this.assignDropAreaSize(this.newArea, this.dragFieldPlaceholder, {
|
||||
maskW: this.$refs.mask.clientWidth,
|
||||
maskH: this.$refs.mask.clientHeight
|
||||
})
|
||||
|
||||
this.newArea.x = (e.offsetX - 6) / this.$refs.mask.clientWidth
|
||||
this.newArea.y = e.offsetY / this.$refs.mask.clientHeight - this.newArea.h / 2
|
||||
},
|
||||
onDragover (e) {
|
||||
this.newArea.x = (e.offsetX - 6) / this.$refs.mask.clientWidth
|
||||
this.newArea.y = e.offsetY / this.$refs.mask.clientHeight - this.newArea.h / 2
|
||||
},
|
||||
onDrop (e) {
|
||||
this.newArea = null
|
||||
|
||||
this.$emit('drop-field', {
|
||||
x: e.offsetX,
|
||||
y: e.offsetY,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<span
|
||||
class="dropdown dropdown-end"
|
||||
class="dropdown dropdown-end field-settings-dropdown"
|
||||
:class="{ 'dropdown-open': ((!field.preferences?.price && !field.preferences?.formula) || !isConnected) && !isLoading }"
|
||||
>
|
||||
<label
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
>
|
||||
<div>
|
||||
<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"
|
||||
>
|
||||
<IconRouteAltLeft
|
||||
@@ -48,7 +48,7 @@
|
||||
>
|
||||
<div>
|
||||
<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"
|
||||
@click.stop="$emit('remove', item)"
|
||||
>
|
||||
@@ -63,7 +63,7 @@
|
||||
class="tooltip tooltip-left before:text-xs"
|
||||
>
|
||||
<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)"
|
||||
>
|
||||
<IconSortDescending2
|
||||
@@ -75,14 +75,14 @@
|
||||
</span>
|
||||
<template v-if="withArrows">
|
||||
<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"
|
||||
@click.stop="$emit('up', item)"
|
||||
>
|
||||
↑
|
||||
</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"
|
||||
@click.stop="$emit('down', item)"
|
||||
>
|
||||
@@ -94,7 +94,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex pb-2 pt-1.5">
|
||||
<div class="flex pb-2 pt-1.5 document-preview-name">
|
||||
<Contenteditable
|
||||
:model-value="item.name"
|
||||
:icon-width="16"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<label
|
||||
: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 }"
|
||||
>
|
||||
{{ message }}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<label
|
||||
id="add_document_button"
|
||||
:for="inputId"
|
||||
class="btn btn-outline w-full"
|
||||
class="btn btn-outline w-full add-document-button"
|
||||
:class="{ 'btn-disabled': isLoading || isProcessing }"
|
||||
>
|
||||
<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
|
||||
@@ -94,11 +94,27 @@ class ProcessSubmitterCompletionJob
|
||||
end
|
||||
end
|
||||
|
||||
to = build_to_addresses(submitter)
|
||||
maybe_enqueue_copy_emails(submitter)
|
||||
end
|
||||
|
||||
return if to.blank? || submitter.template.preferences['documents_copy_email_enabled'] == false
|
||||
def maybe_enqueue_copy_emails(submitter)
|
||||
return if submitter.template.preferences['documents_copy_email_enabled'] == false
|
||||
|
||||
SubmitterMailer.documents_copy_email(submitter, to:).deliver_later!
|
||||
configs = AccountConfigs.find_or_initialize_for_key(submitter.account,
|
||||
AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY)
|
||||
|
||||
return if configs.value['enabled'] == false
|
||||
|
||||
to = submitter.submission.submitters.reject { |e| e.preferences['send_email'] == false }
|
||||
.sort_by(&:completed_at).select(&:email?).map(&:friendly_name)
|
||||
|
||||
return if to.blank?
|
||||
|
||||
if configs.value['bcc_recipients'] == true
|
||||
to.each { |to| SubmitterMailer.documents_copy_email(submitter, to:).deliver_later! }
|
||||
else
|
||||
SubmitterMailer.documents_copy_email(submitter, to: to.join(', ')).deliver_later!
|
||||
end
|
||||
end
|
||||
|
||||
def build_bcc_addresses(submission)
|
||||
@@ -110,11 +126,6 @@ class ProcessSubmitterCompletionJob
|
||||
bcc.to_s.scan(User::EMAIL_REGEXP)
|
||||
end
|
||||
|
||||
def build_to_addresses(submitter)
|
||||
submitter.submission.submitters.reject { |e| e.preferences['send_email'] == false }
|
||||
.sort_by(&:completed_at).select(&:email?).map(&:friendly_name).join(', ')
|
||||
end
|
||||
|
||||
def enqueue_next_submitter_request_notification(submitter)
|
||||
next_submitter_item =
|
||||
submitter.submission.template_submitters.find do |e|
|
||||
|
||||
@@ -5,7 +5,7 @@ class SendFormCompletedWebhookRequestJob
|
||||
|
||||
sidekiq_options queue: :webhooks
|
||||
|
||||
MAX_ATTEMPTS = 20
|
||||
MAX_ATTEMPTS = 12
|
||||
|
||||
def perform(params = {})
|
||||
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
|
||||
@@ -30,6 +30,7 @@ class AccountConfig < ApplicationRecord
|
||||
ALLOW_TO_RESUBMIT = 'allow_to_resubmit'
|
||||
ALLOW_TO_DECLINE_KEY = 'allow_to_decline'
|
||||
SUBMITTER_REMINDERS = 'submitter_reminders'
|
||||
ENFORCE_SIGNING_ORDER_KEY = 'enforce_signing_order'
|
||||
FORM_COMPLETED_BUTTON_KEY = 'form_completed_button'
|
||||
FORM_COMPLETED_MESSAGE_KEY = 'form_completed_message'
|
||||
FORM_WITH_CONFETTI_KEY = 'form_with_confetti'
|
||||
|
||||
@@ -64,6 +64,7 @@ class Submission < ApplicationRecord
|
||||
through: :template, source: :documents_attachments
|
||||
|
||||
scope :active, -> { where(archived_at: nil) }
|
||||
scope :archived, -> { where.not(archived_at: nil) }
|
||||
scope :pending, -> { joins(:submitters).where(submitters: { completed_at: nil }).group(:id) }
|
||||
scope :completed, lambda {
|
||||
where.not(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
|
||||
|
||||
@@ -55,6 +55,7 @@ class Submitter < ApplicationRecord
|
||||
has_many_attached :attachments
|
||||
has_many_attached :preview_documents
|
||||
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 :submission_events, dependent: :destroy
|
||||
@@ -63,6 +64,8 @@ class Submitter < ApplicationRecord
|
||||
|
||||
scope :completed, -> { where.not(completed_at: nil) }
|
||||
|
||||
after_destroy :anonymize_email_events, if: -> { Docuseal.multitenant? }
|
||||
|
||||
def status
|
||||
if declined_at?
|
||||
'declined'
|
||||
@@ -108,4 +111,12 @@ class Submitter < ApplicationRecord
|
||||
fields.any? { |f| f['submitter_uuid'] == uuid && signature_field_types.include?(f['type']) }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def anonymize_email_events
|
||||
email_events.each do |event|
|
||||
event.update!(email: Digest::MD5.base64digest(event.email))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -25,6 +25,7 @@ class UserConfig < ApplicationRecord
|
||||
INITIALS_KEY = 'initials'
|
||||
RECEIVE_COMPLETED_EMAIL = 'receive_completed_email'
|
||||
RECEIVE_DECLINED_EMAIL = 'receive_declined_email'
|
||||
SHOW_APP_TOUR = 'show_app_tour'
|
||||
|
||||
belongs_to :user
|
||||
|
||||
|
||||
@@ -28,10 +28,12 @@ class WebhookUrl < ApplicationRecord
|
||||
form.started
|
||||
form.completed
|
||||
form.declined
|
||||
submission.created
|
||||
submission.completed
|
||||
submission.expired
|
||||
submission.archived
|
||||
template.created
|
||||
template.updated
|
||||
submission.created
|
||||
submission.archived
|
||||
].freeze
|
||||
|
||||
belongs_to :account
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
<span>
|
||||
<%= t('add_signature_id_to_the_documents') %>
|
||||
</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>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -75,7 +75,7 @@
|
||||
<span>
|
||||
<%= t('require_signing_reason') %>
|
||||
</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>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -151,6 +151,20 @@
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if !Docuseal.multitenant? || can?(:manage, :personalization_advanced) %>
|
||||
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::ENFORCE_SIGNING_ORDER_KEY) %>
|
||||
<% if can?(:manage, account_config) %>
|
||||
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
||||
<%= f.hidden_field :key %>
|
||||
<div class="flex items-center justify-between py-2.5">
|
||||
<span>
|
||||
<%= t('always_enforce_signing_order') %>
|
||||
</span>
|
||||
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= render 'compliances' %>
|
||||
@@ -160,9 +174,10 @@
|
||||
<h2 class="text-2xl font-bold mb-2">
|
||||
<%= t('danger_zone') %>
|
||||
</h2>
|
||||
<%= button_to button_title(title: t('delete_my_account')), settings_account_path, class: 'btn btn-outline btn-error block', data: { turbo_confirm: t('schedule_account_for_deletion_') }, method: :delete, id: :account_delete_button %>
|
||||
<%= button_to button_title(title: t('delete_my_account')), settings_account_path, class: 'btn btn-outline btn-error block', data: { turbo_confirm: t('you_are_scheduling_your_account_for_deletion_after_deletion_your_data_will_be_permanently_removed_and_cannot_be_recovered_click_ok_if_you_would_like_to_continue') }, method: :delete, id: :account_delete_button %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="w-0 md:w-52"></div>
|
||||
</div>
|
||||
<%= render 'shared/app_tour', type: 'account' %>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<form action="<%= root_path %>" method="get" class="bg-base-200 px-1.5 rounded-xl py-1 whitespace-nowrap">
|
||||
<form action="<%= root_path %>" method="get" id="templates_submissions_toggle" class="bg-base-200 px-1.5 rounded-xl py-1 whitespace-nowrap">
|
||||
<toggle-cookies data-value="templates" data-key="dashboard_view" class="sm:tooltip tooltip-top" data-tip="<%= t('templates') %>">
|
||||
<button class="<%= local_assigns[:selected] == 'submissions' ? 'btn !border !rounded-lg btn-square !p-0 !btn-sm !h-8 !w-9' : 'btn btn-neutral !rounded-lg btn-square !p-0 hover:text-neutral-300 !btn-sm !h-8 !w-9 disabled:btn-neutral' %>">
|
||||
<%= svg_icon('layout_grid', class: 'w-6 h-6 stroke-2') %>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="collapse-content">
|
||||
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
||||
<%= f.hidden_field :key %>
|
||||
<%= f.fields_for :value, Struct.new(:subject, :body, :reply_to, :attach_audit_log, :attach_documents).new(*f.object.value.values_at('subject', 'body', 'reply_to', 'attach_audit_log', 'attach_documents')) do |ff| %>
|
||||
<%= f.fields_for :value, Struct.new(:subject, :body, :reply_to, :attach_audit_log, :attach_documents, :bcc_recipients, :enabled).new(*f.object.value.values_at('subject', 'body', 'reply_to', 'attach_audit_log', 'attach_documents', 'bcc_recipients', 'enabled')) do |ff| %>
|
||||
<div class="form-control">
|
||||
<%= ff.label :subject, t('subject'), class: 'label' %>
|
||||
<%= ff.text_field :subject, required: true, class: 'base-input', dir: 'auto' %>
|
||||
@@ -24,23 +24,41 @@
|
||||
<%= ff.text_area :body, required: true, class: 'base-input w-full py-2', dir: 'auto' %>
|
||||
</autoresize-textarea>
|
||||
</div>
|
||||
<% if can?(:manage, :reply_to) %>
|
||||
<% if can?(:manage, :reply_to) || can?(:manage, :personalization_advanced) %>
|
||||
<div class="form-control">
|
||||
<%= ff.label :reply_to, t('reply_to'), class: 'label' %>
|
||||
<%= ff.email_field :reply_to, class: 'base-input', dir: 'auto', placeholder: t(:email) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="flex items-center justify-between pt-2.5 mx-1">
|
||||
<span>
|
||||
<%= t('attach_documents') %>
|
||||
</span>
|
||||
<%= ff.check_box :attach_documents, { checked: ff.object.attach_documents != false, class: 'toggle' }, 'true', 'false' %>
|
||||
</div>
|
||||
<div class="flex items-center justify-between pb-2.5 mx-1">
|
||||
<span>
|
||||
<%= t('attach_audit_log_pdf') %>
|
||||
</span>
|
||||
<%= ff.check_box :attach_audit_log, { checked: ff.object.attach_audit_log != false, class: 'toggle' }, 'true', 'false' %>
|
||||
<div class="space-y-3.5">
|
||||
<div class="flex items-center justify-between mx-1">
|
||||
<span>
|
||||
<%= t('attach_documents') %>
|
||||
</span>
|
||||
<%= ff.check_box :attach_documents, { checked: ff.object.attach_documents != false, class: 'toggle' }, 'true', 'false' %>
|
||||
</div>
|
||||
<div class="flex items-center justify-between mx-1">
|
||||
<span>
|
||||
<%= t('attach_audit_log_pdf') %>
|
||||
</span>
|
||||
<%= ff.check_box :attach_audit_log, { checked: ff.object.attach_audit_log != false, class: 'toggle' }, 'true', 'false' %>
|
||||
</div>
|
||||
<% unless Docuseal.multitenant? %>
|
||||
<div class="flex items-center justify-between mx-1">
|
||||
<span>
|
||||
<%= t('bcc_recipients') %>
|
||||
</span>
|
||||
<%= ff.check_box :bcc_recipients, { checked: ff.object.bcc_recipients == true, class: 'toggle' }, 'true', 'false' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if !Docuseal.multitenant? || can?(:manage, :personalization_advanced) %>
|
||||
<div class="flex items-center justify-between mx-1">
|
||||
<span>
|
||||
<%= t('send_emails_automatically_on_completion') %>
|
||||
</span>
|
||||
<%= ff.check_box :enabled, { checked: ff.object.enabled != false, class: 'toggle' }, 'true', 'false' %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="form-control pt-2">
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<% if current_user.created_at > 2.weeks.ago || params[:tour] == 'true' %>
|
||||
<% user_config = current_user.user_configs.find_or_initialize_by(key: UserConfig::SHOW_APP_TOUR) %>
|
||||
<% if user_config.new_record? || user_config.value || params[:tour] == 'true' %>
|
||||
<app-tour data-show-tour="<%= params[:tour] == 'true' || user_config.value %>" data-type="<%= local_assigns[:type] %>" data-next-page-path="<%= local_assigns[:next_page_path] %>" data-i18n="<%= t('app_tour').to_json %>"></app-tour>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -1 +1 @@
|
||||
<% 'stats stat stat-figure stat-title stat-value text-accent w-fit hover:bg-white' %>
|
||||
<% 'stats stat stat-figure stat-title stat-value text-accent w-fit hover:bg-white dropdown-open' %>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<clipboard-copy data-text="<%= text %>">
|
||||
<label class="<%= local_assigns[:class] %>">
|
||||
<label id="<%= local_assigns[:id] %>" class="<%= local_assigns[:class] %>">
|
||||
<input type="radio" class="peer hidden">
|
||||
<span class="peer-checked:hidden flex items-center space-x-2">
|
||||
<%= svg_icon(local_assigns[:icon] || 'link', class: local_assigns[:icon_class] || 'w-6 h-6 text-white') %>
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
<meta property="og:image" content="">
|
||||
<meta name="twitter:image" content="">
|
||||
<% else %>
|
||||
<meta property="og:image" content="<%= image_pack_url('images/preview.png') %>">
|
||||
<meta property="og:image" content="<%= root_url %>preview.png">
|
||||
<meta property="og:image:width" content="800">
|
||||
<meta property="og:image:height" content="800">
|
||||
<meta name="twitter:image" content="<%= image_pack_url('images/preview.png') %>">
|
||||
<meta name="twitter:image" content="<%= root_url %>preview.png">
|
||||
<% end %>
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:creator" content="@docusealco">
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<% else %>
|
||||
<div class="flex items-center justify-center space-x-4 mr-1">
|
||||
<%= render 'shared/navbar_buttons' %>
|
||||
<%= link_to t('settings'), settings_profile_index_path, class: 'hidden md:inline-flex font-medium text-lg' %>
|
||||
<%= link_to t('settings'), settings_profile_index_path, class: 'hidden md:inline-flex font-medium text-lg', id: 'account_settings_button' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="dropdown dropdown-end">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="block w-full md:w-52 flex-none">
|
||||
<menu-active>
|
||||
<ul class="menu px-0">
|
||||
<ul id="account_settings_menu" class="menu px-0">
|
||||
<li class="menu-title py-0 !bg-transparent mb-3 -mt-5"><a href="<%= '/' %>" class="!bg-transparent !text-neutral font-medium">← <%= t('back') %></a></li>
|
||||
<li class="menu-title py-0 !bg-transparent">
|
||||
<span class="!bg-transparent"><%= t('settings') %></span>
|
||||
@@ -105,7 +105,7 @@
|
||||
</ul>
|
||||
</menu-active>
|
||||
<% if Docuseal.multitenant? || cannot?(:manage, :tenants) %>
|
||||
<div class="mx-4 border-t border-base-300 hidden md:block">
|
||||
<div id="support_channels" class="mx-4 border-t border-base-300 hidden md:block">
|
||||
<div class="text-sm mt-3">
|
||||
<%= t('need_help_ask_a_question_') %>
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
<div dir="auto" class="form-control !mt-0">
|
||||
<%= f.label :email, t('email'), class: 'label' %>
|
||||
<%= f.email_field :email, value: current_user&.email || params[:email] || @submitter.email, required: true, class: 'base-input', placeholder: t('provide_your_email_to_start') %>
|
||||
<%= @error_message %>
|
||||
<% if @error_message %>
|
||||
<span class="label-text-alt text-red-400 mt-1"><%= @error_message %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
<toggle-submit dir="auto" class="form-control">
|
||||
<%= f.button button_title(title: t('start'), disabled_with: t('starting')), class: 'base-button' %>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<% align = field.dig('preferences', 'align') %>
|
||||
<% color = field.dig('preferences', 'color') %>
|
||||
<% 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' %>
|
||||
<div class="flex flex-col justify-between h-full overflow-hidden">
|
||||
<div class="flex-grow flex overflow-hidden" style="min-height: 50%">
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<%= svg_icon('external_link', class: 'w-6 h-6') %>
|
||||
<span class="hidden md:inline"><%= t('audit_log') %></span>
|
||||
</a>
|
||||
<% else %>
|
||||
<% elsif signed_in? %>
|
||||
<%= link_to submission_events_path(@submission), class: 'white-button', data: { turbo_frame: :modal } do %>
|
||||
<%= svg_icon('logs', class: 'w-6 h-6') %>
|
||||
<span class="hidden md:block"><%= t('event_log') %></span>
|
||||
@@ -105,7 +105,18 @@
|
||||
<% value = values[field['uuid']] %>
|
||||
<% value ||= field['default_value'] if field['type'] == 'heading' %>
|
||||
<% 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 %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -232,12 +243,21 @@
|
||||
</div>
|
||||
<% elsif field['type'] == 'checkbox' %>
|
||||
<%= 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 %>
|
||||
<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 %>
|
||||
</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? && 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 %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
<% elsif !submission.archived_at? && !template.archived_at? && !submission.expired? && !submitter.declined_at? %>
|
||||
<% if current_user.email == submitter.email %>
|
||||
<div class="flex-1 md:flex-none md:w-36 flex">
|
||||
<a href="<%= submit_form_url(slug: submitter.slug) %>" data-turbo="false" target="_blank" class="btn btn-sm btn-neutral btn-outline bg-white w-full md:w-36 flex z-[1]">
|
||||
<a href="<%= submit_form_url(slug: submitter.slug) %>" data-turbo="false" target="_blank" id="sign_yourself_button" class="btn btn-sm btn-neutral btn-outline bg-white w-full md:w-36 flex z-[1]">
|
||||
<span class="flex items-center justify-center space-x-1 md:space-x-2">
|
||||
<% if t('sign_now').length < 12 %>
|
||||
<%= svg_icon('writing_sign', class: 'w-4 h-4 stroke-2') %>
|
||||
@@ -167,7 +167,7 @@
|
||||
<% elsif !template.archived_at? && !submission.archived_at? && !is_submission_completed && !submission.expired? && !submitter.declined_at? %>
|
||||
<div class="relative flex items-center space-x-3">
|
||||
<% if current_user.email == submitter.email %>
|
||||
<a href="<%= submit_form_url(slug: submitter.slug) %>" data-turbo="false" target="_blank" class="absolute md:relative top-0 right-0 btn btn-xs btn-outline btn-neutral bg-white w-28 md:w-36 z-[1]">
|
||||
<a href="<%= submit_form_url(slug: submitter.slug) %>" data-turbo="false" target="_blank" id="sign_yourself_button" class="absolute md:relative top-0 right-0 btn btn-xs btn-outline btn-neutral bg-white w-28 md:w-36 z-[1]">
|
||||
<span class="flex items-center justify-center space-x-1 md:space-x-2">
|
||||
<% if t('sign_now').length < 12 %>
|
||||
<%= svg_icon('writing_sign', class: 'w-4 h-4 stroke-2') %>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= render 'shared/clipboard_copy', text: start_form_url(slug: @template.slug), class: 'absolute md:relative bottom-0 right-0 btn btn-xs md:btn-sm whitespace-nowrap btn-neutral text-white mt-1 px-2', icon_class: 'w-4 h-4 md:w-6 md:h-6 text-white', copy_title: t('link'), copied_title: t('copied'), copy_title_md: t('link'), copied_title_md: t('copied') %>
|
||||
<%= render 'shared/clipboard_copy', text: start_form_url(slug: @template.slug), id: 'share_link_clipboard', class: 'absolute md:relative bottom-0 right-0 btn btn-xs md:btn-sm whitespace-nowrap btn-neutral text-white mt-1 px-2', icon_class: 'w-4 h-4 md:w-6 md:h-6 text-white', copy_title: t('link'), copied_title: t('copied'), copy_title_md: t('link'), copied_title_md: t('copied') %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<%= form_for '', url: templates_upload_path, id: form_id = SecureRandom.uuid, method: :post, class: 'inline', html: { enctype: 'multipart/form-data' } do %>
|
||||
<button type="submit" class="btn btn-ghost text-base" onclick="[event.preventDefault(), window.upload_template.click()]">
|
||||
<button id="templates_upload_button" type="submit" class="btn btn-ghost text-base" onclick="[event.preventDefault(), window.upload_template.click()]">
|
||||
<span class="enabled">
|
||||
<span class="flex items-center justify-center space-x-2">
|
||||
<%= svg_icon('upload', class: 'w-6 h-6 stroke-2') %>
|
||||
|
||||
@@ -1 +1,9 @@
|
||||
<template-builder class="grid" data-template="<%= @template_data %>" data-with-sign-yourself-button="<%= !@template.archived_at? %>" data-with-send-button="<%= !@template.archived_at? && can?(:create, @template.submissions.new(account: current_account)) %>" data-locale="<%= I18n.locale %>"></template-builder>
|
||||
<% if current_user.created_at > 2.weeks.ago || params[:tour] == 'true' %>
|
||||
<% user_config = current_user.user_configs.find_or_initialize_by(key: UserConfig::SHOW_APP_TOUR) %>
|
||||
<% @show_tour_start_form = user_config.new_record? && !params.key?(:tour) %>
|
||||
<% if user_config.new_record? || user_config.value || params[:tour] == 'true' %>
|
||||
<app-tour data-show-tour="<%= params[:tour] == 'true' || user_config.value %>" data-type="builder" data-next-page-path="<%= template_path(@template, params.permit(:tour)) %>" data-i18n="<%= t('app_tour').to_json %>"></app-tour>
|
||||
<%= button_to nil, user_configs_path, method: :post, params: { user_config: { key: UserConfig::SHOW_APP_TOUR, value: true } }, class: 'hidden', id: 'start_tour_button' %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<template-builder class="grid" data-template="<%= @template_data %>" data-with-sign-yourself-button="<%= !@template.archived_at? %>" data-with-send-button="<%= !@template.archived_at? && can?(:create, @template.submissions.new(account: current_account)) %>" data-locale="<%= I18n.locale %>" data-show-tour-start-form="<%= @show_tour_start_form %>"></template-builder>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<span><%= t('export') %></span>
|
||||
<% end %>
|
||||
<% if !@template.archived_at? && can?(:create, Submission) %>
|
||||
<%= link_to new_template_submission_path(@template), class: 'white-button !border', data: { turbo_frame: 'modal' } do %>
|
||||
<%= link_to new_template_submission_path(@template), id: 'add_recipients_button', class: 'white-button !border', data: { turbo_frame: 'modal' } do %>
|
||||
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
||||
<%= t('add_recipients_html') %>
|
||||
<% end %>
|
||||
@@ -80,18 +80,18 @@
|
||||
<p><%= t('send_an_invitation_to_fill_and_complete_the_form') %></p>
|
||||
<div class="space-y-2 flex flex-col">
|
||||
<% if can?(:create, Submission) %>
|
||||
<%= link_to new_template_submission_path(@template, with_link: true), class: 'base-button mt-6', data: { turbo_frame: 'modal' } do %>
|
||||
<%= link_to new_template_submission_path(@template, with_link: true), id: 'send_to_recipients_button', class: 'base-button mt-6', data: { turbo_frame: 'modal' } do %>
|
||||
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
||||
<span class="mr-1"><%= t('send_to_recipients') %></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if @template.submitters.size == 1 %>
|
||||
<%= link_to start_form_url(slug: @template.slug), class: 'white-button mt-6', target: '_blank', rel: 'noopener' do %>
|
||||
<%= link_to start_form_url(slug: @template.slug), id: 'sign_yourself_button', class: 'white-button mt-6', target: '_blank', rel: 'noopener' do %>
|
||||
<%= svg_icon('writing', class: 'w-6 h-6') %>
|
||||
<span class="mr-1"><%= t('sign_it_yourself') %></span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= link_to new_template_submission_path(@template, selfsign: true), class: 'white-button mt-6', data: { turbo_frame: 'modal' } do %>
|
||||
<%= link_to new_template_submission_path(@template, selfsign: true), id: 'sign_yourself_button', class: 'white-button mt-6', data: { turbo_frame: 'modal' } do %>
|
||||
<%= svg_icon('writing', class: 'w-6 h-6') %>
|
||||
<span class="mr-1"><%= t('sign_it_yourself') %></span>
|
||||
<% end %>
|
||||
@@ -117,3 +117,4 @@
|
||||
<%= view_archived_html %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= render 'shared/app_tour', type: 'template', next_page_path: settings_account_path(params.permit(:tour)) %>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<% has_archived = current_account.templates.where.not(archived_at: nil).exists? %>
|
||||
<% show_dropzone = params[:q].blank? && @pagy.pages == 1 && ((@template_folders.size < 10 && @templates.size.zero?) || (@template_folders.size < 7 && @templates.size < 4) || (@template_folders.size < 4 && @templates.size < 7)) %>
|
||||
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
|
||||
<div class="flex justify-between items-center w-full mb-4">
|
||||
<div class="flex items-center flex-grow min-w-0">
|
||||
@@ -41,9 +42,31 @@
|
||||
<% if @templates.present? %>
|
||||
<div class="grid gap-4 md:grid-cols-3">
|
||||
<%= render partial: 'templates/template', collection: @templates %>
|
||||
<% if show_dropzone && current_user.created_at > 2.weeks.ago || params[:tour] == 'true' %>
|
||||
<% user_config = current_user.user_configs.find_or_initialize_by(key: UserConfig::SHOW_APP_TOUR) %>
|
||||
<% if user_config.new_record? || user_config.value || params[:tour] == 'true' %>
|
||||
<div class="hidden md:block">
|
||||
<app-tour id="app_tour" data-show-tour="<%= params[:tour] == 'true' || user_config.value %>" data-type="dashboard" data-next-page-path="<%= @templates.first && can?(:edit, @templates.first) ? edit_template_path(@templates.first, params.permit(:tour)) : settings_account_path %>" data-i18n="<%= t('app_tour').to_json %>"></app-tour>
|
||||
<% if user_config.new_record? && !params.key?(:tour) %>
|
||||
<div class="h-36 rounded-2xl pt-3 px-7 w-full border border-dashed border-base-300">
|
||||
<div class="text-xl text-center font-semibold text-base-content">
|
||||
<%= t('welcome_to_docuseal') %>
|
||||
</div>
|
||||
<div class="my-2 text-center text-xs text-base-content/70">
|
||||
<%= t('start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document') %>
|
||||
</div>
|
||||
<div class="flex gap-2 mt-3 w-full">
|
||||
<%= button_to button_title(title: t('skip'), icon_disabled: svg_icon('loader', class: 'w-4 h-4 animate-spin')), user_configs_path, params: { user_config: { key: UserConfig::SHOW_APP_TOUR, value: false } }, class: 'btn btn-sm btn-outline w-full', form_class: 'flex-1', method: :post, form: { onsubmit: 'window.app_tour.parentNode.remove()' } %>
|
||||
<%= button_to t('start_tour'), user_configs_path, params: { user_config: { key: UserConfig::SHOW_APP_TOUR, value: true } }, class: 'btn btn-sm btn-warning w-full', form_class: 'flex-1', method: :post, form: { onsubmit: 'window.app_tour.start()' } %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if params[:q].blank? && @pagy.pages == 1 && ((@template_folders.size < 10 && @templates.size.zero?) || (@template_folders.size < 7 && @templates.size < 4) || (@template_folders.size < 4 && @templates.size < 7)) %>
|
||||
<% if show_dropzone %>
|
||||
<%= render 'templates/dropzone' %>
|
||||
<% end %>
|
||||
<% if @templates.present? || params[:q].blank? %>
|
||||
|
||||
@@ -206,7 +206,7 @@
|
||||
<span>
|
||||
<%= t('send_emails_automatically_on_completion') %>
|
||||
</span>
|
||||
<%= ff.check_box :documents_copy_email_enabled, { checked: ff.object.documents_copy_email_enabled != false, class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'true', 'false' %>
|
||||
<%= ff.check_box :documents_copy_email_enabled, { checked: ff.object.documents_copy_email_enabled != false && configs['enabled'] != false, class: 'toggle', onchange: 'this.form.requestSubmit()', disabled: configs['enabled'] == false }, 'true', 'false' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="form-control pt-2">
|
||||
@@ -322,15 +322,17 @@
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-2' }, data: { close_on_submit: false } do |f| %>
|
||||
<div class="flex items-center pt-4 mt-4 justify-between border-t w-full">
|
||||
<span>
|
||||
<%= t('enforce_recipients_order') %>
|
||||
</span>
|
||||
<%= f.fields_for :preferences, Struct.new(:submitters_order).new(@template.preferences['submitters_order']) do |ff| %>
|
||||
<%= ff.check_box :submitters_order, { class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'preserved', '' %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% unless current_account.account_configs.exists?(key: AccountConfig::ENFORCE_SIGNING_ORDER_KEY, value: true) %>
|
||||
<%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-2' }, data: { close_on_submit: false } do |f| %>
|
||||
<div class="flex items-center pt-4 mt-4 justify-between border-t w-full">
|
||||
<span>
|
||||
<%= t('enforce_recipients_order') %>
|
||||
</span>
|
||||
<%= f.fields_for :preferences, Struct.new(:submitters_order).new(@template.preferences['submitters_order']) do |ff| %>
|
||||
<%= ff.check_box :submitters_order, { class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'preserved', '' %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<div class="form-control mt-5 pb-2">
|
||||
<%= button_tag button_title(title: t('save'), disabled_with: t('updating')), class: 'base-button', form: :submitters_form %>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="flex-wrap space-y-4 md:flex md:flex-nowrap md:space-y-0 md:space-x-10">
|
||||
<%= render 'shared/settings_nav' %>
|
||||
<div class="md:flex-grow">
|
||||
<div class="flex flex-col md:flex-row gap-2 md:justify-between md:items-end mb-4 min-h-12">
|
||||
<div class="flex flex-col md:flex-row md:flex-wrap gap-2 md:justify-between md:items-end mb-4 min-h-12">
|
||||
<h1 class="text-4xl font-bold">
|
||||
<% if params[:status] == 'archived' %>
|
||||
<%= t('archived_users') %>
|
||||
|
||||
@@ -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
|
||||
|
||||
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.
|
||||
# config.action_cable.mount_path = nil
|
||||
|
||||
@@ -187,7 +187,7 @@ Devise.setup do |config|
|
||||
|
||||
# ==> Configuration for :rememberable
|
||||
# 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.
|
||||
config.expire_all_remember_me_on_sign_out = true
|
||||
|
||||
+289
-24
@@ -20,6 +20,8 @@ en: &en
|
||||
language_ko: 한국어
|
||||
hi_there: Hi there
|
||||
thanks: Thanks
|
||||
bcc_recipients: BCC recipients
|
||||
always_enforce_signing_order: Always enforce the signing order
|
||||
edit_per_party: Edit per party
|
||||
reply_to: Reply to
|
||||
pending_by_me: Pending by me
|
||||
@@ -164,7 +166,7 @@ en: &en
|
||||
connect_salesforce: Connect Salesforce
|
||||
danger_zone: Danger Zone
|
||||
delete_my_account: Delete my account
|
||||
schedule_account_for_deletion_: Schedule account for deletion?
|
||||
you_are_scheduling_your_account_for_deletion_after_deletion_your_data_will_be_permanently_removed_and_cannot_be_recovered_click_ok_if_you_would_like_to_continue: "You are scheduling your account for deletion. After deletion, your data will be permanently removed and cannot be recovered.\n\nClick OK if you would like to continue."
|
||||
account_information_has_been_updated: Account information has been updated.
|
||||
should_be_a_valid_url: should be a valid URL
|
||||
your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account: Your account removal request will be processed within 2 months. Please contact us if you want to keep your account.
|
||||
@@ -432,7 +434,7 @@ en: &en
|
||||
share_template_with_test_mode: Share template with Test mode
|
||||
share_template_with_all_tenants: Share template with all Tenants
|
||||
use_following_placeholders_text_: 'Use following placeholders text:'
|
||||
upgrade_plan_to_add_more_users: Upgrade plan to add more users
|
||||
users_count_total_users_count_pro_users_limit_was_reached_to_invite_additional_users_please_purchase_more_pro_user_seats_via_the_manage_plan_button: '%{users_count}/%{total_users_count} Pro users limit was reached. To invite additional users, please purchase more Pro user seats via the "Manage plan" button.'
|
||||
move_into_folder: Move Into Folder
|
||||
new_folder_name: New Folder Name
|
||||
exit_preview: Exit Preview
|
||||
@@ -473,8 +475,6 @@ en: &en
|
||||
team_accounts: Team Accounts
|
||||
tenant_account: Tenant account
|
||||
tenant_accounts: Tenant Accounts
|
||||
upgrade_plan: Upgrade Plan
|
||||
add_user: Add User
|
||||
impersonate: Impersonate
|
||||
loading: Loading
|
||||
documents: Documents
|
||||
@@ -700,6 +700,10 @@ en: &en
|
||||
submission_created_via_source_html: '<b>Submission created</b> via %{source}'
|
||||
pro_user_seats_used: Pro user seats used
|
||||
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.
|
||||
welcome_to_docuseal: Welcome to DocuSeal
|
||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: Start a quick tour to learn how to create an send your first document
|
||||
start_tour: Start Tour
|
||||
submission_sources:
|
||||
api: API
|
||||
bulk: Bulk Send
|
||||
@@ -738,12 +742,54 @@ en: &en
|
||||
or: Or
|
||||
download: download
|
||||
a_sample_spreadsheet_to_fill_and_import: a sample spreadsheet to fill and import
|
||||
app_tour:
|
||||
start: Start
|
||||
previous: Previous
|
||||
next: Next
|
||||
template_and_submissions: 'Templates and Submissions'
|
||||
template_and_submissions_description: "You can select the view that best suits your workflow. Choose the 'Templates' view if you create reusable document templates, or the 'Submissions' view when you sign individual documents or review the status of each signing request."
|
||||
upload_a_pdf_file: 'Upload a PDF file'
|
||||
upload_a_pdf_file_description: 'Upload a document to use it for creating a signing form template.'
|
||||
select_a_signer_party: 'Select a signer party'
|
||||
select_a_signer_party_description: 'This dropdown allows you to select a signing party role or add a new party to the document when it needs to be signed by two or more parties.'
|
||||
available_parties: 'Available parties'
|
||||
available_parties_description: 'This list contains all available parties with their role names. Once selected, you can add and configure fields for the signer party.'
|
||||
available_field_types: 'Available field types'
|
||||
available_field_types_description: 'This section contains all available field types that can be added to the document for data input or signature collection.'
|
||||
text_input_field: 'Text input field'
|
||||
text_input_field_description: 'This field allows users to enter text-based information, such as names or emails.'
|
||||
signature_field: 'Signature field'
|
||||
signature_field_description: 'This field allows for collecting signatures from the signers.'
|
||||
added_fields: 'Added fields'
|
||||
added_fields_description: 'This list displays all fields added to the document. You can reorder, edit, or remove them.'
|
||||
open_field_settings: 'Open field settings'
|
||||
open_field_settings_description: 'On the right, next to the field name, there is a settings icon that opens the field settings.'
|
||||
field_settings: 'Field settings'
|
||||
field_settings_description: 'Each field can be customized through settings to meet your requirements, such as adjusting the font size, making it a required field, or adding a description.'
|
||||
send_document: 'Send document for signing'
|
||||
send_document_description: 'Before sending the document for signing, you can configure recipients, add their email addresses or phone numbers, and customize the email content.'
|
||||
sign_yourself: 'Sign the document yourself'
|
||||
sign_yourself_description: 'This button allows you to add your signature to the documents that require your signature.'
|
||||
copy_and_share_link: 'Copy & share link'
|
||||
copy_and_share_link_description: 'Copy this link to share the document. Anyone with the link can sign it after entering their email.'
|
||||
sign_the_document: 'Sign the document'
|
||||
sign_the_document_description: 'If you are one of the signers signer, this button allows you to sign the document yourself.'
|
||||
send_for_signing: 'Send for signing'
|
||||
add_recipients: 'Add recipients'
|
||||
add_recipients_description: 'Add new recipients for the document to be signed. You can enter their email addresses or phone numbers.'
|
||||
settings: 'Settings'
|
||||
settings_account_description: 'With comprehensive account settings, you can customize the eSigning experience and invite more users.'
|
||||
support: 'Support'
|
||||
support_description: 'You can use our self-service AI assistant or email us at support@docuseal.com if you have any questions.'
|
||||
settings_template_description: 'Use this button to access your account settings page.'
|
||||
doorkeeper:
|
||||
scopes:
|
||||
write: Update your data
|
||||
read: Read your data
|
||||
|
||||
es: &es
|
||||
always_enforce_signing_order: Siempre imponer el orden de firma
|
||||
bcc_recipients: Destinatarios CCO
|
||||
edit_per_party: Editar por parte
|
||||
signed: Firmado
|
||||
reply_to: Responder a
|
||||
@@ -890,7 +936,7 @@ es: &es
|
||||
connect_salesforce: Conectar Salesforce
|
||||
danger_zone: Zona de peligro
|
||||
delete_my_account: Eliminar mi cuenta
|
||||
schedule_account_for_deletion_: ¿Programar la eliminación de la cuenta?
|
||||
you_are_scheduling_your_account_for_deletion_after_deletion_your_data_will_be_permanently_removed_and_cannot_be_recovered_click_ok_if_you_would_like_to_continue: "Estás programando la eliminación de tu cuenta. Después de la eliminación, tus datos se eliminarán permanentemente y no podrán recuperarse.\n\nHaz clic en OK si deseas continuar."
|
||||
account_information_has_been_updated: La información de la cuenta ha sido actualizada.
|
||||
should_be_a_valid_url: debe ser una URL válida
|
||||
your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account: Tu solicitud de eliminación de cuenta se procesará en un plazo de 2 meses. Por favor contáctanos si deseas mantener tu cuenta.
|
||||
@@ -1158,7 +1204,7 @@ es: &es
|
||||
share_template_with_test_mode: Compartir plantilla con el modo de prueba
|
||||
share_template_with_all_tenants: Compartir plantilla con todos los inquilinos
|
||||
use_following_placeholders_text_: 'Usa los siguientes marcadores de posición:'
|
||||
upgrade_plan_to_add_more_users: Actualiza el plan para agregar más usuarios
|
||||
users_count_total_users_count_pro_users_limit_was_reached_to_invite_additional_users_please_purchase_more_pro_user_seats_via_the_manage_plan_button: 'Se alcanzó el límite de %{users_count}/%{total_users_count} usuarios Pro. Para invitar a más usuarios, compra más plazas Pro usando el botón "Gestionar plan".'
|
||||
move_into_folder: Mover a la carpeta
|
||||
new_folder_name: Nuevo nombre de la carpeta
|
||||
exit_preview: Salir de la vista previa
|
||||
@@ -1199,8 +1245,6 @@ es: &es
|
||||
team_accounts: Cuentas de equipo
|
||||
tenant_account: Cuenta de inquilino
|
||||
tenant_accounts: Cuentas de inquilino
|
||||
upgrade_plan: Actualizar plan
|
||||
add_user: Agregar usuario
|
||||
impersonate: Suplantar
|
||||
loading: Cargando
|
||||
documents: Documentos
|
||||
@@ -1426,6 +1470,10 @@ es: &es
|
||||
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
|
||||
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.'
|
||||
welcome_to_docuseal: Bienvenido a DocuSeal
|
||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: Inicia una guía rápida para aprender a crear y enviar tu primer documento.
|
||||
start_tour: Iniciar guía
|
||||
submission_sources:
|
||||
api: API
|
||||
bulk: Envío masivo
|
||||
@@ -1464,12 +1512,54 @@ es: &es
|
||||
or: O
|
||||
download: descarga
|
||||
a_sample_spreadsheet_to_fill_and_import: una hoja de cálculo de ejemplo para completar e importar.
|
||||
app_tour:
|
||||
start: Comenzar
|
||||
previous: Anterior
|
||||
next: Siguiente
|
||||
template_and_submissions: 'Plantillas y Envíos'
|
||||
template_and_submissions_description: "Puedes seleccionar la vista que mejor se adapte a tu flujo de trabajo. Elige la vista de 'Plantillas' si creas plantillas reutilizables de documentos, o la vista de 'Envíos' cuando firmas documentos individuales o revisas el estado de cada solicitud de firma."
|
||||
upload_a_pdf_file: 'Subir un archivo PDF'
|
||||
upload_a_pdf_file_description: 'Sube un documento para usarlo como plantilla de formulario de firma.'
|
||||
select_a_signer_party: 'Seleccionar parte firmante'
|
||||
select_a_signer_party_description: 'Este menú desplegable te permite seleccionar un rol de firmante o agregar una nueva parte al documento cuando debe ser firmado por dos o más partes.'
|
||||
available_parties: 'Partes disponibles'
|
||||
available_parties_description: 'Esta lista contiene todas las partes disponibles con sus roles. Una vez seleccionada, puedes añadir y configurar campos para esa parte.'
|
||||
available_field_types: 'Tipos de campo disponibles'
|
||||
available_field_types_description: 'Esta sección contiene todos los tipos de campo que se pueden añadir al documento para entrada de datos o recolección de firmas.'
|
||||
text_input_field: 'Campo de texto'
|
||||
text_input_field_description: 'Este campo permite a los usuarios introducir información de texto, como nombres o correos electrónicos.'
|
||||
signature_field: 'Campo de firma'
|
||||
signature_field_description: 'Este campo permite recopilar firmas de los firmantes.'
|
||||
added_fields: 'Campos añadidos'
|
||||
added_fields_description: 'Esta lista muestra todos los campos añadidos al documento. Puedes reordenarlos, editarlos o eliminarlos.'
|
||||
open_field_settings: 'Abrir configuración del campo'
|
||||
open_field_settings_description: 'A la derecha del nombre del campo, hay un icono de ajustes que abre la configuración del campo.'
|
||||
field_settings: 'Configuración del campo'
|
||||
field_settings_description: 'Cada campo puede personalizarse según tus necesidades, como ajustar el tamaño de fuente, hacerlo obligatorio o añadir una descripción.'
|
||||
send_document: 'Enviar documento para firmar'
|
||||
send_document_description: 'Antes de enviar el documento para firmar, puedes configurar destinatarios, añadir correos electrónicos o teléfonos, y personalizar el contenido del correo.'
|
||||
sign_yourself: 'Firma el documento tú mismo'
|
||||
sign_yourself_description: 'Este botón te permite añadir tu firma a documentos que requieren tu firma.'
|
||||
copy_and_share_link: 'Copiar y compartir enlace'
|
||||
copy_and_share_link_description: 'Copia este enlace para compartir el documento. Cualquiera con el enlace puede firmarlo tras ingresar su correo electrónico.'
|
||||
sign_the_document: 'Firmar el documento'
|
||||
sign_the_document_description: 'Si eres uno de los firmantes, este botón te permite firmar el documento tú mismo.'
|
||||
send_for_signing: 'Enviar para firma'
|
||||
add_recipients: 'Agregar destinatarios'
|
||||
add_recipients_description: 'Agrega nuevos destinatarios para que el documento sea firmado. Puedes ingresar sus correos electrónicos o números de teléfono.'
|
||||
settings: 'Configuración'
|
||||
settings_account_description: 'Con una configuración completa de la cuenta, puedes personalizar la experiencia de firma y añadir más usuarios.'
|
||||
support: 'Soporte'
|
||||
support_description: 'Puedes usar nuestro asistente de IA o escribirnos a support@docuseal.com si tienes preguntas.'
|
||||
settings_template_description: 'Usa este botón para acceder a la configuración de tu cuenta.'
|
||||
doorkeeper:
|
||||
scopes:
|
||||
write: Actualizar tus datos
|
||||
read: Leer tus datos
|
||||
|
||||
it: &it
|
||||
always_enforce_signing_order: Applicare sempre l'ordine di firma
|
||||
bcc_recipients: Destinatari BCC
|
||||
edit_per_party: Modifica per partito
|
||||
signed: Firmato
|
||||
reply_to: Rispondi a
|
||||
@@ -1615,7 +1705,7 @@ it: &it
|
||||
connect_salesforce: Connetti Salesforce
|
||||
danger_zone: Zona di pericolo
|
||||
delete_my_account: Elimina il mio account
|
||||
schedule_account_for_deletion_: "Programmare l'eliminazione dell'account?"
|
||||
you_are_scheduling_your_account_for_deletion_after_deletion_your_data_will_be_permanently_removed_and_cannot_be_recovered_click_ok_if_you_would_like_to_continue: "Stai programmando l'eliminazione del tuo account. Dopo l'eliminazione, i tuoi dati saranno rimossi in modo permanente e non potranno essere recuperati.\n\nFai clic su OK se desideri continuare."
|
||||
account_information_has_been_updated: "Le informazioni dell'account sono state aggiornate."
|
||||
should_be_a_valid_url: deve essere un URL valido
|
||||
your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account: "La tua richiesta di rimozione dell'account sarà elaborata entro 2 mesi. Contattaci se desideri mantenere il tuo account."
|
||||
@@ -1883,7 +1973,7 @@ it: &it
|
||||
share_template_with_test_mode: Condividi modello con la modalità di test
|
||||
share_template_with_all_tenants: Condividi il modello con tutti i tenant
|
||||
use_following_placeholders_text_: 'Usa i seguenti segnaposto:'
|
||||
upgrade_plan_to_add_more_users: Aggiorna il piano per aggiungere più utenti
|
||||
users_count_total_users_count_pro_users_limit_was_reached_to_invite_additional_users_please_purchase_more_pro_user_seats_via_the_manage_plan_button: 'È stato raggiunto il limite di %{users_count}/%{total_users_count} utenti Pro. Per invitare altri utenti, acquista più posti Pro tramite il pulsante "Gestisci piano".'
|
||||
move_into_folder: Sposta nella cartella
|
||||
new_folder_name: Nuovo nome della cartella
|
||||
exit_preview: "Esci dall'anteprima"
|
||||
@@ -1924,8 +2014,6 @@ it: &it
|
||||
team_accounts: Account di squadra
|
||||
tenant_account: Account tenant
|
||||
tenant_accounts: Account tenant
|
||||
upgrade_plan: Aggiorna piano
|
||||
add_user: Aggiungi utente
|
||||
impersonate: Impersona
|
||||
loading: Caricamento in corso
|
||||
documents: Documenti
|
||||
@@ -2151,6 +2239,10 @@ it: &it
|
||||
submission_created_via_source_html: '<b>Invio creato</b> tramite %{source}'
|
||||
pro_user_seats_used: Posti utente Pro in uso
|
||||
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."
|
||||
welcome_to_docuseal: Benvenuto in DocuSeal
|
||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: Inizia un tour rapido per imparare a creare e inviare il tuo primo documento.
|
||||
start_tour: Inizia il tour
|
||||
submission_sources:
|
||||
api: API
|
||||
bulk: Invio massivo
|
||||
@@ -2189,12 +2281,54 @@ it: &it
|
||||
or: Oppure
|
||||
download: scarica
|
||||
a_sample_spreadsheet_to_fill_and_import: un foglio di calcolo di esempio da compilare e importare.
|
||||
app_tour:
|
||||
start: Inizia
|
||||
previous: Precedente
|
||||
next: Successivo
|
||||
template_and_submissions: 'Modelli e Invii'
|
||||
template_and_submissions_description: "Puoi selezionare la vista che meglio si adatta al tuo flusso di lavoro. Scegli la vista 'Modelli' se crei modelli riutilizzabili di documenti, oppure la vista 'Invii' quando firmi documenti singoli o controlli lo stato di ogni richiesta di firma."
|
||||
upload_a_pdf_file: 'Carica un file PDF'
|
||||
upload_a_pdf_file_description: 'Carica un documento da utilizzare per creare un modello di modulo di firma.'
|
||||
select_a_signer_party: 'Seleziona una parte firmataria'
|
||||
select_a_signer_party_description: 'Questo menu a discesa consente di selezionare un ruolo di firmatario o aggiungere una nuova parte al documento quando deve essere firmato da due o più parti.'
|
||||
available_parties: 'Parti disponibili'
|
||||
available_parties_description: 'Questo elenco contiene tutte le parti disponibili con i relativi ruoli. Una volta selezionata, puoi aggiungere e configurare i campi per la parte firmataria.'
|
||||
available_field_types: 'Tipi di campo disponibili'
|
||||
available_field_types_description: "Questa sezione contiene tutti i tipi di campo disponibili che possono essere aggiunti al documento per l'inserimento di dati o la raccolta delle firme."
|
||||
text_input_field: 'Campo di testo'
|
||||
text_input_field_description: 'Questo campo consente agli utenti di inserire informazioni testuali, come nomi o email.'
|
||||
signature_field: 'Campo firma'
|
||||
signature_field_description: 'Questo campo consente di raccogliere firme dai firmatari.'
|
||||
added_fields: 'Campi aggiunti'
|
||||
added_fields_description: 'Questo elenco mostra tutti i campi aggiunti al documento. Puoi riordinarli, modificarli o rimuoverli.'
|
||||
open_field_settings: 'Apri le impostazioni del campo'
|
||||
open_field_settings_description: "A destra del nome del campo c'è un'icona delle impostazioni che apre il menu di configurazione."
|
||||
field_settings: 'Impostazioni del campo'
|
||||
field_settings_description: 'Ogni campo può essere personalizzato tramite le impostazioni per soddisfare le tue esigenze, come modificare la dimensione del testo, renderlo obbligatorio o aggiungere una descrizione.'
|
||||
send_document: 'Invia documento per la firma'
|
||||
send_document_description: "Prima di inviare il documento per la firma, puoi configurare i destinatari, aggiungere email o numeri di telefono e personalizzare il contenuto dell'email."
|
||||
sign_yourself: 'Firma il documento personalmente'
|
||||
sign_yourself_description: 'Questo pulsante ti consente di aggiungere la tua firma ai documenti che richiedono la tua firma.'
|
||||
copy_and_share_link: 'Copia e condividi il link'
|
||||
copy_and_share_link_description: 'Copia questo link per condividere il documento. Chiunque abbia il link potrà firmarlo dopo aver inserito la propria email.'
|
||||
sign_the_document: 'Firma il documento'
|
||||
sign_the_document_description: 'Se sei uno dei firmatari, questo pulsante ti consente di firmare il documento personalmente.'
|
||||
send_for_signing: 'Invia per la firma'
|
||||
add_recipients: 'Aggiungi destinatari'
|
||||
add_recipients_description: 'Aggiungi nuovi destinatari per la firma del documento. Puoi inserire il loro indirizzo email o numero di telefono.'
|
||||
settings: 'Impostazioni'
|
||||
settings_account_description: "Con impostazioni complete dell'account, puoi personalizzare l'esperienza di firma elettronica e invitare altri utenti."
|
||||
support: 'Supporto'
|
||||
support_description: 'Puoi usare il nostro assistente AI oppure scriverci a support@docuseal.com se hai domande.'
|
||||
settings_template_description: "Usa questo pulsante per accedere alla pagina delle impostazioni dell'account."
|
||||
doorkeeper:
|
||||
scopes:
|
||||
write: Aggiorna i tuoi dati
|
||||
read: Leggi i tuoi dati
|
||||
|
||||
fr: &fr
|
||||
always_enforce_signing_order: Toujours appliquer l'ordre de signature
|
||||
bcc_recipients: Destinataires en CCI
|
||||
edit_per_party: Éditer par partie
|
||||
signed: Signé
|
||||
reply_to: Répondre à
|
||||
@@ -2342,7 +2476,7 @@ fr: &fr
|
||||
connect_salesforce: Connecter Salesforce
|
||||
danger_zone: Zone dangereuse
|
||||
delete_my_account: Supprimer mon compte
|
||||
schedule_account_for_deletion_: Programmer la suppression du compte?
|
||||
you_are_scheduling_your_account_for_deletion_after_deletion_your_data_will_be_permanently_removed_and_cannot_be_recovered_click_ok_if_you_would_like_to_continue: "Vous programmez la suppression de votre compte. Après suppression, vos données seront définitivement supprimées et ne pourront pas être récupérées.\n\nCliquez sur OK si vous souhaitez continuer."
|
||||
account_information_has_been_updated: Les informations du compte ont été mises à jour.
|
||||
should_be_a_valid_url: doit être une URL valide
|
||||
your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account: Votre demande de suppression du compte sera traitée dans un délai de 2 mois. Veuillez nous contacter si vous souhaitez conserver votre compte.
|
||||
@@ -2610,7 +2744,7 @@ fr: &fr
|
||||
share_template_with_test_mode: Partager le modèle avec le mode test
|
||||
share_template_with_all_tenants: Partager le modèle avec tous les locataires
|
||||
use_following_placeholders_text_: 'Utilisez les espaces réservés suivants:'
|
||||
upgrade_plan_to_add_more_users: "Mettez à jour le plan pour ajouter plus d'utilisateurs"
|
||||
users_count_total_users_count_pro_users_limit_was_reached_to_invite_additional_users_please_purchase_more_pro_user_seats_via_the_manage_plan_button: 'La limite de %{users_count}/%{total_users_count} utilisateurs Pro a été atteinte. Pour inviter d''autres utilisateurs, veuillez acheter plus de places Pro via le bouton "Gérer le plan".'
|
||||
move_into_folder: Déplacer dans le dossier
|
||||
new_folder_name: Nouveau nom du dossier
|
||||
exit_preview: "Quitter l'aperçu"
|
||||
@@ -2651,8 +2785,6 @@ fr: &fr
|
||||
team_accounts: "Comptes d'équipe"
|
||||
tenant_account: Compte locataire
|
||||
tenant_accounts: Comptes locataires
|
||||
upgrade_plan: Mettre à jour le plan
|
||||
add_user: Ajouter un utilisateur
|
||||
impersonate: Usurper
|
||||
loading: Chargement en cours
|
||||
documents: Documents
|
||||
@@ -2878,6 +3010,10 @@ fr: &fr
|
||||
submission_created_via_source_html: '<b>Soumission créée</b> via %{source}'
|
||||
pro_user_seats_used: Places utilisateur Pro en cours d'utilisation
|
||||
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.'
|
||||
welcome_to_docuseal: Bienvenue sur DocuSeal
|
||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: Lancez une visite rapide pour apprendre à créer et envoyer votre premier document.
|
||||
start_tour: Démarrer
|
||||
submission_sources:
|
||||
api: API
|
||||
bulk: Envoi en masse
|
||||
@@ -2916,12 +3052,54 @@ fr: &fr
|
||||
or: Un
|
||||
download: téléchargez
|
||||
a_sample_spreadsheet_to_fill_and_import: un modèle de feuille de calcul à remplir et importer.
|
||||
app_tour:
|
||||
start: Démarrer
|
||||
previous: Précédent
|
||||
next: Suivant
|
||||
template_and_submissions: 'Modèles et Soumissions'
|
||||
template_and_submissions_description: "Vous pouvez sélectionner la vue qui correspond le mieux à votre flux de travail. Choisissez la vue 'Modèles' si vous créez des modèles de documents réutilisables, ou la vue 'Soumissions' lorsque vous signez des documents individuels ou vérifiez l'état de chaque demande de signature."
|
||||
upload_a_pdf_file: 'Téléverser un fichier PDF'
|
||||
upload_a_pdf_file_description: "Téléversez un document pour l'utiliser comme modèle de formulaire de signature."
|
||||
select_a_signer_party: 'Sélectionner une partie signataire'
|
||||
select_a_signer_party_description: "Ce menu déroulant vous permet de sélectionner un rôle de signataire ou d'ajouter une nouvelle partie au document lorsqu'il doit être signé par deux parties ou plus."
|
||||
available_parties: 'Parties disponibles'
|
||||
available_parties_description: 'Cette liste contient toutes les parties disponibles avec leurs rôles. Une fois sélectionnée, vous pouvez ajouter et configurer les champs pour cette partie.'
|
||||
available_field_types: 'Types de champs disponibles'
|
||||
available_field_types_description: 'Cette section contient tous les types de champs pouvant être ajoutés au document pour la saisie de données ou la collecte de signatures.'
|
||||
text_input_field: 'Champ de texte'
|
||||
text_input_field_description: 'Ce champ permet aux utilisateurs de saisir des informations textuelles telles que des noms ou des adresses e-mail.'
|
||||
signature_field: 'Champ de signature'
|
||||
signature_field_description: 'Ce champ permet de collecter les signatures des signataires.'
|
||||
added_fields: 'Champs ajoutés'
|
||||
added_fields_description: 'Cette liste affiche tous les champs ajoutés au document. Vous pouvez les réorganiser, les modifier ou les supprimer.'
|
||||
open_field_settings: 'Ouvrir les paramètres du champ'
|
||||
open_field_settings_description: "À droite du nom du champ se trouve une icône permettant d'ouvrir les paramètres du champ."
|
||||
field_settings: 'Paramètres du champ'
|
||||
field_settings_description: "Chaque champ peut être personnalisé à l'aide des paramètres pour répondre à vos besoins, comme ajuster la taille de la police, le rendre obligatoire ou ajouter une description."
|
||||
send_document: 'Envoyer le document pour signature'
|
||||
send_document_description: "Avant d'envoyer le document pour signature, vous pouvez configurer les destinataires, ajouter leurs e-mails ou numéros de téléphone et personnaliser le contenu de l'e-mail."
|
||||
sign_yourself: 'Signer le document vous-même'
|
||||
sign_yourself_description: "Ce bouton vous permet d'ajouter votre propre signature aux documents qui en nécessitent une."
|
||||
copy_and_share_link: 'Copier et partager le lien'
|
||||
copy_and_share_link_description: 'Copiez ce lien pour partager le document. Toute personne disposant du lien pourra le signer après avoir saisi son e-mail.'
|
||||
sign_the_document: 'Signer le document'
|
||||
sign_the_document_description: 'Si vous êtes l’un des signataires, ce bouton vous permet de signer le document vous-même.'
|
||||
send_for_signing: 'Envoyer pour signature'
|
||||
add_recipients: 'Ajouter des destinataires'
|
||||
add_recipients_description: 'Ajoutez de nouveaux destinataires pour que le document soit signé. Vous pouvez saisir leur adresse e-mail ou leur numéro de téléphone.'
|
||||
settings: 'Paramètres'
|
||||
settings_account_description: "Grâce à des paramètres de compte complets, vous pouvez personnaliser l'expérience de signature électronique et inviter d'autres utilisateurs."
|
||||
support: 'Assistance'
|
||||
support_description: 'Vous pouvez utiliser notre assistant IA ou nous écrire à support@docuseal.com si vous avez des questions.'
|
||||
settings_template_description: 'Utilisez ce bouton pour accéder à la page des paramètres de votre compte.'
|
||||
doorkeeper:
|
||||
scopes:
|
||||
write: Mettre à jour vos données
|
||||
read: Lire vos données
|
||||
|
||||
pt: &pt
|
||||
always_enforce_signing_order: Sempre impor a ordem de assinatura
|
||||
bcc_recipients: Destinatários BCC
|
||||
edit_per_party: Edita por festa
|
||||
signed: Assinado
|
||||
reply_to: Responder a
|
||||
@@ -3068,7 +3246,7 @@ pt: &pt
|
||||
connect_salesforce: Conectar Salesforce
|
||||
danger_zone: Zona de perigo
|
||||
delete_my_account: Excluir minha conta
|
||||
schedule_account_for_deletion_: Agendar exclusão da conta?
|
||||
you_are_scheduling_your_account_for_deletion_after_deletion_your_data_will_be_permanently_removed_and_cannot_be_recovered_click_ok_if_you_would_like_to_continue: "Você está agendando a exclusão da sua conta. Após a exclusão, seus dados serão permanentemente removidos e não poderão ser recuperados.\n\nClique em OK se desejar continuar."
|
||||
account_information_has_been_updated: As informações da conta foram atualizadas.
|
||||
should_be_a_valid_url: deve ser um URL válido
|
||||
your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account: Seu pedido de remoção da conta será processado em até 2 meses. Entre em contato conosco se você quiser manter sua conta.
|
||||
@@ -3336,7 +3514,7 @@ pt: &pt
|
||||
share_template_with_test_mode: Compartilhar modelo com o modo de teste
|
||||
share_template_with_all_tenants: Compartilhar modelo com todos os locatários
|
||||
use_following_placeholders_text_: 'Use os seguintes textos de substituição:'
|
||||
upgrade_plan_to_add_more_users: Faça upgrade do plano para adicionar mais usuários
|
||||
users_count_total_users_count_pro_users_limit_was_reached_to_invite_additional_users_please_purchase_more_pro_user_seats_via_the_manage_plan_button: 'O limite de %{users_count}/%{total_users_count} usuários Pro foi atingido. Para convidar mais usuários, adquira mais licenças Pro através do botão "Gerenciar plano".'
|
||||
move_into_folder: Mover para pasta
|
||||
new_folder_name: Novo nome da pasta
|
||||
exit_preview: Sair da pré-visualização
|
||||
@@ -3377,8 +3555,6 @@ pt: &pt
|
||||
team_accounts: Contas de equipe
|
||||
tenant_account: Conta de locatário
|
||||
tenant_accounts: Contas de locatário
|
||||
upgrade_plan: Fazer upgrade do plano
|
||||
add_user: Adicionar usuário
|
||||
impersonate: Usurpar
|
||||
loading: Carregando
|
||||
documents: Documentos
|
||||
@@ -3604,6 +3780,10 @@ pt: &pt
|
||||
submission_created_via_source_html: '<b>Envio criado</b> via %{source}'
|
||||
pro_user_seats_used: Lugares de usuário Pro em uso
|
||||
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.'
|
||||
welcome_to_docuseal: Bem-vindo ao DocuSeal
|
||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: Comece um tour rápido para aprender a criar e enviar seu primeiro documento.
|
||||
start_tour: Iniciar tour
|
||||
submission_sources:
|
||||
api: API
|
||||
bulk: Envio em massa
|
||||
@@ -3642,12 +3822,55 @@ pt: &pt
|
||||
or: Ou
|
||||
download: baixe
|
||||
a_sample_spreadsheet_to_fill_and_import: uma planilha de exemplo para preencher e importar.
|
||||
app_tour:
|
||||
start: Iniciar
|
||||
previous: Anterior
|
||||
next: Próximo
|
||||
template_and_submissions: 'Modelos e Envios'
|
||||
template_and_submissions_description: "Você pode selecionar a visualização que melhor se adapta ao seu fluxo de trabalho. Escolha a visualização 'Modelos' se criar modelos de documentos reutilizáveis, ou 'Envios' ao assinar documentos individuais ou revisar o status de cada solicitação de assinatura."
|
||||
upload_a_pdf_file: 'Enviar um arquivo PDF'
|
||||
upload_a_pdf_file_description: 'Envie um documento para usá-lo como modelo de formulário de assinatura.'
|
||||
select_a_signer_party: 'Selecionar parte assinante'
|
||||
select_a_signer_party_description: 'Este menu suspenso permite selecionar um papel de assinante ou adicionar uma nova parte ao documento quando ele precisa ser assinado por duas ou mais partes.'
|
||||
available_parties: 'Partes disponíveis'
|
||||
available_parties_description: 'Esta lista contém todas as partes disponíveis com seus respectivos papéis. Após selecionar, você poderá adicionar e configurar os campos para essa parte.'
|
||||
available_field_types: 'Tipos de campo disponíveis'
|
||||
available_field_types_description: 'Esta seção contém todos os tipos de campos que podem ser adicionados ao documento para entrada de dados ou coleta de assinaturas.'
|
||||
text_input_field: 'Campo de texto'
|
||||
text_input_field_description: 'Este campo permite que os usuários insiram informações baseadas em texto, como nomes ou e-mails.'
|
||||
signature_field: 'Campo de assinatura'
|
||||
signature_field_description: 'Este campo permite coletar assinaturas dos signatários.'
|
||||
added_fields: 'Campos adicionados'
|
||||
added_fields_description: 'Esta lista exibe todos os campos adicionados ao documento. Você pode reordená-los, editá-los ou removê-los.'
|
||||
open_field_settings: 'Abrir configurações do campo'
|
||||
open_field_settings_description: 'À direita do nome do campo há um ícone de configurações que abre as configurações do campo.'
|
||||
field_settings: 'Configurações do campo'
|
||||
field_settings_description: 'Cada campo pode ser personalizado de acordo com suas necessidades, como ajustar o tamanho da fonte, torná-lo obrigatório ou adicionar uma descrição.'
|
||||
send_document: 'Enviar documento para assinatura'
|
||||
send_document_description: 'Antes de enviar o documento para assinatura, você pode configurar os destinatários, adicionar e-mails ou números de telefone e personalizar o conteúdo do e-mail.'
|
||||
sign_yourself: 'Assinar o documento você mesmo'
|
||||
sign_yourself_description: 'Este botão permite que você adicione sua própria assinatura aos documentos que exigem sua assinatura.'
|
||||
copy_and_share_link: 'Copiar e compartilhar link'
|
||||
copy_and_share_link_description: 'Copie este link para compartilhar o documento. Qualquer pessoa com o link pode assiná-lo após inserir seu e-mail.'
|
||||
sign_the_document: 'Assinar o documento'
|
||||
sign_the_document_description: 'Se você for um dos signatários, este botão permite que você assine o documento.'
|
||||
send_for_signing: 'Enviar para assinatura'
|
||||
add_recipients: 'Adicionar destinatários'
|
||||
add_recipients_description: 'Adicione novos destinatários para que o documento seja assinado. Você pode inserir seus e-mails ou números de telefone.'
|
||||
settings: 'Configurações'
|
||||
settings_account_description: 'Com configurações completas de conta, você pode personalizar a experiência de assinatura eletrônica e convidar mais usuários.'
|
||||
support: 'Suporte'
|
||||
support_description: 'Você pode usar nosso assistente de IA ou nos escrever para support@docuseal.com se tiver dúvidas.'
|
||||
settings: 'Configurações'
|
||||
settings_template_description: 'Use este botão para acessar a página de configurações da sua conta.'
|
||||
doorkeeper:
|
||||
scopes:
|
||||
write: Atualizar seus dados
|
||||
read: Ler seus dados
|
||||
|
||||
de: &de
|
||||
always_enforce_signing_order: Immer die Reihenfolge der Unterschriften erzwingen
|
||||
bcc_recipients: BCC-Empfänger
|
||||
edit_per_party: Bearbeiten pro Partei
|
||||
signed: Unterschrieben
|
||||
reply_to: Antworten auf
|
||||
@@ -3794,7 +4017,7 @@ de: &de
|
||||
connect_salesforce: Salesforce verbinden
|
||||
danger_zone: Gefahrenzone
|
||||
delete_my_account: Mein Konto löschen
|
||||
schedule_account_for_deletion_: Konto zur Löschung einplanen?
|
||||
you_are_scheduling_your_account_for_deletion_after_deletion_your_data_will_be_permanently_removed_and_cannot_be_recovered_click_ok_if_you_would_like_to_continue: "Du planst die Löschung deines Kontos. Nach der Löschung werden deine Daten dauerhaft entfernt und können nicht wiederhergestellt werden.\n\nKlicke auf OK, wenn du fortfahren möchtest."
|
||||
account_information_has_been_updated: Die Kontoinformationen wurden aktualisiert.
|
||||
should_be_a_valid_url: sollte eine gültige URL sein
|
||||
your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account: Deine Anfrage zur Kontolöschung wird innerhalb von 2 Monaten bearbeitet. Bitte kontaktiere uns, wenn du dein Konto behalten möchtest.
|
||||
@@ -4062,7 +4285,7 @@ de: &de
|
||||
share_template_with_test_mode: Vorlage mit dem Testmodus teilen
|
||||
share_template_with_all_tenants: Vorlage mit allen Mietern teilen
|
||||
use_following_placeholders_text_: 'Verwende die folgenden Platzhaltertexte:'
|
||||
upgrade_plan_to_add_more_users: Upgrade des Plans, um weitere Benutzer hinzuzufügen
|
||||
users_count_total_users_count_pro_users_limit_was_reached_to_invite_additional_users_please_purchase_more_pro_user_seats_via_the_manage_plan_button: 'Das Limit von %{users_count}/%{total_users_count} Pro-Benutzern wurde erreicht. Um weitere Benutzer einzuladen, kaufen Sie bitte zusätzliche Pro-Benutzerplätze über die Schaltfläche "Plan verwalten".'
|
||||
move_into_folder: In Ordner verschieben
|
||||
new_folder_name: Neuer Ordnername
|
||||
exit_preview: Vorschau beenden
|
||||
@@ -4103,8 +4326,6 @@ de: &de
|
||||
team_accounts: Teamkonten
|
||||
tenant_account: Mieterkonto
|
||||
tenant_accounts: Mieterkonten
|
||||
upgrade_plan: Plan upgraden
|
||||
add_user: Benutzer hinzufügen
|
||||
impersonate: Nachahmen
|
||||
loading: Wird geladen
|
||||
documents: Dokumente
|
||||
@@ -4330,6 +4551,10 @@ de: &de
|
||||
submission_created_via_source_html: '<b>Übermittlung erstellt</b> über %{source}'
|
||||
pro_user_seats_used: Verwendete Pro-Benutzerplätze
|
||||
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.'
|
||||
welcome_to_docuseal: Willkommen bei DocuSeal
|
||||
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: Starte eine kurze Tour, um zu lernen, wie du dein erstes Dokument erstellst und versendest.
|
||||
start_tour: Starten
|
||||
submission_sources:
|
||||
api: API
|
||||
bulk: Massenversand
|
||||
@@ -4368,6 +4593,46 @@ de: &de
|
||||
or: Oder
|
||||
download: laden
|
||||
a_sample_spreadsheet_to_fill_and_import: Sie eine Beispieltabelle herunter, um sie auszufüllen und zu importieren.
|
||||
app_tour:
|
||||
start: Start
|
||||
previous: Zurück
|
||||
next: Weiter
|
||||
template_and_submissions: 'Vorlagen und Übermittlungen'
|
||||
template_and_submissions_description: "Du kannst die Ansicht wählen, die am besten zu deinem Arbeitsablauf passt. Wähle die Ansicht 'Vorlagen', wenn du wiederverwendbare Dokumentenvorlagen erstellst, oder 'Übermittlungen', wenn du einzelne Dokumente unterschreibst oder den Status jeder Signaturanfrage überprüfen möchtest."
|
||||
upload_a_pdf_file: 'PDF-Datei hochladen'
|
||||
upload_a_pdf_file_description: 'Lade ein Dokument hoch, um es als Vorlage für ein Signaturformular zu verwenden.'
|
||||
select_a_signer_party: 'Signaturpartei auswählen'
|
||||
select_a_signer_party_description: 'Dieses Dropdown-Menü ermöglicht es dir, eine Signaturrolle auszuwählen oder eine neue Partei zum Dokument hinzuzufügen, wenn es von zwei oder mehr Parteien unterzeichnet werden muss.'
|
||||
available_parties: 'Verfügbare Parteien'
|
||||
available_parties_description: 'Diese Liste enthält alle verfügbaren Parteien mit ihren Rollennamen. Nach der Auswahl kannst du Felder für die Partei hinzufügen und konfigurieren.'
|
||||
available_field_types: 'Verfügbare Feldtypen'
|
||||
available_field_types_description: 'Dieser Bereich enthält alle verfügbaren Feldtypen, die dem Dokument für Dateneingabe oder Signaturerfassung hinzugefügt werden können.'
|
||||
text_input_field: 'Textfeld'
|
||||
text_input_field_description: 'Dieses Feld ermöglicht es Benutzern, textbasierte Informationen wie Namen oder E-Mails einzugeben.'
|
||||
signature_field: 'Signaturfeld'
|
||||
signature_field_description: 'Dieses Feld dient der Erfassung von Signaturen der Unterzeichner.'
|
||||
added_fields: 'Hinzugefügte Felder'
|
||||
added_fields_description: 'Diese Liste zeigt alle dem Dokument hinzugefügten Felder. Du kannst sie neu anordnen, bearbeiten oder entfernen.'
|
||||
open_field_settings: 'Feld-Einstellungen öffnen'
|
||||
open_field_settings_description: 'Rechts neben dem Feldnamen befindet sich ein Zahnradsymbol, um die Einstellungen des Feldes zu öffnen.'
|
||||
field_settings: 'Feld-Einstellungen'
|
||||
field_settings_description: 'Jedes Feld kann individuell angepasst werden, z. B. durch Schriftgröße, Pflichtfeld-Option oder Beschreibung.'
|
||||
send_document: 'Dokument zum Unterschreiben senden'
|
||||
send_document_description: 'Bevor du das Dokument zum Unterschreiben sendest, kannst du Empfänger hinzufügen, ihre E-Mail-Adressen oder Telefonnummern eingeben und den E-Mail-Inhalt anpassen.'
|
||||
sign_yourself: 'Dokument selbst unterschreiben'
|
||||
sign_yourself_description: 'Mit diesem Button kannst du deine eigene Unterschrift zu Dokumenten hinzufügen, die deine Signatur erfordern.'
|
||||
copy_and_share_link: 'Link kopieren und teilen'
|
||||
copy_and_share_link_description: 'Kopiere diesen Link, um das Dokument zu teilen. Jeder mit dem Link kann es unterschreiben, nachdem er seine E-Mail-Adresse eingegeben hat.'
|
||||
sign_the_document: 'Dokument unterschreiben'
|
||||
sign_the_document_description: 'Wenn du einer der Unterzeichner bist, kannst du das Dokument mit diesem Button selbst unterschreiben.'
|
||||
send_for_signing: 'Zum Unterschreiben senden'
|
||||
add_recipients: 'Empfänger hinzufügen'
|
||||
add_recipients_description: 'Füge neue Empfänger hinzu, damit das Dokument unterschrieben werden kann. Du kannst deren E-Mail-Adressen oder Telefonnummern eingeben.'
|
||||
settings: 'Einstellungen'
|
||||
settings_account_description: 'Mit umfangreichen Kontoeinstellungen kannst du das Signiererlebnis anpassen und weitere Benutzer einladen.'
|
||||
support: 'Support'
|
||||
support_description: 'Du kannst unseren KI-Assistenten nutzen oder uns bei Fragen an support@docuseal.com schreiben.'
|
||||
settings_template_description: 'Mit diesem Button gelangst du zu den Kontoeinstellungen.'
|
||||
doorkeeper:
|
||||
scopes:
|
||||
write: Aktualisiere deine Daten
|
||||
|
||||
+8
-2
@@ -33,8 +33,14 @@ environment ENV.fetch('RAILS_ENV', 'development')
|
||||
# the concurrency of the application would be max `threads` * `workers`.
|
||||
# Workers do not work on JRuby or Windows (both of which do not support
|
||||
# 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.
|
||||
# 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
|
||||
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
|
||||
|
||||
ActiveSupport.run_load_hooks(:multitenant_routes, self)
|
||||
end
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
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
|
||||
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|
|
||||
t.bigint "submitter_id", null: false
|
||||
t.string "event_name", null: false
|
||||
|
||||
@@ -28,7 +28,7 @@ module ActionMailerConfigsInterceptor
|
||||
end
|
||||
|
||||
unless Docuseal.multitenant?
|
||||
email_configs = EncryptedConfig.find_by(key: EncryptedConfig::EMAIL_SMTP_KEY)
|
||||
email_configs = EncryptedConfig.order(:account_id).find_by(key: EncryptedConfig::EMAIL_SMTP_KEY)
|
||||
|
||||
if email_configs
|
||||
message.delivery_method(:smtp, build_smtp_configs_hash(email_configs))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user