Compare commits

...
17 Commits
Author SHA1 Message Date
Alex Turchyn 5a00d4ad5c Merge from docusealco/wip 2026-11-09 15:41:58 +02:00
Pete Matsyburka 30704b59c2 optimize query 2026-11-09 12:06:04 +02:00
Pete Matsyburka d56c903596 update readme 2026-11-08 16:05:24 +02:00
Pete Matsyburka 5353e8fa23 install app message 2026-11-08 10:58:10 +02:00
Pete Matsyburka 038d780265 sms fail event 2026-11-08 10:27:40 +02:00
Pete Matsyburka f3a8100734 app os 2026-11-08 09:59:56 +02:00
Pete Matsyburka dbf446efe1 block search bots 2026-11-08 09:48:22 +02:00
Pete Matsyburka 621431166b update security.md 2026-11-07 10:00:28 +02:00
Pete Matsyburka 92cfd5624d validate email 2026-11-07 09:37:32 +02:00
Pete Matsyburka e39e84adfa update gem 2026-11-05 23:04:12 +02:00
Pete Matsyburka 1b63b16691 adjust filename 2026-11-05 01:15:16 +02:00
Pete Matsyburka 256d14eb94 update rails 2026-11-04 14:32:43 +02:00
Pete Matsyburka 49463d611c unify native modals 2026-11-04 12:36:47 +02:00
Alex Turchyn 4fec8db589 fix native turbo modal 2026-11-04 12:36:47 +02:00
Pete Matsyburka 0bc620fa90 cleanup routes 2026-11-04 00:26:50 +02:00
Pete Matsyburka c0c5ba48a0 block active storage routes 2026-11-04 00:05:39 +02:00
Alex Turchyn 8fe0293ebe update hero image 2026-11-02 20:50:32 +02:00
66 changed files with 298 additions and 160 deletions

No files matched your search

