From 2c9f1b999e5aec74abd9d56dcb1bf184fd8fd5eb Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Mon, 13 Jul 2026 17:16:42 +0300 Subject: [PATCH 01/14] update icon --- app/views/icons/_message.html.erb | 6 ++++++ app/views/shared/_settings_nav.html.erb | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 app/views/icons/_message.html.erb diff --git a/app/views/icons/_message.html.erb b/app/views/icons/_message.html.erb new file mode 100644 index 00000000..84f1c537 --- /dev/null +++ b/app/views/icons/_message.html.erb @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/views/shared/_settings_nav.html.erb b/app/views/shared/_settings_nav.html.erb index f5fdce90..217854da 100644 --- a/app/views/shared/_settings_nav.html.erb +++ b/app/views/shared/_settings_nav.html.erb @@ -132,7 +132,7 @@ <%= capture do %>
- <%= svg_icon('brand_openai', class: 'w-8 h-8') %> + <%= svg_icon('message', class: 'w-8 h-8') %>
<% end %> From 09bd831d540357ad74837d48b9307decb6d4f7e5 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Mon, 13 Jul 2026 19:13:46 +0300 Subject: [PATCH 02/14] adjust submission.name variable --- lib/replace_email_variables.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/replace_email_variables.rb b/lib/replace_email_variables.rb index 392c585d..f09548f9 100644 --- a/lib/replace_email_variables.rb +++ b/lib/replace_email_variables.rb @@ -35,7 +35,9 @@ module ReplaceEmailVariables text = replace(text, TEMPLATE_NAME, html_escape:) do (submitter.template || submitter.submission.template || submitter.submission).name end - text = replace(text, SUBMISSION_NAME, html_escape:) { submitter.submission.name } + text = replace(text, SUBMISSION_NAME, html_escape:) do + submitter.submission.name.presence || submitter.submission.template&.name + end text = replace(text, TEMPLATE_ID, html_escape:) { submitter.template.id } text = replace(text, SUBMITTER_ID, html_escape:) { submitter.id } text = replace(text, SUBMITTER_SLUG, html_escape:) { submitter.slug } From 73734b0de7a1e6f0bc3bd6ef980d4ea99020d729 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Mon, 13 Jul 2026 19:14:52 +0300 Subject: [PATCH 03/14] add variables_schema response --- lib/templates/serialize_for_api.rb | 19 +++++++++++++------ spec/requests/templates_spec.rb | 1 + 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/templates/serialize_for_api.rb b/lib/templates/serialize_for_api.rb index 64907f50..d9ba79fa 100644 --- a/lib/templates/serialize_for_api.rb +++ b/lib/templates/serialize_for_api.rb @@ -4,9 +4,9 @@ module Templates module SerializeForApi SERIALIZE_PARAMS = { only: %w[ - id archived_at fields name preferences schema - slug source submitters created_at updated_at - author_id external_id folder_id shared_link + id name slug schema submitters fields variables_schema + preferences source created_at updated_at archived_at author_id + external_id folder_id shared_link ], methods: %i[application_key folder_name], include: { author: { only: %i[id email first_name last_name] } } @@ -22,7 +22,16 @@ module Templates preview_image_attachments ||= preload_preview_image_attachments(schema_documents, dynamic_documents) - json['documents'] = template.schema.filter_map do |item| + json['variables_schema'] ||= {} + + json['documents'] = build_documents_array(template, schema_documents, dynamic_documents, + preview_image_attachments, expires_at) + + json + end + + def build_documents_array(template, schema_documents, dynamic_documents, preview_image_attachments, expires_at) + template.schema.filter_map do |item| if item['dynamic'] dynamic_document = dynamic_documents.find { |e| e.uuid == item['attachment_uuid'] } @@ -44,8 +53,6 @@ module Templates 'filename' => attachment.filename } end - - json end def preload_dynamic_documents(template) diff --git a/spec/requests/templates_spec.rb b/spec/requests/templates_spec.rb index 94c4205b..0cf3e1ab 100644 --- a/spec/requests/templates_spec.rb +++ b/spec/requests/templates_spec.rb @@ -230,6 +230,7 @@ describe 'Templates API' do name: 'sample-document' } ], + variables_schema: template.variables_schema || {}, shared_link: template.shared_link, author_id: author.id, archived_at: nil, From 61fc24d35deae3392e8933bd578781d63bf973c0 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 14 Jul 2026 12:47:38 +0300 Subject: [PATCH 04/14] add with signature id completed config --- app/models/account_config.rb | 1 + app/views/submissions/_value.html.erb | 3 ++- app/views/submissions/show.html.erb | 5 +++-- app/views/submit_form/show.html.erb | 2 +- .../generate_preview_attachments.rb | 7 +++++-- .../generate_result_attachments.rb | 20 +++++++++++++------ lib/submitters/form_configs.rb | 6 +++++- 7 files changed, 31 insertions(+), 13 deletions(-) diff --git a/app/models/account_config.rb b/app/models/account_config.rb index 857f0081..5881302e 100644 --- a/app/models/account_config.rb +++ b/app/models/account_config.rb @@ -48,6 +48,7 @@ class AccountConfig < ApplicationRecord WITH_SIGNATURE_ID = 'with_signature_id' WITH_FILE_LINKS_KEY = 'with_file_links' WITH_SIGNATURE_ID_REASON_KEY = 'with_signature_id_reason' + WITH_SIGNATURE_ID_COMPLETED_AT_KEY = 'with_signature_id_completed_at' RECIPIENT_FORM_FIELDS_KEY = 'recipient_form_fields' WITH_AUDIT_VALUES_KEY = 'with_audit_values' WITH_AUDIT_SENDER_KEY = 'with_audit_sender' diff --git a/app/views/submissions/_value.html.erb b/app/views/submissions/_value.html.erb index 6c94e65d..161b9073 100644 --- a/app/views/submissions/_value.html.erb +++ b/app/views/submissions/_value.html.erb @@ -29,7 +29,8 @@
<% timezone = local_assigns[:with_submitter_timezone] ? (submitter.timezone || local_assigns[:timezone]) : local_assigns[:timezone] %> <% time_format = local_assigns[:with_timestamp_seconds] ? :detailed : :long %> - <%= l(attachment.created_at.in_time_zone(timezone), format: time_format, locale: local_assigns[:locale]) %> <%= TimeUtils.timezone_abbr(timezone, attachment.created_at) %> + <% signature_timestamp = (local_assigns[:with_signature_id_completed_at] ? submitter.completed_at : nil) || attachment.created_at %> + <%= l(signature_timestamp.in_time_zone(timezone), format: time_format, locale: local_assigns[:locale]) %> <%= TimeUtils.timezone_abbr(timezone, signature_timestamp) %>
<% end %> diff --git a/app/views/submissions/show.html.erb b/app/views/submissions/show.html.erb index 6ac75a5b..eb385fa1 100644 --- a/app/views/submissions/show.html.erb +++ b/app/views/submissions/show.html.erb @@ -2,12 +2,13 @@ <%= render 'submissions/preview_tags' %> <% end %> <% font_scale = 1040.0 / PdfUtils::US_LETTER_W %> -<% configs = AccountConfig.where(account_id: @submission.account_id, key: [AccountConfig::COMBINE_PDF_RESULT_KEY, AccountConfig::WITH_SIGNATURE_ID, AccountConfig::WITH_SUBMITTER_TIMEZONE_KEY, AccountConfig::WITH_SIGNATURE_ID_REASON_KEY, AccountConfig::WITH_TIMESTAMP_SECONDS_KEY]) %> +<% configs = AccountConfig.where(account_id: @submission.account_id, key: [AccountConfig::COMBINE_PDF_RESULT_KEY, AccountConfig::WITH_SIGNATURE_ID, AccountConfig::WITH_SUBMITTER_TIMEZONE_KEY, AccountConfig::WITH_SIGNATURE_ID_REASON_KEY, AccountConfig::WITH_TIMESTAMP_SECONDS_KEY, AccountConfig::WITH_SIGNATURE_ID_COMPLETED_AT_KEY]) %> <% with_signature_id = configs.find { |e| e.key == AccountConfig::WITH_SIGNATURE_ID }&.value == true %> <% is_combined_enabled = configs.find { |e| e.key == AccountConfig::COMBINE_PDF_RESULT_KEY }&.value == true && !@submission.template_fields&.any? { |f| f['type'] == 'verification' } %> <% with_submitter_timezone = configs.find { |e| e.key == AccountConfig::WITH_SUBMITTER_TIMEZONE_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_completed_at = configs.find { |e| e.key == AccountConfig::WITH_SIGNATURE_ID_COMPLETED_AT_KEY }&.value == true %>
@@ -144,7 +145,7 @@ <% else %> - <%= render 'submissions/value', page_width: width, page_height: height, font_scale:, area:, field:, attachments_index:, value: mask.present? ? Array.wrap(value).map { |e| TextUtils.mask_value(e, mask) }.join(', ') : value, locale: @submission.account.locale, timezone: @submission.account.timezone, submitter:, with_signature_id:, with_submitter_timezone:, with_timestamp_seconds:, with_signature_id_reason: %> + <%= render 'submissions/value', page_width: width, page_height: height, font_scale:, area:, field:, attachments_index:, value: mask.present? ? Array.wrap(value).map { |e| TextUtils.mask_value(e, mask) }.join(', ') : value, locale: @submission.account.locale, timezone: @submission.account.timezone, submitter:, with_signature_id:, with_submitter_timezone:, with_timestamp_seconds:, with_signature_id_reason:, with_signature_id_completed_at: %> <% end %> <% elsif field['readonly'] != true && submitter && !submitter.completed_at? %> <% submitters_order_index ||= (@submission.template_submitters || @submission.template.submitters).each_with_index.to_h { |s, i| [s['uuid'], i] } %> diff --git a/app/views/submit_form/show.html.erb b/app/views/submit_form/show.html.erb index 5c806057..48580c8f 100644 --- a/app/views/submit_form/show.html.erb +++ b/app/views/submit_form/show.html.erb @@ -152,7 +152,7 @@ <% next if field['conditions'].present? && values[field['uuid']].blank? && field['submitter_uuid'] != @submitter.uuid %> <% next if field['conditions'].present? && field['submitter_uuid'] == @submitter.uuid %> <% next if field.dig('preferences', 'formula').present? && field['submitter_uuid'] == @submitter.uuid %> - <%= render 'submissions/value', page_width: width, page_height: height, font_scale:, area:, field:, attachments_index: @attachments_index, value: field.dig('preferences', 'mask').present? ? TextUtils.mask_value(value, field.dig('preferences', 'mask')) : value, locale: @submitter.account.locale, timezone: @submitter.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: @form_configs[:with_signature_id], with_submitter_timezone: @form_configs[:with_submitter_timezone], with_timestamp_seconds: @form_configs[:with_timestamp_seconds], with_signature_id_reason: @form_configs[:with_signature_id_reason] %> + <%= render 'submissions/value', page_width: width, page_height: height, font_scale:, area:, field:, attachments_index: @attachments_index, value: field.dig('preferences', 'mask').present? ? TextUtils.mask_value(value, field.dig('preferences', 'mask')) : value, locale: @submitter.account.locale, timezone: @submitter.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: @form_configs[:with_signature_id], with_submitter_timezone: @form_configs[:with_submitter_timezone], with_timestamp_seconds: @form_configs[:with_timestamp_seconds], with_signature_id_reason: @form_configs[:with_signature_id_reason], with_signature_id_completed_at: @form_configs[:with_signature_id_completed_at] %> <% end %>
diff --git a/lib/submissions/generate_preview_attachments.rb b/lib/submissions/generate_preview_attachments.rb index 10bbdcd0..eb9011db 100644 --- a/lib/submissions/generate_preview_attachments.rb +++ b/lib/submissions/generate_preview_attachments.rb @@ -18,7 +18,8 @@ module Submissions AccountConfig::WITH_TIMESTAMP_SECONDS_KEY, AccountConfig::ROTATE_INCREMENTAL_PDF_KEY, AccountConfig::WITH_FILE_LINKS_KEY, - AccountConfig::WITH_SIGNATURE_ID_REASON_KEY]) + AccountConfig::WITH_SIGNATURE_ID_REASON_KEY, + AccountConfig::WITH_SIGNATURE_ID_COMPLETED_AT_KEY]) with_signature_id = configs.find { |c| c.key == AccountConfig::WITH_SIGNATURE_ID }&.value == true with_file_links = configs.find { |c| c.key == AccountConfig::WITH_FILE_LINKS_KEY }&.value == true @@ -28,6 +29,8 @@ module Submissions with_timestamp_seconds = configs.find { |c| c.key == AccountConfig::WITH_TIMESTAMP_SECONDS_KEY }&.value == true with_signature_id_reason = configs.find { |c| c.key == AccountConfig::WITH_SIGNATURE_ID_REASON_KEY }&.value != false + with_signature_id_completed_at = + configs.find { |c| c.key == AccountConfig::WITH_SIGNATURE_ID_COMPLETED_AT_KEY }&.value == true file_links_expire_at = Accounts.link_expires_at(submission.account) if with_file_links @@ -45,7 +48,7 @@ module Submissions with_signature_id:, is_flatten:, with_headings: index.zero?, with_submitter_timezone:, with_file_links:, file_links_expire_at:, with_signature_id_reason:, - with_timestamp_seconds:) + with_timestamp_seconds:, with_signature_id_completed_at:) end template = submission.template diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index 850b7908..309d9cd7 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -140,7 +140,8 @@ module Submissions AccountConfig::WITH_TIMESTAMP_SECONDS_KEY, AccountConfig::ROTATE_INCREMENTAL_PDF_KEY, AccountConfig::WITH_SUBMITTER_TIMEZONE_KEY, - AccountConfig::WITH_SIGNATURE_ID_REASON_KEY]) + AccountConfig::WITH_SIGNATURE_ID_REASON_KEY, + AccountConfig::WITH_SIGNATURE_ID_COMPLETED_AT_KEY]) with_signature_id = configs.find { |c| c.key == AccountConfig::WITH_SIGNATURE_ID }&.value == true is_flatten = configs.find { |c| c.key == AccountConfig::FLATTEN_RESULT_PDF_KEY }&.value != false @@ -150,6 +151,8 @@ module Submissions with_file_links = configs.find { |c| c.key == AccountConfig::WITH_FILE_LINKS_KEY }&.value == true with_signature_id_reason = configs.find { |c| c.key == AccountConfig::WITH_SIGNATURE_ID_REASON_KEY }&.value != false + with_signature_id_completed_at = + configs.find { |c| c.key == AccountConfig::WITH_SIGNATURE_ID_COMPLETED_AT_KEY }&.value == true file_links_expire_at = Accounts.link_expires_at(submitter.account) if with_file_links @@ -201,12 +204,14 @@ module Submissions with_file_links:, with_timestamp_seconds:, with_signature_id_reason:, + with_signature_id_completed_at:, file_links_expire_at:) end def fill_submitter_fields(submitter, account, pdfs_index, with_signature_id:, is_flatten:, with_headings: nil, with_submitter_timezone: false, with_signature_id_reason: true, - with_timestamp_seconds: false, with_file_links: nil, + with_timestamp_seconds: false, with_signature_id_completed_at: false, + with_file_links: nil, file_links_expire_at: Accounts.link_expires_at(account)) cell_layouters = Hash.new do |hash, valign| hash[valign] = HexaPDF::Layout::TextLayouter.new(text_valign: valign.to_sym, text_align: :center) @@ -337,14 +342,17 @@ module Submissions time_format = with_timestamp_seconds ? :detailed : :long + signature_timestamp = + (with_signature_id_completed_at ? submitter.completed_at : nil) || attachment.created_at + if with_signature_id_reason || field.dig('preferences', 'reasons').present? "#{"#{I18n.t('reason')}: " if reason_value}#{reason_value || I18n.t('digitally_signed_by')} " \ "#{submitter.name}#{" <#{submitter.email}>" if submitter.email.present?}\n" \ - "#{I18n.l(attachment.created_at.in_time_zone(timezone), format: time_format)} " \ - "#{TimeUtils.timezone_abbr(timezone, attachment.created_at)}" + "#{I18n.l(signature_timestamp.in_time_zone(timezone), format: time_format)} " \ + "#{TimeUtils.timezone_abbr(timezone, signature_timestamp)}" else - "#{I18n.l(attachment.created_at.in_time_zone(timezone), format: time_format)} " \ - "#{TimeUtils.timezone_abbr(timezone, attachment.created_at)}" + "#{I18n.l(signature_timestamp.in_time_zone(timezone), format: time_format)} " \ + "#{TimeUtils.timezone_abbr(timezone, signature_timestamp)}" end end diff --git a/lib/submitters/form_configs.rb b/lib/submitters/form_configs.rb index 1b21fa70..71d0fed4 100644 --- a/lib/submitters/form_configs.rb +++ b/lib/submitters/form_configs.rb @@ -18,6 +18,7 @@ module Submitters AccountConfig::WITH_SUBMITTER_TIMEZONE_KEY, AccountConfig::WITH_TIMESTAMP_SECONDS_KEY, AccountConfig::WITH_SIGNATURE_ID_REASON_KEY, + AccountConfig::WITH_SIGNATURE_ID_COMPLETED_AT_KEY, *(Docuseal.multitenant? ? [] : [AccountConfig::POLICY_LINKS_KEY])].freeze module_function @@ -40,6 +41,8 @@ module Submitters with_submitter_timezone = find_safe_value(configs, AccountConfig::WITH_SUBMITTER_TIMEZONE_KEY) == true with_timestamp_seconds = find_safe_value(configs, AccountConfig::WITH_TIMESTAMP_SECONDS_KEY) == true with_signature_id_reason = find_safe_value(configs, AccountConfig::WITH_SIGNATURE_ID_REASON_KEY) != false + with_signature_id_completed_at = + find_safe_value(configs, AccountConfig::WITH_SIGNATURE_ID_COMPLETED_AT_KEY) == true with_field_labels = find_safe_value(configs, AccountConfig::WITH_FIELD_LABELS_KEY) != false policy_links = find_safe_value(configs, AccountConfig::POLICY_LINKS_KEY) @@ -47,7 +50,8 @@ module Submitters reuse_signature:, with_decline:, with_delegate:, with_partial_download:, policy_links:, enforce_signing_order:, completed_message:, require_signing_reason:, prefill_signature:, with_submitter_timezone:, - with_signature_id_reason:, with_signature_id:, with_field_labels:, with_timestamp_seconds: } + with_signature_id_reason:, with_signature_id:, with_field_labels:, with_timestamp_seconds:, + with_signature_id_completed_at: } keys.each do |key| attrs[key.to_sym] = configs.find { |e| e.key == key.to_s }&.value From c8e783a210ca69cb2b6d5c27d86139e3a1d5a5c3 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 14 Jul 2026 17:30:41 +0300 Subject: [PATCH 05/14] multi select context menu readonly and required --- app/javascript/template_builder/area.vue | 4 +- app/javascript/template_builder/page.vue | 14 +++ .../selection_context_menu.vue | 104 ++++++++++++++---- 3 files changed, 101 insertions(+), 21 deletions(-) diff --git a/app/javascript/template_builder/area.vue b/app/javascript/template_builder/area.vue index e15bf2f8..88253fa2 100644 --- a/app/javascript/template_builder/area.vue +++ b/app/javascript/template_builder/area.vue @@ -319,7 +319,7 @@ export default { default: false } }, - emits: ['start-resize', 'stop-resize', 'start-drag', 'stop-drag', 'remove', 'scroll-to', 'add-custom-field', 'click-title'], + emits: ['start-resize', 'stop-resize', 'start-drag', 'stop-drag', 'remove', 'scroll-to', 'add-custom-field', 'click-title', 'multi-select'], data () { return { isContenteditable: false, @@ -798,6 +798,8 @@ export default { this.selectedAreasRef.value.splice(this.selectedAreasRef.value.indexOf(this.area), 1) } + this.$emit('multi-select', e) + return } diff --git a/app/javascript/template_builder/page.vue b/app/javascript/template_builder/page.vue index 163a386f..1248cfe7 100644 --- a/app/javascript/template_builder/page.vue +++ b/app/javascript/template_builder/page.vue @@ -57,6 +57,7 @@ @scroll-to="$emit('scroll-to', $event)" @add-custom-field="$emit('add-custom-field', $event)" @contextmenu="openAreaContextMenu($event, item.area, item.field)" + @multi-select="openMultiSelectContextMenu" @click-title="closeContextMenu" /> = 2) { + this.openSelectionContextMenu(event) + + return + } + event.preventDefault() event.stopPropagation() @@ -443,6 +450,13 @@ export default { areas: this.selectedAreasRef.value } }, + openMultiSelectContextMenu (event) { + if (this.selectedAreasRef.value.length >= 2) { + this.openSelectionContextMenu(event) + } else { + this.closeContextMenu() + } + }, handleSelectionCopy () { this.$emit('copy-selected-areas') diff --git a/app/javascript/template_builder/selection_context_menu.vue b/app/javascript/template_builder/selection_context_menu.vue index 4649e011..fb275271 100644 --- a/app/javascript/template_builder/selection_context_menu.vue +++ b/app/javascript/template_builder/selection_context_menu.vue @@ -9,6 +9,62 @@ @mousedown.stop @pointerdown.stop > + + +
+ + +
{{ t('height') }} -
- -
- <%= button_to '', destroy_user_session_path, method: :delete, data: { turbo: false }, form: { id: 'destroy_user_session_form' }, form_class: 'hidden' %> + <%= button_to '', destroy_user_session_path, method: :delete, form: { id: 'destroy_user_session_form', data: { turbo: false } }, form_class: 'hidden' %>