From 18f80020ee55ec3a70dcdccf9ec22d86cc2d56e1 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 21 Feb 2024 10:44:19 +0200 Subject: [PATCH 01/91] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b9e06d8a..af0a1b1e 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ DocuSeal is an open source platform that provides secure and efficient digital d - Invitation and identify verification via SMS - SSO / SAML - Template creation with HTML API ([Guide](https://www.docuseal.co/guides/create-pdf-document-fillable-form-with-html-api)) -- Template creation with PDF or DOCX and text tags API ([Guide](https://www.docuseal.co/guides/use-embedded-text-field-tags-in-the-pdf-to-create-a-fillable-form)) +- Template creation with PDF or DOCX and field tags API ([Guide](https://www.docuseal.co/guides/use-embedded-text-field-tags-in-the-pdf-to-create-a-fillable-form)) - Embedded signing form ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue) or [JavaScript](https://www.docuseal.co/docs/embedded)) - Embedded document form builder ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue) or [JavaScript](https://www.docuseal.co/docs/embedded)) - [Learn more](https://www.docuseal.co/pricing) From 0a6c15b6b85059347baa2ae2a2bc64d7672d3bac Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 21 Feb 2024 16:31:17 +0200 Subject: [PATCH 02/91] add AR form i18n --- app/javascript/submission_form/i18n.js | 66 +++++++++++++++++++++++++- config/locales/en.yml | 21 ++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/app/javascript/submission_form/i18n.js b/app/javascript/submission_form/i18n.js index c82e8764..a46c7e2b 100644 --- a/app/javascript/submission_form/i18n.js +++ b/app/javascript/submission_form/i18n.js @@ -694,6 +694,70 @@ const nl = { files: 'Bestanden' } -const i18n = { en, es, it, de, fr, pl, uk, cs, pt, he, nl } +const ar = { + text: 'نص', + signature: 'توقيع', + initials: 'الاختصارات', + date: 'تاريخ', + image: 'صورة', + take_photo: 'التقاط صورة', + file: 'ملف', + select: 'اختيار', + checkbox: 'خانة اختيار', + multiple: 'متعدد', + radio: 'راديو', + cells: 'خلايا', + stamp: 'ختم', + minimize: 'تصغير', + payment: 'الدفع', + phone: 'هاتف', + submit_form: 'إرسال النموذج', + type_here_: 'اكتب هنا', + optional: 'اختياري', + option: 'خيار', + appears_on: 'يظهر على', + page: 'صفحة', + select_your_option: 'اختر خيارك', + complete_hightlighted_checkboxes_and_click: 'أكمل الخانات المميزة وانقر', + submit: 'إرسال', + next: 'التالي', + click_to_upload: 'انقر للتحميل', + or_drag_and_drop_files: 'أو اسحب وأسقط الملفات', + send_copy_via_email: 'إرسال نسخة عبر البريد الإلكتروني', + download: 'تحميل', + clear: 'مسح', + redraw: 'إعادة الرسم', + draw_initials: 'ارسم الاختصارات', + type_signature_here: 'اكتب التوقيع هنا', + type_initial_here: 'اكتب الاختصارات هنا', + form_has_been_completed: 'تم إكمال النموذج!', + create_a_free_account: 'إنشاء حساب مجاني', + signed_with: 'تم التوقيع بواسطة', + please_check_the_box_to_continue: 'الرجاء التحقق من الخانة للمتابعة', + open_source_documents_software: 'برنامج وثائق مفتوح المصدر', + verified_phone_number: 'تحقق من رقم الهاتف', + use_international_format: 'استخدم الشكل الدولي: +1xxx', + six_digits_code: 'رمز مكون من 6 أرقام', + change_phone_number: 'تغيير رقم الهاتف', + sending: 'جارٍ الإرسال', + resend_code: 'إعادة إرسال الرمز', + verification_code_has_been_resent: 'تم إعادة إرسال رمز التحقق عبر الرسائل القصيرة', + please_fill_all_required_fields: 'الرجاء ملء جميع الحقول المطلوبة', + set_today: 'تعيين اليوم', + toggle_multiline_text: 'تبديل النصوص متعددة الأسطر', + draw_signature: 'ارسم التوقيع', + type_initial: 'اكتب الاختصارات', + draw: 'ارسم', + type: 'اكتب', + type_text: 'اكتب نصًا', + email_has_been_sent: 'تم إرسال البريد الإلكتروني', + processing: 'جارٍ المعالجة', + pay_with_strip: 'الدفع بواسطة Stripe', + reupload: 'إعادة التحميل', + upload: 'تحميل', + files: 'الملفات' +} + +const i18n = { en, es, it, de, fr, pl, uk, cs, pt, he, nl, ar } export default i18n diff --git a/config/locales/en.yml b/config/locales/en.yml index fa8f30d7..fcf7e558 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -224,6 +224,27 @@ nl: downloading: Downloaden completed_successfully: Succesvol voltooid +ar: + email: البريد الإلكتروني + digitally_signed_by: تم التوقيع الرقمي بواسطة + role: الدور + provide_your_email_to_start: قدم بريدك الإلكتروني للبدء + start: بدء + starting: بداية + form_has_been_deleted_by_html: 'تم حذف الاستمارة بواسطة %{name}.' + invited_by_html: 'تمت الدعوة بواسطة %{name}' + you_have_been_invited_to_submit_a_form: تمت دعوتك لتقديم استمارة + signed_on_time: 'تم التوقيع في %{time}' + form_has_been_submitted_already: تم تقديم الاستمارة بالفعل + send_copy_to_email: إرسال نسخة إلى البريد الإلكتروني + sending: جارٍ الإرسال + resubmit: إعادة التقديم + form_has_been_deleted_by_html: 'تم حذف الاستمارة بواسطة %{name}.' + or: أو + download_documents: تحميل الوثائق + downloading: جارٍ التحميل + completed_successfully: تم الانتهاء بنجاح + en-US: <<: *en date: From 295860a9de2683febc162070180e9de72226132f Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 21 Feb 2024 17:06:27 +0200 Subject: [PATCH 03/91] update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index af0a1b1e..8f10002f 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ DocuSeal is an open source platform that provides secure and efficient digital d - PDF signature verification - Users management - Mobile-optimized +- Signing available in 12 Languages - API and Webhooks for integrations - Easy to deploy in minutes From 179ae01551305046263a5eb52058c07dfdf6e550 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 21 Feb 2024 20:15:51 +0200 Subject: [PATCH 04/91] remove rollbar --- Gemfile | 1 - Gemfile.lock | 2 -- config/initializers/rollbar.rb | 30 ------------------------------ 3 files changed, 33 deletions(-) delete mode 100644 config/initializers/rollbar.rb diff --git a/Gemfile b/Gemfile index d4983509..e1d86028 100644 --- a/Gemfile +++ b/Gemfile @@ -32,7 +32,6 @@ gem 'rack' gem 'rails' gem 'rails_autolink' gem 'rails-i18n' -gem 'rollbar', require: ENV.key?('ROLLBAR_ACCESS_TOKEN') gem 'rotp' gem 'rqrcode' gem 'ruby-vips' diff --git a/Gemfile.lock b/Gemfile.lock index be3a7959..68327e93 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -433,7 +433,6 @@ GEM railties (>= 5.2) retriable (3.1.2) rexml (3.2.6) - rollbar (3.4.2) rotp (6.3.0) rqrcode (2.2.0) chunky_png (~> 1.0) @@ -605,7 +604,6 @@ DEPENDENCIES rails rails-i18n rails_autolink - rollbar rotp rqrcode rspec-rails diff --git a/config/initializers/rollbar.rb b/config/initializers/rollbar.rb deleted file mode 100644 index 2805e07e..00000000 --- a/config/initializers/rollbar.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -require 'rollbar' if ENV.key?('ROLLBAR_ACCESS_TOKEN') - -if defined?(Rollbar) - Rollbar.configure do |config| - config.access_token = ENV.fetch('ROLLBAR_ACCESS_TOKEN', nil) - - config.transform << proc do |options| - data = options[:payload]['data'] - - if data[:request] - data[:request][:cookies] = {} - data[:request][:session] = {} - data[:request][:url] = - data[:request][:url].to_s.sub(%r{(/[sde]/)\w{8}}, '\1********').sub(/\A(.*?)--(.*)/, '\1--********') - end - end - - config.enabled = true - config.collect_user_ip = false - config.anonymize_user_ip = true - config.scrub_headers += %w[X-Auth-Token Cookie X-Csrf-Token Referer] - config.scrub_fields += %i[slug uuid attachment_uuid] - - config.exception_level_filters['ActionController::RoutingError'] = 'ignore' - - config.environment = ENV['ROLLBAR_ENV'].presence || Rails.env - end -end From 9738cdcf8f715e6b758010527befb0602f5277f4 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 23 Feb 2024 02:15:33 +0200 Subject: [PATCH 05/91] fix preview page images --- app/controllers/preview_document_page_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/preview_document_page_controller.rb b/app/controllers/preview_document_page_controller.rb index cfee948a..271d6169 100644 --- a/app/controllers/preview_document_page_controller.rb +++ b/app/controllers/preview_document_page_controller.rb @@ -8,7 +8,7 @@ class PreviewDocumentPageController < ActionController::API def show attachment_uuid = ApplicationRecord.signed_id_verifier.verified(params[:signed_uuid], purpose: :attachment) - attachment = ActiveStorage::Attachment.find_by(uuid: attachment_uuid, name: :preview_images) if attachment_uuid + attachment = ActiveStorage::Attachment.find_by(uuid: attachment_uuid) if attachment_uuid return head :not_found unless attachment From b83abc9499723adce93766756f55cfec25aeffb6 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 23 Feb 2024 10:49:28 +0200 Subject: [PATCH 06/91] fix copy field to multiple pages --- app/javascript/template_builder/field.vue | 10 ++++++---- lib/submissions/generate_result_attachments.rb | 5 ++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/app/javascript/template_builder/field.vue b/app/javascript/template_builder/field.vue index a70937c6..586b8d1b 100644 --- a/app/javascript/template_builder/field.vue +++ b/app/javascript/template_builder/field.vue @@ -485,11 +485,13 @@ export default { const areaString = JSON.stringify(field.areas[0]) this.template.documents.forEach((attachment) => { - attachment.preview_images.forEach((page) => { - if (!field.areas.find((area) => area.attachment_uuid === attachment.uuid && area.page === parseInt(page.filename))) { - field.areas.push({ ...JSON.parse(areaString), attachment_uuid: attachment.uuid, page: parseInt(page.filename) }) + const numberOfPages = attachment.metadata?.pdf?.number_of_pages || attachment.preview_images.length + + for (let page = 0; page <= numberOfPages - 1; page++) { + if (!field.areas.find((area) => area.attachment_uuid === attachment.uuid && area.page === page)) { + field.areas.push({ ...JSON.parse(areaString), attachment_uuid: attachment.uuid, page }) } - }) + } }) this.$nextTick(() => { diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index d1529519..80d88031 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -37,6 +37,7 @@ module Submissions account = submitter.submission.template.account pkcs = Accounts.load_signing_pkcs(account) tsa_url = Accounts.load_timeserver_url(account) + attachments_data_cache = {} pdfs_index = build_pdfs_index(submitter) @@ -78,7 +79,9 @@ module Submissions when 'image', 'signature', 'initials', 'stamp' attachment = submitter.attachments.find { |a| a.uuid == value } - image = Vips::Image.new_from_buffer(attachment.download, '').autorot + attachments_data_cache[attachment.uuid] ||= attachment.download + + image = Vips::Image.new_from_buffer(attachments_data_cache[attachment.uuid], '').autorot scale = [(area['w'] * width) / image.width, (area['h'] * height) / image.height].min From 03db636d51a2e2c060533180c8c830eedde46f5c Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 23 Feb 2024 15:58:49 +0200 Subject: [PATCH 07/91] add number field type --- app/javascript/application.scss | 8 +- app/javascript/submission_form/area.vue | 4 +- app/javascript/submission_form/form.vue | 30 ++++++- app/javascript/submission_form/i18n.js | 12 +++ .../submission_form/number_step.vue | 79 +++++++++++++++++++ .../template_builder/field_type.vue | 8 +- app/javascript/template_builder/fields.vue | 2 +- app/javascript/template_builder/i18n.js | 1 + lib/submitters/normalize_values.rb | 2 + lib/submitters/submit_values.rb | 2 + 10 files changed, 138 insertions(+), 10 deletions(-) create mode 100644 app/javascript/submission_form/number_step.vue diff --git a/app/javascript/application.scss b/app/javascript/application.scss index 9a220258..991fc9cd 100644 --- a/app/javascript/application.scss +++ b/app/javascript/application.scss @@ -75,16 +75,16 @@ button[disabled] .enabled { @apply select base-input w-full font-normal; } -.tooltip-bottom-start:before { - transform: translateX(-30%); +.tooltip-bottom-end:before { + transform: translateX(-95%); top: var(--tooltip-offset); left: 100%; right: auto; bottom: auto; } -.tooltip-bottom-start:after { - transform: translateX(-75%); +.tooltip-bottom-end:after { + transform: translateX(-25%); border-color: transparent transparent var(--tooltip-color) transparent; top: var(--tooltip-tail-offset); left: 50%; diff --git a/app/javascript/submission_form/area.vue b/app/javascript/submission_form/area.vue index 54fbb618..c27f0d80 100644 --- a/app/javascript/submission_form/area.vue +++ b/app/javascript/submission_form/area.vue @@ -173,7 +173,7 @@ diff --git a/app/javascript/template_builder/field_type.vue b/app/javascript/template_builder/field_type.vue index 207f556f..58edd755 100644 --- a/app/javascript/template_builder/field_type.vue +++ b/app/javascript/template_builder/field_type.vue @@ -50,7 +50,7 @@ diff --git a/app/javascript/submission_form/number_step.vue b/app/javascript/submission_form/number_step.vue index 9b8ae752..701a561f 100644 --- a/app/javascript/submission_form/number_step.vue +++ b/app/javascript/submission_form/number_step.vue @@ -59,7 +59,7 @@ export default { default: true }, modelValue: { - type: String, + type: [String, Number], required: false, default: '' } diff --git a/app/javascript/template_builder/area.vue b/app/javascript/template_builder/area.vue index e60d606e..6ce118b4 100644 --- a/app/javascript/template_builder/area.vue +++ b/app/javascript/template_builder/area.vue @@ -206,9 +206,11 @@ export default { } }, computed: { - defaultName: Field.computed.defaultName, fieldNames: FieldType.computed.fieldNames, fieldIcons: FieldType.computed.fieldIcons, + defaultName () { + return this.buildDefaultName(this.field, this.template.fields) + }, optionIndexText () { if (this.area.option_uuid && this.field.options) { return `${this.field.options.findIndex((o) => o.uuid === this.area.option_uuid) + 1}.` @@ -312,6 +314,7 @@ export default { } }, methods: { + buildDefaultName: Field.methods.buildDefaultName, onNameFocus (e) { this.selectedAreaRef.value = this.area diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue index a1d8951a..227ce948 100644 --- a/app/javascript/template_builder/builder.vue +++ b/app/javascript/template_builder/builder.vue @@ -273,6 +273,7 @@ @select="startFieldDraw($event)" /> +
@@ -321,6 +322,7 @@ export default { backgroundColor: this.backgroundColor, withPhone: this.withPhone, withPayment: this.withPayment, + withFormula: this.withFormula, defaultDrawFieldType: this.defaultDrawFieldType, selectedAreaRef: computed(() => this.selectedAreaRef) } @@ -449,6 +451,11 @@ export default { required: false, default: false }, + withFormula: { + type: Boolean, + required: false, + default: false + }, onlyDefinedFields: { type: Boolean, required: false, diff --git a/app/javascript/template_builder/field.vue b/app/javascript/template_builder/field.vue index 586b8d1b..81a66407 100644 --- a/app/javascript/template_builder/field.vue +++ b/app/javascript/template_builder/field.vue @@ -65,6 +65,17 @@ :stroke-width="1.6" /> + {{ t('required') }} +
  • + +
  • + + + @@ -366,7 +407,8 @@ import Contenteditable from './contenteditable' import FieldType from './field_type' import PaymentSettings from './payment_settings' -import { IconShape, IconNewSection, IconTrashX, IconCopy, IconSettings } from '@tabler/icons-vue' +import FormulaModal from './formula_modal' +import { IconMathFunction, IconShape, IconNewSection, IconTrashX, IconCopy, IconSettings } from '@tabler/icons-vue' import { v4 } from 'uuid' export default { @@ -377,11 +419,13 @@ export default { IconShape, PaymentSettings, IconNewSection, + FormulaModal, IconTrashX, + IconMathFunction, IconCopy, FieldType }, - inject: ['template', 'save', 'backgroundColor', 'selectedAreaRef', 't'], + inject: ['template', 'save', 'backgroundColor', 'selectedAreaRef', 't', 'withFormula'], props: { field: { type: Object, @@ -403,11 +447,15 @@ export default { return { isNameFocus: false, showPaymentModal: false, + isShowFormulaModal: false, renderDropdown: false } }, computed: { fieldNames: FieldType.computed.fieldNames, + modalContainerEl () { + return this.$el.getRootNode().querySelector('#docuseal_modal_container') + }, dateFormats () { return [ 'MM/DD/YYYY', @@ -422,22 +470,7 @@ export default { ] }, defaultName () { - if (this.field.type === 'payment' && this.field.preferences?.price) { - const { price, currency } = this.field.preferences || {} - - const formattedPrice = new Intl.NumberFormat([], { - style: 'currency', - currency - }).format(price) - - return `${this.fieldNames[this.field.type]} ${formattedPrice}` - } else { - const typeIndex = this.template.fields.filter((f) => f.type === this.field.type).indexOf(this.field) - - const suffix = { multiple: this.t('select'), radio: this.t('group') }[this.field.type] || this.t('field') - - return `${this.fieldNames[this.field.type]} ${suffix} ${typeIndex + 1}` - } + return this.buildDefaultName(this.field, this.template.fields) }, areas () { return this.field.areas || [] @@ -452,6 +485,24 @@ export default { } }, methods: { + buildDefaultName (field, fields) { + if (field.type === 'payment' && field.preferences?.price) { + const { price, currency } = field.preferences || {} + + const formattedPrice = new Intl.NumberFormat([], { + style: 'currency', + currency + }).format(price) + + return `${this.fieldNames[field.type]} ${formattedPrice}` + } else { + const typeIndex = fields.filter((f) => f.type === field.type).indexOf(field) + + const suffix = { multiple: this.t('select'), radio: this.t('group') }[field.type] || this.t('field') + + return `${this.fieldNames[field.type]} ${suffix} ${typeIndex + 1}` + } + }, formatDate (date, format) { const monthFormats = { M: 'numeric', diff --git a/app/javascript/template_builder/formula_modal.vue b/app/javascript/template_builder/formula_modal.vue new file mode 100644 index 00000000..144750e2 --- /dev/null +++ b/app/javascript/template_builder/formula_modal.vue @@ -0,0 +1,216 @@ +