+65 -65
View File
@@ -3,29 +3,29 @@ GEM
specs: specs:
action_text-trix (2.1.19) action_text-trix (2.1.19)
railties railties
actioncable (8.1.4) actioncable (8.1.4.1)
actionpack (= 8.1.4) actionpack (= 8.1.4.1)
activesupport (= 8.1.4) activesupport (= 8.1.4.1)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
actionmailbox (8.1.4) actionmailbox (8.1.4.1)
actionpack (= 8.1.4) actionpack (= 8.1.4.1)
activejob (= 8.1.4) activejob (= 8.1.4.1)
activerecord (= 8.1.4) activerecord (= 8.1.4.1)
activestorage (= 8.1.4) activestorage (= 8.1.4.1)
activesupport (= 8.1.4) activesupport (= 8.1.4.1)
mail (>= 2.8.0) mail (>= 2.8.0)
actionmailer (8.1.4) actionmailer (8.1.4.1)
actionpack (= 8.1.4) actionpack (= 8.1.4.1)
actionview (= 8.1.4) actionview (= 8.1.4.1)
activejob (= 8.1.4) activejob (= 8.1.4.1)
activesupport (= 8.1.4) activesupport (= 8.1.4.1)
mail (>= 2.8.0) mail (>= 2.8.0)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
actionpack (8.1.4) actionpack (8.1.4.1)
actionview (= 8.1.4) actionview (= 8.1.4.1)
activesupport (= 8.1.4) activesupport (= 8.1.4.1)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
rack (>= 2.2.4) rack (>= 2.2.4)
rack-session (>= 1.0.1) rack-session (>= 1.0.1)
@@ -33,36 +33,36 @@ GEM
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
useragent (~> 0.16) useragent (~> 0.16)
actiontext (8.1.4) actiontext (8.1.4.1)
action_text-trix (~> 2.1.15) action_text-trix (~> 2.1.15)
actionpack (= 8.1.4) actionpack (= 8.1.4.1)
activerecord (= 8.1.4) activerecord (= 8.1.4.1)
activestorage (= 8.1.4) activestorage (= 8.1.4.1)
activesupport (= 8.1.4) activesupport (= 8.1.4.1)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (8.1.4) actionview (8.1.4.1)
activesupport (= 8.1.4) activesupport (= 8.1.4.1)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.11) erubi (~> 1.11)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6, >= 1.7.2)
activejob (8.1.4) activejob (8.1.4.1)
activesupport (= 8.1.4) activesupport (= 8.1.4.1)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (8.1.4) activemodel (8.1.4.1)
activesupport (= 8.1.4) activesupport (= 8.1.4.1)
activerecord (8.1.4) activerecord (8.1.4.1)
activemodel (= 8.1.4) activemodel (= 8.1.4.1)
activesupport (= 8.1.4) activesupport (= 8.1.4.1)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activestorage (8.1.4) activestorage (8.1.4.1)
actionpack (= 8.1.4) actionpack (= 8.1.4.1)
activejob (= 8.1.4) activejob (= 8.1.4.1)
activerecord (= 8.1.4) activerecord (= 8.1.4.1)
activesupport (= 8.1.4) activesupport (= 8.1.4.1)
marcel (~> 1.0) marcel (~> 1.0)
activesupport (8.1.4) activesupport (8.1.4.1)
base64 base64
bigdecimal bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1) concurrent-ruby (~> 1.0, >= 1.3.1)
@@ -293,9 +293,9 @@ GEM
activesupport (>= 4) activesupport (>= 4)
railties (>= 4) railties (>= 4)
request_store (~> 1.0) request_store (~> 1.0)
loofah (2.25.2) loofah (2.25.3)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.12.0) nokogiri (>= 1.19.5)
mail (2.9.1) mail (2.9.1)
logger logger
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
@@ -323,15 +323,15 @@ GEM
net-smtp (0.5.2) net-smtp (0.5.2)
net-protocol net-protocol
nio4r (2.7.5) nio4r (2.7.5)
nokogiri (1.19.4-aarch64-linux-gnu) nokogiri (1.19.7-aarch64-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.19.4-aarch64-linux-musl) nokogiri (1.19.7-aarch64-linux-musl)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.19.4-arm64-darwin) nokogiri (1.19.7-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.19.4-x86_64-linux-gnu) nokogiri (1.19.7-x86_64-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.19.4-x86_64-linux-musl) nokogiri (1.19.7-x86_64-linux-musl)
racc (~> 1.4) racc (~> 1.4)
numo-narray-alt (0.10.3) numo-narray-alt (0.10.3)
onnxruntime (0.10.1-aarch64-linux) onnxruntime (0.10.1-aarch64-linux)
@@ -373,7 +373,7 @@ GEM
puma (8.0.2) puma (8.0.2)
nio4r (~> 2.0) nio4r (~> 2.0)
racc (1.8.1) racc (1.8.1)
rack (3.2.7) rack (3.2.8)
rack-proxy (0.7.7) rack-proxy (0.7.7)
rack rack
rack-session (2.1.2) rack-session (2.1.2)
@@ -383,33 +383,33 @@ GEM
rack (>= 1.3) rack (>= 1.3)
rackup (2.3.1) rackup (2.3.1)
rack (>= 3) rack (>= 3)
rails (8.1.4) rails (8.1.4.1)
actioncable (= 8.1.4) actioncable (= 8.1.4.1)
actionmailbox (= 8.1.4) actionmailbox (= 8.1.4.1)
actionmailer (= 8.1.4) actionmailer (= 8.1.4.1)
actionpack (= 8.1.4) actionpack (= 8.1.4.1)
actiontext (= 8.1.4) actiontext (= 8.1.4.1)
actionview (= 8.1.4) actionview (= 8.1.4.1)
activejob (= 8.1.4) activejob (= 8.1.4.1)
activemodel (= 8.1.4) activemodel (= 8.1.4.1)
activerecord (= 8.1.4) activerecord (= 8.1.4.1)
activestorage (= 8.1.4) activestorage (= 8.1.4.1)
activesupport (= 8.1.4) activesupport (= 8.1.4.1)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 8.1.4) railties (= 8.1.4.1)
rails-dom-testing (2.3.0) rails-dom-testing (2.3.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
minitest minitest
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.7.1) rails-html-sanitizer (1.7.2)
loofah (~> 2.25, >= 2.25.2) loofah (~> 2.25, >= 2.25.3)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
rails-i18n (8.1.0) rails-i18n (8.1.0)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
railties (>= 8.0.0, < 9) railties (>= 8.0.0, < 9)
railties (8.1.4) railties (8.1.4.1)
actionpack (= 8.1.4) actionpack (= 8.1.4.1)
activesupport (= 8.1.4) activesupport (= 8.1.4.1)
irb (~> 1.13) irb (~> 1.13)
rackup (>= 1.0.0) rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
@@ -535,7 +535,7 @@ GEM
thor (1.5.0) thor (1.5.0)
timeout (0.6.1) timeout (0.6.1)
trailblazer-option (0.1.2) trailblazer-option (0.1.2)
trilogy (2.12.2) trilogy (2.13.3)
bigdecimal bigdecimal
tsort (0.2.0) tsort (0.2.0)
turbo-rails (2.0.23) turbo-rails (2.0.23)
+3 -2
View File
@@ -30,7 +30,7 @@ DocuSeal is an open source platform that provides secure and efficient digital d
<a href="https://docuseal.com/sign_up">☁️ Try in Cloud</a> <a href="https://docuseal.com/sign_up">☁️ Try in Cloud</a>
</h2> </h2>
[![Demo](https://github.com/docusealco/docuseal/assets/5418788/d8703ea3-361a-423f-8bfe-eff1bd9dbe14)](https://demo.docuseal.tech) [![Demo](https://github.com/user-attachments/assets/8344a341-96cf-4df3-aaae-5458a21a65b3)](https://demo.docuseal.tech)
## Features ## Features
- PDF form fields builder (WYSIWYG) - PDF form fields builder (WYSIWYG)
@@ -41,10 +41,11 @@ DocuSeal is an open source platform that provides secure and efficient digital d
- Automatic PDF eSignature - Automatic PDF eSignature
- PDF signature verification - PDF signature verification
- User management - User management
- Mobile-optimized - Mobile-optimized signing
- 7 UI languages with signing available in 14 languages - 7 UI languages with signing available in 14 languages
- API and Webhooks for integrations - API and Webhooks for integrations
- Easy to deploy in minutes - Easy to deploy in minutes
- Mobile apps for [iOS](https://apps.apple.com/us/app/docuseal-e-signature/id6823564236) and [Android](https://play.google.com/store/apps/details?id=com.docuseal.app)
## Pro Features ## Pro Features
- Company logo and white-label - Company logo and white-label
+1
View File
@@ -10,6 +10,7 @@ Out of scope vulnerabilities:
- DNSSEC, CAA, CSP headers - DNSSEC, CAA, CSP headers
- DNS or email security related - DNS or email security related
- SSRF - SSRF
- DoS
- Rate Limiting - Rate Limiting
Note: We reserve the right to classify any reported vulnerability as out of scope for the bug bounty program. Note: We reserve the right to classify any reported vulnerability as out of scope for the bug bounty program.
@@ -34,6 +34,12 @@ module Api
@record = attachment.record @record = attachment.record
@record = @record.record if @record.is_a?(ActiveStorage::Attachment) @record = @record.record if @record.is_a?(ActiveStorage::Attachment)
if DetectBrowserDevice.search_bot?(request.user_agent)
Rollbar.warning("Search bot: #{@record.class.name} #{@record.id}") if defined?(Rollbar)
raise ActionController::RoutingError, 'Not Found'
end
authorization_check!(attachment, @record, exp) authorization_check!(attachment, @record, exp)
if request.headers['Range'].present? if request.headers['Range'].present?
@@ -22,7 +22,7 @@ module Api
render json: { render json: {
id: @submission.id, id: @submission.id,
documents: documents.map do |attachment| documents: documents.map do |attachment|
{ name: attachment.filename.base, url: ActiveStorage::Blob.proxy_url(attachment.blob, expires_at:) } { name: FilenameUtils.basename(attachment.blob[:filename]), url: ActiveStorage::Blob.proxy_url(attachment.blob, expires_at:) }
end end
} }
end end
@@ -22,6 +22,7 @@ class ApplicationController < ActionController::Base
:true_ability, :true_ability,
:form_link_host, :form_link_host,
:svg_icon, :svg_icon,
:browser_os,
:turbo_native_ios?, :turbo_native_ios?,
:turbo_native_android? :turbo_native_android?
@@ -58,6 +59,10 @@ class ApplicationController < ActionController::Base
request.session[:impersonated_user_id] = user.uuid request.session[:impersonated_user_id] = user.uuid
end end
def browser_os
@browser_os ||= DetectBrowserDevice.os(request.user_agent)
end
def pagy_auto(collection, **keyword_args) def pagy_auto(collection, **keyword_args)
if current_ability.can?(:manage, :countless) if current_ability.can?(:manage, :countless)
pagy(:countless, collection, **keyword_args) pagy(:countless, collection, **keyword_args)
@@ -66,7 +66,9 @@ module Mcp
@template.save! @template.save!
documents, = Templates::CreateAttachments.call(@template, { files: [file] }, extract_fields: true) documents, = Templates::CreateAttachments.call(@template, { files: [file] }, extract_fields: true)
schema = documents.map { |doc| { attachment_uuid: doc.uuid, name: doc.filename.base } } schema = documents.map do |doc|
{ attachment_uuid: doc.uuid, name: FilenameUtils.basename(doc.blob[:filename]) }
end
if @template.fields.blank? if @template.fields.blank?
@template.fields = Templates::ProcessDocument.normalize_attachment_fields(@template, documents) @template.fields = Templates::ProcessDocument.normalize_attachment_fields(@template, documents)
+10 -1
View File
@@ -92,8 +92,11 @@ class StartFormController < ApplicationController
blank_fields = required_fields.select { |key| submitter_params[key].blank? } blank_fields = required_fields.select { |key| submitter_params[key].blank? }
is_invalid_email = required_fields.include?('email') && submitter_params[:email].present? &&
!submitter_params[:email].match?(User::VALIDATE_EMAIL_REGEXP)
submitter = submitter =
if blank_fields.present? if blank_fields.present? || is_invalid_email
Submitter.new(submitter_params) Submitter.new(submitter_params)
else else
Submitters::StartForm.find_or_initialize_submitter( Submitters::StartForm.find_or_initialize_submitter(
@@ -104,6 +107,12 @@ class StartFormController < ApplicationController
blank_fields.each { |key| submitter.errors.add(key.to_sym, :blank) } blank_fields.each { |key| submitter.errors.add(key.to_sym, :blank) }
if is_invalid_email
Rollbar.warning("Invalid start form email: #{submitter_params[:email]}") if defined?(Rollbar)
submitter.errors.add(:email, :invalid)
end
submitter submitter
end end
@@ -12,6 +12,7 @@ class SubmissionEventsController < ApplicationController
'send_2fa_sms' => '2fa', 'send_2fa_sms' => '2fa',
'send_2fa_email' => '2fa', 'send_2fa_email' => '2fa',
'send_sms' => 'send', 'send_sms' => 'send',
'fail_sms' => 'alert_circle',
'phone_verified' => 'phone_check', 'phone_verified' => 'phone_check',
'email_verified' => 'email_check', 'email_verified' => 'email_check',
'click_sms' => 'hand_click', 'click_sms' => 'hand_click',
@@ -26,6 +26,6 @@ class SubmissionsExportController < ApplicationController
end end
def new def new
render layout: turbo_native_android? ? 'plain' : true render layout: 'plain'
end end
end end
@@ -14,6 +14,6 @@ class SubmissionsFiltersController < ApplicationController
def show def show
return head :not_found unless ALLOWED_NAMES.include?(params[:name]) return head :not_found unless ALLOWED_NAMES.include?(params[:name])
render params[:name], layout: turbo_native_android? ? 'plain' : true render params[:name], layout: 'plain'
end end
end end
@@ -8,6 +8,7 @@ class SubmitFormController < ApplicationController
skip_authorization_check skip_authorization_check
before_action :load_submitter, only: %i[show update completed] before_action :load_submitter, only: %i[show update completed]
before_action :maybe_render_not_found_for_search_bot, only: %i[show completed]
before_action :maybe_redirect_delegated, only: %i[show completed] before_action :maybe_redirect_delegated, only: %i[show completed]
before_action :maybe_render_locked_page, only: :show before_action :maybe_render_locked_page, only: :show
before_action :maybe_require_link_2fa, only: %i[show] before_action :maybe_require_link_2fa, only: %i[show]
@@ -111,6 +112,14 @@ class SubmitFormController < ApplicationController
private private
def maybe_render_not_found_for_search_bot
return unless DetectBrowserDevice.search_bot?(request.user_agent)
Rollbar.warning("Search bot: submitter #{@submitter&.id}") if defined?(Rollbar)
raise ActionController::RoutingError, I18n.t('not_found')
end
def maybe_require_link_2fa def maybe_require_link_2fa
return if Submitters::AuthorizedForForm.pass_link_2fa?(@submitter, current_user, request) return if Submitters::AuthorizedForForm.pass_link_2fa?(@submitter, current_user, request)
@@ -31,7 +31,7 @@ class TemplateDocumentsController < ApplicationController
documents, = Templates::CreateAttachments.call(@template, params, extract_fields: true) documents, = Templates::CreateAttachments.call(@template, params, extract_fields: true)
schema = documents.map do |doc| schema = documents.map do |doc|
{ attachment_uuid: doc.uuid, name: doc.filename.base } { attachment_uuid: doc.uuid, name: FilenameUtils.basename(doc.blob[:filename]) }
end end
render json: { render json: {
@@ -42,7 +42,7 @@ class TemplateFoldersController < ApplicationController
end end
def edit def edit
render layout: turbo_native_android? ? 'plain' : true render layout: 'plain'
end end
def update def update
@@ -8,7 +8,7 @@ class TemplatesCloneController < ApplicationController
@template = Template.new(name: "#{@base_template.name} (#{I18n.t('clone')})") @template = Template.new(name: "#{@base_template.name} (#{I18n.t('clone')})")
render layout: turbo_native_android? ? 'plain' : true render layout: 'plain'
end end
def create def create
@@ -67,7 +67,7 @@ class TemplatesDebugController < ApplicationController
schema = documents.map do |doc| schema = documents.map do |doc|
{ {
attachment_uuid: doc.uuid, attachment_uuid: doc.uuid,
name: doc.filename.base, name: FilenameUtils.basename(doc.blob[:filename]),
dynamic: dynamic_documents.find { |e| e.uuid == doc.uuid }.present? dynamic: dynamic_documents.find { |e| e.uuid == doc.uuid }.present?
} }
end end
@@ -4,7 +4,7 @@ class TemplatesFoldersController < ApplicationController
load_and_authorize_resource :template load_and_authorize_resource :template
def edit def edit
render layout: turbo_native_android? ? 'plain' : true render layout: 'plain'
end end
def update def update
@@ -17,7 +17,7 @@ class TemplatesUploadsController < ApplicationController
save_template!(@template, url_params) save_template!(@template, url_params)
documents, = Templates::CreateAttachments.call(@template, url_params || params, extract_fields: true) documents, = Templates::CreateAttachments.call(@template, url_params || params, extract_fields: true)
schema = documents.map { |doc| { attachment_uuid: doc.uuid, name: doc.filename.base } } schema = documents.map { |doc| { attachment_uuid: doc.uuid, name: FilenameUtils.basename(doc.blob[:filename]) } }
if @template.fields.blank? if @template.fields.blank?
@template.fields = Templates::ProcessDocument.normalize_attachment_fields(@template, documents) @template.fields = Templates::ProcessDocument.normalize_attachment_fields(@template, documents)
+1 -1
View File
@@ -5,7 +5,7 @@ class UserInitialsController < ApplicationController
authorize_resource :user_config authorize_resource :user_config
def edit def edit
render layout: turbo_native_android? ? 'plain' : true render layout: 'plain'
end end
def update def update
@@ -5,7 +5,7 @@ class UserSignaturesController < ApplicationController
authorize_resource :user_config authorize_resource :user_config
def edit def edit
render layout: turbo_native_android? ? 'plain' : true render layout: 'plain'
end end
def update def update
+2 -2
View File
@@ -32,11 +32,11 @@ class UsersController < ApplicationController
end end
def new def new
render layout: turbo_native_android? ? 'plain' : true render layout: turbo_native_app? ? 'plain' : true
end end
def edit def edit
render layout: turbo_native_android? ? 'plain' : true render layout: turbo_native_app? ? 'plain' : true
end end
def create def create
@@ -13,7 +13,9 @@ class WebhookSettingsController < ApplicationController
else else
@webhook_events = @webhook_url.webhook_events @webhook_events = @webhook_url.webhook_events
@webhook_events = @webhook_events.where(status: params[:status]) if %w[success error].include?(params[:status]) if %w[success error].include?(params[:status])
@webhook_events = @webhook_events.where(WebhookEvent.arel_table[:status].eq(params[:status]))
end
@pagy, @webhook_events = pagy(:countless, @webhook_events.order(id: :desc)) @pagy, @webhook_events = pagy(:countless, @webhook_events.order(id: :desc))
@@ -24,7 +26,9 @@ class WebhookSettingsController < ApplicationController
def show def show
@webhook_events = @webhook_url.webhook_events @webhook_events = @webhook_url.webhook_events
@webhook_events = @webhook_events.where(status: params[:status]) if %w[success error].include?(params[:status]) if %w[success error].include?(params[:status])
@webhook_events = @webhook_events.where(WebhookEvent.arel_table[:status].eq(params[:status]))
end
@pagy, @webhook_events = pagy(:countless, @webhook_events.order(id: :desc)) @pagy, @webhook_events = pagy(:countless, @webhook_events.order(id: :desc))
end end
@@ -7,7 +7,7 @@
class="absolute top-0 bottom-0 right-0 left-0" class="absolute top-0 bottom-0 right-0 left-0"
@click.prevent="$emit('close')" @click.prevent="$emit('close')"
/> />
<div :class="inline ? (nativePlatform === 'android' ? 'px-4 pt-1.5 pb-6' : 'px-4 pt-3 pb-6') : 'modal-box pt-4 pb-6 px-6 mt-20 w-full max-w-xl'"> <div :class="inline ? (nativePlatform === 'android' ? 'pt-1.5 pb-6' : 'pt-3 pb-6') : 'modal-box pt-4 pb-6 px-6 mt-20 w-full max-w-xl'" :style="inline ? { paddingInline: '18px' } : null">
<div <div
v-if="!inline" v-if="!inline"
class="flex justify-between items-center border-b pb-2 mb-2 font-medium" class="flex justify-between items-center border-b pb-2 mb-2 font-medium"
@@ -7,7 +7,7 @@
class="absolute top-0 bottom-0 right-0 left-0" class="absolute top-0 bottom-0 right-0 left-0"
@click.prevent="$emit('close')" @click.prevent="$emit('close')"
/> />
<div :class="inline ? (nativePlatform === 'android' ? 'px-4 pt-1.5 pb-6' : 'px-4 pt-3 pb-6') : 'modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl'"> <div :class="inline ? (nativePlatform === 'android' ? 'pt-1.5 pb-6' : 'pt-3 pb-6') : 'modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl'" :style="inline ? { paddingInline: '18px' } : null">
<div <div
v-if="!inline" v-if="!inline"
class="flex justify-between items-center border-b pb-2 mb-2 font-medium" class="flex justify-between items-center border-b pb-2 mb-2 font-medium"
@@ -63,7 +63,7 @@
> >
<div <div
class="flex-1 overflow-y-auto overflow-x-hidden px-4 md:px-6 py-4 space-y-4" class="flex-1 overflow-y-auto overflow-x-hidden px-4 md:px-6 py-4 space-y-4"
:style="inline && nativePlatform === 'ios' ? { paddingTop: '86px' } : null" :style="inline ? { paddingInline: '18px', ...(nativePlatform === 'ios' ? { paddingTop: '86px' } : {}) } : null"
> >
<div <div
v-for="(doc, docIndex) in layout" v-for="(doc, docIndex) in layout"
@@ -7,7 +7,7 @@
class="absolute top-0 bottom-0 right-0 left-0" class="absolute top-0 bottom-0 right-0 left-0"
@click.prevent="$emit('close')" @click.prevent="$emit('close')"
/> />
<div :class="inline ? (nativePlatform === 'android' ? 'px-4 pt-1.5 pb-6' : 'px-4 pt-3 pb-6') : 'modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl'"> <div :class="inline ? (nativePlatform === 'android' ? 'pt-1.5 pb-6' : 'pt-3 pb-6') : 'modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl'" :style="inline ? { paddingInline: '18px' } : null">
<div <div
v-if="!inline" v-if="!inline"
class="flex justify-between items-center border-b pb-2 mb-2 font-medium" class="flex justify-between items-center border-b pb-2 mb-2 font-medium"
@@ -7,7 +7,7 @@
class="absolute top-0 bottom-0 right-0 left-0" class="absolute top-0 bottom-0 right-0 left-0"
@click.prevent="$emit('close')" @click.prevent="$emit('close')"
/> />
<div :class="inline ? (nativePlatform === 'android' ? 'px-4 pt-1.5 pb-6' : 'px-4 pt-3 pb-6') : 'modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl'"> <div :class="inline ? (nativePlatform === 'android' ? 'pt-1.5 pb-6' : 'pt-3 pb-6') : 'modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl'" :style="inline ? { paddingInline: '18px' } : null">
<div <div
v-if="!inline" v-if="!inline"
class="flex justify-between items-center border-b pb-2 mb-2 font-medium" class="flex justify-between items-center border-b pb-2 mb-2 font-medium"
@@ -5,7 +5,7 @@
class="absolute top-0 bottom-0 right-0 left-0" class="absolute top-0 bottom-0 right-0 left-0"
@click.prevent="$emit('close')" @click.prevent="$emit('close')"
/> />
<div :class="inline ? (nativePlatform === 'android' ? 'px-4 pt-1.5 pb-6' : 'px-4 pt-3 pb-6') : 'modal-box pt-4 pb-6 mt-20 w-full'"> <div :class="inline ? (nativePlatform === 'android' ? 'pt-1.5 pb-6' : 'pt-3 pb-6') : 'modal-box pt-4 pb-6 mt-20 w-full'" :style="inline ? { paddingInline: '18px' } : null">
<div <div
v-if="!inline" v-if="!inline"
class="flex justify-between items-center border-b pb-2 mb-3 font-medium" class="flex justify-between items-center border-b pb-2 mb-3 font-medium"
+1
View File
@@ -45,6 +45,7 @@ class SubmissionEvent < ApplicationRecord
send_email: 'send_email', send_email: 'send_email',
bounce_email: 'bounce_email', bounce_email: 'bounce_email',
complaint_email: 'complaint_email', complaint_email: 'complaint_email',
fail_sms: 'fail_sms',
send_reminder_email: 'send_reminder_email', send_reminder_email: 'send_reminder_email',
send_sms: 'send_sms', send_sms: 'send_sms',
send_2fa_sms: 'send_2fa_sms', send_2fa_sms: 'send_2fa_sms',
+3 -1
View File
@@ -56,6 +56,8 @@ class User < ApplicationRecord
FULL_EMAIL_REGEXP = FULL_EMAIL_REGEXP =
/\A[a-z0-9][.']?(?:(?:[a-z0-9_-]+[.+'])*[a-z0-9_-]+)*@(?:[a-z0-9]+[.-])*[a-z0-9]+\.[a-z]{2,}\z/i /\A[a-z0-9][.']?(?:(?:[a-z0-9_-]+[.+'])*[a-z0-9_-]+)*@(?:[a-z0-9]+[.-])*[a-z0-9]+\.[a-z]{2,}\z/i
VALIDATE_EMAIL_REGEXP = /\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\z/
has_one_attached :signature has_one_attached :signature
has_one_attached :initials has_one_attached :initials
@@ -78,7 +80,7 @@ class User < ApplicationRecord
scope :archived, -> { where.not(archived_at: nil) } scope :archived, -> { where.not(archived_at: nil) }
scope :admins, -> { where(role: ADMIN_ROLE) } scope :admins, -> { where(role: ADMIN_ROLE) }
validates :email, format: { with: /\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\z/ } validates :email, format: { with: VALIDATE_EMAIL_REGEXP }
def access_token def access_token
super || build_access_token.tap(&:save!) super || build_access_token.tap(&:save!)
+1 -3
View File
@@ -28,9 +28,7 @@
<open-modal src="<%= url_for(**url_params, params: modal_uri.query_values) %>"></open-modal> <open-modal src="<%= url_for(**url_params, params: modal_uri.query_values) %>"></open-modal>
<% end %> <% end %>
<% end %> <% end %>
<% unless content_for?(:native_modal) %> <turbo-frame id="modal"></turbo-frame>
<turbo-frame id="modal"></turbo-frame>
<% 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' %>
<main class="max-w-6xl mx-auto px-4 md:px-2 mb-8"> <main class="max-w-6xl mx-auto px-4 md:px-2 mb-8">
@@ -0,0 +1,13 @@
<div id="mobile_app_alert" class="px-4 md:px-2 py-2 text-center bg-yellow-300 text-base-content w-full flex items-center justify-center gap-2">
<span>
<%= t('get_the_docuseal_app_for_a_better_mobile_experience') %>
<%= link_to t('install_app'), browser_os == 'ios' ? Docuseal::APP_STORE_URL : Docuseal::GOOGLE_PLAY_URL, class: 'link font-medium', target: '_blank', rel: 'noopener noreferrer nofollow' %>
</span>
<toggle-cookies data-key="mobile_app_alert" data-value="hidden" class="flex">
<remove-on-event data-selector-id="mobile_app_alert" class="flex">
<button type="button" class="btn btn-ghost btn-circle btn-sm" aria-label="<%= t('close') %>">
<%= svg_icon('x', class: 'w-5 h-5') %>
</button>
</remove-on-event>
</toggle-cookies>
</div>
+1
View File
@@ -1,4 +1,5 @@
<%= render 'shared/navbar_warning' %> <%= render 'shared/navbar_warning' %>
<%= render 'shared/mobile_app_alert' if signed_in? && cookies[:mobile_app_alert].blank? && (browser_os == 'ios' || browser_os == 'android') %>
<nav 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">
+2 -2
View File
@@ -1,7 +1,7 @@
<% if turbo_native_app? && !turbo_frame_request? %> <% if turbo_native_app? %>
<% content_for(:native_title, local_assigns[:title]) if local_assigns[:title] %> <% content_for(:native_title, local_assigns[:title]) if local_assigns[:title] %>
<turbo-frame id="drawer"> <turbo-frame id="drawer">
<native-modal class="block w-full" data-close-after-submit="<%= local_assigns.key?(:close_after_submit) ? local_assigns[:close_after_submit] : true %>"> <native-modal class="block max-w-lg mx-auto <%= turbo_native_android? ? 'pb-4' : 'pb-6' %>" data-close-after-submit="<%= local_assigns.key?(:close_after_submit) ? local_assigns[:close_after_submit] : true %>">
<%= yield %> <%= yield %>
</native-modal> </native-modal>
</turbo-frame> </turbo-frame>
+1 -2
View File
@@ -1,5 +1,4 @@
<% if turbo_native_ios? || (turbo_native_app? && !turbo_frame_request?) %> <% if turbo_native_app? %>
<% content_for(:native_modal, 'true') %>
<% content_for(:native_title, local_assigns[:title]) if local_assigns[:title] %> <% content_for(:native_title, local_assigns[:title]) if local_assigns[:title] %>
<turbo-frame id="modal"> <turbo-frame id="modal">
<native-modal class="block max-w-lg mx-auto <%= turbo_native_android? ? 'pb-4' : 'pb-6' %>" data-close-after-submit="<%= local_assigns.key?(:close_after_submit) ? local_assigns[:close_after_submit] : true %>"> <native-modal class="block max-w-lg mx-auto <%= turbo_native_android? ? 'pb-4' : 'pb-6' %>" data-close-after-submit="<%= local_assigns.key?(:close_after_submit) ? local_assigns[:close_after_submit] : true %>">
+8 -6
View File
@@ -1,4 +1,11 @@
<% if turbo_frame_request? %> <% if turbo_native_app? %>
<% content_for(:native_title, local_assigns[:title]) if local_assigns[:title] %>
<turbo-frame id="modal">
<native-modal class="block max-w-lg mx-auto <%= turbo_native_android? ? 'pb-4' : 'pb-6' %>" data-close-after-submit="<%= local_assigns.key?(:close_after_submit) ? local_assigns[:close_after_submit] : true %>">
<%= yield %>
</native-modal>
</turbo-frame>
<% elsif turbo_frame_request? %>
<turbo-frame id="modal"> <turbo-frame id="modal">
<turbo-modal class="modal modal-open items-start !animate-none overflow-y-auto !justify-normal md:!justify-center" data-close-after-submit="<%= local_assigns.key?(:close_after_submit) ? local_assigns[:close_after_submit] : true %>"> <turbo-modal class="modal modal-open items-start !animate-none overflow-y-auto !justify-normal md:!justify-center" data-close-after-submit="<%= local_assigns.key?(:close_after_submit) ? local_assigns[:close_after_submit] : true %>">
<div class="absolute top-0 bottom-0 right-0 left-0" data-action="click:turbo-modal#close"></div> <div class="absolute top-0 bottom-0 right-0 left-0" data-action="click:turbo-modal#close"></div>
@@ -17,11 +24,6 @@
</div> </div>
</turbo-modal> </turbo-modal>
</turbo-frame> </turbo-frame>
<% elsif turbo_native_app? %>
<% content_for(:native_title, local_assigns[:title]) if local_assigns[:title] %>
<native-modal class="block w-full [&>.px-5]:px-4" data-close-after-submit="<%= local_assigns.key?(:close_after_submit) ? local_assigns[:close_after_submit] : true %>">
<%= yield %>
</native-modal>
<% else %> <% else %>
<% if local_assigns[:title] %> <% if local_assigns[:title] %>
<div class="sticky top-0 z-10 flex justify-between bg-base-100 py-2 px-5 items-center border-b pb-2 font-medium"> <div class="sticky top-0 z-10 flex justify-between bg-base-100 py-2 px-5 items-center border-b pb-2 font-medium">
+4 -1
View File
@@ -50,8 +50,11 @@
<% elsif event.event_type == 'delegate_form' %> <% elsif event.event_type == 'delegate_form' %>
<% delegate_from = event.data['old_email'].presence || versions.rfind { |v| v.created_at <= event.event_timestamp }&.then { |v| v.name || v.phone } %> <% delegate_from = event.data['old_email'].presence || versions.rfind { |v| v.created_at <= event.event_timestamp }&.then { |v| v.name || v.phone } %>
<%= t('submission_event_names.delegate_form_by_html', from: delegate_from, to: event.data['email']) %> <%= t('submission_event_names.delegate_form_by_html', from: delegate_from, to: event.data['email']) %>
<% elsif event.event_type.include?('send_') %> <% elsif event.event_type.include?('send_') || event.event_type == 'fail_sms' %>
<%= t("submission_event_names.#{event.event_type}_to_html", submitter_name:) %> <%= t("submission_event_names.#{event.event_type}_to_html", submitter_name:) %>
<% if event.data['error_code'].present? %>
<span class="text-base-content/60">(<%= t('error') %>: <%= event.data['error_code'] %>)</span>
<% end %>
<% elsif event.event_type.start_with?('bounce_') || event.event_type.start_with?('complaint_') %> <% elsif event.event_type.start_with?('bounce_') || event.event_type.start_with?('complaint_') %>
<%= t("submission_event_names.#{event.event_type}_html", submitter_name:) %> <%= t("submission_event_names.#{event.event_type}_html", submitter_name:) %>
<% else %> <% else %>
+1 -1
View File
@@ -26,7 +26,7 @@
</div> </div>
</toggle-visible> </toggle-visible>
<% end %> <% end %>
<div class="px-5 mb-5 mt-5"> <div class="px-5 mt-5 <%= 'mb-5' unless turbo_native_app? %>" style="<%= 'padding-inline: 18px' if turbo_native_app? %>">
<% 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: %>
+2 -2
View File
@@ -134,9 +134,9 @@
<% document = @submission.schema_documents.find { |a| item['attachment_uuid'] == a.uuid } %> <% document = @submission.schema_documents.find { |a| item['attachment_uuid'] == a.uuid } %>
<% if document.preview_images.first %> <% if document.preview_images.first %>
<scroll-to data-selector-id="page-<%= document.uuid %>-0" class="block cursor-pointer"> <scroll-to data-selector-id="page-<%= document.uuid %>-0" class="block cursor-pointer">
<img src="<%= (document.preview_images.find { |e| e.filename.base.to_i.zero? } || document.preview_images.first).url(time: service_url_time) %>" width="<%= document.preview_images.first.metadata['width'] %>" height="<%= document.preview_images.first.metadata['height'] %>" class="rounded border" loading="lazy" alt="<%= item['name'].presence || document.filename.base %>"> <img src="<%= (document.preview_images.find { |e| e.filename.base.to_i.zero? } || document.preview_images.first).url(time: service_url_time) %>" width="<%= document.preview_images.first.metadata['width'] %>" height="<%= document.preview_images.first.metadata['height'] %>" class="rounded border" loading="lazy" alt="<%= item['name'].presence || FilenameUtils.basename(document.blob[:filename]) %>">
<div class="pb-2 pt-1.5 text-center" dir="auto"> <div class="pb-2 pt-1.5 text-center" dir="auto">
<%= item['name'].presence || document.filename.base %> <%= item['name'].presence || FilenameUtils.basename(document.blob[:filename]) %>
</div> </div>
</scroll-to> </scroll-to>
<% end %> <% end %>
+1 -1
View File
@@ -1,6 +1,6 @@
<% filter_params = params.permit(:q, :archived, *Submissions::Filter::ALLOWED_PARAMS) %> <% filter_params = params.permit(:q, :archived, *Submissions::Filter::ALLOWED_PARAMS) %>
<%= render 'shared/turbo_modal', title: t('export'), close_after_submit: false do %> <%= render 'shared/turbo_modal', title: t('export'), close_after_submit: false do %>
<div class="space-y-2 <%= 'mt-1.5 px-4' if turbo_native_android? %>"> <div class="space-y-2 <%= 'mt-1.5' if turbo_native_android? %>" style="<%= 'padding-inline: 18px' if turbo_native_app? %>">
<%= button_to template_submissions_export_index_path(@template), params: { format: :xlsx, **filter_params }, method: :get, data: { turbo_frame: :_top } do %> <%= button_to template_submissions_export_index_path(@template), params: { format: :xlsx, **filter_params }, method: :get, data: { turbo_frame: :_top } do %>
<div class="flex items-center p-4 text-left rounded-2xl border border-neutral-300 hover:cursor-pointer hover:bg-neutral hover:text-gray-300"> <div class="flex items-center p-4 text-left rounded-2xl border border-neutral-300 hover:cursor-pointer hover:bg-neutral hover:text-gray-300">
<div class="enabled"> <div class="enabled">
@@ -1,7 +1,7 @@
<% path_uri = Addressable::URI.parse(params[:path].to_s) %> <% path_uri = Addressable::URI.parse(params[:path].to_s) %>
<% path = url_for(**Rails.application.routes.recognize_path(path_uri.path, method: :get)) %> <% path = url_for(**Rails.application.routes.recognize_path(path_uri.path, method: :get)) %>
<%= render 'shared/turbo_modal', title: local_assigns[:title] do %> <%= render 'shared/turbo_modal', title: local_assigns[:title] do %>
<%= form_for '', url: path, method: :get, data: { turbo_frame: :_top }, html: { autocomplete: :off, class: ('px-4' if turbo_native_android?) } do |f| %> <%= form_for '', url: path, method: :get, data: { turbo_frame: :_top }, html: { autocomplete: :off, style: ('padding-inline: 18px' if turbo_native_app?) } do |f| %>
<%= hidden_field_tag :q, params[:q] if params[:q].present? %> <%= hidden_field_tag :q, params[:q] if params[:q].present? %>
<%= hidden_field_tag :scope, params[:scope] if params[:scope].present? %> <%= hidden_field_tag :scope, params[:scope] if params[:scope].present? %>
<%= hidden_field_tag :archived, params[:archived] if params[:archived].present? %> <%= hidden_field_tag :archived, params[:archived] if params[:archived].present? %>
+1 -1
View File
@@ -1,5 +1,5 @@
<%= render 'shared/turbo_modal_large', title: t('edit_recipient') do %> <%= render 'shared/turbo_modal_large', title: t('edit_recipient') do %>
<div class="px-5 mb-5 mt-4"> <div class="px-5 mt-4 <%= 'mb-5' unless turbo_native_app? %>" style="<%= 'padding-inline: 18px' if turbo_native_app? %>">
<%= form_for '', url: submitter_path(@submitter), method: :patch, html: { autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %> <%= form_for '', url: submitter_path(@submitter), method: :patch, html: { autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
<div class="mb-2"> <div class="mb-2">
<submitter-item class="form-control"> <submitter-item class="form-control">
+1 -1
View File
@@ -1,5 +1,5 @@
<%= render 'shared/turbo_modal', title: t('rename_folder') do %> <%= render 'shared/turbo_modal', title: t('rename_folder') do %>
<%= form_for @template_folder, url: folder_path(@template_folder), data: { turbo_frame: :_top }, html: { autocomplete: :off, class: ('px-4' if turbo_native_android?) } do |f| %> <%= form_for @template_folder, url: folder_path(@template_folder), data: { turbo_frame: :_top }, html: { autocomplete: :off, style: ('padding-inline: 18px' if turbo_native_app?) } do |f| %>
<div class="form-control <%= turbo_native_android? ? 'mt-1.5 mb-6' : 'my-6' %>"> <div class="form-control <%= turbo_native_android? ? 'mt-1.5 mb-6' : 'my-6' %>">
<%= f.text_field :name, required: true, placeholder: "#{t('folder_name')}...", class: 'base-input w-full', autofocus: true, dir: 'auto' %> <%= f.text_field :name, required: true, placeholder: "#{t('folder_name')}...", class: 'base-input w-full', autofocus: true, dir: 'auto' %>
</div> </div>
+1 -1
View File
@@ -1,5 +1,5 @@
<%= render 'shared/turbo_modal', title: @base_template ? t('clone_template') : t('new_document_template') do %> <%= render 'shared/turbo_modal', title: @base_template ? t('clone_template') : t('new_document_template') do %>
<div class="<%= 'px-4' if turbo_native_app? %>"> <div style="<%= 'padding-inline: 18px' if turbo_native_app? %>">
<%= render 'templates/file_form' %> <%= render 'templates/file_form' %>
</div> </div>
<% end %> <% end %>
+1 -1
View File
@@ -1,4 +1,4 @@
<%= form_for @template, url: template_clone_index_path(@base_template), data: { turbo_frame: :_top }, html: { autocomplete: :off, class: ('px-4' if turbo_native_android?) } do |f| %> <%= form_for @template, url: template_clone_index_path(@base_template), data: { turbo_frame: :_top }, html: { autocomplete: :off, style: ('padding-inline: 18px' if turbo_native_app?) } do |f| %>
<% accounts = Account.accessible_by(true_ability).where.not(id: true_user.account.testing_accounts).where(User.where(User.arel_table[:account_id].eq(Account.arel_table[:id])).arel.exists).active %> <% accounts = Account.accessible_by(true_ability).where.not(id: true_user.account.testing_accounts).where(User.where(User.arel_table[:account_id].eq(Account.arel_table[:id])).arel.exists).active %>
<% with_accounts = (can?(:manage, :tenants) || true_user != current_user) && accounts.where.not(id: current_account.id).exists? %> <% with_accounts = (can?(:manage, :tenants) || true_user != current_user) && accounts.where.not(id: current_account.id).exists? %>
<% if with_accounts %> <% if with_accounts %>
+2 -2
View File
@@ -1,7 +1,7 @@
<% with_subfolder = @template.folder.name != TemplateFolder::DEFAULT_NAME && params[:subfolder] != 'false' %> <% with_subfolder = @template.folder.name != TemplateFolder::DEFAULT_NAME && params[:subfolder] != 'false' %>
<%= render 'shared/turbo_modal', title: t('move_into_folder') do %> <%= render 'shared/turbo_modal', title: t('move_into_folder') do %>
<% if with_subfolder %> <% if with_subfolder %>
<%= form_for '', url: template_folder_path(@template), method: :put, data: { turbo_frame: :_top }, html: { id: 'subfolder_form', autocomplete: :off, class: ('px-4' if turbo_native_android?) } do |f| %> <%= form_for '', url: template_folder_path(@template), method: :put, data: { turbo_frame: :_top }, html: { id: 'subfolder_form', autocomplete: :off, style: ('padding-inline: 18px' if turbo_native_app?) } do |f| %>
<%= f.hidden_field :parent_name, value: @template.folder.parent_folder&.name || @template.folder.name %> <%= f.hidden_field :parent_name, value: @template.folder.parent_folder&.name || @template.folder.name %>
<toggle-visible data-element-ids="<%= %w[folder_form subfolder_form].to_json %>" class="block relative" data-focus-id="name"> <toggle-visible data-element-ids="<%= %w[folder_form subfolder_form].to_json %>" class="block relative" data-focus-id="name">
<div class="flex items-center justify-between mb-2.5"> <div class="flex items-center justify-between mb-2.5">
@@ -25,7 +25,7 @@
</div> </div>
<% end %> <% end %>
<% end %> <% end %>
<%= form_for '', url: template_folder_path(@template), method: :put, data: { turbo_frame: :_top }, html: { id: 'folder_form', autocomplete: :off, class: "#{turbo_native_android? ? 'mt-1.5 px-4' : 'mt-6'} #{'hidden' if with_subfolder}" } do |f| %> <%= form_for '', url: template_folder_path(@template), method: :put, data: { turbo_frame: :_top }, html: { id: 'folder_form', autocomplete: :off, class: "#{turbo_native_android? ? 'mt-1.5' : 'mt-6'} #{'hidden' if with_subfolder}", style: ('padding-inline: 18px' if turbo_native_app?) } do |f| %>
<div class="form-control mb-6"> <div class="form-control mb-6">
<folder-autocomplete class="block" data-submit-on-select="true" data-enabled="<%= params[:autocomplete] != 'false' %>"> <folder-autocomplete class="block" data-submit-on-select="true" data-enabled="<%= params[:autocomplete] != 'false' %>">
<%= f.text_field :name, required: true, placeholder: "#{t('new_folder_name')}...", class: 'base-input w-full', autofocus: true %> <%= f.text_field :name, required: true, placeholder: "#{t('new_folder_name')}...", class: 'base-input w-full', autofocus: true %>
@@ -18,7 +18,7 @@
</div> </div>
</toggle-visible> </toggle-visible>
<% end %> <% end %>
<div id="general" class="px-5 mb-4"> <div id="general" class="px-5 mb-4" style="<%= 'padding-inline: 18px' if turbo_native_app? %>">
<%= render 'access' %> <%= render 'access' %>
<%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-2' }, data: { close_on_submit: false } do |f| %> <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-2' }, data: { close_on_submit: false } do |f| %>
<toggle-on-submit data-element-id="bcc_saved_alert"></toggle-on-submit> <toggle-on-submit data-element-id="bcc_saved_alert"></toggle-on-submit>
@@ -146,14 +146,14 @@
<%= render 'templates_code_modal/preferences', class: 'pt-2' %> <%= render 'templates_code_modal/preferences', class: 'pt-2' %>
</div> </div>
<% if show_recipients %> <% if show_recipients %>
<div id="recipients" class="hidden mt-2 mb-4 px-5 pt-1 pb-2"> <div id="recipients" class="hidden mt-2 mb-4 px-5 pt-1 pb-2" style="<%= 'padding-inline: 18px' if turbo_native_app? %>">
<native-event data-on="submit" data-name="template-builder:update" class="block"> <native-event data-on="submit" data-name="template-builder:update" class="block">
<%= render 'recipients', template: @template %> <%= render 'recipients', template: @template %>
</native-event> </native-event>
</div> </div>
<% end %> <% end %>
<% if show_api %> <% if show_api %>
<div id="api" class="hidden mt-2 mb-4 px-5"> <div id="api" class="hidden mt-2 mb-4 px-5" style="<%= 'padding-inline: 18px' if turbo_native_app? %>">
<div> <div>
<label class="text-sm font-semibold" for="template_id"> <label class="text-sm font-semibold" for="template_id">
<%= t('template_id') %> <%= t('template_id') %>
+1 -1
View File
@@ -1,7 +1,7 @@
<% multiple_submitters = @template.submitters.to_a.length > 1 %> <% multiple_submitters = @template.submitters.to_a.length > 1 %>
<% enough_defined_submitters = Templates.filter_undefined_submitters(@template.submitters).size < 2 %> <% enough_defined_submitters = Templates.filter_undefined_submitters(@template.submitters).size < 2 %>
<%= render 'shared/turbo_modal_large', title: t('share_link') do %> <%= render 'shared/turbo_modal_large', title: t('share_link') do %>
<div class="<%= 'mt-2' unless turbo_native_android? %> mb-4 px-5"> <div class="<%= 'mt-2' unless turbo_native_android? %> <%= 'mb-4' unless turbo_native_app? %> px-5" style="<%= 'padding-inline: 18px' if turbo_native_app? %>">
<% if @template.variables_schema.present? %> <% if @template.variables_schema.present? %>
<div class="flex items-start gap-4 p-4 border border-base-300 rounded-2xl bg-base-100 mt-4"> <div class="flex items-start gap-4 p-4 border border-base-300 rounded-2xl bg-base-100 mt-4">
<%= svg_icon('info_circle', class: 'stroke-current shrink-0 h-6 w-6') %> <%= svg_icon('info_circle', class: 'stroke-current shrink-0 h-6 w-6') %>
+2 -2
View File
@@ -12,7 +12,7 @@
<% end %> <% end %>
</div> </div>
</toggle-visible> </toggle-visible>
<div id="draw" class="mt-3 <%= 'px-4' if turbo_native_android? %>"> <div id="draw" class="mt-3" style="<%= 'padding-inline: 18px' if turbo_native_app? %>">
<%= form_for @user_config, url: user_initials_path, method: :put, data: { turbo_frame: :_top }, html: { autocomplete: :off, enctype: 'multipart/form-data' } do |f| %> <%= form_for @user_config, url: user_initials_path, method: :put, data: { turbo_frame: :_top }, html: { autocomplete: :off, enctype: 'multipart/form-data' } do |f| %>
<signature-form class="relative block"> <signature-form class="relative block">
<a class="absolute top-1 right-1 link text-sm" data-target="signature-form.clear" href="#"><%= t('clear') %></a> <a class="absolute top-1 right-1 link text-sm" data-target="signature-form.clear" href="#"><%= t('clear') %></a>
@@ -24,7 +24,7 @@
</signature-form> </signature-form>
<% end %> <% end %>
</div> </div>
<div id="upload" class="hidden mt-3 <%= 'px-4' if turbo_native_android? %>"> <div id="upload" class="hidden mt-3" style="<%= 'padding-inline: 18px' if turbo_native_app? %>">
<%= form_for @user_config, url: user_initials_path, method: :put, data: { turbo_frame: :_top }, html: { autocomplete: :off, enctype: 'multipart/form-data' } do |f| %> <%= form_for @user_config, url: user_initials_path, method: :put, data: { turbo_frame: :_top }, html: { autocomplete: :off, enctype: 'multipart/form-data' } do |f| %>
<file-dropzone data-submit-on-upload="true" class="w-full"> <file-dropzone data-submit-on-upload="true" class="w-full">
<label for="file" class="w-full block h-32 relative bg-base-200 hover:bg-base-200/70 rounded-md border border-base-content border-dashed"> <label for="file" class="w-full block h-32 relative bg-base-200 hover:bg-base-200/70 rounded-md border border-base-content border-dashed">
+2 -2
View File
@@ -12,7 +12,7 @@
<% end %> <% end %>
</div> </div>
</toggle-visible> </toggle-visible>
<div id="draw" class="mt-3 <%= 'px-4' if turbo_native_android? %>"> <div id="draw" class="mt-3" style="<%= 'padding-inline: 18px' if turbo_native_app? %>">
<%= form_for @user_config, url: user_signature_path, method: :put, data: { turbo_frame: :_top }, html: { autocomplete: :off, enctype: 'multipart/form-data' } do |f| %> <%= form_for @user_config, url: user_signature_path, method: :put, data: { turbo_frame: :_top }, html: { autocomplete: :off, enctype: 'multipart/form-data' } do |f| %>
<signature-form class="relative block"> <signature-form class="relative block">
<a class="absolute top-1 right-1 link text-sm" data-target="signature-form.clear" href="#"><%= t('clear') %></a> <a class="absolute top-1 right-1 link text-sm" data-target="signature-form.clear" href="#"><%= t('clear') %></a>
@@ -24,7 +24,7 @@
</signature-form> </signature-form>
<% end %> <% end %>
</div> </div>
<div id="upload" class="hidden mt-3 <%= 'px-4' if turbo_native_android? %>"> <div id="upload" class="hidden mt-3" style="<%= 'padding-inline: 18px' if turbo_native_app? %>">
<%= form_for @user_config, url: user_signature_path, method: :put, data: { turbo_frame: :_top }, html: { autocomplete: :off, enctype: 'multipart/form-data' } do |f| %> <%= form_for @user_config, url: user_signature_path, method: :put, data: { turbo_frame: :_top }, html: { autocomplete: :off, enctype: 'multipart/form-data' } do |f| %>
<file-dropzone data-submit-on-upload="true" class="w-full"> <file-dropzone data-submit-on-upload="true" class="w-full">
<label for="file" class="w-full block h-32 relative bg-base-200 hover:bg-base-200/70 rounded-md border border-base-content border-dashed"> <label for="file" class="w-full block h-32 relative bg-base-200 hover:bg-base-200/70 rounded-md border border-base-content border-dashed">
+1 -1
View File
@@ -1,4 +1,4 @@
<%= form_for user, html: { class: "space-y-4 #{'px-4' if turbo_native_android?}" }, data: { turbo_frame: :_top } do |f| %> <%= form_for user, html: { class: 'space-y-4', style: ('padding-inline: 18px' if turbo_native_app?) }, data: { turbo_frame: :_top } do |f| %>
<div class="space-y-2"> <div class="space-y-2">
<div class="flex space-x-4"> <div class="flex space-x-4">
<div class="w-full"> <div class="w-full">
+1 -1
View File
@@ -25,7 +25,7 @@ module DocuSeal
config.active_storage.routes_prefix = '' config.active_storage.routes_prefix = ''
config.active_storage.draw_routes = ENV['MULTITENANT'] != 'true' config.active_storage.draw_routes = false
config.active_storage.analyzers = [] config.active_storage.analyzers = []
+8 -1
View File
@@ -166,7 +166,14 @@ Rails.application.configure do
current_user = controller.instance_variable_get(:@current_user) current_user = controller.instance_variable_get(:@current_user)
os = DetectBrowserDevice.os(controller.request.user_agent) if ENV['MULTITENANT'] == 'true' if ENV['MULTITENANT'] == 'true'
os =
if defined?(controller.browser_os)
controller.browser_os
else
DetectBrowserDevice.os(controller.request.user_agent)
end
end
{ {
host: controller.request.host, host: controller.request.host,
+2 -20
View File
@@ -63,26 +63,8 @@ end
ActiveStorage::LogSubscriber.detach_from(:active_storage) if Rails.env.production? ActiveStorage::LogSubscriber.detach_from(:active_storage) if Rails.env.production?
Rails.configuration.to_prepare do Rails.configuration.to_prepare do
ActiveStorage::DiskController.after_action do ActiveStorage::DiskController.after_action(only: :show) do
response.set_header('cache-control', 'public, max-age=31536000') if action_name == 'show' response.set_header('cache-control', 'public, max-age=31536000')
end
ActiveStorage::Blobs::ProxyController.before_action do
response.set_header('Access-Control-Allow-Origin', '*')
response.set_header('Access-Control-Allow-Methods', 'GET')
response.set_header('Access-Control-Allow-Headers', '*')
response.set_header('Access-Control-Max-Age', '1728000')
end
ActiveStorage::Blobs::RedirectController.before_action do
response.set_header('Access-Control-Allow-Origin', '*')
response.set_header('Access-Control-Allow-Methods', 'GET')
response.set_header('Access-Control-Allow-Headers', '*')
response.set_header('Access-Control-Max-Age', '1728000')
end
ActiveStorage::DirectUploadsController.before_action do
head :forbidden
end end
LoadActiveStorageConfigs.call LoadActiveStorageConfigs.call
+28
View File
@@ -830,6 +830,8 @@ en: &en
click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: <a href="%{url}" class="link">Click here</a> to update your payment details and clear the invoice to ensure uninterrupted service. click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: <a href="%{url}" class="link">Click here</a> to update your payment details and clear the invoice to ensure uninterrupted service.
overdue_payment: Overdue Payment overdue_payment: Overdue Payment
your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Your Pro Plan has been suspended due to unpaid invoices. You can update your payment details to settle the invoice and continue using DocuSeal or cancel your subscription. your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Your Pro Plan has been suspended due to unpaid invoices. You can update your payment details to settle the invoice and continue using DocuSeal or cancel your subscription.
get_the_docuseal_app_for_a_better_mobile_experience: Get the DocuSeal app for a better mobile experience.
install_app: Install app
manage_subscription: Manage Subscription manage_subscription: Manage Subscription
docuseal_version_security_release_is_available: "DocuSeal v%{version} security release is available." docuseal_version_security_release_is_available: "DocuSeal v%{version} security release is available."
click_here_to_view_the_instructions_and_update_your_app_html: '<a href="%{url}" class="link" target="_blank" rel="noopener noreferrer nofollow">Click here</a> to view the instructions and update your app.' click_here_to_view_the_instructions_and_update_your_app_html: '<a href="%{url}" class="link" target="_blank" rel="noopener noreferrer nofollow">Click here</a> to view the instructions and update your app.'
@@ -1046,10 +1048,12 @@ en: &en
link: Link link: Link
mcp: MCP mcp: MCP
self: Sign Yourself self: Sign Yourself
error: Error
submission_event_names: submission_event_names:
send_email_to_html: '<b>Email sent</b> to %{submitter_name}' send_email_to_html: '<b>Email sent</b> to %{submitter_name}'
bounce_email_html: '<b>Email bounced</b> %{submitter_name}' bounce_email_html: '<b>Email bounced</b> %{submitter_name}'
complaint_email_html: '<b>Spam complaint</b> %{submitter_name}' complaint_email_html: '<b>Spam complaint</b> %{submitter_name}'
fail_sms_to_html: '<b>SMS failed</b> to %{submitter_name}'
send_reminder_email_to_html: '<b>Reminder email sent</b> to %{submitter_name}' send_reminder_email_to_html: '<b>Reminder email sent</b> to %{submitter_name}'
send_sms_to_html: '<b>SMS sent</b> to %{submitter_name}' send_sms_to_html: '<b>SMS sent</b> to %{submitter_name}'
send_2fa_sms_to_html: '<b>Verification SMS sent</b> to %{submitter_name}' send_2fa_sms_to_html: '<b>Verification SMS sent</b> to %{submitter_name}'
@@ -1988,6 +1992,8 @@ es: &es
click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: '<a href="%{url}" class="link">Haz clic aquí</a> para actualizar tus datos de pago y liquidar la factura para garantizar un servicio ininterrumpido.' click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: '<a href="%{url}" class="link">Haz clic aquí</a> para actualizar tus datos de pago y liquidar la factura para garantizar un servicio ininterrumpido.'
overdue_payment: Pago Atrasado overdue_payment: Pago Atrasado
your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Tu plan Pro ha sido suspendido debido a facturas impagas. Puedes actualizar tus datos de pago para liquidar la factura y seguir usando DocuSeal o cancelar tu suscripción. your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Tu plan Pro ha sido suspendido debido a facturas impagas. Puedes actualizar tus datos de pago para liquidar la factura y seguir usando DocuSeal o cancelar tu suscripción.
get_the_docuseal_app_for_a_better_mobile_experience: Descarga la app de DocuSeal para una mejor experiencia móvil.
install_app: Instalar app
manage_subscription: Gestionar Suscripción manage_subscription: Gestionar Suscripción
docuseal_version_security_release_is_available: "La versión de seguridad DocuSeal v%{version} está disponible." docuseal_version_security_release_is_available: "La versión de seguridad DocuSeal v%{version} está disponible."
click_here_to_view_the_instructions_and_update_your_app_html: '<a href="%{url}" class="link" target="_blank" rel="noopener noreferrer nofollow">Haz clic aquí</a> para ver las instrucciones y actualizar tu aplicación.' click_here_to_view_the_instructions_and_update_your_app_html: '<a href="%{url}" class="link" target="_blank" rel="noopener noreferrer nofollow">Haz clic aquí</a> para ver las instrucciones y actualizar tu aplicación.'
@@ -2196,10 +2202,12 @@ es: &es
link: Enlace link: Enlace
mcp: MCP mcp: MCP
self: Firma tú mismo self: Firma tú mismo
error: Error
submission_event_names: submission_event_names:
send_email_to_html: '<b>Correo electrónico enviado</b> a %{submitter_name}' send_email_to_html: '<b>Correo electrónico enviado</b> a %{submitter_name}'
bounce_email_html: '<b>Correo electrónico rebotado</b> %{submitter_name}' bounce_email_html: '<b>Correo electrónico rebotado</b> %{submitter_name}'
complaint_email_html: '<b>Queja de spam</b> %{submitter_name}' complaint_email_html: '<b>Queja de spam</b> %{submitter_name}'
fail_sms_to_html: '<b>SMS fallido</b> a %{submitter_name}'
send_reminder_email_to_html: '<b>Correo de recordatorio enviado</b> a %{submitter_name}' send_reminder_email_to_html: '<b>Correo de recordatorio enviado</b> a %{submitter_name}'
send_sms_to_html: '<b>SMS enviado</b> a %{submitter_name}' send_sms_to_html: '<b>SMS enviado</b> a %{submitter_name}'
send_2fa_sms_to_html: '<b>SMS de verificación enviado</b> a %{submitter_name}' send_2fa_sms_to_html: '<b>SMS de verificación enviado</b> a %{submitter_name}'
@@ -3152,6 +3160,8 @@ it: &it
click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: '<a href="%{url}" class="link">Fai clic qui</a> per aggiornare i tuoi dati di pagamento e saldare la fattura per garantire un servizio ininterrotto.' click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: '<a href="%{url}" class="link">Fai clic qui</a> per aggiornare i tuoi dati di pagamento e saldare la fattura per garantire un servizio ininterrotto.'
overdue_payment: Pagamento Scaduto overdue_payment: Pagamento Scaduto
your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Il tuo piano Pro è stato sospeso a causa di fatture non pagate. Puoi aggiornare i tuoi dati di pagamento per saldare la fattura e continuare a utilizzare DocuSeal o annullare l'abbonamento. your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Il tuo piano Pro è stato sospeso a causa di fatture non pagate. Puoi aggiornare i tuoi dati di pagamento per saldare la fattura e continuare a utilizzare DocuSeal o annullare l'abbonamento.
get_the_docuseal_app_for_a_better_mobile_experience: Scarica l'app DocuSeal per un'esperienza mobile migliore.
install_app: Installa app
manage_subscription: Gestisci Abbonamento manage_subscription: Gestisci Abbonamento
docuseal_version_security_release_is_available: "È disponibile il rilascio di sicurezza DocuSeal v%{version}." docuseal_version_security_release_is_available: "È disponibile il rilascio di sicurezza DocuSeal v%{version}."
click_here_to_view_the_instructions_and_update_your_app_html: '<a href="%{url}" class="link" target="_blank" rel="noopener noreferrer nofollow">Fai clic qui</a> per vedere le istruzioni e aggiornare la tua app.' click_here_to_view_the_instructions_and_update_your_app_html: '<a href="%{url}" class="link" target="_blank" rel="noopener noreferrer nofollow">Fai clic qui</a> per vedere le istruzioni e aggiornare la tua app.'
@@ -3360,10 +3370,12 @@ it: &it
link: Link link: Link
mcp: MCP mcp: MCP
self: Firma te stesso self: Firma te stesso
error: Errore
submission_event_names: submission_event_names:
send_email_to_html: '<b>Email inviata</b> a %{submitter_name}' send_email_to_html: '<b>Email inviata</b> a %{submitter_name}'
bounce_email_html: '<b>Email respinta</b> %{submitter_name}' bounce_email_html: '<b>Email respinta</b> %{submitter_name}'
complaint_email_html: '<b>Segnalazione spam</b> %{submitter_name}' complaint_email_html: '<b>Segnalazione spam</b> %{submitter_name}'
fail_sms_to_html: '<b>SMS non consegnato</b> a %{submitter_name}'
send_reminder_email_to_html: '<b>E-mail di promemoria inviata</b> a %{submitter_name}' send_reminder_email_to_html: '<b>E-mail di promemoria inviata</b> a %{submitter_name}'
send_sms_to_html: '<b>SMS inviato</b> a %{submitter_name}' send_sms_to_html: '<b>SMS inviato</b> a %{submitter_name}'
send_2fa_sms_to_html: '<b>SMS di verifica inviato</b> a %{submitter_name}' send_2fa_sms_to_html: '<b>SMS di verifica inviato</b> a %{submitter_name}'
@@ -4302,6 +4314,8 @@ fr: &fr
click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: <a href="%{url}" class="link">Cliquez ici</a> pour mettre à jour vos informations de paiement et régler la facture afin de garantir la continuité du service. click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: <a href="%{url}" class="link">Cliquez ici</a> pour mettre à jour vos informations de paiement et régler la facture afin de garantir la continuité du service.
overdue_payment: Paiement en retard overdue_payment: Paiement en retard
your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: "Votre offre Pro a été suspendue en raison de factures impayées.\nVous pouvez mettre à jour vos informations de paiement pour régler la facture et continuer à utiliser DocuSeal, ou annuler votre abonnement." your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: "Votre offre Pro a été suspendue en raison de factures impayées.\nVous pouvez mettre à jour vos informations de paiement pour régler la facture et continuer à utiliser DocuSeal, ou annuler votre abonnement."
get_the_docuseal_app_for_a_better_mobile_experience: Téléchargez l’application DocuSeal pour une meilleure expérience mobile.
install_app: Installer l’application
manage_subscription: Gérer l’abonnement manage_subscription: Gérer l’abonnement
docuseal_version_security_release_is_available: "La mise à jour de sécurité DocuSeal v%{version} est disponible." docuseal_version_security_release_is_available: "La mise à jour de sécurité DocuSeal v%{version} est disponible."
click_here_to_view_the_instructions_and_update_your_app_html: '<a href="%{url}" class="link" target="_blank" rel="noopener noreferrer nofollow">Cliquez ici</a> pour voir les instructions et mettre à jour votre application.' click_here_to_view_the_instructions_and_update_your_app_html: '<a href="%{url}" class="link" target="_blank" rel="noopener noreferrer nofollow">Cliquez ici</a> pour voir les instructions et mettre à jour votre application.'
@@ -4507,10 +4521,12 @@ fr: &fr
link: Lien link: Lien
mcp: MCP mcp: MCP
self: Signer vous-même self: Signer vous-même
error: Erreur
submission_event_names: submission_event_names:
send_email_to_html: "<b>E‑mail envoyé</b> à %{submitter_name}" send_email_to_html: "<b>E‑mail envoyé</b> à %{submitter_name}"
bounce_email_html: "<b>E‑mail rejeté</b> %{submitter_name}" bounce_email_html: "<b>E‑mail rejeté</b> %{submitter_name}"
complaint_email_html: "<b>Plainte spam</b> %{submitter_name}" complaint_email_html: "<b>Plainte spam</b> %{submitter_name}"
fail_sms_to_html: "<b>SMS non distribué</b> à %{submitter_name}"
send_reminder_email_to_html: "<b>E‑mail de rappel envoyé</b> à %{submitter_name}" send_reminder_email_to_html: "<b>E‑mail de rappel envoyé</b> à %{submitter_name}"
send_sms_to_html: "<b>SMS envoyé</b> à %{submitter_name}" send_sms_to_html: "<b>SMS envoyé</b> à %{submitter_name}"
send_2fa_sms_to_html: "<b>SMS de vérification envoyé</b> à %{submitter_name}" send_2fa_sms_to_html: "<b>SMS de vérification envoyé</b> à %{submitter_name}"
@@ -5462,6 +5478,8 @@ pt: &pt
click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: '<a href="%{url}" class="link">Clique aqui</a> para atualizar seus dados de pagamento e quitar a fatura para garantir um serviço ininterrupto.' click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: '<a href="%{url}" class="link">Clique aqui</a> para atualizar seus dados de pagamento e quitar a fatura para garantir um serviço ininterrupto.'
overdue_payment: Pagamento Atrasado overdue_payment: Pagamento Atrasado
your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Seu plano Pro foi suspenso devido a faturas não pagas. Você pode atualizar seus dados de pagamento para quitar a fatura e continuar usando o DocuSeal ou cancelar sua assinatura. your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Seu plano Pro foi suspenso devido a faturas não pagas. Você pode atualizar seus dados de pagamento para quitar a fatura e continuar usando o DocuSeal ou cancelar sua assinatura.
get_the_docuseal_app_for_a_better_mobile_experience: Baixe o app DocuSeal para uma melhor experiência no celular.
install_app: Instalar app
manage_subscription: Gerenciar Assinatura manage_subscription: Gerenciar Assinatura
docuseal_version_security_release_is_available: "A versão de segurança DocuSeal v%{version} está disponível." docuseal_version_security_release_is_available: "A versão de segurança DocuSeal v%{version} está disponível."
click_here_to_view_the_instructions_and_update_your_app_html: '<a href="%{url}" class="link" target="_blank" rel="noopener noreferrer nofollow">Clique aqui</a> para ver as instruções e atualizar seu aplicativo.' click_here_to_view_the_instructions_and_update_your_app_html: '<a href="%{url}" class="link" target="_blank" rel="noopener noreferrer nofollow">Clique aqui</a> para ver as instruções e atualizar seu aplicativo.'
@@ -5670,10 +5688,12 @@ pt: &pt
link: Link link: Link
mcp: MCP mcp: MCP
self: Assine você mesmo self: Assine você mesmo
error: Erro
submission_event_names: submission_event_names:
send_email_to_html: '<b>Email enviado</b> para %{submitter_name}' send_email_to_html: '<b>Email enviado</b> para %{submitter_name}'
bounce_email_html: '<b>Email não entregue</b> %{submitter_name}' bounce_email_html: '<b>Email não entregue</b> %{submitter_name}'
complaint_email_html: '<b>Reclamação de spam</b> %{submitter_name}' complaint_email_html: '<b>Reclamação de spam</b> %{submitter_name}'
fail_sms_to_html: '<b>SMS não entregue</b> para %{submitter_name}'
send_reminder_email_to_html: '<b>Email de lembrete enviado</b> para %{submitter_name}' send_reminder_email_to_html: '<b>Email de lembrete enviado</b> para %{submitter_name}'
send_sms_to_html: '<b>SMS enviado</b> para %{submitter_name}' send_sms_to_html: '<b>SMS enviado</b> para %{submitter_name}'
send_2fa_sms_to_html: '<b>SMS de verificação enviado</b> para %{submitter_name}' send_2fa_sms_to_html: '<b>SMS de verificação enviado</b> para %{submitter_name}'
@@ -6625,6 +6645,8 @@ de: &de
click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: '<a href="%{url}" class="link">Klicken Sie hier</a>, um Ihre Zahlungsdaten zu aktualisieren und die Rechnung zu begleichen, um einen unterbrechungsfreien Service sicherzustellen.' click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: '<a href="%{url}" class="link">Klicken Sie hier</a>, um Ihre Zahlungsdaten zu aktualisieren und die Rechnung zu begleichen, um einen unterbrechungsfreien Service sicherzustellen.'
overdue_payment: Überfällige Zahlung overdue_payment: Überfällige Zahlung
your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Ihr Pro-Plan wurde aufgrund unbezahlter Rechnungen ausgesetzt. Sie können Ihre Zahlungsdaten aktualisieren, um die Rechnung zu begleichen und DocuSeal weiterhin zu nutzen, oder Ihr Abonnement kündigen. your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Ihr Pro-Plan wurde aufgrund unbezahlter Rechnungen ausgesetzt. Sie können Ihre Zahlungsdaten aktualisieren, um die Rechnung zu begleichen und DocuSeal weiterhin zu nutzen, oder Ihr Abonnement kündigen.
get_the_docuseal_app_for_a_better_mobile_experience: Holen Sie sich die DocuSeal-App für ein besseres mobiles Erlebnis.
install_app: App installieren
manage_subscription: Abonnement verwalten manage_subscription: Abonnement verwalten
docuseal_version_security_release_is_available: "Das Sicherheitsupdate DocuSeal v%{version} ist verfügbar." docuseal_version_security_release_is_available: "Das Sicherheitsupdate DocuSeal v%{version} ist verfügbar."
click_here_to_view_the_instructions_and_update_your_app_html: '<a href="%{url}" class="link" target="_blank" rel="noopener noreferrer nofollow">Klicken Sie hier</a>, um die Anleitung anzuzeigen und Ihre App zu aktualisieren.' click_here_to_view_the_instructions_and_update_your_app_html: '<a href="%{url}" class="link" target="_blank" rel="noopener noreferrer nofollow">Klicken Sie hier</a>, um die Anleitung anzuzeigen und Ihre App zu aktualisieren.'
@@ -6833,10 +6855,12 @@ de: &de
link: Link link: Link
mcp: MCP mcp: MCP
self: Selbst unterschreiben self: Selbst unterschreiben
error: Fehler
submission_event_names: submission_event_names:
send_email_to_html: '<b>E-Mail gesendet</b> an %{submitter_name}' send_email_to_html: '<b>E-Mail gesendet</b> an %{submitter_name}'
bounce_email_html: '<b>E-Mail unzustellbar</b> %{submitter_name}' bounce_email_html: '<b>E-Mail unzustellbar</b> %{submitter_name}'
complaint_email_html: '<b>Spam-Beschwerde</b> %{submitter_name}' complaint_email_html: '<b>Spam-Beschwerde</b> %{submitter_name}'
fail_sms_to_html: '<b>SMS unzustellbar</b> an %{submitter_name}'
send_reminder_email_to_html: '<b>Erinnerungs-E-Mail gesendet</b> an %{submitter_name}' send_reminder_email_to_html: '<b>Erinnerungs-E-Mail gesendet</b> an %{submitter_name}'
send_sms_to_html: '<b>SMS gesendet</b> an %{submitter_name}' send_sms_to_html: '<b>SMS gesendet</b> an %{submitter_name}'
send_2fa_sms_to_html: '<b>Verifizierungs-SMS gesendet</b> an %{submitter_name}' send_2fa_sms_to_html: '<b>Verifizierungs-SMS gesendet</b> an %{submitter_name}'
@@ -8316,6 +8340,8 @@ nl: &nl
click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: <a href="%{url}" class="link">Klik hier</a> om uw betaalgegevens bij te werken en de factuur te voldoen om ononderbroken service te garanderen. click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: <a href="%{url}" class="link">Klik hier</a> om uw betaalgegevens bij te werken en de factuur te voldoen om ononderbroken service te garanderen.
overdue_payment: Achterstallige betaling overdue_payment: Achterstallige betaling
your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Uw Pro-abonnement is opgeschort vanwege onbetaalde facturen. U kunt uw betaalgegevens bijwerken om de factuur te voldoen en DocuSeal te blijven gebruiken of uw abonnement opzeggen. your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Uw Pro-abonnement is opgeschort vanwege onbetaalde facturen. U kunt uw betaalgegevens bijwerken om de factuur te voldoen en DocuSeal te blijven gebruiken of uw abonnement opzeggen.
get_the_docuseal_app_for_a_better_mobile_experience: Download de DocuSeal-app voor een betere mobiele ervaring.
install_app: App installeren
manage_subscription: Abonnement beheren manage_subscription: Abonnement beheren
docuseal_version_security_release_is_available: "Beveiligingsupdate DocuSeal v%{version} is beschikbaar." docuseal_version_security_release_is_available: "Beveiligingsupdate DocuSeal v%{version} is beschikbaar."
click_here_to_view_the_instructions_and_update_your_app_html: '<a href="%{url}" class="link" target="_blank" rel="noopener noreferrer nofollow">Klik hier</a> om de instructies te bekijken en uw app bij te werken.' click_here_to_view_the_instructions_and_update_your_app_html: '<a href="%{url}" class="link" target="_blank" rel="noopener noreferrer nofollow">Klik hier</a> om de instructies te bekijken en uw app bij te werken.'
@@ -8520,10 +8546,12 @@ nl: &nl
link: Link link: Link
mcp: MCP mcp: MCP
self: Zelf ondertekenen self: Zelf ondertekenen
error: Fout
submission_event_names: submission_event_names:
send_email_to_html: "<b>E-mail verzonden</b> naar %{submitter_name}" send_email_to_html: "<b>E-mail verzonden</b> naar %{submitter_name}"
bounce_email_html: "<b>E-mail geretourneerd</b> %{submitter_name}" bounce_email_html: "<b>E-mail geretourneerd</b> %{submitter_name}"
complaint_email_html: "<b>Spamklacht</b> %{submitter_name}" complaint_email_html: "<b>Spamklacht</b> %{submitter_name}"
fail_sms_to_html: "<b>SMS niet afgeleverd</b> naar %{submitter_name}"
send_reminder_email_to_html: "<b>Herinneringsmail verzonden</b> naar %{submitter_name}" send_reminder_email_to_html: "<b>Herinneringsmail verzonden</b> naar %{submitter_name}"
send_sms_to_html: "<b>SMS verzonden</b> naar %{submitter_name}" send_sms_to_html: "<b>SMS verzonden</b> naar %{submitter_name}"
send_2fa_sms_to_html: "<b>Verificatie-SMS verzonden</b> naar %{submitter_name}" send_2fa_sms_to_html: "<b>Verificatie-SMS verzonden</b> naar %{submitter_name}"
+4 -4
View File
@@ -125,12 +125,12 @@ Rails.application.routes.draw do
resource :blobs_proxy, only: %i[show], path: '/blobs_proxy/:signed_uuid/*filename', resource :blobs_proxy, only: %i[show], path: '/blobs_proxy/:signed_uuid/*filename',
controller: 'api/active_storage_blobs_proxy' controller: 'api/active_storage_blobs_proxy'
if Docuseal.multitenant? if !Docuseal.multitenant? || Rails.env.local?
get '/disk/:encoded_key/*filename' => 'active_storage/disk#show', as: :rails_disk_service if Rails.env.local? get '/disk/:encoded_key/*filename' => 'active_storage/disk#show', as: :rails_disk_service
ActiveSupport.run_load_hooks(:multitenant_routes, self)
end end
ActiveSupport.run_load_hooks(:multitenant_routes, self) if Docuseal.multitenant?
resources :start_form, only: %i[show update], path: 'd', param: 'slug' do resources :start_form, only: %i[show update], path: 'd', param: 'slug' do
get :completed get :completed
end end
+45
View File
@@ -26,6 +26,9 @@ module DetectBrowserDevice
Silk Silk
/ix /ix
IOS_APP_USER_AGENT_REGEXP = /Hotwire Native iOS/
ANDROID_APP_USER_AGENT_REGEXP = /Hotwire Native Android/
WINDOWS_USER_AGENT_REGEXP = / WINDOWS_USER_AGENT_REGEXP = /
Windows| Windows|
Win64 | Win64 |
@@ -63,6 +66,42 @@ module DetectBrowserDevice
NetBSD NetBSD
/ix /ix
SEARCH_BOT_USER_AGENT_REGEXP = /
Googlebot |
Google-InspectionTool|
Storebot-Google |
AdsBot-Google |
Mediapartners-Google |
bingbot |
BingPreview |
adidxbot |
msnbot |
Slurp |
DuckDuckBot |
Baiduspider |
YandexBot |
YandexImages |
Applebot |
PetalBot |
SeznamBot |
Sogou |
Exabot |
Qwantify |
YisouSpider |
Bytespider |
AhrefsBot |
SemrushBot |
MJ12bot |
DotBot |
GPTBot |
OAI-SearchBot |
ClaudeBot |
Claude-SearchBot |
PerplexityBot |
CCBot |
Amazonbot
/ix
SDK_USER_AGENT_REGEXP = /\ADocuSeal (?<sdk>Ruby|Python|PHP|Java|C#|JS|Go|CLI) v/i SDK_USER_AGENT_REGEXP = /\ADocuSeal (?<sdk>Ruby|Python|PHP|Java|C#|JS|Go|CLI) v/i
def call(user_agent) def call(user_agent)
@@ -74,6 +113,10 @@ module DetectBrowserDevice
'desktop' 'desktop'
end end
def search_bot?(user_agent)
SEARCH_BOT_USER_AGENT_REGEXP.match?(user_agent.to_s)
end
def os(user_agent) def os(user_agent)
return if user_agent.blank? return if user_agent.blank?
@@ -82,6 +125,8 @@ module DetectBrowserDevice
return sdk.downcase if sdk return sdk.downcase if sdk
case user_agent case user_agent
when IOS_APP_USER_AGENT_REGEXP then 'iosapp'
when ANDROID_APP_USER_AGENT_REGEXP then 'andapp'
when WINDOWS_USER_AGENT_REGEXP then 'windows' when WINDOWS_USER_AGENT_REGEXP then 'windows'
when ANDROID_USER_AGENT_REGEXP then 'android' when ANDROID_USER_AGENT_REGEXP then 'android'
when IOS_USER_AGENT_REGEXP then 'ios' when IOS_USER_AGENT_REGEXP then 'ios'
+2
View File
@@ -11,6 +11,8 @@ module Docuseal
TWITTER_URL = 'https://twitter.com/docusealco' TWITTER_URL = 'https://twitter.com/docusealco'
TWITTER_HANDLE = '@docusealco' TWITTER_HANDLE = '@docusealco'
CHATGPT_URL = "#{PRODUCT_URL}/chat".freeze CHATGPT_URL = "#{PRODUCT_URL}/chat".freeze
APP_STORE_URL = 'https://apps.apple.com/us/app/docuseal-e-signature/id6823564236'
GOOGLE_PLAY_URL = 'https://play.google.com/store/apps/details?id=com.docuseal.app'
SUPPORT_EMAIL = 'support@docuseal.com' SUPPORT_EMAIL = 'support@docuseal.com'
HOST = ENV.fetch('HOST', 'localhost') HOST = ENV.fetch('HOST', 'localhost')
AATL_CERT_NAME = 'docuseal_aatl' AATL_CERT_NAME = 'docuseal_aatl'
+4
View File
@@ -18,4 +18,8 @@ module FilenameUtils
def dangerous_extension(filename) def dangerous_extension(filename)
ActiveStorage::Filename.wrap(filename).sanitized[DANGEROUS_EXTENSIONS_REGEXP, 1]&.downcase ActiveStorage::Filename.wrap(filename).sanitized[DANGEROUS_EXTENSIONS_REGEXP, 1]&.downcase
end end
def basename(filename)
File.basename(filename, File.extname(filename))
end
end end
+1 -1
View File
@@ -469,7 +469,7 @@ module Submissions
end end
events_data = submission.submission_events.sort_by(&:event_timestamp).filter_map do |event| events_data = submission.submission_events.sort_by(&:event_timestamp).filter_map do |event|
next if event.event_type.in?(%w[bounce_email complaint_email]) next if event.event_type.in?(%w[bounce_email complaint_email fail_sms])
submitter = submission.submitters.find { |e| e.id == event.submitter_id } submitter = submission.submitters.find { |e| e.id == event.submitter_id }
versions = submitter_versions_index[submitter.id] || [] versions = submitter_versions_index[submitter.id] || []
+1 -1
View File
@@ -232,7 +232,7 @@ module Submitters
def build_document_filename(submitter, blob, filename_format) def build_document_filename(submitter, blob, filename_format)
return blob.filename.to_s if filename_format.blank? return blob.filename.to_s if filename_format.blank?
filename = filename_format.gsub('{document.name}', blob.filename.base) filename = filename_format.gsub('{document.name}', FilenameUtils.basename(blob[:filename]))
filename = ReplaceEmailVariables.call(filename, submitter:) filename = ReplaceEmailVariables.call(filename, submitter:)
filename = filename.gsub(' - {submission.status}') do filename = filename.gsub(' - {submission.status}') do
+8 -1
View File
@@ -8,6 +8,13 @@ module Submitters
methods: %i[status application_key] methods: %i[status application_key]
}.freeze }.freeze
EVENT_DATA_KEYS = {
'decline_form' => %w[reason],
'complete_verification' => %w[firstname lastname method country idcode],
'fail_kba' => %w[issues],
'fail_sms' => %w[status error_code]
}.freeze
module_function module_function
def call(submitter, with_template: false, with_events: false, with_documents: true, with_urls: false, def call(submitter, with_template: false, with_events: false, with_documents: true, with_urls: false,
@@ -55,7 +62,7 @@ module Submitters
def serialize_events(events) def serialize_events(events)
events.map do |event| events.map do |event|
event.as_json(only: %i[id submitter_id event_type event_timestamp]) event.as_json(only: %i[id submitter_id event_type event_timestamp])
.merge('data' => event.data.slice('reason', 'firstname', 'lastname', 'method', 'country', 'idcode')) .merge('data' => event.data.slice(*EVENT_DATA_KEYS[event.event_type]))
end end
end end
end end
+4 -1
View File
@@ -104,7 +104,10 @@ module Submitters
def build_documents_array(submitter, expires_at: nil) def build_documents_array(submitter, expires_at: nil)
submitter.documents.map do |attachment| submitter.documents.map do |attachment|
{ 'name' => attachment.filename.base, 'url' => rails_storage_proxy_url(attachment, expires_at:) } {
'name' => FilenameUtils.basename(attachment.blob[:filename]),
'url' => rails_storage_proxy_url(attachment, expires_at:)
}
end end
end end
+1 -1
View File
@@ -26,7 +26,7 @@ module Templates
def create_document!(template, attachment, data, scan) def create_document!(template, attachment, data, scan)
blob = ActiveStorage::Blob.create_and_upload!( blob = ActiveStorage::Blob.create_and_upload!(
io: StringIO.new(data), io: StringIO.new(data),
filename: "#{attachment.filename.base}.#{scan ? 'png' : 'jpg'}", filename: "#{FilenameUtils.basename(attachment.blob[:filename])}.#{scan ? 'png' : 'jpg'}",
metadata: { identified: true, analyzed: true }, metadata: { identified: true, analyzed: true },
content_type: scan ? 'image/png' : 'image/jpeg' content_type: scan ? 'image/png' : 'image/jpeg'
) )
+3 -3
View File
@@ -52,7 +52,7 @@ module Templates
schema_item = schema_item =
template.schema.find { |item| item['attachment_uuid'] == entry['attachment_uuid'] } || template.schema.find { |item| item['attachment_uuid'] == entry['attachment_uuid'] } ||
{ 'attachment_uuid' => entry['attachment_uuid'], { 'attachment_uuid' => entry['attachment_uuid'],
'name' => attachments_index[entry['attachment_uuid']].filename.base } 'name' => FilenameUtils.basename(attachments_index[entry['attachment_uuid']].blob[:filename]) }
next if entry['pages'].blank? next if entry['pages'].blank?
@@ -270,7 +270,7 @@ module Templates
blob = ActiveStorage::Blob.create_and_upload!( blob = ActiveStorage::Blob.create_and_upload!(
io: StringIO.new(data), io: StringIO.new(data),
filename: "#{attachment.filename.base}#{extension}", filename: "#{FilenameUtils.basename(attachment.blob[:filename])}#{extension}",
metadata: { identified: true, analyzed: true }, metadata: { identified: true, analyzed: true },
content_type: content_type:
) )
@@ -439,7 +439,7 @@ module Templates
blob = ActiveStorage::Blob.create_and_upload!( blob = ActiveStorage::Blob.create_and_upload!(
io: StringIO.new(data), io: StringIO.new(data),
filename: "#{old_attachment.filename.base}.pdf", filename: "#{FilenameUtils.basename(old_attachment.blob[:filename])}.pdf",
metadata: { identified: true, analyzed: true, metadata: { identified: true, analyzed: true,
pdf: { annotations: }.compact_blank, sha256: }.compact_blank, pdf: { annotations: }.compact_blank, sha256: }.compact_blank,
content_type: Templates::ProcessDocument::PDF_CONTENT_TYPE content_type: Templates::ProcessDocument::PDF_CONTENT_TYPE
+4 -1
View File
@@ -12,7 +12,10 @@ module Templates
documents.each_with_index do |document, index| documents.each_with_index do |document, index|
replaced_document_schema = template.schema[index] replaced_document_schema = template.schema[index]
template.schema[index] = { attachment_uuid: document.uuid, name: document.filename.base } template.schema[index] = {
attachment_uuid: document.uuid,
name: FilenameUtils.basename(document.blob[:filename])
}
if replaced_document_schema if replaced_document_schema
template.fields.each do |field| template.fields.each do |field|