From 23dcd626723dee6054e6881964071eed4c75d9c7 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Mon, 3 Mar 2025 00:14:14 +0200 Subject: [PATCH 01/13] add field mask --- app/views/submissions/show.html.erb | 34 +++++++++++++++---- app/views/submit_form/show.html.erb | 2 +- lib/submissions/generate_audit_trail.rb | 2 ++ .../generate_result_attachments.rb | 4 +++ lib/text_utils.rb | 6 ++++ 5 files changed, 40 insertions(+), 8 deletions(-) diff --git a/app/views/submissions/show.html.erb b/app/views/submissions/show.html.erb index 3256ad36..9b39a43c 100644 --- a/app/views/submissions/show.html.erb +++ b/app/views/submissions/show.html.erb @@ -19,7 +19,7 @@ <%= svg_icon('external_link', class: 'w-6 h-6') %> - <% else %> + <% elsif signed_in? %> <%= link_to submission_events_path(@submission), class: 'white-button', data: { turbo_frame: :modal } do %> <%= svg_icon('logs', class: 'w-6 h-6') %> @@ -105,7 +105,18 @@ <% value = values[field['uuid']] %> <% value ||= field['default_value'] if field['type'] == 'heading' %> <% next if value.blank? %> - <%= render 'submissions/value', area:, field:, attachments_index:, value:, locale: @submission.account.locale, timezone: @submission.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: %> + <% if field.dig('preferences', 'mask').present? && signed_in? && can?(:read, @submission) %> + + + + <%= render 'submissions/value', area:, field:, attachments_index:, value: Array.wrap(value).map { |e| TextUtils.mask_value(e) }.join(', '), locale: @submission.account.locale, timezone: @submission.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: %> + + + <% else %> + <%= render 'submissions/value', area:, field:, attachments_index:, value: field.dig('preferences', 'mask').present? ? Array.wrap(value).map { |e| TextUtils.mask_value(e) }.join(', ') : value, locale: @submission.account.locale, timezone: @submission.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: %> + <% end %> <% end %> @@ -232,12 +243,21 @@ <% elsif field['type'] == 'checkbox' %> <%= svg_icon('check', class: 'w-6 h-6') %> - <% elsif field['type'] == 'number' %> - <%= NumberUtils.format_number(value, field.dig('preferences', 'format')) %> - <% elsif field['type'] == 'date' %> - <%= TimeUtils.format_date_string(value, field.dig('preferences', 'format'), @submission.account.locale) %> <% else %> -
<%= Array.wrap(value).join(', ') %>
+ <% if field['type'] == 'number' %> + <% value = NumberUtils.format_number(value, field.dig('preferences', 'format')) %> + <% elsif field['type'] == 'date' %> + <% value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), @submission.account.locale) %> + <% end %> + <% if field.dig('preferences', 'mask').present? %> + <% if signed_in? && can?(:read, @submission) %> +
+ <% else %> +
<%= Array.wrap(value).map { |e| TextUtils.mask_value(e) }.join(', ') %>
+ <% end %> + <% else %> +
<%= Array.wrap(value).join(', ') %>
+ <% end %> <% end %> diff --git a/app/views/submit_form/show.html.erb b/app/views/submit_form/show.html.erb index 02967492..0ef05cc8 100644 --- a/app/views/submit_form/show.html.erb +++ b/app/views/submit_form/show.html.erb @@ -58,7 +58,7 @@ <% next if field['conditions'].present? && values[field['uuid']].blank? && field['submitter_uuid'] != @submitter.uuid %> <% next if field['conditions'].present? && field['submitter_uuid'] == @submitter.uuid %> <% next if field.dig('preferences', 'formula').present? && field['submitter_uuid'] == @submitter.uuid %> - <%= render 'submissions/value', area:, field:, attachments_index: @attachments_index, value:, locale: @submitter.account.locale, timezone: @submitter.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: @form_configs[:with_signature_id] %> + <%= render 'submissions/value', area:, field:, attachments_index: @attachments_index, value: field.dig('preferences', 'mask').present? ? TextUtils.mask_value(value) : value, locale: @submitter.account.locale, timezone: @submitter.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: @form_configs[:with_signature_id] %> <% end %> diff --git a/lib/submissions/generate_audit_trail.rb b/lib/submissions/generate_audit_trail.rb index f787c5fb..c5870418 100644 --- a/lib/submissions/generate_audit_trail.rb +++ b/lib/submissions/generate_audit_trail.rb @@ -366,6 +366,8 @@ module Submissions value = value.join(', ') if value.is_a?(Array) + value = TextUtils.mask_value(value) if field.dig('preferences', 'mask').present? + composer.formatted_text_box([{ text: TextUtils.maybe_rtl_reverse(value.to_s.presence || 'n/a') }], text_align: value.to_s.match?(RTL_REGEXP) ? :right : :left, padding: [0, 0, 10, 0]) diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index 3039ac19..4692cad1 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -391,6 +391,8 @@ module Submissions when ->(type) { type == 'cells' && !area['cell_w'].to_f.zero? } cell_width = area['cell_w'] * width + value = TextUtils.mask_value(value) if field.dig('preferences', 'mask').present? + chars = TextUtils.maybe_rtl_reverse(value).chars chars = chars.reverse if field.dig('preferences', 'align') == 'right' @@ -440,6 +442,8 @@ module Submissions value = TextUtils.maybe_rtl_reverse(Array.wrap(value).join(', ')) + value = TextUtils.mask_value(value) if field.dig('preferences', 'mask').present? + text_params = { font:, fill_color:, font_size: } text_params[:line_height] = text_params[:font_size] * 1.6 if font_name == COURIER_FONT diff --git a/lib/text_utils.rb b/lib/text_utils.rb index 8d33f3ef..c5c00e03 100644 --- a/lib/text_utils.rb +++ b/lib/text_utils.rb @@ -2,6 +2,8 @@ module TextUtils RTL_REGEXP = /[\p{Hebrew}\p{Arabic}]/ + MASK_REGEXP = /[^\s\-_\[\]\(\)\+\?\.\,]/ + MASK_SYMBOL = 'X' module_function @@ -13,6 +15,10 @@ module TextUtils false end + def mask_value(text) + text.to_s.gsub(MASK_REGEXP, MASK_SYMBOL) + end + def maybe_rtl_reverse(text) if text.match?(RTL_REGEXP) TwitterCldr::Shared::Bidi From 55ad70fabc9f4665d3cd5709ae51992ad4521b27 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Mon, 3 Mar 2025 11:24:38 +0200 Subject: [PATCH 02/13] fix builder default date format --- app/javascript/template_builder/builder.vue | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue index 7a01c5f0..4619ed1c 100644 --- a/app/javascript/template_builder/builder.vue +++ b/app/javascript/template_builder/builder.vue @@ -1338,9 +1338,8 @@ export default { } if (field.type === 'date') { - field.preferences = { - format: this.defaultDateFormat - } + field.preferences ||= {} + field.preferences.format ||= this.defaultDateFormat } } From 8773bb2f6211d68ad6889e416aff4dfeaa39b279 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Mon, 3 Mar 2025 13:21:10 +0200 Subject: [PATCH 03/13] disable cfr toggle --- app/views/accounts/show.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/accounts/show.html.erb b/app/views/accounts/show.html.erb index d0f63ce7..b8e945d5 100644 --- a/app/views/accounts/show.html.erb +++ b/app/views/accounts/show.html.erb @@ -63,7 +63,7 @@ <%= t('add_signature_id_to_the_documents') %> - <%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %> + <%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()', disabled: can?(:manage, :cfr) %> <% end %> <% end %> @@ -75,7 +75,7 @@ <%= t('require_signing_reason') %> - <%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %> + <%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()', disabled: can?(:manage, :cfr) %> <% end %> <% end %> From febdc0b48d95930a0a22b59c7513b0492ab50e0d Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Mon, 3 Mar 2025 19:01:59 +0200 Subject: [PATCH 04/13] add specs for masked fields --- spec/system/signing_form_spec.rb | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/spec/system/signing_form_spec.rb b/spec/system/signing_form_spec.rb index c6e512fe..be486c65 100644 --- a/spec/system/signing_form_spec.rb +++ b/spec/system/signing_form_spec.rb @@ -772,6 +772,30 @@ RSpec.describe 'Signing Form', type: :system do end end + context 'when the masked field' do + let(:template) { create(:template, submitter_count: 2, account:, author:, only_field_types: %w[text]) } + let(:submission) { create(:submission, template: template) } + let!(:first_submitter) { create(:submitter, submission:, uuid: template.submitters[0]['uuid'], account:) } + let!(:second_submitter) { create(:submitter, submission:, uuid: template.submitters[1]['uuid'], account:) } + + it 'shows the masked value instead of the real value' do + field = submission.template_fields.find do |f| + f['name'] == 'First Name' && f['submitter_uuid'] == first_submitter.uuid + end + field['preferences']['mask'] = true + submission.save! + + visit submit_form_path(slug: first_submitter.slug) + + fill_in 'First Name', with: 'Jahn' + click_button 'Complete' + + visit submit_form_path(slug: second_submitter.slug) + + expect(page).to have_content('XXXX') + end + end + it 'sends completed email' do template = create(:template, account:, author:, only_field_types: %w[text signature]) submission = create(:submission, template:) From 3b572702d7a8974dbbcf4ca792e6895c25c465fa Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 4 Mar 2025 00:12:56 +0200 Subject: [PATCH 05/13] add mask size --- app/views/submissions/show.html.erb | 12 ++++++------ app/views/submit_form/show.html.erb | 2 +- lib/submissions/generate_audit_trail.rb | 9 ++++----- lib/submissions/generate_result_attachments.rb | 8 ++++++-- lib/text_utils.rb | 18 ++++++++++++++++-- 5 files changed, 33 insertions(+), 16 deletions(-) diff --git a/app/views/submissions/show.html.erb b/app/views/submissions/show.html.erb index 9b39a43c..b027c0c8 100644 --- a/app/views/submissions/show.html.erb +++ b/app/views/submissions/show.html.erb @@ -105,17 +105,17 @@ <% value = values[field['uuid']] %> <% value ||= field['default_value'] if field['type'] == 'heading' %> <% next if value.blank? %> - <% if field.dig('preferences', 'mask').present? && signed_in? && can?(:read, @submission) %> + <% if (mask = field.dig('preferences', 'mask').presence) && signed_in? && can?(:read, @submission) %> - <%= render 'submissions/value', area:, field:, attachments_index:, value: Array.wrap(value).map { |e| TextUtils.mask_value(e) }.join(', '), locale: @submission.account.locale, timezone: @submission.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: %> + <%= render 'submissions/value', area:, field:, attachments_index:, value: Array.wrap(value).map { |e| TextUtils.mask_value(e, mask) }.join(', '), locale: @submission.account.locale, timezone: @submission.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: %> <% else %> - <%= render 'submissions/value', area:, field:, attachments_index:, value: field.dig('preferences', 'mask').present? ? Array.wrap(value).map { |e| TextUtils.mask_value(e) }.join(', ') : value, locale: @submission.account.locale, timezone: @submission.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: %> + <%= render 'submissions/value', area:, field:, attachments_index:, value: mask.present? ? Array.wrap(value).map { |e| TextUtils.mask_value(e, mask) }.join(', ') : value, locale: @submission.account.locale, timezone: @submission.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: %> <% end %> <% end %> @@ -249,11 +249,11 @@ <% elsif field['type'] == 'date' %> <% value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), @submission.account.locale) %> <% end %> - <% if field.dig('preferences', 'mask').present? %> + <% if (mask = field.dig('preferences', 'mask').presence) %> <% if signed_in? && can?(:read, @submission) %> -
+
<% else %> -
<%= Array.wrap(value).map { |e| TextUtils.mask_value(e) }.join(', ') %>
+
<%= Array.wrap(value).map { |e| TextUtils.mask_value(e, mask) }.join(', ') %>
<% end %> <% else %>
<%= Array.wrap(value).join(', ') %>
diff --git a/app/views/submit_form/show.html.erb b/app/views/submit_form/show.html.erb index 0ef05cc8..b4774423 100644 --- a/app/views/submit_form/show.html.erb +++ b/app/views/submit_form/show.html.erb @@ -58,7 +58,7 @@ <% next if field['conditions'].present? && values[field['uuid']].blank? && field['submitter_uuid'] != @submitter.uuid %> <% next if field['conditions'].present? && field['submitter_uuid'] == @submitter.uuid %> <% next if field.dig('preferences', 'formula').present? && field['submitter_uuid'] == @submitter.uuid %> - <%= render 'submissions/value', area:, field:, attachments_index: @attachments_index, value: field.dig('preferences', 'mask').present? ? TextUtils.mask_value(value) : value, locale: @submitter.account.locale, timezone: @submitter.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: @form_configs[:with_signature_id] %> + <%= render 'submissions/value', area:, field:, attachments_index: @attachments_index, value: field.dig('preferences', 'mask').present? ? TextUtils.mask_value(value, field.dig('preferences', 'mask')) : value, locale: @submitter.account.locale, timezone: @submitter.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: @form_configs[:with_signature_id] %> <% end %> diff --git a/lib/submissions/generate_audit_trail.rb b/lib/submissions/generate_audit_trail.rb index c5870418..3bc461a6 100644 --- a/lib/submissions/generate_audit_trail.rb +++ b/lib/submissions/generate_audit_trail.rb @@ -359,14 +359,13 @@ module Submissions value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), account.locale) end - if field['type'] == 'number' - value = NumberUtils.format_number(value, - field.dig('preferences', 'format')) - end + value = NumberUtils.format_number(value, field.dig('preferences', 'format')) if field['type'] == 'number' value = value.join(', ') if value.is_a?(Array) - value = TextUtils.mask_value(value) if field.dig('preferences', 'mask').present? + if (mask = field.dig('preferences', 'mask').presence) + value = TextUtils.mask_value(value, mask) + end composer.formatted_text_box([{ text: TextUtils.maybe_rtl_reverse(value.to_s.presence || 'n/a') }], text_align: value.to_s.match?(RTL_REGEXP) ? :right : :left, diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index 4692cad1..3295ad2f 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -391,7 +391,9 @@ module Submissions when ->(type) { type == 'cells' && !area['cell_w'].to_f.zero? } cell_width = area['cell_w'] * width - value = TextUtils.mask_value(value) if field.dig('preferences', 'mask').present? + if (mask = field.dig('preferences', 'mask').presence) + value = TextUtils.mask_value(value, mask) + end chars = TextUtils.maybe_rtl_reverse(value).chars chars = chars.reverse if field.dig('preferences', 'align') == 'right' @@ -442,7 +444,9 @@ module Submissions value = TextUtils.maybe_rtl_reverse(Array.wrap(value).join(', ')) - value = TextUtils.mask_value(value) if field.dig('preferences', 'mask').present? + if (mask = field.dig('preferences', 'mask').presence) + value = TextUtils.mask_value(value, mask) + end text_params = { font:, fill_color:, font_size: } text_params[:line_height] = text_params[:font_size] * 1.6 if font_name == COURIER_FONT diff --git a/lib/text_utils.rb b/lib/text_utils.rb index c5c00e03..972f1ef0 100644 --- a/lib/text_utils.rb +++ b/lib/text_utils.rb @@ -15,8 +15,22 @@ module TextUtils false end - def mask_value(text) - text.to_s.gsub(MASK_REGEXP, MASK_SYMBOL) + def mask_value(text, unmask_size = 0) + if unmask_size.is_a?(Numeric) && !unmask_size.zero? + if unmask_size.negative? + [ + text.first(text.length + unmask_size).gsub(MASK_REGEXP, MASK_SYMBOL), + text.last(-unmask_size) + ].join + elsif unmask_size.positive? + [ + text.first(unmask_size), + text.last(text.length - unmask_size).gsub(MASK_REGEXP, MASK_SYMBOL) + ].join + end + else + text.to_s.gsub(MASK_REGEXP, MASK_SYMBOL) + end end def maybe_rtl_reverse(text) From 3fec43384613de4ced0cf7f4f2b2215ad3ce4a14 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 4 Mar 2025 19:46:55 +0200 Subject: [PATCH 06/13] fix builder editable --- app/javascript/template_builder/area.vue | 4 ++++ app/javascript/template_builder/builder.vue | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/app/javascript/template_builder/area.vue b/app/javascript/template_builder/area.vue index 1ada02dc..790039dd 100644 --- a/app/javascript/template_builder/area.vue +++ b/app/javascript/template_builder/area.vue @@ -483,6 +483,10 @@ export default { this.$el.getRootNode().activeElement.blur() }, maybeToggleDefaultValue () { + if (!this.editable) { + return + } + if (['text', 'number'].includes(this.field.type)) { this.isContenteditable = true diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue index 4619ed1c..3f787272 100644 --- a/app/javascript/template_builder/builder.vue +++ b/app/javascript/template_builder/builder.vue @@ -1316,6 +1316,10 @@ export default { } }, onDropfield (area) { + if (!this.editable) { + return + } + const field = this.fieldsDragFieldRef.value || { name: '', uuid: v4(), From 72c8ed9a0097b14653770ed84a2942646cf038e2 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 4 Mar 2025 22:17:01 +0200 Subject: [PATCH 07/13] update gem --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 984b23c4..bf6f2456 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -559,7 +559,7 @@ GEM unicode-emoji (~> 4.0, >= 4.0.4) unicode-emoji (4.0.4) uniform_notifier (1.16.0) - uri (1.0.2) + uri (1.0.3) useragent (0.16.11) warden (1.2.9) rack (>= 2.0.9) From bc1d5a98257fbd8cdd7e62cf7bc9f96a329f550c Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 5 Mar 2025 12:12:16 +0200 Subject: [PATCH 08/13] add font type --- app/javascript/submission_form/area.vue | 28 ++++++++++--- app/javascript/template_builder/area.vue | 18 ++++++--- app/views/submissions/_value.html.erb | 3 +- .../generate_result_attachments.rb | 40 ++++++++++++++++--- 4 files changed, 71 insertions(+), 18 deletions(-) diff --git a/app/javascript/submission_form/area.vue b/app/javascript/submission_form/area.vue index 0a453397..9066684e 100644 --- a/app/javascript/submission_form/area.vue +++ b/app/javascript/submission_form/area.vue @@ -3,7 +3,7 @@ class="field-area flex absolute lg:text-base -outline-offset-1" dir="auto" :style="computedStyle" - :class="{ 'font-mono': field.preferences?.font === 'Courier', 'font-serif': field.preferences?.font === 'Times', 'text-[1.6vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars, 'cursor-default': !submittable, 'border border-red-100 bg-red-100 cursor-pointer': submittable, 'border border-red-100': !isActive && submittable, 'bg-opacity-80': !isActive && !isValueSet && submittable, 'field-area-active outline-red-500 outline-dashed outline-2 z-10': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }" + :class="{ 'text-[1.6vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars, 'cursor-default': !submittable, 'border border-red-100 bg-red-100 cursor-pointer': submittable, 'border border-red-100': !isActive && submittable, 'bg-opacity-80': !isActive && !isValueSet && submittable, 'field-area-active outline-red-500 outline-dashed outline-2 z-10': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }" >
<% color = field.dig('preferences', 'color') %> <% font = field.dig('preferences', 'font') %> -width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; <%= "font-size: clamp(4pt, 1.6vw, #{field['preferences']['font_size'].to_i * 1.23}pt); line-height: `clamp(6pt, 2.0vw, #{(field['preferences']['font_size'].to_i * 1.23) + 3}pt)`" if field.dig('preferences', 'font_size') %>"> +<% font_type = field.dig('preferences', 'font_type') %> +width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; <%= "font-size: clamp(4pt, 1.6vw, #{field['preferences']['font_size'].to_i * 1.23}pt); line-height: `clamp(6pt, 2.0vw, #{(field['preferences']['font_size'].to_i * 1.23) + 3}pt)`" if field.dig('preferences', 'font_size') %>"> <% if field['type'] == 'signature' %>
diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index 3295ad2f..2fe766c0 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -4,12 +4,29 @@ module Submissions module GenerateResultAttachments FONT_SIZE = 11 FONT_PATH = '/fonts/GoNotoKurrent-Regular.ttf' + FONT_BOLD_PATH = '/fonts/GoNotoKurrent-Bold.ttf' FONT_NAME = if File.exist?(FONT_PATH) FONT_PATH else 'Helvetica' end + FONT_BOLD_NAME = if File.exist?(FONT_BOLD_PATH) + FONT_BOLD_PATH + else + 'Helvetica' + end + + FONT_ITALIC_NAME = 'Helvetica' + FONT_BOLD_ITALIC_NAME = 'Helvetica' + + FONT_VARIANS = { + none: FONT_NAME, + bold: FONT_BOLD_NAME, + italic: FONT_ITALIC_NAME, + bold_italic: FONT_BOLD_ITALIC_NAME + }.freeze + SIGN_REASON = 'Signed by %s with DocuSeal.com' RTL_REGEXP = TextUtils::RTL_REGEXP @@ -18,12 +35,15 @@ module Submissions TEXT_TOP_MARGIN = 1 MAX_PAGE_ROTATE = 20 - COURIER_FONT = 'Courier' - A4_SIZE = [595, 842].freeze TESTING_FOOTER = 'Testing Document - NOT LEGALLY BINDING' DEFAULT_FONTS = %w[Times Helvetica Courier].freeze + FONTS_LINE_HEIGHT = { + 'Times' => 1.4, + 'Helvetica' => 1.4, + 'Courier' => 1.6 + }.freeze MISSING_GLYPH_REPLACE = { '▪' => '-', @@ -192,8 +212,16 @@ module Submissions fill_color = field.dig('preferences', 'color').presence font_name = field.dig('preferences', 'font') + font_variant = (field.dig('preferences', 'font_type').presence || 'none').to_sym + font_name = FONT_NAME unless font_name.in?(DEFAULT_FONTS) - font = pdf.fonts.add(font_name) + + if font_variant != :none && font_name == FONT_NAME + font_name = FONT_VARIANS[font_variant] + font_variant = nil unless font_name.in?(DEFAULT_FONTS) + end + + font = pdf.fonts.add(font_name, variant: font_variant) value = submitter.values[field['uuid']] value = field['default_value'] if field['type'] == 'heading' @@ -449,7 +477,7 @@ module Submissions end text_params = { font:, fill_color:, font_size: } - text_params[:line_height] = text_params[:font_size] * 1.6 if font_name == COURIER_FONT + text_params[:line_height] = text_params[:font_size] * (FONTS_LINE_HEIGHT[font_name] || 1) text = HexaPDF::Layout::TextFragment.create(value, **text_params) @@ -458,7 +486,7 @@ module Submissions if preferences_font_size.blank? && box_height > (area['h'] * height) + 1 text_params[:font_size] = (font_size / 1.4).to_i - text_params[:line_height] = text_params[:font_size] * 1.6 if font_name == COURIER_FONT + text_params[:line_height] = text_params[:font_size] * (FONTS_LINE_HEIGHT[font_name] || 1) text = HexaPDF::Layout::TextFragment.create(value, **text_params) @@ -469,7 +497,7 @@ module Submissions if preferences_font_size.blank? && box_height > (area['h'] * height) + 1 text_params[:font_size] = (font_size / 1.9).to_i - text_params[:line_height] = text_params[:font_size] * 1.6 if font_name == COURIER_FONT + text_params[:line_height] = text_params[:font_size] * (FONTS_LINE_HEIGHT[font_name] || 1) text = HexaPDF::Layout::TextFragment.create(value, **text_params) From 3f872da435530d6ef045bdb093bdb9eb19112a2a Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 5 Mar 2025 18:07:38 +0200 Subject: [PATCH 09/13] fix mask --- lib/text_utils.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/text_utils.rb b/lib/text_utils.rb index 972f1ef0..f286311f 100644 --- a/lib/text_utils.rb +++ b/lib/text_utils.rb @@ -16,7 +16,7 @@ module TextUtils end def mask_value(text, unmask_size = 0) - if unmask_size.is_a?(Numeric) && !unmask_size.zero? + if unmask_size.is_a?(Numeric) && !unmask_size.zero? && unmask_size.abs < text.length if unmask_size.negative? [ text.first(text.length + unmask_size).gsub(MASK_REGEXP, MASK_SYMBOL), From 06415ad86920882e25d04f746f35656cb288da06 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 5 Mar 2025 18:29:39 +0200 Subject: [PATCH 10/13] add routes hook --- config/routes.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/routes.rb b/config/routes.rb index 7e24097b..f5672d4f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -122,6 +122,8 @@ Rails.application.routes.draw do get '/disk/:encoded_key/*filename' => 'active_storage/disk#show', as: :rails_disk_service put '/disk/:encoded_token' => 'active_storage/disk#update', as: :update_rails_disk_service post '/direct_uploads' => 'active_storage/direct_uploads#create', as: :rails_direct_uploads + + ActiveSupport.run_load_hooks(:multitenant_routes, self) end resources :start_form, only: %i[show update], path: 'd', param: 'slug' do From 9629cc7226a6b0ab588119be7a30f0cb17c114a1 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 5 Mar 2025 20:44:32 +0200 Subject: [PATCH 11/13] make remember configurable --- config/initializers/devise.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 147e5113..154deb25 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -187,7 +187,7 @@ Devise.setup do |config| # ==> Configuration for :rememberable # The time the user will be remembered without asking for credentials again. - config.remember_for = 2.years + config.remember_for = ENV.fetch('SESSION_REMEMBER_DAYS', '730').to_i.days # Invalidates all the remember me tokens when the user signs out. config.expire_all_remember_me_on_sign_out = true From d5a98a0f9ef8f2c7ece1d487bb7017e5d6a28ed3 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sat, 8 Mar 2025 16:11:11 +0200 Subject: [PATCH 12/13] web concurrency auto --- config/puma.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/config/puma.rb b/config/puma.rb index 964a3298..ed99f0c9 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -33,8 +33,14 @@ environment ENV.fetch('RAILS_ENV', 'development') # the concurrency of the application would be max `threads` * `workers`. # Workers do not work on JRuby or Windows (both of which do not support # processes). -# -workers ENV.fetch('WEB_CONCURRENCY', 0) + +if ENV['WEB_CONCURRENCY_AUTO'] == 'true' + require 'etc' + + workers Etc.nprocessors +else + workers ENV.fetch('WEB_CONCURRENCY', 0) +end # Use the `preload_app!` method when specifying a `workers` number. # This directive tells Puma to first boot the application and load code From 3affd67d24e7561294aae480d8d77e806af6f30a Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sun, 9 Mar 2025 02:08:44 +0200 Subject: [PATCH 13/13] add font field preferences --- app/javascript/template_builder/area.vue | 15 + app/javascript/template_builder/field.vue | 15 + .../template_builder/field_settings.vue | 18 +- .../template_builder/font_modal.vue | 290 ++++++++++++++++++ app/javascript/template_builder/i18n.js | 6 + 5 files changed, 342 insertions(+), 2 deletions(-) create mode 100644 app/javascript/template_builder/font_modal.vue diff --git a/app/javascript/template_builder/area.vue b/app/javascript/template_builder/area.vue index 0ca22efc..75d9e00a 100644 --- a/app/javascript/template_builder/area.vue +++ b/app/javascript/template_builder/area.vue @@ -141,6 +141,7 @@ :with-required="false" :with-areas="false" @click-formula="isShowFormulaModal = true" + @click-font="isShowFontModal = true" @click-description="isShowDescriptionModal = true" @click-condition="isShowConditionsModal = true" @scroll-to="[selectedAreaRef.value = $event, $emit('scroll-to', $event)]" @@ -253,6 +254,17 @@ @close="isShowFormulaModal = false" /> + + + + + + +
  • + +
  • @@ -427,7 +440,7 @@ diff --git a/app/javascript/template_builder/i18n.js b/app/javascript/template_builder/i18n.js index 73a6c17f..d38c6385 100644 --- a/app/javascript/template_builder/i18n.js +++ b/app/javascript/template_builder/i18n.js @@ -1,4 +1,5 @@ const en = { + font: 'Font', party: 'Party', method: 'Method', reorder_fields: 'Reorder fields', @@ -161,6 +162,7 @@ const en = { } const es = { + fuente: 'Fuente', party: 'Parte', method: 'Método', reorder_fields: 'Reordenar campos', @@ -323,6 +325,7 @@ const es = { } const it = { + font: 'Carattere', party: 'Parte', method: 'Metodo', reorder_fields: 'Riordina i campi', @@ -485,6 +488,7 @@ const it = { } const pt = { + fonte: 'Fonte', party: 'Parte', method: 'Método', reorder_fields: 'Reorganizar campos', @@ -647,6 +651,7 @@ const pt = { } const fr = { + font: 'Police', party: 'Partie', method: 'Méthode', reorder_fields: 'Réorganiser les champs', @@ -809,6 +814,7 @@ const fr = { } const de = { + font: 'Schriftart', party: 'Partei', method: 'Verfahren', reorder_fields: 'Felder neu anordnen',