mirror of
https://github.com/docusealco/docuseal
synced 2026-11-05 06:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13a1835c00 | ||
|
|
c35407d7a9 | ||
|
|
e55ccc0b76 | ||
|
|
b8d00870b4 | ||
|
|
5413fbc248 | ||
|
|
2d2eb7ce4d | ||
|
|
ed2ddbf74e | ||
|
|
b99e5d1262 | ||
|
|
87f6878eda | ||
|
|
99b815eb4b | ||
|
|
e40a29b9ab | ||
|
|
151890bb72 | ||
|
|
78edd87cc1 | ||
|
|
88c99a2b35 | ||
|
|
282b8cfa73 | ||
|
|
e9888c7466 | ||
|
|
0647befb3a | ||
|
|
85aa3d0451 | ||
|
|
6d2b3bb654 | ||
|
|
b394854e9d | ||
|
|
2e586e930e | ||
|
|
8f8f69f0bc | ||
|
|
04663d55eb | ||
|
|
93d0b935e2 | ||
|
|
829b2b2855 | ||
|
|
cf6de444f4 | ||
|
|
126f5d58a2 | ||
|
|
09b93067f4 | ||
|
|
f14458f238 | ||
|
|
445139f05c | ||
|
|
e5e4965f05 | ||
|
|
1c8f7b0316 | ||
|
|
9fe3656652 | ||
|
|
17ae46bd61 | ||
|
|
a036483938 | ||
|
|
3c65b0e4ba | ||
|
|
ced5ab67ab | ||
|
|
28c4177128 | ||
|
|
5d86a08b5f | ||
|
|
a69fb81429 | ||
|
|
e6ac3380d4 | ||
|
|
3a535e7112 | ||
|
|
7464f9cfad | ||
|
|
58e24ef758 | ||
|
|
c1d809f989 | ||
|
|
80b24f0e2a | ||
|
|
2e253904a3 | ||
|
|
a7f381caf0 |
No files matched your search
@@ -12,7 +12,7 @@ jobs:
|
|||||||
- name: Install Ruby
|
- name: Install Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: 4.0.5
|
ruby-version: 4.0.9
|
||||||
- name: Cache gems
|
- name: Cache gems
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
- name: Install Ruby
|
- name: Install Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: 4.0.5
|
ruby-version: 4.0.9
|
||||||
- name: Cache gems
|
- name: Cache gems
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
@@ -89,7 +89,7 @@ jobs:
|
|||||||
- name: Install Ruby
|
- name: Install Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: 4.0.5
|
ruby-version: 4.0.9
|
||||||
- name: Cache gems
|
- name: Cache gems
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
@@ -113,7 +113,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
container:
|
container:
|
||||||
image: ruby:4.0.5-alpine
|
image: ruby:4.0.9-alpine
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
|
|||||||
+8
-5
@@ -1,4 +1,4 @@
|
|||||||
FROM ruby:4.0.5-alpine AS download
|
FROM ruby:4.0.9-alpine AS download
|
||||||
|
|
||||||
WORKDIR /fonts
|
WORKDIR /fonts
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ RUN apk --no-cache add wget unzip && \
|
|||||||
mkdir -p /pdfium-linux && \
|
mkdir -p /pdfium-linux && \
|
||||||
unzip -q pdfium-linux.zip -d /pdfium-linux
|
unzip -q pdfium-linux.zip -d /pdfium-linux
|
||||||
|
|
||||||
FROM ruby:4.0.5-alpine AS webpack
|
FROM ruby:4.0.9-alpine AS webpack
|
||||||
|
|
||||||
ENV RAILS_ENV=production
|
ENV RAILS_ENV=production
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
@@ -44,7 +44,7 @@ COPY ./app/views ./app/views
|
|||||||
|
|
||||||
RUN echo "gem 'shakapacker'" > Gemfile && ./bin/shakapacker
|
RUN echo "gem 'shakapacker'" > Gemfile && ./bin/shakapacker
|
||||||
|
|
||||||
FROM ruby:4.0.5-alpine AS app
|
FROM ruby:4.0.9-alpine AS app
|
||||||
|
|
||||||
ENV RAILS_ENV=production
|
ENV RAILS_ENV=production
|
||||||
ENV BUNDLE_WITHOUT="development:test"
|
ENV BUNDLE_WITHOUT="development:test"
|
||||||
@@ -52,7 +52,8 @@ ENV OPENSSL_CONF=/etc/openssl_legacy.cnf
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apk add --no-cache libpq vips redis onnxruntime leptonica && \
|
RUN apk upgrade --no-cache ca-certificates-bundle && \
|
||||||
|
apk add --no-cache libpq vips redis onnxruntime leptonica jemalloc font-liberation font-carlito && \
|
||||||
rm -f /usr/bin/onnx_test_runner /usr/bin/onnxruntime_test
|
rm -f /usr/bin/onnx_test_runner /usr/bin/onnxruntime_test
|
||||||
|
|
||||||
RUN addgroup -g 2000 docuseal && adduser -u 2000 -G docuseal -s /bin/sh -D -h /home/docuseal docuseal
|
RUN addgroup -g 2000 docuseal && adduser -u 2000 -G docuseal -s /bin/sh -D -h /home/docuseal docuseal
|
||||||
@@ -91,7 +92,7 @@ COPY --chown=docuseal:docuseal --from=download /model.onnx /app/tmp/model.onnx
|
|||||||
COPY --chown=docuseal:docuseal --from=webpack /app/public/packs ./public/packs
|
COPY --chown=docuseal:docuseal --from=webpack /app/public/packs ./public/packs
|
||||||
|
|
||||||
RUN mkdir -p /app/public/fonts && ln -s /fonts/DancingScript-Regular.otf /app/public/fonts/ && \
|
RUN mkdir -p /app/public/fonts && ln -s /fonts/DancingScript-Regular.otf /app/public/fonts/ && \
|
||||||
mkdir -p /usr/share/fonts/noto && ln -s /fonts/GoNotoKurrent-Regular.ttf /usr/share/fonts/noto/ && ln -s /fonts/GoNotoKurrent-Bold.ttf /usr/share/fonts/noto/ && fc-cache -f && \
|
mkdir -p /usr/share/fonts/noto/bold && ln -s /fonts/GoNotoKurrent-Regular.ttf /usr/share/fonts/noto/ && ln -s /fonts/GoNotoKurrent-Bold.ttf /usr/share/fonts/noto/bold/ && fc-cache -f && \
|
||||||
bundle exec bootsnap precompile -j 1 --gemfile app/ lib/ && \
|
bundle exec bootsnap precompile -j 1 --gemfile app/ lib/ && \
|
||||||
chown -R docuseal:docuseal /app/tmp/cache
|
chown -R docuseal:docuseal /app/tmp/cache
|
||||||
|
|
||||||
@@ -100,6 +101,8 @@ ENV HOME=/home/docuseal
|
|||||||
ENV WORKDIR=/data/docuseal
|
ENV WORKDIR=/data/docuseal
|
||||||
ENV VIPS_MAX_COORD=17000
|
ENV VIPS_MAX_COORD=17000
|
||||||
ENV VIPS_BLOCK_UNTRUSTED=1
|
ENV VIPS_BLOCK_UNTRUSTED=1
|
||||||
|
# ENV LD_PRELOAD=/usr/lib/libjemalloc.so.2
|
||||||
|
ENV MALLOC_CONF=dirty_decay_ms:1000,narenas:2,background_thread:true
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
CMD ["/app/bin/bundle", "exec", "puma", "-C", "/app/config/puma.rb", "--dir", "/app"]
|
CMD ["/app/bin/bundle", "exec", "puma", "-C", "/app/config/puma.rb", "--dir", "/app"]
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
ruby '4.0.5'
|
ruby '4.0.9'
|
||||||
|
|
||||||
gem 'addressable'
|
gem 'addressable'
|
||||||
gem 'arabic-letter-connector', require: false
|
gem 'arabic-letter-connector', require: false
|
||||||
@@ -45,6 +45,7 @@ gem 'trilogy', require: false
|
|||||||
gem 'turbo-rails'
|
gem 'turbo-rails'
|
||||||
gem 'twitter_cldr', require: false
|
gem 'twitter_cldr', require: false
|
||||||
gem 'tzinfo-data'
|
gem 'tzinfo-data'
|
||||||
|
gem 'wordcraft', require: false, group: :ce
|
||||||
gem 'xlsxtream', require: false
|
gem 'xlsxtream', require: false
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
|
|||||||
+17
-6
@@ -524,11 +524,11 @@ GEM
|
|||||||
simplecov-html (0.13.2)
|
simplecov-html (0.13.2)
|
||||||
simplecov_json_formatter (0.1.4)
|
simplecov_json_formatter (0.1.4)
|
||||||
smart_properties (1.17.0)
|
smart_properties (1.17.0)
|
||||||
sqlite3 (2.9.5-aarch64-linux-gnu)
|
sqlite3 (2.9.6-aarch64-linux-gnu)
|
||||||
sqlite3 (2.9.5-aarch64-linux-musl)
|
sqlite3 (2.9.6-aarch64-linux-musl)
|
||||||
sqlite3 (2.9.5-arm64-darwin)
|
sqlite3 (2.9.6-arm64-darwin)
|
||||||
sqlite3 (2.9.5-x86_64-linux-gnu)
|
sqlite3 (2.9.6-x86_64-linux-gnu)
|
||||||
sqlite3 (2.9.5-x86_64-linux-musl)
|
sqlite3 (2.9.6-x86_64-linux-musl)
|
||||||
strip_attributes (2.0.1)
|
strip_attributes (2.0.1)
|
||||||
activemodel (>= 3.0, < 9.0)
|
activemodel (>= 3.0, < 9.0)
|
||||||
strscan (3.1.8)
|
strscan (3.1.8)
|
||||||
@@ -572,6 +572,16 @@ GEM
|
|||||||
base64
|
base64
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
|
wordcraft (0.11.0-aarch64-linux-gnu)
|
||||||
|
ffi (>= 1.15)
|
||||||
|
wordcraft (0.11.0-aarch64-linux-musl)
|
||||||
|
ffi (>= 1.15)
|
||||||
|
wordcraft (0.11.0-arm64-darwin)
|
||||||
|
ffi (>= 1.15)
|
||||||
|
wordcraft (0.11.0-x86_64-linux-gnu)
|
||||||
|
ffi (>= 1.15)
|
||||||
|
wordcraft (0.11.0-x86_64-linux-musl)
|
||||||
|
ffi (>= 1.15)
|
||||||
xlsxtream (3.1.0)
|
xlsxtream (3.1.0)
|
||||||
zip_kit (>= 6.2, < 7)
|
zip_kit (>= 6.2, < 7)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
@@ -650,10 +660,11 @@ DEPENDENCIES
|
|||||||
tzinfo-data
|
tzinfo-data
|
||||||
web-console
|
web-console
|
||||||
webmock
|
webmock
|
||||||
|
wordcraft
|
||||||
xlsxtream
|
xlsxtream
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 4.0.5
|
ruby 4.0.9
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
4.0.3
|
4.0.3
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Api
|
|
||||||
class AttachmentsController < ActionController::API
|
|
||||||
include ActionController::Cookies
|
|
||||||
include ActiveStorage::SetCurrent
|
|
||||||
|
|
||||||
COOKIE_STORE_LIMIT = 10
|
|
||||||
|
|
||||||
def create
|
|
||||||
@submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
|
||||||
|
|
||||||
unless can_upload?(@submitter)
|
|
||||||
return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_content
|
|
||||||
end
|
|
||||||
|
|
||||||
file = params[:file]
|
|
||||||
|
|
||||||
if params[:type].in?(%w[initials signature])
|
|
||||||
image = ImageUtils.load_vips(file.read, content_type: file.content_type)
|
|
||||||
|
|
||||||
if ImageUtils.blank?(image)
|
|
||||||
Rollbar.error("Empty signature: #{@submitter.id}") if defined?(Rollbar)
|
|
||||||
|
|
||||||
return render json: { error: "#{params[:type]} is empty" }, status: :unprocessable_content
|
|
||||||
end
|
|
||||||
|
|
||||||
if ImageUtils.error?(image)
|
|
||||||
Rollbar.error("Error signature: #{@submitter.id}") if defined?(Rollbar)
|
|
||||||
|
|
||||||
return render json: { error: "#{params[:type]} error, try to sign on another device" },
|
|
||||||
status: :unprocessable_content
|
|
||||||
end
|
|
||||||
|
|
||||||
metadata = { analyzed: true, identified: true, width: image.width, height: image.height }
|
|
||||||
end
|
|
||||||
|
|
||||||
attachment = Submitters.create_attachment!(@submitter, file, metadata:)
|
|
||||||
|
|
||||||
if params[:remember_signature] == 'true' && @submitter.email.present?
|
|
||||||
cookies.encrypted[:signature_uuids] = build_new_cookie_signatures_json(@submitter, attachment)
|
|
||||||
end
|
|
||||||
|
|
||||||
render json: attachment.as_json(only: %i[uuid created_at], methods: %i[url filename content_type])
|
|
||||||
rescue Submitters::MaliciousFileExtension => e
|
|
||||||
Rollbar.error(e) if defined?(Rollbar)
|
|
||||||
|
|
||||||
render json: { error: e.message }, status: :unprocessable_content
|
|
||||||
end
|
|
||||||
|
|
||||||
def can_upload?(submitter)
|
|
||||||
!submitter.declined_at? &&
|
|
||||||
!submitter.completed_at? &&
|
|
||||||
!submitter.submission.archived_at? &&
|
|
||||||
!submitter.submission.expired? &&
|
|
||||||
!submitter.submission.template&.archived_at?
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_new_cookie_signatures_json(submitter, attachment)
|
|
||||||
values =
|
|
||||||
begin
|
|
||||||
JSON.parse(cookies.encrypted[:signature_uuids].presence || '{}')
|
|
||||||
rescue JSON::ParserError
|
|
||||||
{}
|
|
||||||
end
|
|
||||||
|
|
||||||
values[submitter.email] = attachment.uuid
|
|
||||||
|
|
||||||
values = values.to_a.last(COOKIE_STORE_LIMIT).to_h if values.size > COOKIE_STORE_LIMIT
|
|
||||||
|
|
||||||
values.to_json
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Api
|
|
||||||
class SubmitterEmailClicksController < ActionController::API
|
|
||||||
def create
|
|
||||||
@submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
|
||||||
|
|
||||||
if params[:t] == SubmissionEvents.build_tracking_param(@submitter, 'click_email')
|
|
||||||
SubmissionEvents.create_with_tracking_data(@submitter, 'click_email', request)
|
|
||||||
end
|
|
||||||
|
|
||||||
render json: {}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -179,10 +179,7 @@ module Api
|
|||||||
submitters = submitters.where(external_id: params[:external_id]) if params[:external_id].present?
|
submitters = submitters.where(external_id: params[:external_id]) if params[:external_id].present?
|
||||||
submitters = submitters.where(slug: params[:slug]) if params[:slug].present?
|
submitters = submitters.where(slug: params[:slug]) if params[:slug].present?
|
||||||
submitters = submitters.where(submission_id: params[:submission_id]) if params[:submission_id].present?
|
submitters = submitters.where(submission_id: params[:submission_id]) if params[:submission_id].present?
|
||||||
|
submitters = submitters.where(template_id: params[:template_id]) if params[:template_id].present?
|
||||||
if params[:template_id].present?
|
|
||||||
submitters = submitters.joins(:submission).where(submissions: { template_id: params[:template_id] })
|
|
||||||
end
|
|
||||||
|
|
||||||
maybe_filter_by_completed_at(submitters, params)
|
maybe_filter_by_completed_at(submitters, params)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -118,7 +118,12 @@ class ApplicationController < ActionController::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def sign_in_for_demo
|
def sign_in_for_demo
|
||||||
sign_in(User.active.order('random()').take) unless signed_in?
|
return if signed_in?
|
||||||
|
|
||||||
|
account = Account.create!(name: "Demo #{SecureRandom.hex(3)}")
|
||||||
|
|
||||||
|
sign_in(account.users.create!(first_name: 'Demo', last_name: 'User',
|
||||||
|
email: "#{SecureRandom.hex}@docuseal.com", password: SecureRandom.hex))
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_account
|
def current_account
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ class SubmissionsController < ApplicationController
|
|||||||
create_submissions(@template, submissions_params, params)
|
create_submissions(@template, submissions_params, params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
raise Submissions::CreateFromSubmitters::BaseError, 'Invalid email address' if submissions.blank?
|
||||||
|
|
||||||
WebhookUrls.enqueue_events(submissions, 'submission.created')
|
WebhookUrls.enqueue_events(submissions, 'submission.created')
|
||||||
|
|
||||||
Submissions.send_signature_requests(submissions)
|
Submissions.send_signature_requests(submissions)
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class SubmitFormUploadController < ActionController::API
|
|||||||
attachment = Submitters.create_attachment!(@submitter, file, metadata:)
|
attachment = Submitters.create_attachment!(@submitter, file, metadata:)
|
||||||
|
|
||||||
if params[:remember_signature] == 'true' && @submitter.email.present?
|
if params[:remember_signature] == 'true' && @submitter.email.present?
|
||||||
cookies.encrypted[:signature_uuids] = build_new_cookie_signatures_json(@submitter, attachment)
|
cookies.permanent.encrypted[:signature_uuids] = build_new_cookie_signatures_json(@submitter, attachment)
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: attachment.as_json(only: %i[uuid created_at], methods: %i[url filename content_type])
|
render json: attachment.as_json(only: %i[uuid created_at], methods: %i[url filename content_type])
|
||||||
|
|||||||
@@ -81,9 +81,26 @@ class SubmittersController < ApplicationController
|
|||||||
|
|
||||||
submitter.name = attrs[:name] if attrs.key?(:name)
|
submitter.name = attrs[:name] if attrs.key?(:name)
|
||||||
|
|
||||||
|
if submitter_changed?(submitter)
|
||||||
|
submitter.submitter_versions.new(slug: submitter.slug, email: submitter.email_was,
|
||||||
|
name: submitter.name_was, phone: submitter.phone_was)
|
||||||
|
|
||||||
|
submitter.slug = SecureRandom.base58(14)
|
||||||
|
submitter.values = Submitters.fetch_values_for_delegate(submitter)
|
||||||
|
end
|
||||||
|
|
||||||
submitter
|
submitter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def submitter_changed?(submitter)
|
||||||
|
if submitter.email_was.blank? && submitter.phone_was.blank?
|
||||||
|
submitter.name_was.present? && submitter.name_changed?
|
||||||
|
else
|
||||||
|
(submitter.email_was.present? && submitter.email_changed?) ||
|
||||||
|
(submitter.phone_was.present? && submitter.phone_changed?)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def submitter_params
|
def submitter_params
|
||||||
params.require(:submitter).permit(:email, :name, :phone).transform_values(&:strip)
|
params.require(:submitter).permit(:email, :name, :phone).transform_values(&:strip)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ class TemplateSharingsTestingController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
testing_account = Accounts.find_or_create_testing_user(true_user.account).account
|
account = can?(:manage, :tenants_test) ? current_account : true_user.account
|
||||||
|
|
||||||
|
testing_account = Accounts.find_or_create_testing_user(account).account
|
||||||
|
|
||||||
if params[:value] == '1'
|
if params[:value] == '1'
|
||||||
TemplateSharing.create!(ability: :manage, account: testing_account, template: @template)
|
TemplateSharing.create!(ability: :manage, account: testing_account, template: @template)
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ import PromptPassword from './elements/prompt_password'
|
|||||||
import EmailsTextarea from './elements/emails_textarea'
|
import EmailsTextarea from './elements/emails_textarea'
|
||||||
import ToggleSubmit from './elements/toggle_submit'
|
import ToggleSubmit from './elements/toggle_submit'
|
||||||
import ToggleOnSubmit from './elements/toggle_on_submit'
|
import ToggleOnSubmit from './elements/toggle_on_submit'
|
||||||
|
import ToggleOnChecked from './elements/toggle_on_checked'
|
||||||
|
import ToggleSendTitle from './elements/toggle_send_title'
|
||||||
|
import AppendTemplate from './elements/append_template'
|
||||||
import CheckOnClick from './elements/check_on_click'
|
import CheckOnClick from './elements/check_on_click'
|
||||||
import PasswordInput from './elements/password_input'
|
import PasswordInput from './elements/password_input'
|
||||||
import SearchInput from './elements/search_input'
|
import SearchInput from './elements/search_input'
|
||||||
@@ -159,6 +162,9 @@ safeRegisterElement('emails-textarea', EmailsTextarea)
|
|||||||
safeRegisterElement('toggle-cookies', ToggleCookies)
|
safeRegisterElement('toggle-cookies', ToggleCookies)
|
||||||
safeRegisterElement('toggle-submit', ToggleSubmit)
|
safeRegisterElement('toggle-submit', ToggleSubmit)
|
||||||
safeRegisterElement('toggle-on-submit', ToggleOnSubmit)
|
safeRegisterElement('toggle-on-submit', ToggleOnSubmit)
|
||||||
|
safeRegisterElement('toggle-on-checked', ToggleOnChecked)
|
||||||
|
safeRegisterElement('toggle-send-title', ToggleSendTitle)
|
||||||
|
safeRegisterElement('append-template', AppendTemplate)
|
||||||
safeRegisterElement('password-input', PasswordInput)
|
safeRegisterElement('password-input', PasswordInput)
|
||||||
safeRegisterElement('search-input', SearchInput)
|
safeRegisterElement('search-input', SearchInput)
|
||||||
safeRegisterElement('toggle-attribute', ToggleAttribute)
|
safeRegisterElement('toggle-attribute', ToggleAttribute)
|
||||||
@@ -222,6 +228,7 @@ safeRegisterElement('template-builder', class extends HTMLElement {
|
|||||||
template,
|
template,
|
||||||
customFields: reactive(JSON.parse(this.dataset.customFields || '[]')),
|
customFields: reactive(JSON.parse(this.dataset.customFields || '[]')),
|
||||||
dateFormats: JSON.parse(this.dataset.dateFormats || '[]'),
|
dateFormats: JSON.parse(this.dataset.dateFormats || '[]'),
|
||||||
|
autosave: this.dataset.autosave !== 'false',
|
||||||
dynamicDocuments: reactive(JSON.parse(this.dataset.dynamicDocuments || '[]')),
|
dynamicDocuments: reactive(JSON.parse(this.dataset.dynamicDocuments || '[]')),
|
||||||
backgroundColor: '#faf7f5',
|
backgroundColor: '#faf7f5',
|
||||||
locale: this.dataset.locale,
|
locale: this.dataset.locale,
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
this.addEventListener('click', this.onClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback () {
|
||||||
|
this.removeEventListener('click', this.onClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const target = document.getElementById(this.dataset.targetId)
|
||||||
|
const item = this.querySelector('template').content.firstElementChild.cloneNode(true)
|
||||||
|
|
||||||
|
item.querySelector('a').addEventListener('click', (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
item.remove()
|
||||||
|
|
||||||
|
target.classList.toggle('hidden', !target.childElementCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
target.append(item)
|
||||||
|
target.classList.remove('hidden')
|
||||||
|
|
||||||
|
item.querySelector('input').focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import { actionable } from '@github/catalyst/lib/actionable'
|
|||||||
import { targets, targetable } from '@github/catalyst/lib/targetable'
|
import { targets, targetable } from '@github/catalyst/lib/targetable'
|
||||||
|
|
||||||
export default actionable(targetable(class extends HTMLElement {
|
export default actionable(targetable(class extends HTMLElement {
|
||||||
static [targets.static] = ['items']
|
static [targets.static] = ['items', 'groups']
|
||||||
|
|
||||||
addItem (e) {
|
addItem (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -24,11 +24,28 @@ export default actionable(targetable(class extends HTMLElement {
|
|||||||
duplicateItem.querySelectorAll('a.hidden').forEach((button) => button.classList.toggle('hidden'))
|
duplicateItem.querySelectorAll('a.hidden').forEach((button) => button.classList.toggle('hidden'))
|
||||||
|
|
||||||
originalItem.parentNode.append(duplicateItem)
|
originalItem.parentNode.append(duplicateItem)
|
||||||
|
|
||||||
|
this.toggleMultipleClasses()
|
||||||
|
|
||||||
|
duplicateItem.querySelector("input:not([type='hidden'])")?.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
removeItem (e) {
|
removeItem (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
this.items.find((item) => item.contains(e.target))?.remove()
|
this.items.find((item) => item.contains(e.target))?.remove()
|
||||||
|
|
||||||
|
this.toggleMultipleClasses()
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMultipleClasses () {
|
||||||
|
const isMultiple = this.items.length > 1
|
||||||
|
const elements = [...this.items, ...this.groups]
|
||||||
|
|
||||||
|
elements.forEach((element) => {
|
||||||
|
const classes = element.dataset.multipleClasses || this.dataset.multipleClasses
|
||||||
|
|
||||||
|
classes?.split(' ').forEach((cls) => element.classList.toggle(cls, isMultiple))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@@ -22,7 +22,7 @@ export default class extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async flashFrom (fetchResponse) {
|
async flashFrom (fetchResponse) {
|
||||||
const html = await fetchResponse?.responseHTML
|
const html = await fetchResponse?.responseHTML.catch(() => null)
|
||||||
const flash = html && new DOMParser().parseFromString(html, 'text/html').getElementById('native_flash')
|
const flash = html && new DOMParser().parseFromString(html, 'text/html').getElementById('native_flash')
|
||||||
|
|
||||||
if (flash?.dataset.alert) return { alert: flash.dataset.alert }
|
if (flash?.dataset.alert) return { alert: flash.dataset.alert }
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
this.form.addEventListener('change', this.toggle)
|
||||||
|
|
||||||
|
this.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback () {
|
||||||
|
this.form.removeEventListener('change', this.toggle)
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle = () => {
|
||||||
|
const selector = this.dataset.names.split(' ').map((name) => `[name="${name}"]:checked:enabled`).join(', ')
|
||||||
|
const isChecked = !!this.form.querySelector(selector)
|
||||||
|
|
||||||
|
this.firstElementChild.classList.toggle('hidden', !isChecked)
|
||||||
|
this.lastElementChild.classList.toggle('hidden', isChecked)
|
||||||
|
}
|
||||||
|
|
||||||
|
get form () {
|
||||||
|
return this.closest('form')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
this.events.forEach((event) => this.form.addEventListener(event, this.toggle))
|
||||||
|
|
||||||
|
this.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback () {
|
||||||
|
this.events.forEach((event) => this.form.removeEventListener(event, this.toggle))
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle = (event) => {
|
||||||
|
if (event?.type === 'input' && event.target.matches(this.fieldSelector('name'))) return
|
||||||
|
|
||||||
|
if (event?.type === 'focusin' && !this.isKeepSendUsed && this.isFocusFromName(event)) {
|
||||||
|
this.isKeepSendUsed = true
|
||||||
|
this.keepSendInput = event.target
|
||||||
|
} else if (event?.type === 'focusout' && event.target === this.keepSendInput) {
|
||||||
|
this.keepSendInput = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const isNameOnly = (field) => this.isFilled('name') && !this.isFilled(field) && !this.keepSendInput?.matches(this.fieldSelector(field))
|
||||||
|
|
||||||
|
const isSend = (this.isChecked('send_email') && !isNameOnly('email')) ||
|
||||||
|
(this.isChecked('send_sms') && !isNameOnly('phone'))
|
||||||
|
|
||||||
|
this.firstElementChild.classList.toggle('hidden', !isSend)
|
||||||
|
this.lastElementChild.classList.toggle('hidden', isSend)
|
||||||
|
}
|
||||||
|
|
||||||
|
isChecked (name) {
|
||||||
|
return !!this.form.querySelector(`[name="${name}"]:checked:enabled`)
|
||||||
|
}
|
||||||
|
|
||||||
|
isFilled (field) {
|
||||||
|
return [...this.form.querySelectorAll(this.fieldSelector(field))].some((input) => input.value.trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
isFocusFromName ({ target, relatedTarget }) {
|
||||||
|
return !!relatedTarget?.matches(this.fieldSelector('name')) && !!relatedTarget.value.trim() &&
|
||||||
|
['email', 'phone'].some((field) => target.matches(this.fieldSelector(field)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldSelector (field) {
|
||||||
|
return `[name$="[][${field}]"]`
|
||||||
|
}
|
||||||
|
|
||||||
|
get events () {
|
||||||
|
return ['change', 'input', 'linked-input.update', 'focusin', 'focusout']
|
||||||
|
}
|
||||||
|
|
||||||
|
get form () {
|
||||||
|
return this.closest('form')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,9 @@ const ICONS = {
|
|||||||
user: 'person',
|
user: 'person',
|
||||||
users: 'person.2',
|
users: 'person.2',
|
||||||
users_plus: 'person.badge.plus',
|
users_plus: 'person.badge.plus',
|
||||||
|
user_plus: 'person.badge.plus',
|
||||||
|
eye_plus: 'eye',
|
||||||
|
mail_plus: 'envelope',
|
||||||
photo: 'photo',
|
photo: 'photo',
|
||||||
settings: 'gearshape',
|
settings: 'gearshape',
|
||||||
logout: 'rectangle.portrait.and.arrow.right',
|
logout: 'rectangle.portrait.and.arrow.right',
|
||||||
|
|||||||
@@ -3,11 +3,19 @@
|
|||||||
v-if="!error"
|
v-if="!error"
|
||||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
||||||
>
|
>
|
||||||
|
<span class="flex items-end">
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
v-if="field.title"
|
v-if="field.title"
|
||||||
:string="field.title"
|
:string="field.title"
|
||||||
/>
|
/>
|
||||||
<template v-else>{{ field.name || 'Knowledge Based Authentication' }}</template>
|
<template v-else>{{ field.name || 'Knowledge Based Authentication' }}</template>
|
||||||
|
<span
|
||||||
|
class="tooltip tooltip-bottom ml-2 mb-1"
|
||||||
|
data-tip="To verify your identity, the details you enter are checked against third-party records, which may include credit bureau data, and used to generate questions based on those records. Only these identity details are shared with the identity verification provider; this document and your tax return information are not shared with it or with any credit reporting company. If the check is recorded on your credit report, it appears as a soft inquiry, which does not affect your credit score and is not shown to lenders. This check does not give the sender of this document or the IRS access to your credit report."
|
||||||
|
>
|
||||||
|
<IconInfoCircle class="w-4 h-4 text-neutral-500" />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="questions"
|
v-if="questions"
|
||||||
class="float-right font-normal text-neutral-500 mt-1 whitespace-nowrap text-right text-sm"
|
class="float-right font-normal text-neutral-500 mt-1 whitespace-nowrap text-right text-sm"
|
||||||
@@ -282,13 +290,14 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import MarkdownContent from './markdown_content'
|
import MarkdownContent from './markdown_content'
|
||||||
import { IconInnerShadowTop } from '@tabler/icons-vue'
|
import { IconInnerShadowTop, IconInfoCircle } from '@tabler/icons-vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'KbaStep',
|
name: 'KbaStep',
|
||||||
components: {
|
components: {
|
||||||
MarkdownContent,
|
MarkdownContent,
|
||||||
IconInnerShadowTop
|
IconInnerShadowTop,
|
||||||
|
IconInfoCircle
|
||||||
},
|
},
|
||||||
inject: ['baseUrl', 't'],
|
inject: ['baseUrl', 't'],
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ export default {
|
|||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['start-resize', 'stop-resize', 'start-drag', 'stop-drag', 'remove', 'scroll-to', 'add-custom-field', 'click-title', 'multi-select'],
|
emits: ['start-resize', 'stop-resize', 'start-drag', 'stop-drag', 'remove', 'scroll-to', 'add-custom-field', 'click-title'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isContenteditable: false,
|
isContenteditable: false,
|
||||||
@@ -798,8 +798,6 @@ export default {
|
|||||||
this.selectedAreasRef.value.splice(this.selectedAreasRef.value.indexOf(this.area), 1)
|
this.selectedAreasRef.value.splice(this.selectedAreasRef.value.indexOf(this.area), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit('multi-select', e)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
ref="dragContainer"
|
ref="dragContainer"
|
||||||
style="max-width: 1600px"
|
style="max-width: 1600px"
|
||||||
:class="nativeView ? (isSheetView ? 'bg-base-100' : 'px-4') : 'mx-auto pl-3 md:pl-4 h-full'"
|
:class="nativeView ? (isSheetView ? 'bg-base-100' : 'px-4') : 'mx-auto pl-3 md:pl-4 h-full'"
|
||||||
|
@dragstart="isInternalDrag = true"
|
||||||
|
@dragend="isInternalDrag = false"
|
||||||
@dragover="onDragover"
|
@dragover="onDragover"
|
||||||
@drop="isDragFile = false"
|
@drop="[isDragFile = false, isInternalDrag = false]"
|
||||||
>
|
>
|
||||||
<HoverDropzone
|
<HoverDropzone
|
||||||
v-if="sortedDocuments.length && withUploadButton && editable && !isSheetView"
|
v-if="sortedDocuments.length && withUploadButton && editable && !isSheetView"
|
||||||
@@ -413,9 +415,9 @@
|
|||||||
<a
|
<a
|
||||||
:href="`/templates/${template.id}/form`"
|
:href="`/templates/${template.id}/form`"
|
||||||
:data-native-actions="template.submitters.length > 1 ? 'users' : null"
|
:data-native-actions="template.submitters.length > 1 ? 'users' : null"
|
||||||
data-turbo="false"
|
data-turbo-prefetch="false"
|
||||||
class="flex items-center justify-center space-x-2"
|
class="flex items-center justify-center space-x-2"
|
||||||
@click.exact="isPreviewLoading = true"
|
@click.exact="onSaveAndPreviewClick"
|
||||||
>
|
>
|
||||||
<IconInnerShadowTop
|
<IconInnerShadowTop
|
||||||
v-if="isPreviewLoading"
|
v-if="isPreviewLoading"
|
||||||
@@ -638,6 +640,7 @@
|
|||||||
:draw-field-type="drawFieldType"
|
:draw-field-type="drawFieldType"
|
||||||
:draw-custom-field="drawCustomField"
|
:draw-custom-field="drawCustomField"
|
||||||
:draw-option="drawOption"
|
:draw-option="drawOption"
|
||||||
|
:autosave="autosave"
|
||||||
@update="onDynamicDocumentUpdate"
|
@update="onDynamicDocumentUpdate"
|
||||||
@draw="clearDrawField"
|
@draw="clearDrawField"
|
||||||
@add-custom-field="addCustomField"
|
@add-custom-field="addCustomField"
|
||||||
@@ -1377,6 +1380,7 @@ export default {
|
|||||||
isPreviewLoading: false,
|
isPreviewLoading: false,
|
||||||
isLoadingBlankPage: false,
|
isLoadingBlankPage: false,
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
|
hasUnsavedChanges: false,
|
||||||
isDetectingPageFields: false,
|
isDetectingPageFields: false,
|
||||||
detectFieldsQueue: [],
|
detectFieldsQueue: [],
|
||||||
detectingAnalyzingProgress: null,
|
detectingAnalyzingProgress: null,
|
||||||
@@ -1390,6 +1394,7 @@ export default {
|
|||||||
drawOption: null,
|
drawOption: null,
|
||||||
dragField: null,
|
dragField: null,
|
||||||
isDragFile: false,
|
isDragFile: false,
|
||||||
|
isInternalDrag: false,
|
||||||
isMathLoaded: false,
|
isMathLoaded: false,
|
||||||
isRevisionsModalOpen: false,
|
isRevisionsModalOpen: false,
|
||||||
isDocumentsDrawerOpen: false,
|
isDocumentsDrawerOpen: false,
|
||||||
@@ -1659,6 +1664,11 @@ export default {
|
|||||||
|
|
||||||
window.addEventListener('dragleave', this.onWindowDragLeave)
|
window.addEventListener('dragleave', this.onWindowDragLeave)
|
||||||
|
|
||||||
|
if (!this.autosave) {
|
||||||
|
window.addEventListener('beforeunload', this.onBeforeUnload)
|
||||||
|
document.addEventListener('turbo:before-visit', this.onBeforeVisit)
|
||||||
|
}
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (document.location.search?.includes('stripe_connect_success')) {
|
if (document.location.search?.includes('stripe_connect_success')) {
|
||||||
document.querySelector('form[action="/auth/stripe_connect"]')?.closest('.dropdown')?.querySelector('label')?.focus()
|
document.querySelector('form[action="/auth/stripe_connect"]')?.closest('.dropdown')?.querySelector('label')?.focus()
|
||||||
@@ -1682,6 +1692,11 @@ export default {
|
|||||||
|
|
||||||
this.resizeObserver.disconnect()
|
this.resizeObserver.disconnect()
|
||||||
window.removeEventListener('dragleave', this.onWindowDragLeave)
|
window.removeEventListener('dragleave', this.onWindowDragLeave)
|
||||||
|
|
||||||
|
if (!this.autosave) {
|
||||||
|
window.removeEventListener('beforeunload', this.onBeforeUnload)
|
||||||
|
document.removeEventListener('turbo:before-visit', this.onBeforeVisit)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
beforeUpdate () {
|
beforeUpdate () {
|
||||||
this.documentRefs = []
|
this.documentRefs = []
|
||||||
@@ -2039,9 +2054,19 @@ export default {
|
|||||||
|
|
||||||
ref.x = e.clientX - ref.offsetX
|
ref.x = e.clientX - ref.offsetX
|
||||||
ref.y = e.clientY - ref.offsetY
|
ref.y = e.clientY - ref.offsetY
|
||||||
} else if (e.dataTransfer?.types?.includes('Files') && !this.editModalDocumentUuid) {
|
} else if (e.dataTransfer?.types?.includes('Files') && !this.editModalDocumentUuid && !this.isInternalDrag) {
|
||||||
|
const items = Array.from(e.dataTransfer.items)
|
||||||
|
|
||||||
|
if (this.dynamicDocuments.length && items.length && items.every((item) => item.type.startsWith('image/'))) {
|
||||||
|
if (!e.defaultPrevented) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
e.dataTransfer.dropEffect = 'none'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
this.isDragFile = true
|
this.isDragFile = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onWindowDragLeave (event) {
|
onWindowDragLeave (event) {
|
||||||
if (event.clientX <= 0 || event.clientY <= 0 || event.clientX >= window.innerWidth || event.clientY >= window.innerHeight) {
|
if (event.clientX <= 0 || event.clientY <= 0 || event.clientX >= window.innerWidth || event.clientY >= window.innerHeight) {
|
||||||
@@ -2228,6 +2253,37 @@ export default {
|
|||||||
this.insertField(field)
|
this.insertField(field)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onSaveAndPreviewClick (event) {
|
||||||
|
this.isPreviewLoading = true
|
||||||
|
|
||||||
|
if (!this.autosave) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
const dynamicDocumentRefs = this.documentRefs.filter((ref) => ref.isDynamic)
|
||||||
|
|
||||||
|
dynamicDocumentRefs.forEach((ref) => ref.update())
|
||||||
|
|
||||||
|
this.rebuildVariablesSchema({ disable: false })
|
||||||
|
|
||||||
|
Promise.all([this.save({ force: true }), ...dynamicDocumentRefs.map((ref) => ref.saveBody())]).then(() => {
|
||||||
|
window.Turbo.visit(`/templates/${this.template.id}/form`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onBeforeUnload (event) {
|
||||||
|
if (this.hasUnsavedChanges) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
event.returnValue = ''
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onBeforeVisit (event) {
|
||||||
|
if (this.hasUnsavedChanges && !window.confirm(this.t('close_without_saving_changes'))) {
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
},
|
||||||
closeDropdown () {
|
closeDropdown () {
|
||||||
document.activeElement.blur()
|
document.activeElement.blur()
|
||||||
},
|
},
|
||||||
@@ -4023,6 +4079,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.autosave && !force) {
|
if (!this.autosave && !force) {
|
||||||
|
this.hasUnsavedChanges = true
|
||||||
|
|
||||||
return Promise.resolve({})
|
return Promise.resolve({})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4045,6 +4103,8 @@ export default {
|
|||||||
}),
|
}),
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
this.hasUnsavedChanges = false
|
||||||
|
|
||||||
if (this.onSave) {
|
if (this.onSave) {
|
||||||
this.onSave(this.template)
|
this.onSave(this.template)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
|
autosave: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
selectedSubmitter: {
|
selectedSubmitter: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -279,7 +284,7 @@ export default {
|
|||||||
|
|
||||||
this.updatedSectionEditors.set(section.id, editor)
|
this.updatedSectionEditors.set(section.id, editor)
|
||||||
|
|
||||||
this.saveTimer = setTimeout(() => this.updateSectionsAndSave(), 1000)
|
this.saveTimer = setTimeout(() => this.autosave ? this.updateSectionsAndSave() : this.updateSections(), 1000)
|
||||||
},
|
},
|
||||||
loadListsStyle () {
|
loadListsStyle () {
|
||||||
this.listsStyle = this.bodyDom.querySelector('body > style[data-lists]')?.textContent || null
|
this.listsStyle = this.bodyDom.querySelector('body > style[data-lists]')?.textContent || null
|
||||||
@@ -299,7 +304,9 @@ export default {
|
|||||||
updateVariablesSchema () {
|
updateVariablesSchema () {
|
||||||
this.document.variables_schema = buildVariablesSchema(this.bodyDom.body)
|
this.document.variables_schema = buildVariablesSchema(this.bodyDom.body)
|
||||||
},
|
},
|
||||||
updateSectionsAndSave () {
|
updateSections () {
|
||||||
|
delete this.saveTimer
|
||||||
|
|
||||||
this.updatedSectionEditors.forEach((editor, sectionId) => {
|
this.updatedSectionEditors.forEach((editor, sectionId) => {
|
||||||
const target = this.bodyDom.getElementById(sectionId)
|
const target = this.bodyDom.getElementById(sectionId)
|
||||||
|
|
||||||
@@ -315,6 +322,9 @@ export default {
|
|||||||
this.updateVariablesSchema()
|
this.updateVariablesSchema()
|
||||||
|
|
||||||
this.$emit('update', this.document)
|
this.$emit('update', this.document)
|
||||||
|
},
|
||||||
|
updateSectionsAndSave () {
|
||||||
|
this.updateSections()
|
||||||
|
|
||||||
return this.saveBody()
|
return this.saveBody()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,13 +27,17 @@ tiptapStylesheet.replaceSync(
|
|||||||
font-feature-settings: "liga" 0;
|
font-feature-settings: "liga" 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column nowrap;
|
flex-flow: column nowrap;
|
||||||
min-height: inherit;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ProseMirror > article {
|
.ProseMirror > article:last-of-type {
|
||||||
margin-bottom: auto;
|
margin-bottom: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ProseMirror p.doc-trailing-break::after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
.ProseMirror [contenteditable="false"] {
|
.ProseMirror [contenteditable="false"] {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -639,6 +639,20 @@ export default {
|
|||||||
|
|
||||||
if (!file) return
|
if (!file) return
|
||||||
|
|
||||||
|
const image = await this.uploadImage(file)
|
||||||
|
|
||||||
|
if (image) {
|
||||||
|
this.editor.chain().focus().insertContent(image).run()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async insertImageFiles (files, pos) {
|
||||||
|
const images = (await Promise.all(files.map(this.uploadImage))).filter(Boolean)
|
||||||
|
|
||||||
|
if (images.length) {
|
||||||
|
this.editor.chain().focus().insertContentAt(pos, images).run()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async uploadImage (file) {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
|
|
||||||
formData.append('file', file)
|
formData.append('file', file)
|
||||||
@@ -656,9 +670,7 @@ export default {
|
|||||||
|
|
||||||
await this.$nextTick()
|
await this.$nextTick()
|
||||||
|
|
||||||
this.editor.chain().focus()
|
return { type: 'image', attrs: { htmlAttrs: { src: `blob:${data.uuid}`, style: 'max-width: 100%' } } }
|
||||||
.insertContent({ type: 'image', attrs: { htmlAttrs: { src: `blob:${data.uuid}`, style: 'max-width: 100%' } } })
|
|
||||||
.run()
|
|
||||||
},
|
},
|
||||||
setDynamicMenuCoords (editor) {
|
setDynamicMenuCoords (editor) {
|
||||||
const { from, to } = editor.state.selection
|
const { from, to } = editor.state.selection
|
||||||
@@ -975,7 +987,7 @@ export default {
|
|||||||
onSelectedAreaChange () {
|
onSelectedAreaChange () {
|
||||||
this.save()
|
this.save()
|
||||||
},
|
},
|
||||||
onFieldDrop (view, event, _slice, moved) {
|
onFieldDrop (view, event, slice, moved) {
|
||||||
this.isAreaDrag = false
|
this.isAreaDrag = false
|
||||||
|
|
||||||
if (moved) {
|
if (moved) {
|
||||||
@@ -983,8 +995,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const draggedField = this.fieldsDragFieldRef?.value || this.customDragFieldRef?.value || this.dragField
|
const draggedField = this.fieldsDragFieldRef?.value || this.customDragFieldRef?.value || this.dragField
|
||||||
|
const imageFiles = slice.size ? [] : Array.from(event.dataTransfer.files).filter((file) => file.type.startsWith('image/'))
|
||||||
|
|
||||||
if (!draggedField) return false
|
if (!draggedField && !imageFiles.length) return false
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
@@ -992,6 +1005,7 @@ export default {
|
|||||||
|
|
||||||
if (!pos) return false
|
if (!pos) return false
|
||||||
|
|
||||||
|
if (draggedField) {
|
||||||
this.insertFieldAtRange({
|
this.insertFieldAtRange({
|
||||||
sourceField: draggedField,
|
sourceField: draggedField,
|
||||||
existingField: this.fieldsDragFieldRef?.value,
|
existingField: this.fieldsDragFieldRef?.value,
|
||||||
@@ -1000,6 +1014,9 @@ export default {
|
|||||||
|
|
||||||
this.fieldsDragFieldRef.value = null
|
this.fieldsDragFieldRef.value = null
|
||||||
this.customDragFieldRef.value = null
|
this.customDragFieldRef.value = null
|
||||||
|
} else {
|
||||||
|
this.insertImageFiles(imageFiles, pos.pos)
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ const en = {
|
|||||||
remove_condition: 'Remove condition',
|
remove_condition: 'Remove condition',
|
||||||
add_condition: 'Add condition',
|
add_condition: 'Add condition',
|
||||||
are_you_sure_: 'Are you sure?',
|
are_you_sure_: 'Are you sure?',
|
||||||
|
close_without_saving_changes: 'Close without saving changes?',
|
||||||
sign_yourself: 'Sign Yourself',
|
sign_yourself: 'Sign Yourself',
|
||||||
set_signing_date: 'Set signing date',
|
set_signing_date: 'Set signing date',
|
||||||
signing_date: 'Signing Date',
|
signing_date: 'Signing Date',
|
||||||
@@ -371,6 +372,7 @@ const es = {
|
|||||||
down: 'Abajo',
|
down: 'Abajo',
|
||||||
set_signing_date: 'Establecer fecha de firma',
|
set_signing_date: 'Establecer fecha de firma',
|
||||||
are_you_sure_: '¿Estás seguro?',
|
are_you_sure_: '¿Estás seguro?',
|
||||||
|
close_without_saving_changes: '¿Cerrar sin guardar los cambios?',
|
||||||
sign_yourself: 'Firma tú mismo',
|
sign_yourself: 'Firma tú mismo',
|
||||||
checked: 'Marcado',
|
checked: 'Marcado',
|
||||||
current_date: 'Fecha actual',
|
current_date: 'Fecha actual',
|
||||||
@@ -652,6 +654,7 @@ const it = {
|
|||||||
remove_condition: 'Rimuovi condizione',
|
remove_condition: 'Rimuovi condizione',
|
||||||
add_condition: 'Aggiungi condizione',
|
add_condition: 'Aggiungi condizione',
|
||||||
are_you_sure_: 'Sei sicuro?',
|
are_you_sure_: 'Sei sicuro?',
|
||||||
|
close_without_saving_changes: 'Chiudere senza salvare le modifiche?',
|
||||||
sign_yourself: 'Firma te stesso',
|
sign_yourself: 'Firma te stesso',
|
||||||
set_signing_date: 'Imposta data di firma',
|
set_signing_date: 'Imposta data di firma',
|
||||||
signing_date: 'Data di firma',
|
signing_date: 'Data di firma',
|
||||||
@@ -953,6 +956,7 @@ const pt = {
|
|||||||
down: 'Para baixo',
|
down: 'Para baixo',
|
||||||
set_signing_date: 'Definir data de assinatura',
|
set_signing_date: 'Definir data de assinatura',
|
||||||
are_you_sure_: 'Tem certeza?',
|
are_you_sure_: 'Tem certeza?',
|
||||||
|
close_without_saving_changes: 'Fechar sem salvar as alterações?',
|
||||||
sign_yourself: 'Assine você mesmo',
|
sign_yourself: 'Assine você mesmo',
|
||||||
checked: 'Marcado',
|
checked: 'Marcado',
|
||||||
current_date: 'Data atual',
|
current_date: 'Data atual',
|
||||||
@@ -1234,6 +1238,7 @@ const fr = {
|
|||||||
remove_condition: 'Supprimer la condition',
|
remove_condition: 'Supprimer la condition',
|
||||||
add_condition: 'Ajouter une condition',
|
add_condition: 'Ajouter une condition',
|
||||||
are_you_sure_: 'Êtes-vous sûr ?',
|
are_you_sure_: 'Êtes-vous sûr ?',
|
||||||
|
close_without_saving_changes: 'Fermer sans enregistrer les modifications ?',
|
||||||
sign_yourself: 'Signer vous-même',
|
sign_yourself: 'Signer vous-même',
|
||||||
set_signing_date: 'Définir la date de signature',
|
set_signing_date: 'Définir la date de signature',
|
||||||
signing_date: 'Date de signature',
|
signing_date: 'Date de signature',
|
||||||
@@ -1525,6 +1530,7 @@ const de = {
|
|||||||
remove_condition: 'Bedingung entfernen',
|
remove_condition: 'Bedingung entfernen',
|
||||||
add_condition: 'Bedingung hinzufügen',
|
add_condition: 'Bedingung hinzufügen',
|
||||||
are_you_sure_: 'Sind Sie sicher?',
|
are_you_sure_: 'Sind Sie sicher?',
|
||||||
|
close_without_saving_changes: 'Ohne Speichern der Änderungen schließen?',
|
||||||
sign_yourself: 'Selbst unterschreiben',
|
sign_yourself: 'Selbst unterschreiben',
|
||||||
set_signing_date: 'Unterzeichnungsdatum festlegen',
|
set_signing_date: 'Unterzeichnungsdatum festlegen',
|
||||||
signing_date: 'Unterzeichnungsdatum',
|
signing_date: 'Unterzeichnungsdatum',
|
||||||
@@ -1816,6 +1822,7 @@ const nl = {
|
|||||||
remove_condition: 'Voorwaarde verwijderen',
|
remove_condition: 'Voorwaarde verwijderen',
|
||||||
add_condition: 'Voorwaarde toevoegen',
|
add_condition: 'Voorwaarde toevoegen',
|
||||||
are_you_sure_: 'Weet u het zeker?',
|
are_you_sure_: 'Weet u het zeker?',
|
||||||
|
close_without_saving_changes: 'Sluiten zonder wijzigingen op te slaan?',
|
||||||
sign_yourself: 'Zelf ondertekenen',
|
sign_yourself: 'Zelf ondertekenen',
|
||||||
set_signing_date: 'Ondertekeningsdatum instellen',
|
set_signing_date: 'Ondertekeningsdatum instellen',
|
||||||
signing_date: 'Ondertekeningsdatum',
|
signing_date: 'Ondertekeningsdatum',
|
||||||
|
|||||||
@@ -57,7 +57,6 @@
|
|||||||
@scroll-to="$emit('scroll-to', $event)"
|
@scroll-to="$emit('scroll-to', $event)"
|
||||||
@add-custom-field="$emit('add-custom-field', $event)"
|
@add-custom-field="$emit('add-custom-field', $event)"
|
||||||
@contextmenu="openAreaContextMenu($event, item.area, item.field)"
|
@contextmenu="openAreaContextMenu($event, item.area, item.field)"
|
||||||
@multi-select="openMultiSelectContextMenu"
|
|
||||||
@click-title="closeContextMenu"
|
@click-title="closeContextMenu"
|
||||||
/>
|
/>
|
||||||
<FieldArea
|
<FieldArea
|
||||||
@@ -450,13 +449,6 @@ export default {
|
|||||||
areas: this.selectedAreasRef.value
|
areas: this.selectedAreasRef.value
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openMultiSelectContextMenu (event) {
|
|
||||||
if (this.selectedAreasRef.value.length >= 2) {
|
|
||||||
this.openSelectionContextMenu(event)
|
|
||||||
} else {
|
|
||||||
this.closeContextMenu()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleSelectionCopy () {
|
handleSelectionCopy () {
|
||||||
this.$emit('copy-selected-areas')
|
this.$emit('copy-selected-areas')
|
||||||
|
|
||||||
|
|||||||
@@ -30,14 +30,16 @@ class SendTestWebhookRequestJob
|
|||||||
raise LocalhostError, "Can't send to localhost." if uri.host.in?(SendWebhookRequest::LOCALHOSTS)
|
raise LocalhostError, "Can't send to localhost." if uri.host.in?(SendWebhookRequest::LOCALHOSTS)
|
||||||
end
|
end
|
||||||
|
|
||||||
Faraday.post(webhook_url.url,
|
body_json = {
|
||||||
{
|
|
||||||
event_type: 'form.completed',
|
event_type: 'form.completed',
|
||||||
timestamp: Time.current.iso8601,
|
timestamp: Time.current.iso8601,
|
||||||
data: Submitters::SerializeForWebhook.call(submitter)
|
data: Submitters::SerializeForWebhook.call(submitter)
|
||||||
}.to_json,
|
}.to_json
|
||||||
|
|
||||||
|
Faraday.post(webhook_url.url, body_json,
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT,
|
'User-Agent' => USER_AGENT,
|
||||||
|
'X-Docuseal-Signature' => WebhookUrls::Signatures.sign(webhook_url.hmac_secret, body: body_json),
|
||||||
**webhook_url.secret.to_h)
|
**webhook_url.secret.to_h)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -61,6 +61,7 @@ class AccountConfig < ApplicationRecord
|
|||||||
DOCUMENT_FILENAME_FORMAT_KEY = 'document_filename_format'
|
DOCUMENT_FILENAME_FORMAT_KEY = 'document_filename_format'
|
||||||
TEMPLATE_CUSTOM_FIELDS_KEY = 'template_custom_fields'
|
TEMPLATE_CUSTOM_FIELDS_KEY = 'template_custom_fields'
|
||||||
TEMPLATE_DATE_FORMATS_KEY = 'template_date_formats'
|
TEMPLATE_DATE_FORMATS_KEY = 'template_date_formats'
|
||||||
|
TEMPLATE_BUILDER_AUTOSAVE_KEY = 'template_builder_autosave'
|
||||||
POLICY_LINKS_KEY = 'policy_links'
|
POLICY_LINKS_KEY = 'policy_links'
|
||||||
ENABLE_MCP_KEY = 'enable_mcp'
|
ENABLE_MCP_KEY = 'enable_mcp'
|
||||||
|
|
||||||
|
|||||||
@@ -25,15 +25,17 @@
|
|||||||
# account_id :bigint not null
|
# account_id :bigint not null
|
||||||
# external_id :string
|
# external_id :string
|
||||||
# submission_id :bigint not null
|
# submission_id :bigint not null
|
||||||
|
# template_id :bigint
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
# index_submitters_on_account_id_and_completed_at (account_id,completed_at) WHERE (completed_at IS NOT NULL)
|
# index_submitters_on_account_id_and_completed_at (account_id,completed_at) WHERE (completed_at IS NOT NULL)
|
||||||
# index_submitters_on_account_id_and_id (account_id,id)
|
# index_submitters_on_account_id_and_id (account_id,id)
|
||||||
# index_submitters_on_email (email)
|
# index_submitters_on_email_and_template_id (email,template_id) WHERE (email IS NOT NULL)
|
||||||
# index_submitters_on_external_id (external_id)
|
# index_submitters_on_external_id (external_id)
|
||||||
# index_submitters_on_slug (slug) UNIQUE
|
# index_submitters_on_slug (slug) UNIQUE
|
||||||
# index_submitters_on_submission_id (submission_id)
|
# index_submitters_on_submission_id (submission_id)
|
||||||
|
# index_submitters_on_template_id_and_id (template_id,id)
|
||||||
#
|
#
|
||||||
# Foreign Keys
|
# Foreign Keys
|
||||||
#
|
#
|
||||||
@@ -68,6 +70,8 @@ class Submitter < ApplicationRecord
|
|||||||
|
|
||||||
scope :completed, -> { where.not(completed_at: nil) }
|
scope :completed, -> { where.not(completed_at: nil) }
|
||||||
|
|
||||||
|
before_validation :set_template_id, on: :create
|
||||||
|
|
||||||
after_destroy :anonymize_email_events, if: -> { Docuseal.multitenant? }
|
after_destroy :anonymize_email_events, if: -> { Docuseal.multitenant? }
|
||||||
|
|
||||||
def status
|
def status
|
||||||
@@ -124,6 +128,10 @@ class Submitter < ApplicationRecord
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def set_template_id
|
||||||
|
self.template_id = submission&.template_id
|
||||||
|
end
|
||||||
|
|
||||||
def anonymize_email_events
|
def anonymize_email_events
|
||||||
email_events.each do |event|
|
email_events.each do |event|
|
||||||
event.update!(email: Digest::MD5.base64digest(event.email))
|
event.update!(email: Digest::MD5.base64digest(event.email))
|
||||||
|
|||||||
@@ -196,7 +196,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::DOWNLOAD_LINKS_EXPIRE_KEY) %>
|
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::DOWNLOAD_LINKS_EXPIRE_KEY) %>
|
||||||
<% if can?(:manage, account_config) %>
|
<% if !Docuseal.demo? && can?(:manage, account_config) %>
|
||||||
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
||||||
<%= f.hidden_field :key %>
|
<%= f.hidden_field :key %>
|
||||||
<div class="flex items-center justify-between gap-4 py-2.5">
|
<div class="flex items-center justify-between gap-4 py-2.5">
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
|
||||||
|
<path d="M12 18c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" />
|
||||||
|
<path d="M16 19h6" />
|
||||||
|
<path d="M19 16v6" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 460 B |
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M12 19h-7a2 2 0 0 1 -2 -2v-10a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v5.5" />
|
||||||
|
<path d="M16 19h6" />
|
||||||
|
<path d="M19 16v6" />
|
||||||
|
<path d="M3 7l9 6l9 -6" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 441 B |
@@ -1,8 +1,7 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<path d="M5 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0" />
|
<path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0" />
|
||||||
<path d="M3 21v-2a4 4 0 0 1 4 -4h4c.96 0 1.84 .338 2.53 .901" />
|
|
||||||
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
|
||||||
<path d="M16 19h6" />
|
<path d="M16 19h6" />
|
||||||
<path d="M19 16v6" />
|
<path d="M19 16v6" />
|
||||||
|
<path d="M6 21v-2a4 4 0 0 1 4 -4h4" />
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 488 B After Width: | Height: | Size: 421 B |
@@ -33,10 +33,10 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<turbo-frame id="drawer"></turbo-frame>
|
<turbo-frame id="drawer"></turbo-frame>
|
||||||
<%= render turbo_native_app? ? 'shared/native_navbar' : 'shared/navbar' %>
|
<%= render turbo_native_app? ? 'shared/native_navbar' : 'shared/navbar' %>
|
||||||
<% if flash.present? %><%= render 'shared/flash' %><% end %>
|
<main class="max-w-6xl mx-auto px-4 md:px-2 mb-8">
|
||||||
<div class="max-w-6xl mx-auto px-4 md:px-2 mb-8">
|
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
</div>
|
</main>
|
||||||
|
<% if flash.present? %><%= render 'shared/flash' %><% end %>
|
||||||
<%= render 'shared/body_scripts' %>
|
<%= render 'shared/body_scripts' %>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -13,6 +13,8 @@
|
|||||||
<%= stylesheet_pack_tag 'form', media: 'all', data: { turbo_track: 'dynamic' } %>
|
<%= stylesheet_pack_tag 'form', media: 'all', data: { turbo_track: 'dynamic' } %>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<main>
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -13,8 +13,10 @@
|
|||||||
<%= stylesheet_pack_tag 'application', media: 'all', data: { turbo_track: 'dynamic' } %>
|
<%= stylesheet_pack_tag 'application', media: 'all', data: { turbo_track: 'dynamic' } %>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<% if flash.present? %><%= render 'shared/flash' %><% end %>
|
<main>
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
|
</main>
|
||||||
<turbo-frame id="modal"></turbo-frame>
|
<turbo-frame id="modal"></turbo-frame>
|
||||||
|
<% if flash.present? %><%= render 'shared/flash' %><% end %>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="w-full text-center">
|
<div class="w-full text-center">
|
||||||
<span class="font-bold"><%= t('demo_environment') %></span>
|
<span class="font-bold"><%= t('demo_environment') %></span>
|
||||||
<br>
|
<br>
|
||||||
<%= t('create_a_new_template_document_form_or_submit_the_existing_one_html', new_template_link: new_template_path, start_form_link: start_form_url(slug: ::Template.first&.slug)) %> 😊
|
<%= t('create_a_new_template_document_form_html', new_template_link: new_template_path) %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -22,13 +22,13 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:create, ::Template) && controller_name.in?(%w[templates_dashboard submissions_dashboard template_folders templates_archived submissions_archived]) %>
|
<% if can?(:create, ::Template) && controller_name.in?(%w[templates_dashboard submissions_dashboard template_folders templates_archived submissions_archived]) %>
|
||||||
<% folder_name = @template_folder&.full_name %>
|
<% folder_name = @template_folder&.full_name %>
|
||||||
<native-action data-placement="create" data-icon="upload" data-native="upload" data-label="<%= t('upload') %>" data-url="<%= templates_upload_path(folder_name: folder_name) %>" data-accept="image/*, application/pdf, application/zip, application/json<%= ", #{Templates::CreateAttachments::DOCUMENT_EXTENSIONS.join(', ')}" if Docuseal.advanced_formats? %>"></native-action>
|
<native-action data-placement="create" data-icon="upload" data-native="upload" data-label="<%= t('upload') %>" data-url="<%= templates_upload_path(folder_name: folder_name) %>" data-accept="image/*, application/pdf, application/zip, application/json, <%= Templates::CreateAttachments.document_extensions.join(', ') %>"></native-action>
|
||||||
<native-action data-placement="create" data-icon="scan" data-native="scan" data-label="<%= t('scan') %>" data-url="<%= templates_upload_path(folder_name: folder_name) %>"></native-action>
|
<native-action data-placement="create" data-icon="scan" data-native="scan" data-label="<%= t('scan') %>" data-url="<%= templates_upload_path(folder_name: folder_name) %>"></native-action>
|
||||||
<native-action data-placement="create" data-icon="pencil_plus">
|
<native-action data-placement="create" data-icon="pencil_plus">
|
||||||
<%= link_to t('create'), new_template_path(folder_name: folder_name), data: { turbo_frame: :modal, native_modal: :medium } %>
|
<%= link_to t('create'), new_template_path(folder_name: folder_name), data: { turbo_frame: :modal, native_modal: :medium } %>
|
||||||
</native-action>
|
</native-action>
|
||||||
<% unless controller_name.in?(%w[templates_archived submissions_archived]) %>
|
<% unless controller_name.in?(%w[templates_archived submissions_archived]) %>
|
||||||
<native-action data-placement="menu" data-menu="<%= t('new') %>" data-menu-icon="pencil_plus" data-icon="upload" data-native="upload" data-label="<%= t('upload') %>" data-url="<%= templates_upload_path(folder_name: folder_name) %>" data-accept="image/*, application/pdf, application/zip, application/json<%= ", #{Templates::CreateAttachments::DOCUMENT_EXTENSIONS.join(', ')}" if Docuseal.advanced_formats? %>"></native-action>
|
<native-action data-placement="menu" data-menu="<%= t('new') %>" data-menu-icon="pencil_plus" data-icon="upload" data-native="upload" data-label="<%= t('upload') %>" data-url="<%= templates_upload_path(folder_name: folder_name) %>" data-accept="image/*, application/pdf, application/zip, application/json, <%= Templates::CreateAttachments.document_extensions.join(', ') %>"></native-action>
|
||||||
<native-action data-placement="menu" data-menu="<%= t('new') %>" data-menu-icon="pencil_plus" data-icon="scan" data-native="scan" data-label="<%= t('scan') %>" data-url="<%= templates_upload_path(folder_name: folder_name) %>"></native-action>
|
<native-action data-placement="menu" data-menu="<%= t('new') %>" data-menu-icon="pencil_plus" data-icon="scan" data-native="scan" data-label="<%= t('scan') %>" data-url="<%= templates_upload_path(folder_name: folder_name) %>"></native-action>
|
||||||
<native-action data-placement="menu" data-menu="<%= t('new') %>" data-menu-icon="pencil_plus" data-icon="pencil_plus">
|
<native-action data-placement="menu" data-menu="<%= t('new') %>" data-menu-icon="pencil_plus" data-icon="pencil_plus">
|
||||||
<%= link_to t('create'), new_template_path(folder_name: folder_name), data: { turbo_frame: :modal, native_modal: :medium } %>
|
<%= link_to t('create'), new_template_path(folder_name: folder_name), data: { turbo_frame: :modal, native_modal: :medium } %>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<%= render 'shared/navbar_warning' %>
|
<%= render 'shared/navbar_warning' %>
|
||||||
<div class="max-w-6xl mb-4 mx-auto px-4 md:px-2 py-3 flex items-center justify-between">
|
<nav class="max-w-6xl mb-4 mx-auto px-4 md:px-2 py-3 flex items-center justify-between">
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<a href="<%= root_path %>" class="text-2xl font-bold items-center flex space-x-2">
|
<a href="<%= root_path %>" class="text-2xl font-bold items-center flex space-x-2">
|
||||||
<%= render 'shared/title' %>
|
<%= render 'shared/title' %>
|
||||||
@@ -106,4 +106,4 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</nav>
|
||||||
@@ -93,14 +93,14 @@
|
|||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) && (current_user == true_user || current_account.testing?) %>
|
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) && (current_user == true_user || current_account.testing?) %>
|
||||||
<% if Docuseal.multitenant? || !turbo_native_ios? %>
|
<% unless turbo_native_app? %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/api") : "#{Docuseal::CONSOLE_URL}/on_premises", class: 'text-base hover:bg-base-300', data: { turbo: false } do %>
|
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/api") : "#{Docuseal::CONSOLE_URL}/on_premises", class: 'text-base hover:bg-base-300', data: { turbo: false } do %>
|
||||||
<% if Docuseal.multitenant? %> API <% else %> <%= t('console') %> <% end %>
|
<% if Docuseal.multitenant? %> API <% else %> <%= t('console') %> <% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if Docuseal.multitenant? %>
|
<% if Docuseal.multitenant? && !turbo_native_app? %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/embedding/form"), class: 'text-base hover:bg-base-300', data: { turbo: false } do %>
|
<%= link_to console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/embedding/form"), class: 'text-base hover:bg-base-300', data: { turbo: false } do %>
|
||||||
<%= t('embedding') %>
|
<%= t('embedding') %>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<main class="max-w-md mx-auto px-2 mt-12 mb-4">
|
<div class="max-w-md mx-auto px-2 mt-12 mb-4">
|
||||||
<div class="space-y-6 mx-auto">
|
<div class="space-y-6 mx-auto">
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.template&.name %></p>
|
<p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.template&.name %></p>
|
||||||
<p dir="auto" class="text-sm">
|
<p dir="auto" class="text-sm">
|
||||||
<%= t(@submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(@submitter.completed_at.to_date, format: :long)) %>
|
<%= t(@submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(@submitter.completed_at.in_time_zone(@submitter.account.timezone).to_date, format: :long)) %>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -32,5 +32,5 @@
|
|||||||
</toggle-submit>
|
</toggle-submit>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
<%= render 'shared/attribution', link_path: '/start', account: @submitter.account %>
|
<%= render 'shared/attribution', link_path: '/start', account: @submitter.account %>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<% I18n.with_locale(@template.account.locale) do %>
|
<% I18n.with_locale(@template.account.locale) do %>
|
||||||
<% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @template.account.name)) %>
|
<% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @template.account.name)) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<main class="max-w-md mx-auto px-2 mt-12 mb-4">
|
<div class="max-w-md mx-auto px-2 mt-12 mb-4">
|
||||||
<div class="space-y-6 mx-auto">
|
<div class="space-y-6 mx-auto">
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
@@ -55,4 +55,4 @@
|
|||||||
<%= button_to t(:re_send_email), start_form_email_2fa_send_index_path, params: { slug: @template.slug, resend: true, submitter: { name: params[:name] || params.dig(:submitter, :name), email: params[:email] || params.dig(:submitter, :email), phone: params[:phone] || params.dig(:submitter, :phone) } }.compact, method: :post, id: 'resend_code', class: 'hidden' %>
|
<%= button_to t(:re_send_email), start_form_email_2fa_send_index_path, params: { slug: @template.slug, resend: true, submitter: { name: params[:name] || params.dig(:submitter, :name), email: params[:email] || params.dig(:submitter, :email), phone: params[:phone] || params.dig(:submitter, :phone) } }.compact, method: :post, id: 'resend_code', class: 'hidden' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<% I18n.with_locale(@template.account.locale) do %>
|
<% I18n.with_locale(@template.account.locale) do %>
|
||||||
<% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @template.account.name)) %>
|
<% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @template.account.name)) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<main class="max-w-md mx-auto px-2 mt-12 mb-4">
|
<div class="max-w-md mx-auto px-2 mt-12 mb-4">
|
||||||
<div class="space-y-6 mx-auto">
|
<div class="space-y-6 mx-auto">
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
@@ -28,4 +28,4 @@
|
|||||||
<%= link_to t('back'), @resubmit_submitter ? submit_form_path(@resubmit_submitter.slug) : start_form_path(@template.slug, @submitter.slice(:name, :email, :phone).compact_blank), class: 'base-button' %>
|
<%= link_to t('back'), @resubmit_submitter ? submit_form_path(@resubmit_submitter.slug) : start_form_path(@template.slug, @submitter.slice(:name, :email, :phone).compact_blank), class: 'base-button' %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<% I18n.with_locale(@template.account.locale) do %>
|
<% I18n.with_locale(@template.account.locale) do %>
|
||||||
<% content_for(:html_description, t('share_link_is_currently_disabled')) %>
|
<% content_for(:html_description, t('share_link_is_currently_disabled')) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<main class="max-w-md space-y-6 mx-auto px-2 mt-12 mb-4">
|
<div class="max-w-md space-y-6 mx-auto px-2 mt-12 mb-4">
|
||||||
<div class="text-center w-full space-y-6">
|
<div class="text-center w-full space-y-6">
|
||||||
<%= render 'start_form/banner' %>
|
<%= render 'start_form/banner' %>
|
||||||
<p class="text-xl font-semibold text-center">
|
<p class="text-xl font-semibold text-center">
|
||||||
@@ -27,5 +27,5 @@
|
|||||||
<%= button_to button_title(title: t('enable_shared_link'), icon: svg_icon('lock_open', class: 'w-6 h-6')), template_share_link_path(@template), params: { template: { shared_link: true }, redir: start_form_path(slug: @template.slug) }, method: :post, class: 'white-button w-full' %>
|
<%= button_to button_title(title: t('enable_shared_link'), icon: svg_icon('lock_open', class: 'w-6 h-6')), template_share_link_path(@template), params: { template: { shared_link: true }, redir: start_form_path(slug: @template.slug) }, method: :post, class: 'white-button w-full' %>
|
||||||
</toggle-submit>
|
</toggle-submit>
|
||||||
<% end %>
|
<% end %>
|
||||||
</main>
|
</div>
|
||||||
<%= render 'shared/attribution', link_path: '/start', account: @template.account %>
|
<%= render 'shared/attribution', link_path: '/start', account: @template.account %>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<% I18n.with_locale(@template.account.locale) do %>
|
<% I18n.with_locale(@template.account.locale) do %>
|
||||||
<% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @template.account.name)) %>
|
<% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @template.account.name)) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<main class="max-w-md mx-auto px-2 mt-12 mb-4">
|
<div class="max-w-md mx-auto px-2 mt-12 mb-4">
|
||||||
<div class="space-y-6 mx-auto">
|
<div class="space-y-6 mx-auto">
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div class="text-center w-full space-y-6">
|
<div class="text-center w-full space-y-6">
|
||||||
@@ -66,4 +66,4 @@
|
|||||||
<%= render 'shared/attribution', link_path: '/start', account: @template.account %>
|
<%= render 'shared/attribution', link_path: '/start', account: @template.account %>
|
||||||
<%= render 'start_form/policy', account: @template.account %>
|
<%= render 'start_form/policy', account: @template.account %>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<% fields_id = "add_new_fields_#{SecureRandom.hex(3)}" %>
|
||||||
|
<div id="<%= fields_id %>" class="space-y-3 mt-4 hidden" data-targets="dynamic-list.groups" data-multiple-classes="px-3 pt-3 pb-4 rounded-xl border border-dashed border-base-300"></div>
|
||||||
|
<native-menu class="<%= turbo_native_app? ? 'block' : 'dropdown block max-sm:hidden' %> mt-4 -mb-2">
|
||||||
|
<label tabindex="0" class="btn btn-ghost btn-sm normal-case font-normal w-full flex items-center justify-center border-dashed border-base-300 hover:border-base-content/20 !bg-transparent">
|
||||||
|
<%= svg_icon('users_plus', class: 'w-4 h-4 stroke-2') %>
|
||||||
|
<span><%= t('add_new') %></span>
|
||||||
|
</label>
|
||||||
|
<ul tabindex="0" class="<%= turbo_native_app? ? 'hidden' : 'z-10 dropdown-content p-2 mt-1 shadow menu bg-base-100 rounded-box' %>" style="left: 50%; --tw-translate-x: -50%">
|
||||||
|
<li>
|
||||||
|
<native-menu-action data-icon="user_plus" data-label="<%= t('recipients') %>" class="grid-cols-1 p-0">
|
||||||
|
<a href="#" class="flex items-center gap-2 px-4 py-2" data-action="click:dynamic-list#addItem">
|
||||||
|
<%= svg_icon('user_plus', class: 'w-4 h-4 stroke-2') %>
|
||||||
|
<span class="whitespace-nowrap"><%= t('recipients') %></span>
|
||||||
|
</a>
|
||||||
|
</native-menu-action>
|
||||||
|
</li>
|
||||||
|
<append-template class="contents" data-target-id="<%= fields_id %>">
|
||||||
|
<li>
|
||||||
|
<native-menu-action data-icon="eye_plus" data-label="<%= t('cc_view_only') %>" class="grid-cols-1 p-0">
|
||||||
|
<a href="#" class="flex items-center gap-2 px-4 py-2">
|
||||||
|
<%= svg_icon('eye_plus', class: 'w-4 h-4 stroke-2') %>
|
||||||
|
<span class="whitespace-nowrap"><%= t('cc_view_only') %></span>
|
||||||
|
</a>
|
||||||
|
</native-menu-action>
|
||||||
|
</li>
|
||||||
|
<template>
|
||||||
|
<submitter-item class="form-control">
|
||||||
|
<label class="label pt-0 pb-1">
|
||||||
|
<span class="label-text"><%= t('cc_view_only') %></span>
|
||||||
|
</label>
|
||||||
|
<div class="relative">
|
||||||
|
<submitters-autocomplete data-field="email">
|
||||||
|
<input type="email" name="cc_recipients[]" autocomplete="off" class="base-input !h-10 w-full pr-10" placeholder="<%= t('email') %>">
|
||||||
|
</submitters-autocomplete>
|
||||||
|
<a href="#" class="absolute right-2 top-2 block w-6 h-6 text-neutral-700 p-1 hover:text-base-content">
|
||||||
|
<%= svg_icon('trash', class: 'w-4 h-4') %>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</submitter-item>
|
||||||
|
</template>
|
||||||
|
</append-template>
|
||||||
|
<append-template class="contents" data-target-id="<%= fields_id %>">
|
||||||
|
<li>
|
||||||
|
<native-menu-action data-icon="mail_plus" data-label="<%= t('bcc_completed') %>" class="grid-cols-1 p-0">
|
||||||
|
<a href="#" class="flex items-center gap-2 px-4 py-2">
|
||||||
|
<%= svg_icon('mail_plus', class: 'w-4 h-4 stroke-2') %>
|
||||||
|
<span class="whitespace-nowrap"><%= t('bcc_completed') %></span>
|
||||||
|
</a>
|
||||||
|
</native-menu-action>
|
||||||
|
</li>
|
||||||
|
<template>
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label pt-0 pb-1">
|
||||||
|
<span class="label-text"><%= t('bcc_completed') %></span>
|
||||||
|
</label>
|
||||||
|
<div class="relative">
|
||||||
|
<input type="email" name="bcc_completed[]" autocomplete="off" class="base-input !h-10 w-full pr-10" placeholder="<%= t('email') %>">
|
||||||
|
<a href="#" class="absolute right-2 top-2 block w-6 h-6 text-neutral-700 p-1 hover:text-base-content">
|
||||||
|
<%= svg_icon('trash', class: 'w-4 h-4') %>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<% default_bcc_completed = template.preferences['bcc_completed'].presence || current_account.account_configs.find_by(key: AccountConfig::BCC_EMAILS)&.value %>
|
||||||
|
<% if default_bcc_completed.present? %>
|
||||||
|
<input type="hidden" name="bcc_completed[]" value="<%= default_bcc_completed %>">
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</append-template>
|
||||||
|
</ul>
|
||||||
|
</native-menu>
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
<% has_phone_field = false %>
|
<% has_phone_field = false %>
|
||||||
<%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
<%= form_for '', url: template_submissions_path(template), html: { autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
||||||
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %>
|
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %>
|
||||||
<dynamic-list class="space-y-4">
|
<dynamic-list class="block" data-multiple-classes="px-3 <%= submitters.size > 1 ? 'pt-3 pb-4' : 'py-4' %> rounded-xl border border-dashed border-base-300">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="relative <%= 'p-4 rounded-xl bg-base-300/40 max-sm:p-0 max-sm:bg-transparent' unless local_assigns[:variables_form] %>" data-targets="dynamic-list.items">
|
<div class="relative" data-targets="dynamic-list.items">
|
||||||
<div>
|
<div>
|
||||||
<div class="absolute right-4 top-5">
|
<div class="absolute top-5" style="right: 12px">
|
||||||
<a href="#" data-action="click:dynamic-list#removeItem" class="<%= submitters.size == 1 ? 'right-2 top-1' : '-top-3' %> relative block w-6 h-6 rounded-lg text-neutral-700 text-center bg-base-300 p-1 hidden hover:bg-neutral hover:text-white">
|
<a href="#" data-action="click:dynamic-list#removeItem" class="<%= submitters.size == 1 ? 'right-2 top-1' : '-top-3' %> relative block w-6 h-6 text-neutral-700 p-1 hidden hover:text-base-content">
|
||||||
<%= svg_icon('trash', class: 'w-4 h-4') %>
|
<%= svg_icon('trash', class: 'w-4 h-4') %>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -86,14 +86,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% if params[:selfsign].blank? && local_assigns[:prefillable_fields].blank? && local_assigns[:recipient_form_fields].blank? && local_assigns[:variables_form].blank? %>
|
<% if params[:selfsign].blank? && local_assigns[:prefillable_fields].blank? && local_assigns[:recipient_form_fields].blank? && local_assigns[:variables_form].blank? %>
|
||||||
<a href="#" class="btn btn-primary btn-sm w-full flex items-center justify-center max-sm:hidden" data-action="click:dynamic-list#addItem">
|
<%= render 'add_new', template: %>
|
||||||
<%= svg_icon('user_plus', class: 'w-4 h-4 stroke-2') %>
|
|
||||||
<span><%= t('add_new') %></span>
|
|
||||||
</a>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</dynamic-list>
|
</dynamic-list>
|
||||||
<%= local_assigns[:variables_form] %>
|
<%= local_assigns[:variables_form] %>
|
||||||
<div>
|
<div class="mt-4">
|
||||||
<%= render('submitters_order', f:, template:) if can_send_emails %>
|
<%= render('submitters_order', f:, template:) if can_send_emails %>
|
||||||
<%= render 'send_email', f:, template:, can_send_emails:, viewer_submitter_uuids: local_assigns[:viewer_submitter_uuids] %>
|
<%= render 'send_email', f:, template:, can_send_emails:, viewer_submitter_uuids: local_assigns[:viewer_submitter_uuids] %>
|
||||||
<% if has_phone_field %>
|
<% if has_phone_field %>
|
||||||
@@ -101,7 +98,12 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<%= render 'extra_fields', f: %>
|
<%= render 'extra_fields', f: %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control mt-4">
|
||||||
<%= f.button button_title(title: t('add_recipients')), class: 'base-button' %>
|
<%= f.button class: 'base-button' do %>
|
||||||
|
<toggle-send-title>
|
||||||
|
<span><%= button_title(title: t('send'), disabled_with: t('sending')) %></span>
|
||||||
|
<span class="hidden"><%= button_title(title: t('add_recipients')) %></span>
|
||||||
|
</toggle-send-title>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
<%= form_for '', url: template_submissions_path(template), html: { autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
||||||
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %>
|
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %>
|
||||||
<% if submitters.size == 1 && local_assigns[:variables_form] %>
|
<% if submitters.size == 1 && local_assigns[:variables_form] %>
|
||||||
<% item = submitters.first %>
|
<% item = submitters.first %>
|
||||||
@@ -26,18 +26,18 @@
|
|||||||
<emails-textarea data-bulk-enabled="<%= Docuseal.demo? || !Docuseal.multitenant? || can?(:manage, :bulk_send) %>" data-limit="<%= Docuseal.multitenant? ? (can?(:manage, :bulk_send) ? 40 : 1) : nil %>">
|
<emails-textarea data-bulk-enabled="<%= Docuseal.demo? || !Docuseal.multitenant? || can?(:manage, :bulk_send) %>" data-limit="<%= Docuseal.multitenant? ? (can?(:manage, :bulk_send) ? 40 : 1) : nil %>">
|
||||||
<submitters-autocomplete data-field="email" class="block relative">
|
<submitters-autocomplete data-field="email" class="block relative">
|
||||||
<autoresize-textarea>
|
<autoresize-textarea>
|
||||||
<%= f.text_area :emails, required: true, class: 'base-textarea w-full !text-lg', placeholder: "#{t('type_emails_here')}...", rows: 2 %>
|
<%= f.text_area :emails, required: true, class: 'base-textarea block w-full !text-lg', placeholder: "#{t('type_emails_here')}...", rows: 2 %>
|
||||||
</autoresize-textarea>
|
</autoresize-textarea>
|
||||||
</submitters-autocomplete>
|
</submitters-autocomplete>
|
||||||
</emails-textarea>
|
</emails-textarea>
|
||||||
</submitter-item>
|
</submitter-item>
|
||||||
<% else %>
|
<% else %>
|
||||||
<dynamic-list class="space-y-4">
|
<dynamic-list class="block" data-multiple-classes="px-3 pt-3 pb-4 rounded-xl border border-dashed border-base-300">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="relative <%= 'p-4 rounded-xl bg-base-300/40 max-sm:p-0 max-sm:bg-transparent' unless local_assigns[:variables_form] %>" data-targets="dynamic-list.items">
|
<div class="relative" data-targets="dynamic-list.items">
|
||||||
<div>
|
<div>
|
||||||
<div class="absolute right-4 top-5">
|
<div class="absolute top-5" style="right: 12px">
|
||||||
<a href="#" data-action="click:dynamic-list#removeItem" class="-top-3 relative block w-6 h-6 rounded-lg text-neutral-700 text-center bg-base-300 p-1 hidden hover:bg-neutral hover:text-white">
|
<a href="#" data-action="click:dynamic-list#removeItem" class="-top-3 relative block w-6 h-6 text-neutral-700 p-1 hidden hover:text-base-content">
|
||||||
<%= svg_icon('trash', class: 'w-4 h-4') %>
|
<%= svg_icon('trash', class: 'w-4 h-4') %>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -60,22 +60,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% if params[:selfsign].blank? && local_assigns[:variables_form].blank? %>
|
<% if params[:selfsign].blank? && local_assigns[:variables_form].blank? %>
|
||||||
<a href="#" class="btn btn-primary btn-sm w-full flex items-center justify-center max-sm:hidden" data-action="click:dynamic-list#addItem">
|
<%= render 'add_new', template: %>
|
||||||
<%= svg_icon('user_plus', class: 'w-4 h-4 stroke-2') %>
|
|
||||||
<span><%= t('add_new') %></span>
|
|
||||||
</a>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</dynamic-list>
|
</dynamic-list>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= local_assigns[:variables_form] %>
|
<%= local_assigns[:variables_form] %>
|
||||||
<div>
|
<div class="mt-4">
|
||||||
<%= render('submitters_order', f:, template:) if can_send_emails %>
|
<%= render('submitters_order', f:, template:) if can_send_emails %>
|
||||||
<%= render 'send_email', f:, template:, can_send_emails:, viewer_submitter_uuids: local_assigns[:viewer_submitter_uuids] %>
|
<%= render 'send_email', f:, template:, can_send_emails:, viewer_submitter_uuids: local_assigns[:viewer_submitter_uuids] %>
|
||||||
<%= render 'extra_fields', f: %>
|
<%= render 'extra_fields', f: %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control mt-4">
|
||||||
<%= content_for(:submit_button) || capture do %>
|
<%= content_for(:submit_button) || capture do %>
|
||||||
<%= f.button button_title(title: t('add_recipients')), class: 'base-button' %>
|
<%= f.button class: 'base-button' do %>
|
||||||
|
<toggle-on-checked data-names="send_email send_sms">
|
||||||
|
<span><%= button_title(title: t('send'), disabled_with: t('sending')) %></span>
|
||||||
|
<span class="hidden"><%= button_title(title: t('add_recipients')) %></span>
|
||||||
|
</toggle-on-checked>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
<%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
<%= form_for '', url: template_submissions_path(template), html: { autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
||||||
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %>
|
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %>
|
||||||
<dynamic-list class="space-y-4">
|
<dynamic-list class="block" data-multiple-classes="px-3 <%= submitters.size > 1 ? 'pt-3 pb-4' : 'py-4' %> rounded-xl border border-dashed border-base-300">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="relative <%= 'p-4 rounded-xl bg-base-300/40 max-sm:p-0 max-sm:bg-transparent' unless local_assigns[:variables_form] %>" data-targets="dynamic-list.items">
|
<div class="relative" data-targets="dynamic-list.items">
|
||||||
<div>
|
<div>
|
||||||
<div class="absolute right-4 top-5">
|
<div class="absolute top-5" style="right: 12px">
|
||||||
<a href="#" data-action="click:dynamic-list#removeItem" class="<%= submitters.size == 1 ? 'right-2 top-1' : '-top-3' %> relative block w-6 h-6 rounded-lg text-neutral-700 text-center bg-base-300 p-1 hidden hover:bg-neutral hover:text-white">
|
<a href="#" data-action="click:dynamic-list#removeItem" class="<%= submitters.size == 1 ? 'right-2 top-1' : '-top-3' %> relative block w-6 h-6 text-neutral-700 p-1 hidden hover:text-base-content">
|
||||||
<%= svg_icon('trash', class: 'w-4 h-4') %>
|
<%= svg_icon('trash', class: 'w-4 h-4') %>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -48,19 +48,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% if params[:selfsign].blank? && local_assigns[:variables_form].blank? %>
|
<% if params[:selfsign].blank? && local_assigns[:variables_form].blank? %>
|
||||||
<a href="#" class="btn btn-primary btn-sm w-full flex items-center justify-center max-sm:hidden" data-action="click:dynamic-list#addItem">
|
<%= render 'add_new', template: %>
|
||||||
<%= svg_icon('user_plus', class: 'w-4 h-4 stroke-2') %>
|
|
||||||
<span><%= t('add_new') %></span>
|
|
||||||
</a>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</dynamic-list>
|
</dynamic-list>
|
||||||
<%= local_assigns[:variables_form] %>
|
<%= local_assigns[:variables_form] %>
|
||||||
<div>
|
<div class="mt-4">
|
||||||
<%= render 'submitters_order', f:, template: %>
|
<%= render 'submitters_order', f:, template: %>
|
||||||
<%= render 'send_sms', f:, checked: true %>
|
<%= render 'send_sms', f:, checked: true %>
|
||||||
<%= render 'extra_phone_fields', f: %>
|
<%= render 'extra_phone_fields', f: %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control mt-4">
|
||||||
<%= f.button button_title(title: t('add_recipients')), class: 'base-button' %>
|
<%= f.button class: 'base-button' do %>
|
||||||
|
<toggle-on-checked data-names="send_email send_sms">
|
||||||
|
<span><%= button_title(title: t('send'), disabled_with: t('sending')) %></span>
|
||||||
|
<span class="hidden"><%= button_title(title: t('add_recipients')) %></span>
|
||||||
|
</toggle-on-checked>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<toggle-visible data-element-ids="<%= [message_field_id].to_json %>" class="flex">
|
<toggle-visible data-element-ids="<%= [message_field_id].to_json %>" class="flex">
|
||||||
<label>
|
<label>
|
||||||
<%= f.check_box :is_custom_message, checked: false, class: 'hidden peer', data: { action: 'change:toggle-visible#trigger', type: 'checkbox' } %>
|
<%= f.check_box :is_custom_message, checked: false, class: 'hidden peer', data: { action: 'change:toggle-visible#trigger', type: 'checkbox' } %>
|
||||||
<span class="link peer-checked:hidden"><%= t('edit_message') %></span>
|
<span class="link link-hover peer-checked:hidden"><%= t('edit_message') %></span>
|
||||||
</label>
|
</label>
|
||||||
</toggle-visible>
|
</toggle-visible>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<% unless turbo_native_ios? %>
|
<% unless turbo_native_ios? %>
|
||||||
|
<toggle-on-checked data-names="is_custom_message" class="block">
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= render 'sms_settings/placeholder' %>
|
<%= render 'sms_settings/placeholder' %>
|
||||||
</div>
|
</div>
|
||||||
|
</toggle-on-checked>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</toggle-visible>
|
</toggle-visible>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="px-5 mb-5 mt-4">
|
<div class="px-5 mb-5 mt-5">
|
||||||
<% unless only_detailed %>
|
<% unless only_detailed %>
|
||||||
<div id="email" class="<%= 'hidden' if default_tab != 'email' %>">
|
<div id="email" class="<%= 'hidden' if default_tab != 'email' %>">
|
||||||
<%= render 'email_form', template: @template, variables_form:, can_send_emails:, viewer_submitter_uuids: %>
|
<%= render 'email_form', template: @template, variables_form:, can_send_emails:, viewer_submitter_uuids: %>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<% with_timestamp_seconds = configs.find { |e| e.key == AccountConfig::WITH_TIMESTAMP_SECONDS_KEY }&.value == true %>
|
<% with_timestamp_seconds = configs.find { |e| e.key == AccountConfig::WITH_TIMESTAMP_SECONDS_KEY }&.value == true %>
|
||||||
<% with_signature_id_reason = configs.find { |e| e.key == AccountConfig::WITH_SIGNATURE_ID_REASON_KEY }&.value != false %>
|
<% with_signature_id_reason = configs.find { |e| e.key == AccountConfig::WITH_SIGNATURE_ID_REASON_KEY }&.value != false %>
|
||||||
<% with_signature_id_completed_at = configs.find { |e| e.key == AccountConfig::WITH_SIGNATURE_ID_COMPLETED_AT_KEY }&.value == true %>
|
<% with_signature_id_completed_at = configs.find { |e| e.key == AccountConfig::WITH_SIGNATURE_ID_COMPLETED_AT_KEY }&.value == true %>
|
||||||
<main style="max-width: 1600px" class="mx-auto pl-4">
|
<div style="max-width: 1600px" class="mx-auto pl-4">
|
||||||
<% unless native_tab == 'recipients' %>
|
<% unless native_tab == 'recipients' %>
|
||||||
<div class="flex justify-between items-center pr-4 sticky top-0 md:relative z-10 bg-base-100 <%= 'py-1.5' unless turbo_native_app? %>">
|
<div class="flex justify-between items-center pr-4 sticky top-0 md:relative z-10 bg-base-100 <%= 'py-1.5' unless turbo_native_app? %>">
|
||||||
<% unless turbo_native_app? %>
|
<% unless turbo_native_app? %>
|
||||||
@@ -54,7 +54,8 @@
|
|||||||
<% if @submission.submitters.to_a.any?(&:completed_at?) %>
|
<% if @submission.submitters.to_a.any?(&:completed_at?) %>
|
||||||
<% show_combined_download = @submission.completed_at? && !is_combined_enabled %>
|
<% show_combined_download = @submission.completed_at? && !is_combined_enabled %>
|
||||||
<% show_unarchive = signed_in? && can?(:destroy, @submission) && @submission.archived_at? && @submission.completed_at? %>
|
<% show_unarchive = signed_in? && can?(:destroy, @submission) && @submission.archived_at? && @submission.completed_at? %>
|
||||||
<% show_download_dropdown = show_combined_download || show_unarchive %>
|
<% show_event_log_download = signed_in? && can?(:manage, :event_log_download) && can?(:read, @submission) %>
|
||||||
|
<% show_download_dropdown = show_combined_download || show_event_log_download || show_unarchive %>
|
||||||
<% if @submission.completed_at? || !is_combined_enabled %>
|
<% if @submission.completed_at? || !is_combined_enabled %>
|
||||||
<div class="join relative">
|
<div class="join relative">
|
||||||
<native-action data-label="<%= t('download') %>" data-icon="download">
|
<native-action data-label="<%= t('download') %>" data-icon="download">
|
||||||
@@ -95,6 +96,9 @@
|
|||||||
</li>
|
</li>
|
||||||
</native-action>
|
</native-action>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if show_event_log_download %>
|
||||||
|
<%= render 'submissions/download_event_log' %>
|
||||||
|
<% end %>
|
||||||
<% if show_unarchive %>
|
<% if show_unarchive %>
|
||||||
<native-action data-label="<%= t('unarchive') %>" data-icon="rotate" data-section="archive">
|
<native-action data-label="<%= t('unarchive') %>" data-icon="rotate" data-section="archive">
|
||||||
<li>
|
<li>
|
||||||
@@ -163,6 +167,7 @@
|
|||||||
<% value = values[field['uuid']].presence || (field['default_value'] != '{{date}}' && field['readonly'] == true && field['conditions'].blank? && field['default_value'].present? ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %>
|
<% value = values[field['uuid']].presence || (field['default_value'] != '{{date}}' && field['readonly'] == true && field['conditions'].blank? && field['default_value'].present? ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %>
|
||||||
<% value ||= field['default_value'] if field['type'] == 'heading' %>
|
<% value ||= field['default_value'] if field['type'] == 'heading' %>
|
||||||
<% submitter = submitters_index[field['submitter_uuid']] %>
|
<% submitter = submitters_index[field['submitter_uuid']] %>
|
||||||
|
<% value = nil if field['type'] == 'signature' && submitter && !submitter.completed_at? %>
|
||||||
<% if value.present? %>
|
<% if value.present? %>
|
||||||
<% if (mask = field.dig('preferences', 'mask').presence) && signed_in? && can?(:read, @submission) %>
|
<% if (mask = field.dig('preferences', 'mask').presence) && signed_in? && can?(:read, @submission) %>
|
||||||
<span class="group">
|
<span class="group">
|
||||||
@@ -222,39 +227,39 @@
|
|||||||
</div>
|
</div>
|
||||||
<% if submitter&.name.present? %>
|
<% if submitter&.name.present? %>
|
||||||
<div class="flex items-center space-x-1 mt-1">
|
<div class="flex items-center space-x-1 mt-1">
|
||||||
<%= svg_icon('user', class: 'w-5 h-5') %>
|
<%= svg_icon('user', class: 'w-5 h-5 shrink-0') %>
|
||||||
<span>
|
<span class="truncate">
|
||||||
<%= submitter&.name %>
|
<%= submitter&.name %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if submitter&.email.present? %>
|
<% if submitter&.email.present? %>
|
||||||
<div class="flex items-center space-x-1 mt-1">
|
<div class="flex items-center space-x-1 mt-1">
|
||||||
<%= svg_icon('mail', class: 'w-5 h-5') %>
|
<%= svg_icon('mail', class: 'w-5 h-5 shrink-0') %>
|
||||||
<span>
|
<span class="truncate">
|
||||||
<%= submitter.email || 'N/A' %>
|
<%= submitter.email || 'N/A' %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if submitter&.phone.present? %>
|
<% if submitter&.phone.present? %>
|
||||||
<div class="flex items-center space-x-1 mt-1">
|
<div class="flex items-center space-x-1 mt-1">
|
||||||
<%= svg_icon('phone', class: 'w-5 h-5') %>
|
<%= svg_icon('phone', class: 'w-5 h-5 shrink-0') %>
|
||||||
<span>
|
<span class="truncate">
|
||||||
<%= submitter.phone %>
|
<%= submitter.phone %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="flex items-center space-x-1 mt-1">
|
<div class="flex items-center space-x-1 mt-1">
|
||||||
<% if submitter&.viewer? %>
|
<% if submitter&.viewer? %>
|
||||||
<%= svg_icon(submitter.opened_at? ? 'file_check' : 'file_unknown', class: 'w-5 h-5') %>
|
<%= svg_icon(submitter.opened_at? ? 'file_check' : 'file_unknown', class: 'w-5 h-5 shrink-0') %>
|
||||||
<% elsif submitter && !submitter.completed_at? && @submission.completed_at? %>
|
<% elsif submitter && !submitter.completed_at? && @submission.completed_at? %>
|
||||||
<%= svg_icon('writing_off', class: 'w-5 h-5') %>
|
<%= svg_icon('writing_off', class: 'w-5 h-5 shrink-0') %>
|
||||||
<% elsif @submission.expire_at? && submitter && !submitter.completed_at? %>
|
<% elsif @submission.expire_at? && submitter && !submitter.completed_at? %>
|
||||||
<%= svg_icon('clock_exclamation', class: 'w-5 h-5') %>
|
<%= svg_icon('clock_exclamation', class: 'w-5 h-5 shrink-0') %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= svg_icon('writing', class: 'w-5 h-5') %>
|
<%= svg_icon('writing', class: 'w-5 h-5 shrink-0') %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<span>
|
<span class="truncate">
|
||||||
<% if submitter&.declined_at? %>
|
<% if submitter&.declined_at? %>
|
||||||
<%= t('declined_on_time', time: l(submitter.declined_at.in_time_zone(@submission.account.timezone), format: :short, locale: @submission.account.locale)) %>
|
<%= t('declined_on_time', time: l(submitter.declined_at.in_time_zone(@submission.account.timezone), format: :short, locale: @submission.account.locale)) %>
|
||||||
<% elsif submitter&.viewer? %>
|
<% elsif submitter&.viewer? %>
|
||||||
@@ -320,6 +325,7 @@
|
|||||||
<% value = values[field['uuid']].presence || (field['default_value'] != '{{date}}' && field['readonly'] == true && field['default_value'].present? ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %>
|
<% value = values[field['uuid']].presence || (field['default_value'] != '{{date}}' && field['readonly'] == true && field['default_value'].present? ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %>
|
||||||
<% next if value.blank? %>
|
<% next if value.blank? %>
|
||||||
<% next if field['type'] == 'heading' || field['type'] == 'strikethrough' %>
|
<% next if field['type'] == 'heading' || field['type'] == 'strikethrough' %>
|
||||||
|
<% next if field['type'] == 'signature' && submitter && !submitter.completed_at? %>
|
||||||
<div class="pt-2.5 border-b border-base-300">
|
<div class="pt-2.5 border-b border-base-300">
|
||||||
<div class="text-xs font-medium uppercase mb-0.5" dir="auto">
|
<div class="text-xs font-medium uppercase mb-0.5" dir="auto">
|
||||||
<%= field['name'].presence || "#{t("#{field['type']}_field")} #{submitter_field_counters[field['type']]}" %>
|
<%= field['name'].presence || "#{t("#{field['type']}_field")} #{submitter_field_counters[field['type']]}" %>
|
||||||
@@ -388,7 +394,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</toggle-visible>
|
</toggle-visible>
|
||||||
<% end %>
|
<% end %>
|
||||||
</main>
|
</div>
|
||||||
<% unless request.headers['HTTP_X_TURBO_REQUEST_ID'] %>
|
<% unless request.headers['HTTP_X_TURBO_REQUEST_ID'] %>
|
||||||
<%= render 'scripts/autosize_field' %>
|
<%= render 'scripts/autosize_field' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
<% last_submitter = @submission.submitters.completed.order(:completed_at).last %>
|
<% last_submitter = @submission.submitters.completed.order(:completed_at).last %>
|
||||||
<% if last_submitter %>
|
<% if last_submitter %>
|
||||||
<p dir="auto" class="text-sm">
|
<p dir="auto" class="text-sm">
|
||||||
<%= t(last_submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(last_submitter.completed_at.to_date, format: :long)) %>
|
<%= t(last_submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(last_submitter.completed_at.in_time_zone(@submission.account.timezone).to_date, format: :long)) %>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<% completed_at = @submitter.completed_at || @submitter.submission.completed_at %>
|
<% completed_at = @submitter.completed_at || @submitter.submission.completed_at %>
|
||||||
<main class="max-w-md mx-auto px-2 mt-12 mb-4">
|
<div class="max-w-md mx-auto px-2 mt-12 mb-4">
|
||||||
<div class="space-y-6 mx-auto">
|
<div class="space-y-6 mx-auto">
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p>
|
<p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p>
|
||||||
<p dir="auto" class="text-sm">
|
<p dir="auto" class="text-sm">
|
||||||
<%= t(@submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(completed_at.to_date, format: :long)) %>
|
<%= t(@submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(completed_at.in_time_zone(@submitter.account.timezone).to_date, format: :long)) %>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -51,5 +51,5 @@
|
|||||||
</toggle-submit>
|
</toggle-submit>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
<%= render 'shared/attribution', link_path: '/start', account: @submitter.account %>
|
<%= render 'shared/attribution', link_path: '/start', account: @submitter.account %>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div dir="auto">
|
<div dir="auto">
|
||||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p>
|
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p>
|
||||||
<p class="text-sm"><%= t('form_has_been_declined_on_html', time: l(@submitter.declined_at, format: :long)) %></p>
|
<p class="text-sm"><%= t('form_has_been_declined_on_html', time: l(@submitter.declined_at.in_time_zone(@submitter.account.timezone).to_date, format: :long)) %></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div dir="auto">
|
<div dir="auto">
|
||||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p>
|
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p>
|
||||||
<p class="text-sm"><%= t('document_has_been_delegated_on_html', time: l(@submitter.submission_events.order(event_timestamp: :desc).find_by!(event_type: :delegate_form).event_timestamp, format: :long)) %></p>
|
<p class="text-sm"><%= t('document_has_been_delegated_on_html', time: l(@submitter.submission_events.order(event_timestamp: :desc).find_by!(event_type: :delegate_form).event_timestamp.in_time_zone(@submitter.account.timezone).to_date, format: :long)) %></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<% I18n.with_locale(@submitter.account.locale) do %>
|
<% I18n.with_locale(@submitter.account.locale) do %>
|
||||||
<% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @submitter.account.name)) %>
|
<% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @submitter.account.name)) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<main class="max-w-md mx-auto px-2 mt-12 mb-4">
|
<div class="max-w-md mx-auto px-2 mt-12 mb-4">
|
||||||
<div class="space-y-6 mx-auto">
|
<div class="space-y-6 mx-auto">
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<% last_submitter = @submitter.submission.submitters.completed.order(:completed_at).last %>
|
<% last_submitter = @submitter.submission.submitters.completed.order(:completed_at).last %>
|
||||||
<% if last_submitter %>
|
<% if last_submitter %>
|
||||||
<p dir="auto" class="text-sm">
|
<p dir="auto" class="text-sm">
|
||||||
<%= t(last_submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(last_submitter.completed_at.to_date, format: :long)) %>
|
<%= t(last_submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(last_submitter.completed_at.in_time_zone(@submitter.account.timezone).to_date, format: :long)) %>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
@@ -70,4 +70,4 @@
|
|||||||
<div><%= t('please_contact_the_requester_to_specify_your_email_for_two_factor_authentication') %></div>
|
<div><%= t('please_contact_the_requester_to_specify_your_email_for_two_factor_authentication') %></div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div dir="auto">
|
<div dir="auto">
|
||||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p>
|
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p>
|
||||||
<p class="text-sm"><%= t('form_expired_at_html', time: l(@submitter.submission.expire_at, format: :long)) %></p>
|
<p class="text-sm"><%= t('form_expired_at_html', time: l(@submitter.submission.expire_at.in_time_zone(@submitter.account.timezone).to_date, format: :long)) %></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<% delegate_modal_id = nil %>
|
<% delegate_modal_id = nil %>
|
||||||
<% banner_html = turbo_native_app? ? '' : local_assigns[:banner_html] %>
|
<% banner_html = turbo_native_app? ? '' : local_assigns[:banner_html] %>
|
||||||
<div style="max-height: -webkit-fill-available;">
|
<div style="max-height: -webkit-fill-available;">
|
||||||
<main id="scrollbox">
|
<div id="scrollbox">
|
||||||
<div id="sr_only_content"></div>
|
<div id="sr_only_content"></div>
|
||||||
<div class="mx-auto block pb-72" style="max-width: 1000px">
|
<div class="mx-auto block pb-72" style="max-width: 1000px">
|
||||||
<%# flex block w-full sticky top-0 z-50 space-x-2 items-center bg-yellow-100 p-2 border-y border-yellow-200 transition-transform duration-300 %>
|
<%# flex block w-full sticky top-0 z-50 space-x-2 items-center bg-yellow-100 p-2 border-y border-yellow-200 transition-transform duration-300 %>
|
||||||
@@ -172,7 +172,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="fixed bottom-0 w-full h-0 z-50">
|
<div class="fixed bottom-0 w-full h-0 z-50">
|
||||||
<div class="mx-auto" style="max-width: 1000px">
|
<div class="mx-auto" style="max-width: 1000px">
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<input id="file_dropzone_input" name="files[]" class="hidden" data-action="change:file-dropzone#onSelectFiles" data-target="file-dropzone.input" type="file" accept="image/*, application/pdf, application/zip, application/json<%= ", #{Templates::CreateAttachments::DOCUMENT_EXTENSIONS.join(', ')}" if Docuseal.advanced_formats? %>" multiple>
|
<input id="file_dropzone_input" name="files[]" class="hidden" data-action="change:file-dropzone#onSelectFiles" data-target="file-dropzone.input" type="file" accept="image/*, application/pdf, application/zip, application/json, <%= Templates::CreateAttachments.document_extensions.join(', ') %>" multiple>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</file-dropzone>
|
</file-dropzone>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<input type="hidden" name="form_id" value="<%= form_id %>">
|
<input type="hidden" name="form_id" value="<%= form_id %>">
|
||||||
<convert-upload>
|
<convert-upload>
|
||||||
<input id="upload_template" name="files[]" class="hidden" type="file" accept="image/*, application/pdf, application/zip, application/json<%= ", #{Templates::CreateAttachments::DOCUMENT_EXTENSIONS.join(', ')}" if Docuseal.advanced_formats? %>" multiple>
|
<input id="upload_template" name="files[]" class="hidden" type="file" accept="image/*, application/pdf, application/zip, application/json, <%= Templates::CreateAttachments.document_extensions.join(', ') %>" multiple>
|
||||||
</convert-upload>
|
</convert-upload>
|
||||||
<input hidden name="folder_name" value="<%= local_assigns[:folder_name] %>">
|
<input hidden name="folder_name" value="<%= local_assigns[:folder_name] %>">
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -7,7 +7,8 @@
|
|||||||
<%= button_to nil, user_configs_path, method: :post, params: { user_config: { key: UserConfig::SHOW_APP_TOUR, value: true } }, class: 'hidden', id: 'start_tour_button' %>
|
<%= 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 %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% account_configs = current_account.account_configs.where(key: [AccountConfig::TEMPLATE_CUSTOM_FIELDS_KEY, AccountConfig::TEMPLATE_DATE_FORMATS_KEY]).to_a %>
|
<% account_configs = current_account.account_configs.where(key: [AccountConfig::TEMPLATE_CUSTOM_FIELDS_KEY, AccountConfig::TEMPLATE_DATE_FORMATS_KEY, AccountConfig::TEMPLATE_BUILDER_AUTOSAVE_KEY]).to_a %>
|
||||||
<% custom_fields = account_configs.find { |e| e.key == AccountConfig::TEMPLATE_CUSTOM_FIELDS_KEY }&.value || [] %>
|
<% custom_fields = account_configs.find { |e| e.key == AccountConfig::TEMPLATE_CUSTOM_FIELDS_KEY }&.value || [] %>
|
||||||
<% date_formats = account_configs.find { |e| e.key == AccountConfig::TEMPLATE_DATE_FORMATS_KEY }&.value || [] %>
|
<% date_formats = account_configs.find { |e| e.key == AccountConfig::TEMPLATE_DATE_FORMATS_KEY }&.value || [] %>
|
||||||
<template-builder data-native-platform="<%= turbo_native_ios? ? 'ios' : (turbo_native_android? ? 'android' : '') %>" class="grid" data-template="<%= @template_data.to_json %>" data-custom-fields="<%= custom_fields.to_json %>" data-date-formats="<%= date_formats.to_json %>" data-with-sign-yourself-button="<%= !@template.archived_at? %>" data-with-fields-detection="true" data-with-send-button="<%= !@template.archived_at? && can?(:create, @template.submissions.new(account: current_account)) %>" data-with-revisions-menu="<%= @template.template_versions.exists? %>" data-locale="<%= I18n.locale %>" data-show-tour-start-form="<%= @show_tour_start_form %>"></template-builder>
|
<% autosave = account_configs.find { |e| e.key == AccountConfig::TEMPLATE_BUILDER_AUTOSAVE_KEY }&.value != false %>
|
||||||
|
<template-builder data-native-platform="<%= turbo_native_ios? ? 'ios' : (turbo_native_android? ? 'android' : '') %>" data-accept-file-types="image/*, application/pdf, application/zip, application/json, <%= Templates::CreateAttachments.document_extensions.join(', ') %>" class="grid" data-template="<%= @template_data.to_json %>" data-custom-fields="<%= custom_fields.to_json %>" data-date-formats="<%= date_formats.to_json %>" data-autosave="<%= autosave %>" data-with-sign-yourself-button="<%= !@template.archived_at? %>" data-with-fields-detection="true" data-with-send-button="<%= !@template.archived_at? && can?(:create, @template.submissions.new(account: current_account)) %>" data-with-revisions-menu="<%= @template.template_versions.exists? %>" data-locale="<%= I18n.locale %>" data-show-tour-start-form="<%= @show_tour_start_form %>"></template-builder>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
<%= t('share_template_with_test_mode') %>
|
<%= t('share_template_with_test_mode') %>
|
||||||
</span>
|
</span>
|
||||||
<submit-form data-on="change">
|
<submit-form data-on="change">
|
||||||
<%= f.check_box :value, class: 'toggle', checked: @template.template_sharings.exists?(account_id: true_user.account.testing_accounts) %>
|
<%= f.check_box :value, class: 'toggle', checked: @template.template_sharings.exists?(account_id: (can?(:manage, :tenants_test) ? current_account : true_user.account).testing_accounts) %>
|
||||||
</submit-form>
|
</submit-form>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -127,4 +127,4 @@
|
|||||||
<%= render 'shared/pagination', pagy: @related_submissions_pagy, items_name: 'submissions', next_page_path: submissions_path(q: params[:q]) %>
|
<%= render 'shared/pagination', pagy: @related_submissions_pagy, items_name: 'submissions', next_page_path: submissions_path(q: params[:q]) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= render 'shared/review_form' %>
|
<%= render 'shared/review_form' unless turbo_native_app? %>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
</submit-form>
|
</submit-form>
|
||||||
</form>
|
</form>
|
||||||
<% end %>
|
<% end %>
|
||||||
<a href="<%= edit_template_path(@template) %>" class="base-button" data-turbo="false" style="flex-shrink: 0; padding: 0px 24px;">
|
<a href="<%= edit_template_path(@template) %>" class="base-button" data-turbo-prefetch="false" style="flex-shrink: 0; padding: 0px 24px;">
|
||||||
<%= t('exit_preview') %>
|
<%= t('exit_preview') %>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -194,7 +194,7 @@
|
|||||||
<%= t('share_template_with_test_mode') %>
|
<%= t('share_template_with_test_mode') %>
|
||||||
</span>
|
</span>
|
||||||
<submit-form data-on="change" class="flex">
|
<submit-form data-on="change" class="flex">
|
||||||
<%= f.check_box :value, class: 'toggle', checked: @template.template_sharings.exists?(account_id: true_user.account.testing_accounts) %>
|
<%= f.check_box :value, class: 'toggle', checked: @template.template_sharings.exists?(account_id: (can?(:manage, :tenants_test) ? current_account : true_user.account).testing_accounts) %>
|
||||||
</submit-form>
|
</submit-form>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
"note": "Permitted parameters are necessary for creating submitters via API"
|
"note": "Permitted parameters are necessary for creating submitters via API"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fingerprint": "645f2f352a813105b8b2eeadc68101d2e7ea04b8e839aa1336616be56f1108ca",
|
"fingerprint": "58619647d807d5bedb9499d6b1432f7f9cee385167aa97b0cb5e1b31a3db3f40",
|
||||||
"note": "Safe SQL"
|
"note": "Safe SQL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fingerprint": "5bcf4e6426d17b0efd98e47fde7c012f09500b9595af9cff226cf09341c023be",
|
"fingerprint": "8538c7cca701df19fdc3c1c6c5b7b0a1a3acacc19c840d63147553014d60ffa0",
|
||||||
"note": "Safe SQL"
|
"note": "Safe SQL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ autoload :OnnxRuntime, 'onnxruntime'
|
|||||||
autoload :RQRCode, 'rqrcode'
|
autoload :RQRCode, 'rqrcode'
|
||||||
autoload :ArabicLetterConnector, 'arabic-letter-connector/logic'
|
autoload :ArabicLetterConnector, 'arabic-letter-connector/logic'
|
||||||
autoload :JWT, 'jwt'
|
autoload :JWT, 'jwt'
|
||||||
|
autoload :WordCraft, 'wordcraft'
|
||||||
+33
-12
@@ -704,6 +704,7 @@ en: &en
|
|||||||
developer_sandbox: Developer Sandbox.
|
developer_sandbox: Developer Sandbox.
|
||||||
upgrade_to_start_using_in_production_html: '<a href="%{url}" target="_blank" class="link font-semibold">Upgrade</a> to start using in Production.'
|
upgrade_to_start_using_in_production_html: '<a href="%{url}" target="_blank" class="link font-semibold">Upgrade</a> to start using in Production.'
|
||||||
download_combined_pdf: Download combined PDF
|
download_combined_pdf: Download combined PDF
|
||||||
|
download_event_log: Download Event Log
|
||||||
audit_log: Audit Log
|
audit_log: Audit Log
|
||||||
signers: Signers
|
signers: Signers
|
||||||
not_invited_yet: Not invited yet
|
not_invited_yet: Not invited yet
|
||||||
@@ -715,7 +716,7 @@ en: &en
|
|||||||
declined_on_time: 'Declined on %{time}'
|
declined_on_time: 'Declined on %{time}'
|
||||||
expire_on_time: 'Expire on %{time}'
|
expire_on_time: 'Expire on %{time}'
|
||||||
sign_in_person: Sign In-person
|
sign_in_person: Sign In-person
|
||||||
create_a_new_template_document_form_or_submit_the_existing_one_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Create a new template</a> document form or <a href="%{start_form_link}" target="_blank" class="inline underline font-medium">submit the existing one</a>
|
create_a_new_template_document_form_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Create a new template</a> document form
|
||||||
send_email_copy_with_completed_documents_to_a_specified_bcc_address: Send email copy with completed documents to a specified BCC address.
|
send_email_copy_with_completed_documents_to_a_specified_bcc_address: Send email copy with completed documents to a specified BCC address.
|
||||||
re_send_email: Re-send Email
|
re_send_email: Re-send Email
|
||||||
re_send_emails: Re-send Emails
|
re_send_emails: Re-send Emails
|
||||||
@@ -1027,6 +1028,8 @@ en: &en
|
|||||||
failed_to_scan_the_document: Failed to scan the document
|
failed_to_scan_the_document: Failed to scan the document
|
||||||
document_scanning_is_not_available_on_this_device: Document scanning is not available on this device
|
document_scanning_is_not_available_on_this_device: Document scanning is not available on this device
|
||||||
click_to_upload: Click to upload
|
click_to_upload: Click to upload
|
||||||
|
cc_view_only: CC View-only
|
||||||
|
bcc_completed: BCC Completed
|
||||||
devise:
|
devise:
|
||||||
confirmations:
|
confirmations:
|
||||||
confirmed: Your email address has been successfully confirmed.
|
confirmed: Your email address has been successfully confirmed.
|
||||||
@@ -1862,6 +1865,7 @@ es: &es
|
|||||||
developer_sandbox: Sandbox para desarrolladores.
|
developer_sandbox: Sandbox para desarrolladores.
|
||||||
upgrade_to_start_using_in_production_html: '<a href="%{url}" target="_blank" class="link font-semibold">Actualiza</a> para empezar a usar en Producción.'
|
upgrade_to_start_using_in_production_html: '<a href="%{url}" target="_blank" class="link font-semibold">Actualiza</a> para empezar a usar en Producción.'
|
||||||
download_combined_pdf: Descargar PDF combinado
|
download_combined_pdf: Descargar PDF combinado
|
||||||
|
download_event_log: Descargar registro de eventos
|
||||||
audit_log: Registro de Auditoría
|
audit_log: Registro de Auditoría
|
||||||
signers: Firmantes
|
signers: Firmantes
|
||||||
not_invited_yet: Aún no invitado
|
not_invited_yet: Aún no invitado
|
||||||
@@ -1873,7 +1877,7 @@ es: &es
|
|||||||
declined_on_time: 'Rechazado el %{time}'
|
declined_on_time: 'Rechazado el %{time}'
|
||||||
expire_on_time: 'Expira el %{time}'
|
expire_on_time: 'Expira el %{time}'
|
||||||
sign_in_person: Firma en persona
|
sign_in_person: Firma en persona
|
||||||
create_a_new_template_document_form_or_submit_the_existing_one_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Crear una nueva plantilla</a> de documento o <a href="%{start_form_link}" target="_blank" class="inline underline font-medium">enviar el existente</a>
|
create_a_new_template_document_form_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Crear una nueva plantilla</a> de documento
|
||||||
send_email_copy_with_completed_documents_to_a_specified_bcc_address: Enviar una copia del correo electrónico con los documentos completados a una dirección BCC especificada.
|
send_email_copy_with_completed_documents_to_a_specified_bcc_address: Enviar una copia del correo electrónico con los documentos completados a una dirección BCC especificada.
|
||||||
re_send_email: Reenviar email
|
re_send_email: Reenviar email
|
||||||
re_send_emails: Reenviar emails
|
re_send_emails: Reenviar emails
|
||||||
@@ -2174,6 +2178,8 @@ es: &es
|
|||||||
failed_to_save_the_scan: No se pudo guardar el escaneo
|
failed_to_save_the_scan: No se pudo guardar el escaneo
|
||||||
failed_to_scan_the_document: No se pudo escanear el documento
|
failed_to_scan_the_document: No se pudo escanear el documento
|
||||||
document_scanning_is_not_available_on_this_device: El escaneo de documentos no está disponible en este dispositivo
|
document_scanning_is_not_available_on_this_device: El escaneo de documentos no está disponible en este dispositivo
|
||||||
|
cc_view_only: CC Solo lectura
|
||||||
|
bcc_completed: CCO Completado
|
||||||
devise:
|
devise:
|
||||||
confirmations:
|
confirmations:
|
||||||
confirmed: Tu dirección de correo electrónico ha sido confirmada correctamente.
|
confirmed: Tu dirección de correo electrónico ha sido confirmada correctamente.
|
||||||
@@ -3023,6 +3029,7 @@ it: &it
|
|||||||
developer_sandbox: Sandbox per sviluppatori
|
developer_sandbox: Sandbox per sviluppatori
|
||||||
upgrade_to_start_using_in_production_html: '<a href="%{url}" target="_blank" class="link font-semibold">Aggiorna</a> per iniziare a usare in produzione.'
|
upgrade_to_start_using_in_production_html: '<a href="%{url}" target="_blank" class="link font-semibold">Aggiorna</a> per iniziare a usare in produzione.'
|
||||||
download_combined_pdf: Scarica PDF combinato
|
download_combined_pdf: Scarica PDF combinato
|
||||||
|
download_event_log: Scarica registro degli eventi
|
||||||
audit_log: Registro di audit
|
audit_log: Registro di audit
|
||||||
signers: Firmatari
|
signers: Firmatari
|
||||||
not_invited_yet: Non ancora invitato
|
not_invited_yet: Non ancora invitato
|
||||||
@@ -3034,7 +3041,7 @@ it: &it
|
|||||||
declined_on_time: 'Rifiutato il %{time}'
|
declined_on_time: 'Rifiutato il %{time}'
|
||||||
expire_on_time: 'Scade il %{time}'
|
expire_on_time: 'Scade il %{time}'
|
||||||
sign_in_person: Firma di persona
|
sign_in_person: Firma di persona
|
||||||
create_a_new_template_document_form_or_submit_the_existing_one_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Crea un nuovo modello</a> di documento o <a href="%{start_form_link}" target="_blank" class="inline underline font-medium">invia quello esistente</a>
|
create_a_new_template_document_form_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Crea un nuovo modello</a> di documento
|
||||||
send_email_copy_with_completed_documents_to_a_specified_bcc_address: Invia una copia dell'email con i documenti completati a un indirizzo BCC specificato.
|
send_email_copy_with_completed_documents_to_a_specified_bcc_address: Invia una copia dell'email con i documenti completati a un indirizzo BCC specificato.
|
||||||
re_send_email: Reinvia email
|
re_send_email: Reinvia email
|
||||||
re_send_emails: Reinvia le email
|
re_send_emails: Reinvia le email
|
||||||
@@ -3335,6 +3342,8 @@ it: &it
|
|||||||
failed_to_save_the_scan: Impossibile salvare la scansione
|
failed_to_save_the_scan: Impossibile salvare la scansione
|
||||||
failed_to_scan_the_document: Impossibile scansionare il documento
|
failed_to_scan_the_document: Impossibile scansionare il documento
|
||||||
document_scanning_is_not_available_on_this_device: La scansione dei documenti non è disponibile su questo dispositivo
|
document_scanning_is_not_available_on_this_device: La scansione dei documenti non è disponibile su questo dispositivo
|
||||||
|
cc_view_only: CC Sola lettura
|
||||||
|
bcc_completed: BCC Completato
|
||||||
devise:
|
devise:
|
||||||
confirmations:
|
confirmations:
|
||||||
confirmed: Il tuo indirizzo email è stato confermato con successo.
|
confirmed: Il tuo indirizzo email è stato confermato con successo.
|
||||||
@@ -4170,6 +4179,7 @@ fr: &fr
|
|||||||
developer_sandbox: Bac à sable développeur.
|
developer_sandbox: Bac à sable développeur.
|
||||||
upgrade_to_start_using_in_production_html: <a href="%{url}" target="_blank" class="link font-semibold">Mettez à niveau</a> pour commencer à utiliser en production.
|
upgrade_to_start_using_in_production_html: <a href="%{url}" target="_blank" class="link font-semibold">Mettez à niveau</a> pour commencer à utiliser en production.
|
||||||
download_combined_pdf: Télécharger le PDF combiné
|
download_combined_pdf: Télécharger le PDF combiné
|
||||||
|
download_event_log: Télécharger le journal des événements
|
||||||
audit_log: Journal d’audit
|
audit_log: Journal d’audit
|
||||||
signers: Signataires
|
signers: Signataires
|
||||||
not_invited_yet: Pas encore invité
|
not_invited_yet: Pas encore invité
|
||||||
@@ -4181,7 +4191,7 @@ fr: &fr
|
|||||||
declined_on_time: Refusé le %{time}
|
declined_on_time: Refusé le %{time}
|
||||||
expire_on_time: Expire le %{time}
|
expire_on_time: Expire le %{time}
|
||||||
sign_in_person: Signer en personne
|
sign_in_person: Signer en personne
|
||||||
create_a_new_template_document_form_or_submit_the_existing_one_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Créez un nouveau modèle</a> de document ou <a href="%{start_form_link}" target="_blank" class="inline underline font-medium">soumettez l’existant</a>
|
create_a_new_template_document_form_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Créez un nouveau modèle</a> de document
|
||||||
send_email_copy_with_completed_documents_to_a_specified_bcc_address: Envoyer une copie e‑mail avec les documents complétés à une adresse Cci spécifiée.
|
send_email_copy_with_completed_documents_to_a_specified_bcc_address: Envoyer une copie e‑mail avec les documents complétés à une adresse Cci spécifiée.
|
||||||
re_send_email: Renvoyer l’e‑mail
|
re_send_email: Renvoyer l’e‑mail
|
||||||
re_send_emails: Renvoyer e‑mails
|
re_send_emails: Renvoyer e‑mails
|
||||||
@@ -4479,6 +4489,8 @@ fr: &fr
|
|||||||
failed_to_save_the_scan: Impossible d’enregistrer la numérisation
|
failed_to_save_the_scan: Impossible d’enregistrer la numérisation
|
||||||
failed_to_scan_the_document: Impossible de numériser le document
|
failed_to_scan_the_document: Impossible de numériser le document
|
||||||
document_scanning_is_not_available_on_this_device: La numérisation de documents n’est pas disponible sur cet appareil
|
document_scanning_is_not_available_on_this_device: La numérisation de documents n’est pas disponible sur cet appareil
|
||||||
|
cc_view_only: Cc Lecture seule
|
||||||
|
bcc_completed: Cci Terminé
|
||||||
devise:
|
devise:
|
||||||
confirmations:
|
confirmations:
|
||||||
confirmed: Votre adresse e-mail a été confirmée avec succès.
|
confirmed: Votre adresse e-mail a été confirmée avec succès.
|
||||||
@@ -5327,6 +5339,7 @@ pt: &pt
|
|||||||
developer_sandbox: Sandbox para desenvolvedores
|
developer_sandbox: Sandbox para desenvolvedores
|
||||||
upgrade_to_start_using_in_production_html: '<a href="%{url}" target="_blank" class="link font-semibold">Atualizar</a> para começar a usar em produção.'
|
upgrade_to_start_using_in_production_html: '<a href="%{url}" target="_blank" class="link font-semibold">Atualizar</a> para começar a usar em produção.'
|
||||||
download_combined_pdf: Baixar PDF combinado
|
download_combined_pdf: Baixar PDF combinado
|
||||||
|
download_event_log: Baixar log de eventos
|
||||||
audit_log: Log de auditoria
|
audit_log: Log de auditoria
|
||||||
signers: Signatários
|
signers: Signatários
|
||||||
not_invited_yet: Ainda não convidado
|
not_invited_yet: Ainda não convidado
|
||||||
@@ -5338,7 +5351,7 @@ pt: &pt
|
|||||||
declined_on_time: 'Recusado em %{time}'
|
declined_on_time: 'Recusado em %{time}'
|
||||||
expire_on_time: 'Expira em %{time}'
|
expire_on_time: 'Expira em %{time}'
|
||||||
sign_in_person: Assinar pessoalmente
|
sign_in_person: Assinar pessoalmente
|
||||||
create_a_new_template_document_form_or_submit_the_existing_one_html: '<a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Criar um novo modelo</a> de documento ou <a href="%{start_form_link}" target="_blank" class="inline underline font-medium">submeter o existente</a>'
|
create_a_new_template_document_form_html: '<a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Criar um novo modelo</a> de documento'
|
||||||
send_email_copy_with_completed_documents_to_a_specified_bcc_address: Envie uma cópia do e-mail com documentos concluídos para um endereço BCC especificado.
|
send_email_copy_with_completed_documents_to_a_specified_bcc_address: Envie uma cópia do e-mail com documentos concluídos para um endereço BCC especificado.
|
||||||
re_send_email: Reenviar e-mail
|
re_send_email: Reenviar e-mail
|
||||||
re_send_emails: Reenviar e-mails
|
re_send_emails: Reenviar e-mails
|
||||||
@@ -5639,6 +5652,8 @@ pt: &pt
|
|||||||
failed_to_save_the_scan: Não foi possível salvar a digitalização
|
failed_to_save_the_scan: Não foi possível salvar a digitalização
|
||||||
failed_to_scan_the_document: Não foi possível digitalizar o documento
|
failed_to_scan_the_document: Não foi possível digitalizar o documento
|
||||||
document_scanning_is_not_available_on_this_device: A digitalização de documentos não está disponível neste dispositivo
|
document_scanning_is_not_available_on_this_device: A digitalização de documentos não está disponível neste dispositivo
|
||||||
|
cc_view_only: CC Somente leitura
|
||||||
|
bcc_completed: BCC Concluído
|
||||||
devise:
|
devise:
|
||||||
confirmations:
|
confirmations:
|
||||||
confirmed: Seu endereço de e-mail foi confirmado com sucesso.
|
confirmed: Seu endereço de e-mail foi confirmado com sucesso.
|
||||||
@@ -6487,6 +6502,7 @@ de: &de
|
|||||||
developer_sandbox: Entwickler-Sandbox.
|
developer_sandbox: Entwickler-Sandbox.
|
||||||
upgrade_to_start_using_in_production_html: '<a href="%{url}" target="_blank" class="link font-semibold">Upgrade</a> durchführen, um die Produktionsnutzung zu starten.'
|
upgrade_to_start_using_in_production_html: '<a href="%{url}" target="_blank" class="link font-semibold">Upgrade</a> durchführen, um die Produktionsnutzung zu starten.'
|
||||||
download_combined_pdf: Kombiniertes PDF herunterladen
|
download_combined_pdf: Kombiniertes PDF herunterladen
|
||||||
|
download_event_log: Ereignisprotokoll herunterladen
|
||||||
audit_log: Prüfprotokoll
|
audit_log: Prüfprotokoll
|
||||||
signers: Unterzeichner
|
signers: Unterzeichner
|
||||||
not_invited_yet: Noch nicht eingeladen
|
not_invited_yet: Noch nicht eingeladen
|
||||||
@@ -6498,7 +6514,7 @@ de: &de
|
|||||||
declined_on_time: 'Abgelehnt am %{time}'
|
declined_on_time: 'Abgelehnt am %{time}'
|
||||||
expire_on_time: 'Läuft ab am %{time}'
|
expire_on_time: 'Läuft ab am %{time}'
|
||||||
sign_in_person: Vor Ort unterschreiben
|
sign_in_person: Vor Ort unterschreiben
|
||||||
create_a_new_template_document_form_or_submit_the_existing_one_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Neue Vorlage erstellen</a> oder <a href="%{start_form_link}" target="_blank" class="inline underline font-medium">bestehende einreichen</a>
|
create_a_new_template_document_form_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Neue Vorlage erstellen</a>
|
||||||
send_email_copy_with_completed_documents_to_a_specified_bcc_address: Senden Sie eine E-Mail-Kopie mit abgeschlossenen Dokumenten an eine angegebene BCC-Adresse.
|
send_email_copy_with_completed_documents_to_a_specified_bcc_address: Senden Sie eine E-Mail-Kopie mit abgeschlossenen Dokumenten an eine angegebene BCC-Adresse.
|
||||||
re_send_email: E-Mail erneut senden
|
re_send_email: E-Mail erneut senden
|
||||||
re_send_emails: E-Mails senden
|
re_send_emails: E-Mails senden
|
||||||
@@ -6799,6 +6815,8 @@ de: &de
|
|||||||
failed_to_save_the_scan: Scan konnte nicht gespeichert werden
|
failed_to_save_the_scan: Scan konnte nicht gespeichert werden
|
||||||
failed_to_scan_the_document: Dokument konnte nicht gescannt werden
|
failed_to_scan_the_document: Dokument konnte nicht gescannt werden
|
||||||
document_scanning_is_not_available_on_this_device: Das Scannen von Dokumenten ist auf diesem Gerät nicht verfügbar
|
document_scanning_is_not_available_on_this_device: Das Scannen von Dokumenten ist auf diesem Gerät nicht verfügbar
|
||||||
|
cc_view_only: CC Nur Ansicht
|
||||||
|
bcc_completed: BCC Abgeschlossen
|
||||||
devise:
|
devise:
|
||||||
confirmations:
|
confirmations:
|
||||||
confirmed: Ihre E-Mail-Adresse wurde erfolgreich bestätigt.
|
confirmed: Ihre E-Mail-Adresse wurde erfolgreich bestätigt.
|
||||||
@@ -6981,14 +6999,14 @@ pl:
|
|||||||
decline_reason: Powód odrzucenia
|
decline_reason: Powód odrzucenia
|
||||||
provide_a_reason: Podaj powód
|
provide_a_reason: Podaj powód
|
||||||
notify_the_sender_with_the_reason_you_declined: Powiadom nadawcę o powodzie, dla którego odrzuciłeś
|
notify_the_sender_with_the_reason_you_declined: Powiadom nadawcę o powodzie, dla którego odrzuciłeś
|
||||||
form_has_been_declined_on_html: 'Formularz został odrzucony o <span class="font-semibold">%{time}</span>'
|
form_has_been_declined_on_html: 'Formularz został odrzucony <span class="font-semibold">%{time}</span>'
|
||||||
document_has_been_delegated_on_html: 'Dokument został oddelegowany o <span class="font-semibold">%{time}</span>'
|
document_has_been_delegated_on_html: 'Dokument został oddelegowany <span class="font-semibold">%{time}</span>'
|
||||||
name_declined_by_submitter: '"%{name}" odrzucono przez %{submitter}'
|
name_declined_by_submitter: '"%{name}" odrzucono przez %{submitter}'
|
||||||
name_declined_by_submitter_with_the_following_reason: '"%{name}" został odrzucony przez %{submitter} z następującym powodem:'
|
name_declined_by_submitter_with_the_following_reason: '"%{name}" został odrzucony przez %{submitter} z następującym powodem:'
|
||||||
email: E-mail
|
email: E-mail
|
||||||
verification_code_code: 'Kod weryfikacyjny: %{code}'
|
verification_code_code: 'Kod weryfikacyjny: %{code}'
|
||||||
digitally_signed_by: Podpis cyfrowy przez
|
digitally_signed_by: Podpis cyfrowy przez
|
||||||
form_expired_at_html: 'Formularz wygasł o <span class="font-semibold">%{time}</span>'
|
form_expired_at_html: 'Formularz wygasł <span class="font-semibold">%{time}</span>'
|
||||||
role: Rola
|
role: Rola
|
||||||
provide_your_email_to_start: Podaj swój e-mail, aby rozpocząć
|
provide_your_email_to_start: Podaj swój e-mail, aby rozpocząć
|
||||||
provide_your_email: Podaj swój e-mail
|
provide_your_email: Podaj swój e-mail
|
||||||
@@ -7123,8 +7141,8 @@ uk:
|
|||||||
decline_reason: Причина відхилення
|
decline_reason: Причина відхилення
|
||||||
provide_a_reason: Надайте причину
|
provide_a_reason: Надайте причину
|
||||||
notify_the_sender_with_the_reason_you_declined: Повідомте відправника про причину свого відхилення
|
notify_the_sender_with_the_reason_you_declined: Повідомте відправника про причину свого відхилення
|
||||||
form_has_been_declined_on_html: 'Форму було відхилено о <span class="font-semibold">%{time}</span>'
|
form_has_been_declined_on_html: 'Форму було відхилено <span class="font-semibold">%{time}</span>'
|
||||||
document_has_been_delegated_on_html: 'Документ було делеговано о <span class="font-semibold">%{time}</span>'
|
document_has_been_delegated_on_html: 'Документ було делеговано <span class="font-semibold">%{time}</span>'
|
||||||
name_declined_by_submitter: '"%{name}" відхилено %{submitter}'
|
name_declined_by_submitter: '"%{name}" відхилено %{submitter}'
|
||||||
name_declined_by_submitter_with_the_following_reason: '"%{name}" було відхилено %{submitter} з такою причиною:'
|
name_declined_by_submitter_with_the_following_reason: '"%{name}" було відхилено %{submitter} з такою причиною:'
|
||||||
email: E-mail
|
email: E-mail
|
||||||
@@ -8175,6 +8193,7 @@ nl: &nl
|
|||||||
developer_sandbox: Ontwikkelaars-sandbox.
|
developer_sandbox: Ontwikkelaars-sandbox.
|
||||||
upgrade_to_start_using_in_production_html: <a href="%{url}" target="_blank" class="link font-semibold">Upgrade</a> om in productie te gaan gebruiken.
|
upgrade_to_start_using_in_production_html: <a href="%{url}" target="_blank" class="link font-semibold">Upgrade</a> om in productie te gaan gebruiken.
|
||||||
download_combined_pdf: Gecombineerde PDF downloaden
|
download_combined_pdf: Gecombineerde PDF downloaden
|
||||||
|
download_event_log: Gebeurtenislogboek downloaden
|
||||||
audit_log: Auditlogboek
|
audit_log: Auditlogboek
|
||||||
signers: Ondertekenaars
|
signers: Ondertekenaars
|
||||||
not_invited_yet: Nog niet uitgenodigd
|
not_invited_yet: Nog niet uitgenodigd
|
||||||
@@ -8186,7 +8205,7 @@ nl: &nl
|
|||||||
declined_on_time: Geweigerd op %{time}
|
declined_on_time: Geweigerd op %{time}
|
||||||
expire_on_time: Verloopt op %{time}
|
expire_on_time: Verloopt op %{time}
|
||||||
sign_in_person: In persoon ondertekenen
|
sign_in_person: In persoon ondertekenen
|
||||||
create_a_new_template_document_form_or_submit_the_existing_one_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Maak een nieuw sjabloon</a> voor een documentformulier of <a href="%{start_form_link}" target="_blank" class="inline underline font-medium">dien het bestaande in</a>
|
create_a_new_template_document_form_html: <a href="%{new_template_link}" data-turbo-frame="modal" class="inline underline font-medium">Maak een nieuw sjabloon</a> voor een documentformulier
|
||||||
send_email_copy_with_completed_documents_to_a_specified_bcc_address: Stuur een e-mailkopie met voltooide documenten naar een opgegeven BCC-adres.
|
send_email_copy_with_completed_documents_to_a_specified_bcc_address: Stuur een e-mailkopie met voltooide documenten naar een opgegeven BCC-adres.
|
||||||
re_send_email: E-mail opnieuw verzenden
|
re_send_email: E-mail opnieuw verzenden
|
||||||
re_send_emails: E-mails verzenden
|
re_send_emails: E-mails verzenden
|
||||||
@@ -8483,6 +8502,8 @@ nl: &nl
|
|||||||
failed_to_save_the_scan: Scan kon niet worden opgeslagen
|
failed_to_save_the_scan: Scan kon niet worden opgeslagen
|
||||||
failed_to_scan_the_document: Document kon niet worden gescand
|
failed_to_scan_the_document: Document kon niet worden gescand
|
||||||
document_scanning_is_not_available_on_this_device: Documenten scannen is niet beschikbaar op dit apparaat
|
document_scanning_is_not_available_on_this_device: Documenten scannen is niet beschikbaar op dit apparaat
|
||||||
|
cc_view_only: CC Alleen bekijken
|
||||||
|
bcc_completed: BCC Voltooid
|
||||||
devise:
|
devise:
|
||||||
confirmations:
|
confirmations:
|
||||||
confirmed: Uw e-mailadres is succesvol bevestigd.
|
confirmed: Uw e-mailadres is succesvol bevestigd.
|
||||||
|
|||||||
+1
-9
@@ -25,8 +25,6 @@ Rails.application.routes.draw do
|
|||||||
|
|
||||||
namespace :api, defaults: { format: :json } do
|
namespace :api, defaults: { format: :json } do
|
||||||
resource :user, only: %i[show]
|
resource :user, only: %i[show]
|
||||||
resources :attachments, only: %i[create]
|
|
||||||
resources :submitter_email_clicks, only: %i[create]
|
|
||||||
resources :submitter_form_views, only: %i[create]
|
resources :submitter_form_views, only: %i[create]
|
||||||
resources :submitters, only: %i[index show update]
|
resources :submitters, only: %i[index show update]
|
||||||
resources :submissions, only: %i[index show create update destroy] do
|
resources :submissions, only: %i[index show create update destroy] do
|
||||||
@@ -128,13 +126,7 @@ Rails.application.routes.draw do
|
|||||||
controller: 'api/active_storage_blobs_proxy'
|
controller: 'api/active_storage_blobs_proxy'
|
||||||
|
|
||||||
if Docuseal.multitenant?
|
if Docuseal.multitenant?
|
||||||
resource :blobs_proxy_legacy, only: %i[show],
|
get '/disk/:encoded_key/*filename' => 'active_storage/disk#show', as: :rails_disk_service if Rails.env.local?
|
||||||
path: '/blobs/proxy/:signed_id/*filename',
|
|
||||||
controller: 'api/active_storage_blobs_proxy_legacy',
|
|
||||||
as: :rails_blob
|
|
||||||
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)
|
ActiveSupport.run_load_hooks(:multitenant_routes, self)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddTemplateIdToSubmitters < ActiveRecord::Migration[8.1]
|
||||||
|
def change
|
||||||
|
add_column :submitters, :template_id, :bigint, if_not_exists: true
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class PopulateSubmitterTemplateId < ActiveRecord::Migration[8.1]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
class MigrationSubmitter < ApplicationRecord
|
||||||
|
self.table_name = 'submitters'
|
||||||
|
end
|
||||||
|
|
||||||
|
class MigrationSubmission < ApplicationRecord
|
||||||
|
self.table_name = 'submissions'
|
||||||
|
end
|
||||||
|
|
||||||
|
def up
|
||||||
|
return if ENV['MULTITENANT'] == 'true'
|
||||||
|
|
||||||
|
max_id = MigrationSubmitter.maximum(:id)
|
||||||
|
|
||||||
|
return unless max_id
|
||||||
|
|
||||||
|
submission_template_id =
|
||||||
|
Arel::Nodes::Grouping.new(
|
||||||
|
MigrationSubmission.arel_table.project(MigrationSubmission.arel_table[:template_id])
|
||||||
|
.where(MigrationSubmission.arel_table[:id].eq(MigrationSubmitter.arel_table[:submission_id]))
|
||||||
|
.ast
|
||||||
|
)
|
||||||
|
|
||||||
|
(1..max_id).step(10_000) do |start_id|
|
||||||
|
MigrationSubmitter.where(template_id: nil, id: start_id...(start_id + 10_000))
|
||||||
|
.update_all(template_id: submission_template_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddSubmittersTemplateIdIndexes < ActiveRecord::Migration[8.1]
|
||||||
|
def up
|
||||||
|
add_index :submitters, %i[email template_id], where: 'email IS NOT NULL', if_not_exists: true
|
||||||
|
add_index :submitters, %i[template_id id], if_not_exists: true
|
||||||
|
remove_index :submitters, :email, if_exists: true
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
add_index :submitters, :email, if_not_exists: true
|
||||||
|
remove_index :submitters, %i[template_id id], if_exists: true
|
||||||
|
remove_index :submitters, %i[email template_id], if_exists: true
|
||||||
|
end
|
||||||
|
end
|
||||||
+4
-2
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[8.1].define(version: 2026_09_30_061927) do
|
ActiveRecord::Schema[8.1].define(version: 2026_10_25_120002) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "btree_gin"
|
enable_extension "btree_gin"
|
||||||
enable_extension "pg_catalog.plpgsql"
|
enable_extension "pg_catalog.plpgsql"
|
||||||
@@ -424,6 +424,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_09_30_061927) do
|
|||||||
t.datetime "sent_at"
|
t.datetime "sent_at"
|
||||||
t.string "slug", null: false
|
t.string "slug", null: false
|
||||||
t.bigint "submission_id", null: false
|
t.bigint "submission_id", null: false
|
||||||
|
t.bigint "template_id"
|
||||||
t.string "timezone"
|
t.string "timezone"
|
||||||
t.string "ua"
|
t.string "ua"
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
@@ -431,10 +432,11 @@ ActiveRecord::Schema[8.1].define(version: 2026_09_30_061927) do
|
|||||||
t.text "values", null: false
|
t.text "values", null: false
|
||||||
t.index ["account_id", "completed_at"], name: "index_submitters_on_account_id_and_completed_at", where: "(completed_at IS NOT NULL)"
|
t.index ["account_id", "completed_at"], name: "index_submitters_on_account_id_and_completed_at", where: "(completed_at IS NOT NULL)"
|
||||||
t.index ["account_id", "id"], name: "index_submitters_on_account_id_and_id"
|
t.index ["account_id", "id"], name: "index_submitters_on_account_id_and_id"
|
||||||
t.index ["email"], name: "index_submitters_on_email"
|
t.index ["email", "template_id"], name: "index_submitters_on_email_and_template_id", where: "(email IS NOT NULL)"
|
||||||
t.index ["external_id"], name: "index_submitters_on_external_id"
|
t.index ["external_id"], name: "index_submitters_on_external_id"
|
||||||
t.index ["slug"], name: "index_submitters_on_slug", unique: true
|
t.index ["slug"], name: "index_submitters_on_slug", unique: true
|
||||||
t.index ["submission_id"], name: "index_submitters_on_submission_id"
|
t.index ["submission_id"], name: "index_submitters_on_submission_id"
|
||||||
|
t.index ["template_id", "id"], name: "index_submitters_on_template_id_and_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "template_accesses", force: :cascade do |t|
|
create_table "template_accesses", force: :cascade do |t|
|
||||||
|
|||||||
@@ -5,34 +5,6 @@ module Accounts
|
|||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
def create_duplicate(account)
|
|
||||||
new_account = account.dup
|
|
||||||
|
|
||||||
new_user = account.users.first.dup
|
|
||||||
|
|
||||||
new_user.uuid = SecureRandom.uuid
|
|
||||||
new_user.account = new_account
|
|
||||||
new_user.encrypted_password = SecureRandom.hex
|
|
||||||
new_user.email = "#{SecureRandom.hex}@docuseal.com"
|
|
||||||
|
|
||||||
account.templates.each do |template|
|
|
||||||
new_template = template.dup
|
|
||||||
|
|
||||||
new_template.account = new_account
|
|
||||||
new_template.slug = SecureRandom.base58(14)
|
|
||||||
|
|
||||||
new_template.archived_at = nil
|
|
||||||
new_template.save!
|
|
||||||
|
|
||||||
Templates::CloneAttachments.call(template: new_template, original_template: template)
|
|
||||||
end
|
|
||||||
|
|
||||||
new_user.save!(validate: false)
|
|
||||||
new_account.templates.update_all(folder_id: new_account.default_template_folder.id)
|
|
||||||
|
|
||||||
new_account
|
|
||||||
end
|
|
||||||
|
|
||||||
def users_count(account)
|
def users_count(account)
|
||||||
rel = User.where(account_id: account.id).or(
|
rel = User.where(account_id: account.id).or(
|
||||||
User.where(account_id: account.account_linked_accounts
|
User.where(account_id: account.account_linked_accounts
|
||||||
|
|||||||
@@ -7,10 +7,7 @@ class ApiPathConsiderJsonMiddleware
|
|||||||
|
|
||||||
def call(env)
|
def call(env)
|
||||||
if env['PATH_INFO'].starts_with?('/api') &&
|
if env['PATH_INFO'].starts_with?('/api') &&
|
||||||
(!env['PATH_INFO'].ends_with?('/documents') || env['REQUEST_METHOD'] != 'POST') &&
|
!env['PATH_INFO'].ends_with?('/submitter_sms_clicks')
|
||||||
!env['PATH_INFO'].ends_with?('/attachments') &&
|
|
||||||
!env['PATH_INFO'].ends_with?('/submitter_sms_clicks') &&
|
|
||||||
!env['PATH_INFO'].ends_with?('/submitter_email_clicks')
|
|
||||||
env['CONTENT_TYPE'] = 'application/json'
|
env['CONTENT_TYPE'] = 'application/json'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ module ReplaceEmailVariables
|
|||||||
DOCUMENTS_LINKS = /\{+documents\.links\}+/i
|
DOCUMENTS_LINKS = /\{+documents\.links\}+/i
|
||||||
DOCUMENTS_LINK = /\{+documents\.link\}+/i
|
DOCUMENTS_LINK = /\{+documents\.link\}+/i
|
||||||
|
|
||||||
|
ESCAPED_BRACES = "\u{E000}\u{E001}"
|
||||||
|
|
||||||
EMAIL_HOST = ENV.fetch('EMAIL_HOST', nil)
|
EMAIL_HOST = ENV.fetch('EMAIL_HOST', nil)
|
||||||
|
|
||||||
module_function
|
module_function
|
||||||
@@ -86,7 +88,11 @@ module ReplaceEmailVariables
|
|||||||
build_submitters_n_field(submitter.submission, index, :values, match[:field_name].to_s.strip)
|
build_submitters_n_field(submitter.submission, index, :values, match[:field_name].to_s.strip)
|
||||||
end
|
end
|
||||||
|
|
||||||
replace(text, SENDER_EMAIL, html_escape:) { submitter.submission.created_by_user&.email.to_s.sub(/\+\w+@/, '@') }
|
text = replace(text, SENDER_EMAIL, html_escape:) do
|
||||||
|
submitter.submission.created_by_user&.email.to_s.sub(/\+\w+@/, '@')
|
||||||
|
end
|
||||||
|
|
||||||
|
text.tr(ESCAPED_BRACES, '{}')
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics
|
# rubocop:enable Metrics
|
||||||
|
|
||||||
@@ -124,6 +130,8 @@ module ReplaceEmailVariables
|
|||||||
|
|
||||||
ActiveStorage::Blob.proxy_url(attachment.blob, expires_at:)
|
ActiveStorage::Blob.proxy_url(attachment.blob, expires_at:)
|
||||||
end
|
end
|
||||||
|
elsif (mask = field.dig('preferences', 'mask').presence)
|
||||||
|
TextUtils.mask_value(value[field['uuid']].to_s, mask)
|
||||||
else
|
else
|
||||||
value[field&.dig('uuid')]
|
value[field&.dig('uuid')]
|
||||||
end
|
end
|
||||||
@@ -134,10 +142,12 @@ module ReplaceEmailVariables
|
|||||||
|
|
||||||
def replace(text, var, html_escape: false)
|
def replace(text, var, html_escape: false)
|
||||||
text.gsub(var) do
|
text.gsub(var) do
|
||||||
|
value = yield(Regexp.last_match).to_s.tr('{}', ESCAPED_BRACES)
|
||||||
|
|
||||||
if html_escape
|
if html_escape
|
||||||
ERB::Util.html_escape(yield(Regexp.last_match))
|
ERB::Util.html_escape(value)
|
||||||
else
|
else
|
||||||
yield(Regexp.last_match)
|
value
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+20
-7
@@ -3,6 +3,8 @@
|
|||||||
module SearchEntries
|
module SearchEntries
|
||||||
MAX_VALUE_LENGTH = 100
|
MAX_VALUE_LENGTH = 100
|
||||||
|
|
||||||
|
MUTEX = Mutex.new
|
||||||
|
|
||||||
UUID_REGEXP = /\A[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i
|
UUID_REGEXP = /\A[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i
|
||||||
|
|
||||||
module_function
|
module_function
|
||||||
@@ -70,10 +72,10 @@ module SearchEntries
|
|||||||
|
|
||||||
arel
|
arel
|
||||||
else
|
else
|
||||||
"tsvector @@ (quote_literal(coalesce((ts_lexize('english_stem', :keyword))[1], :keyword)) || ':*')::tsquery"
|
"tsvector @@ (quote_literal(coalesce((ts_lexize(:dictionary, :keyword))[1], :keyword)) || ':*')::tsquery"
|
||||||
end
|
end
|
||||||
|
|
||||||
[sql, { keyword: }]
|
[sql, { keyword:, dictionary: default_dictionary }]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -83,7 +85,7 @@ module SearchEntries
|
|||||||
Arel.sql("ngram @@ (quote_literal(:term#{terms.size - 1}) || ':' || :weight)::tsquery")
|
Arel.sql("ngram @@ (quote_literal(:term#{terms.size - 1}) || ':' || :weight)::tsquery")
|
||||||
else
|
else
|
||||||
Arel.sql(<<~SQL.squish)
|
Arel.sql(<<~SQL.squish)
|
||||||
(quote_literal(coalesce((ts_lexize('english_stem', :term#{terms.size - 1}))[1], :term#{terms.size - 1})) || ':*' || :weight)::tsquery
|
(quote_literal(coalesce((ts_lexize(:dictionary, :term#{terms.size - 1}))[1], :term#{terms.size - 1})) || ':*' || :weight)::tsquery
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -91,7 +93,7 @@ module SearchEntries
|
|||||||
index = terms.index(term)
|
index = terms.index(term)
|
||||||
|
|
||||||
arel = Arel.sql(<<~SQL.squish)
|
arel = Arel.sql(<<~SQL.squish)
|
||||||
(quote_literal(coalesce((ts_lexize('english_stem', :term#{index}))[1], :term#{index})) || ':' || :weight)::tsquery
|
(quote_literal(coalesce((ts_lexize(:dictionary, :term#{index}))[1], :term#{index})) || ':' || :weight)::tsquery
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
acc ? Arel::Nodes::InfixOperation.new('&&', arel, acc) : arel
|
acc ? Arel::Nodes::InfixOperation.new('&&', arel, acc) : arel
|
||||||
@@ -109,7 +111,8 @@ module SearchEntries
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
[query.to_sql, terms.index_by.with_index { |_, index| :"term#{index}" }.merge(weight:)]
|
[query.to_sql,
|
||||||
|
terms.index_by.with_index { |_, index| :"term#{index}" }.merge(weight:, dictionary: default_dictionary)]
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_weights_wildcard_tsquery(keyword, weight)
|
def build_weights_wildcard_tsquery(keyword, weight)
|
||||||
@@ -122,11 +125,11 @@ module SearchEntries
|
|||||||
SQL
|
SQL
|
||||||
else
|
else
|
||||||
<<~SQL.squish
|
<<~SQL.squish
|
||||||
tsvector @@ (quote_literal(coalesce((ts_lexize('english_stem', :keyword))[1], :keyword)) || ':*' || :weight)::tsquery
|
tsvector @@ (quote_literal(coalesce((ts_lexize(:dictionary, :keyword))[1], :keyword)) || ':*' || :weight)::tsquery
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
|
|
||||||
[sql, { keyword:, weight: }]
|
[sql, { keyword:, weight:, dictionary: default_dictionary }]
|
||||||
end
|
end
|
||||||
|
|
||||||
def index_submitter(submitter)
|
def index_submitter(submitter)
|
||||||
@@ -256,4 +259,14 @@ module SearchEntries
|
|||||||
|
|
||||||
ngrams.uniq { |e| e.sub(/':[\d,]/, "':1") }.join(' ')
|
ngrams.uniq { |e| e.sub(/':[\d,]/, "':1") }.join(' ')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def default_dictionary
|
||||||
|
@default_dictionary || MUTEX.synchronize do
|
||||||
|
@default_dictionary ||= SearchEntry.connection.select_value(<<~SQL.squish)
|
||||||
|
SELECT mapdict::regdictionary::text FROM pg_ts_config_map
|
||||||
|
WHERE mapcfg = get_current_ts_config() AND maptokentype = 1
|
||||||
|
ORDER BY mapseqno LIMIT 1
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -90,6 +90,10 @@ module Submissions
|
|||||||
|
|
||||||
maybe_add_invite_submitters(submission, template, attrs[:submitters])
|
maybe_add_invite_submitters(submission, template, attrs[:submitters])
|
||||||
|
|
||||||
|
maybe_add_cc_submitters(submission, params[:cc_recipients],
|
||||||
|
submitters_order:, user:, params:,
|
||||||
|
preferences: preferences.merge(submission_preferences))
|
||||||
|
|
||||||
assign_submitters_is_viewer(submission)
|
assign_submitters_is_viewer(submission)
|
||||||
|
|
||||||
submission.template = nil unless with_template
|
submission.template = nil unless with_template
|
||||||
@@ -211,6 +215,25 @@ module Submissions
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def maybe_add_cc_submitters(submission, recipients, submitters_order:, user:, params:, preferences:)
|
||||||
|
order = submission.template_submitters.filter_map { |s| s['order'] }.max
|
||||||
|
|
||||||
|
Array.wrap(recipients).each do |recipient|
|
||||||
|
attrs = recipient.is_a?(String) ? { email: recipient } : recipient.slice(:name, :email, :phone)
|
||||||
|
|
||||||
|
next if attrs.compact_blank.blank?
|
||||||
|
|
||||||
|
uuid = SecureRandom.uuid
|
||||||
|
|
||||||
|
submission.template_submitters <<
|
||||||
|
{ 'name' => 'CC', 'uuid' => uuid, 'is_viewer' => true, 'order' => order }.compact
|
||||||
|
|
||||||
|
build_submitter(submission:, attrs:, uuid:, user:, params:,
|
||||||
|
is_order_sent: submitters_order == 'random',
|
||||||
|
preferences: preferences.except('email_message_uuid'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def submitter_message_preferences(uuid, params)
|
def submitter_message_preferences(uuid, params)
|
||||||
return {} if params[:request_email_per_submitter] != '1'
|
return {} if params[:request_email_per_submitter] != '1'
|
||||||
return {} if params[:is_custom_message] != '1'
|
return {} if params[:is_custom_message] != '1'
|
||||||
|
|||||||
@@ -267,7 +267,10 @@ module Submissions
|
|||||||
|
|
||||||
is_id_verified = submission_events.any?(&:complete_verification?)
|
is_id_verified = submission_events.any?(&:complete_verification?)
|
||||||
|
|
||||||
is_kba_passed = submission_events.any?(&:complete_kba?)
|
complete_kba_event = submission_events.find(&:complete_kba?) || SubmissionEvent.new
|
||||||
|
|
||||||
|
kba_answers = complete_kba_event.data['answers']
|
||||||
|
kba_score = " (#{kba_answers['correct']}/#{kba_answers['asked']})" if kba_answers
|
||||||
|
|
||||||
info_rows = [
|
info_rows = [
|
||||||
[
|
[
|
||||||
@@ -297,8 +300,8 @@ module Submissions
|
|||||||
is_id_verified && {
|
is_id_verified && {
|
||||||
text: "#{I18n.t('identity_verification')}: #{I18n.t('verified')}\n"
|
text: "#{I18n.t('identity_verification')}: #{I18n.t('verified')}\n"
|
||||||
},
|
},
|
||||||
is_kba_passed && {
|
complete_kba_event.persisted? && {
|
||||||
text: "#{I18n.t('knowledge_based_authentication')}: #{I18n.t('passed')}\n"
|
text: "#{I18n.t('knowledge_based_authentication')}: #{I18n.t('passed')}#{kba_score}\n"
|
||||||
},
|
},
|
||||||
completed_event.data['ip'] && { text: "IP: #{completed_event.data['ip']}\n" },
|
completed_event.data['ip'] && { text: "IP: #{completed_event.data['ip']}\n" },
|
||||||
completed_event.data['sid'] && { text: "#{I18n.t('session_id')}: #{completed_event.data['sid']}\n" },
|
completed_event.data['sid'] && { text: "#{I18n.t('session_id')}: #{completed_event.data['sid']}\n" },
|
||||||
|
|||||||
@@ -602,6 +602,8 @@ module Submissions
|
|||||||
cell_valign = field.dig('preferences', 'valign').to_s.presence_in(TEXT_VALIGNS) || 'center'
|
cell_valign = field.dig('preferences', 'valign').to_s.presence_in(TEXT_VALIGNS) || 'center'
|
||||||
cell_layouter = cell_layouters[cell_valign]
|
cell_layouter = cell_layouters[cell_valign]
|
||||||
|
|
||||||
|
value = value.to_s
|
||||||
|
|
||||||
if (mask = field.dig('preferences', 'mask').presence)
|
if (mask = field.dig('preferences', 'mask').presence)
|
||||||
value = TextUtils.mask_value(value, mask)
|
value = TextUtils.mask_value(value, mask)
|
||||||
end
|
end
|
||||||
@@ -882,6 +884,8 @@ module Submissions
|
|||||||
end
|
end
|
||||||
|
|
||||||
def maybe_flatten_pdf(pdf)
|
def maybe_flatten_pdf(pdf)
|
||||||
|
pdf.catalog.delete(:AcroForm) if pdf.catalog[:AcroForm].is_a?(Integer)
|
||||||
|
|
||||||
pdf.acro_form.create_appearances(force: true) if pdf.acro_form && pdf.acro_form[:NeedAppearances]
|
pdf.acro_form.create_appearances(force: true) if pdf.acro_form && pdf.acro_form[:NeedAppearances]
|
||||||
pdf.acro_form&.flatten
|
pdf.acro_form&.flatten
|
||||||
rescue HexaPDF::MissingGlyphError
|
rescue HexaPDF::MissingGlyphError
|
||||||
|
|||||||
+9
-1
@@ -152,7 +152,7 @@ module Submitters
|
|||||||
preferences['send_sms'] = params['send_sms'].in?(TRUE_VALUES) if params.key?('send_sms')
|
preferences['send_sms'] = params['send_sms'].in?(TRUE_VALUES) if params.key?('send_sms')
|
||||||
preferences['require_phone_2fa'] = params['require_phone_2fa'].in?(TRUE_VALUES) if params.key?('require_phone_2fa')
|
preferences['require_phone_2fa'] = params['require_phone_2fa'].in?(TRUE_VALUES) if params.key?('require_phone_2fa')
|
||||||
preferences['require_email_2fa'] = params['require_email_2fa'].in?(TRUE_VALUES) if params.key?('require_email_2fa')
|
preferences['require_email_2fa'] = params['require_email_2fa'].in?(TRUE_VALUES) if params.key?('require_email_2fa')
|
||||||
preferences['bcc_completed'] = params['bcc_completed'] if params.key?('bcc_completed')
|
preferences['bcc_completed'] = normalize_bcc_completed(params['bcc_completed']) if params.key?('bcc_completed')
|
||||||
preferences['reply_to'] = params['reply_to'] if params.key?('reply_to')
|
preferences['reply_to'] = params['reply_to'] if params.key?('reply_to')
|
||||||
preferences['go_to_last'] = params['go_to_last'] if params.key?('go_to_last')
|
preferences['go_to_last'] = params['go_to_last'] if params.key?('go_to_last')
|
||||||
preferences['completed_redirect_url'] = params['completed_redirect_url'] if params.key?('completed_redirect_url')
|
preferences['completed_redirect_url'] = params['completed_redirect_url'] if params.key?('completed_redirect_url')
|
||||||
@@ -160,6 +160,14 @@ module Submitters
|
|||||||
preferences
|
preferences
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def normalize_bcc_completed(value)
|
||||||
|
if value.is_a?(Array)
|
||||||
|
value.compact_blank.uniq.join(', ')
|
||||||
|
else
|
||||||
|
value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def fetch_values_for_delegate(submitter)
|
def fetch_values_for_delegate(submitter)
|
||||||
fields = submitter.submission.template_fields || submitter.template.fields
|
fields = submitter.submission.template_fields || submitter.template.fields
|
||||||
default_values = submitter.preferences['default_values'] || {}
|
default_values = submitter.preferences['default_values'] || {}
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ module Submitters
|
|||||||
submissions = submissions.where(source:) if source
|
submissions = submissions.where(source:) if source
|
||||||
|
|
||||||
Submitter
|
Submitter
|
||||||
|
.where(template_id: template.id)
|
||||||
.where(submission: submissions)
|
.where(submission: submissions)
|
||||||
.order(id: :desc)
|
.order(id: :desc)
|
||||||
.where(declined_at: nil)
|
.where(declined_at: nil)
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ module Submitters
|
|||||||
PHONE_REGEXP = /[+\d()\s-]+/
|
PHONE_REGEXP = /[+\d()\s-]+/
|
||||||
NONEDITABLE_FIELD_TYPES = %w[stamp heading strikethrough].freeze
|
NONEDITABLE_FIELD_TYPES = %w[stamp heading strikethrough].freeze
|
||||||
REQUIRED_FIELD_TYPES = %w[payment kba verification].freeze
|
REQUIRED_FIELD_TYPES = %w[payment kba verification].freeze
|
||||||
|
CHOICE_FIELD_TYPES = %w[radio multiple select].freeze
|
||||||
|
BOOLEAN_VALUES = [true, false].freeze
|
||||||
|
|
||||||
STRFTIME_MAP = {
|
STRFTIME_MAP = {
|
||||||
'hour' => '%-k',
|
'hour' => '%-k',
|
||||||
@@ -55,6 +57,7 @@ module Submitters
|
|||||||
validate_values!(reason_field ? values.except(reason_field['uuid']) : values, submitter, params, request)
|
validate_values!(reason_field ? values.except(reason_field['uuid']) : values, submitter, params, request)
|
||||||
|
|
||||||
if params[:completed] == 'true'
|
if params[:completed] == 'true'
|
||||||
|
validate_invite_submitters!(submitter)
|
||||||
maybe_invite_via_field(submitter, request)
|
maybe_invite_via_field(submitter, request)
|
||||||
|
|
||||||
SubmissionEvents.create_with_tracking_data(submitter, 'complete_form', request)
|
SubmissionEvents.create_with_tracking_data(submitter, 'complete_form', request)
|
||||||
@@ -504,7 +507,7 @@ module Submitters
|
|||||||
submitter
|
submitter
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_value!(_value, field, _params, submitter, _request)
|
def validate_value!(value, field, _params, submitter, _request)
|
||||||
raise ValidationError, 'Missing field' unless field
|
raise ValidationError, 'Missing field' unless field
|
||||||
raise ValidationError, 'Invalid field' if field['submitter_uuid'] != submitter.uuid
|
raise ValidationError, 'Invalid field' if field['submitter_uuid'] != submitter.uuid
|
||||||
|
|
||||||
@@ -514,7 +517,34 @@ module Submitters
|
|||||||
raise ValidationError, 'Read-only field'
|
raise ValidationError, 'Read-only field'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
raise ValidationError, 'Value is invalid' if field['type'] == 'checkbox' && !value.in?(BOOLEAN_VALUES)
|
||||||
|
|
||||||
|
validate_choice_value!(value, field, submitter) if field['type'].in?(CHOICE_FIELD_TYPES)
|
||||||
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_choice_value!(value, field, submitter)
|
||||||
|
return if value.blank?
|
||||||
|
|
||||||
|
raise ValidationError, 'Value is invalid' unless value.is_a?(field['type'] == 'multiple' ? Array : String)
|
||||||
|
|
||||||
|
option_names = field['options'].to_a.map.with_index do |option, index|
|
||||||
|
option['value'].presence ||
|
||||||
|
"#{I18n.t('option', locale: submitter.metadata.fetch('lang', submitter.account.locale))} #{index + 1}"
|
||||||
|
end
|
||||||
|
|
||||||
|
raise ValidationError, 'Value is invalid' unless Array.wrap(value).all? { |item| option_names.include?(item) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_invite_submitters!(submitter)
|
||||||
|
submission = submitter.submission
|
||||||
|
|
||||||
|
is_pending_invite = (submission.template_submitters || submission.template.submitters).any? do |s|
|
||||||
|
s['invite_by_uuid'] == submitter.uuid && submission.submitters.none? { |e| e.uuid == s['uuid'] }
|
||||||
|
end
|
||||||
|
|
||||||
|
raise ValidationError, 'Invite required' if is_pending_invite
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -6,6 +6,7 @@ module Templates
|
|||||||
ZIP_CONTENT_TYPE = 'application/zip'
|
ZIP_CONTENT_TYPE = 'application/zip'
|
||||||
X_ZIP_CONTENT_TYPE = 'application/x-zip-compressed'
|
X_ZIP_CONTENT_TYPE = 'application/x-zip-compressed'
|
||||||
JSON_CONTENT_TYPE = 'application/json'
|
JSON_CONTENT_TYPE = 'application/json'
|
||||||
|
WORD_EXTENSIONS = %w[.docx .doc .odt .rtf].freeze
|
||||||
DOCUMENT_EXTENSIONS = %w[.docx .doc .xlsx .xls .odt .rtf].freeze
|
DOCUMENT_EXTENSIONS = %w[.docx .doc .xlsx .xls .odt .rtf].freeze
|
||||||
|
|
||||||
DOCUMENT_CONTENT_TYPES = %w[
|
DOCUMENT_CONTENT_TYPES = %w[
|
||||||
@@ -134,7 +135,27 @@ module Templates
|
|||||||
return [handle_pdf_or_image(template, file, file.read, params, extract_fields:), []]
|
return [handle_pdf_or_image(template, file, file.read, params, extract_fields:), []]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if File.extname(file.original_filename.to_s).downcase.in?(WORD_EXTENSIONS)
|
||||||
|
return [handle_word_document(template, file, params), []]
|
||||||
|
end
|
||||||
|
|
||||||
raise InvalidFileType, "#{file.content_type}/#{dynamic}"
|
raise InvalidFileType, "#{file.content_type}/#{dynamic}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_word_document(template, file, params)
|
||||||
|
pdf = WordCraft.open_buffer(file, &:to_pdf)
|
||||||
|
|
||||||
|
pdf_file = ActionDispatch::Http::UploadedFile.new(
|
||||||
|
tempfile: StringIO.new(pdf),
|
||||||
|
filename: "#{File.basename(file.original_filename, '.*')}.pdf",
|
||||||
|
type: PDF_CONTENT_TYPE
|
||||||
|
)
|
||||||
|
|
||||||
|
handle_pdf_or_image(template, pdf_file, pdf, params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def document_extensions
|
||||||
|
Docuseal.advanced_formats? ? DOCUMENT_EXTENSIONS : WORD_EXTENSIONS
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -90,8 +90,10 @@ module Templates
|
|||||||
|
|
||||||
pool = Concurrent::FixedThreadPool.new(concurrency)
|
pool = Concurrent::FixedThreadPool.new(concurrency)
|
||||||
|
|
||||||
|
blobs =
|
||||||
|
range.each_slice(concurrency).flat_map do |page_numbers|
|
||||||
promises =
|
promises =
|
||||||
range.map do |page_number|
|
page_numbers.map do |page_number|
|
||||||
doc_page = doc.get_page(page_number)
|
doc_page = doc.get_page(page_number)
|
||||||
|
|
||||||
hide_placeholder_widgets(doc_page, hide_empty: !flatten_pages)
|
hide_placeholder_widgets(doc_page, hide_empty: !flatten_pages)
|
||||||
@@ -106,7 +108,10 @@ module Templates
|
|||||||
doc_page&.close
|
doc_page&.close
|
||||||
end
|
end
|
||||||
|
|
||||||
Concurrent::Promise.zip(*promises).value!.each do |blob|
|
Concurrent::Promise.zip(*promises).value!
|
||||||
|
end
|
||||||
|
|
||||||
|
blobs.each do |blob|
|
||||||
next unless blob
|
next unless blob
|
||||||
|
|
||||||
ApplicationRecord.no_touching do
|
ApplicationRecord.no_touching do
|
||||||
|
|||||||
@@ -180,8 +180,11 @@ module VerifyPdfSignature
|
|||||||
|
|
||||||
return false if byte_range.size != 4 || byte_range.any?(&:negative?) || byte_range[0].positive?
|
return false if byte_range.size != 4 || byte_range.any?(&:negative?) || byte_range[0].positive?
|
||||||
return false if signature.signed_end > io.size
|
return false if signature.signed_end > io.size
|
||||||
|
return false if byte_range[2] != byte_range[1] + (signature.contents.bytesize * 2) + 2
|
||||||
|
|
||||||
byte_range[2] == byte_range[1] + (signature.contents.bytesize * 2) + 2
|
io.seek(byte_range[1])
|
||||||
|
|
||||||
|
io.read(byte_range[2] - byte_range[1]).casecmp?("<#{signature.contents.unpack1('H*')}>")
|
||||||
end
|
end
|
||||||
|
|
||||||
def unsigned_changes?(document, io, signed_end)
|
def unsigned_changes?(document, io, signed_end)
|
||||||
|
|||||||
Reference in new issue
Block a user