diff --git a/Gemfile.lock b/Gemfile.lock index 3e602949..13217069 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -257,7 +257,7 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) hashdiff (1.2.1) - hexapdf (1.6.0) + hexapdf (1.7.0) cmdparse (~> 3.0, >= 3.0.3) geom2d (~> 0.4, >= 0.4.1) openssl (>= 2.2.1) @@ -318,7 +318,7 @@ GEM multi_json (1.19.1) net-http (0.9.1) uri (>= 0.11.1) - net-imap (0.6.3) + net-imap (0.6.4) date net-protocol net-pop (0.1.2) @@ -328,15 +328,15 @@ GEM net-smtp (0.5.1) net-protocol nio4r (2.7.5) - nokogiri (1.19.2-aarch64-linux-gnu) + nokogiri (1.19.3-aarch64-linux-gnu) racc (~> 1.4) - nokogiri (1.19.2-aarch64-linux-musl) + nokogiri (1.19.3-aarch64-linux-musl) racc (~> 1.4) - nokogiri (1.19.2-arm64-darwin) + nokogiri (1.19.3-arm64-darwin) racc (~> 1.4) - nokogiri (1.19.2-x86_64-linux-gnu) + nokogiri (1.19.3-x86_64-linux-gnu) racc (~> 1.4) - nokogiri (1.19.2-x86_64-linux-musl) + nokogiri (1.19.3-x86_64-linux-musl) racc (~> 1.4) numo-narray-alt (0.10.3) oj (3.16.16) diff --git a/app/controllers/api/attachments_controller.rb b/app/controllers/api/attachments_controller.rb index c81aa0f9..2f615878 100644 --- a/app/controllers/api/attachments_controller.rb +++ b/app/controllers/api/attachments_controller.rb @@ -8,10 +8,10 @@ module Api COOKIE_STORE_LIMIT = 10 def create - submitter = Submitter.find_by!(slug: params[:submitter_slug]) + @submitter = Submitter.find_by!(slug: params[:submitter_slug]) - unless can_upload?(submitter) - Rollbar.error("Can't upload: #{submitter.id}") if defined?(Rollbar) + unless can_upload?(@submitter) + Rollbar.error("Can't upload: #{@submitter.id}") if defined?(Rollbar) return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_content end @@ -20,23 +20,23 @@ module Api image = Vips::Image.new_from_file(params[:file].path) if ImageUtils.blank?(image) - Rollbar.error("Empty signature: #{submitter.id}") if defined?(Rollbar) + Rollbar.error("Empty signature: #{@submitter.id}") if defined?(Rollbar) return render json: { error: "#{params[:type]} is empty" }, status: :unprocessable_content end if ImageUtils.error?(image) - Rollbar.error("Error signature: #{submitter.id}") if defined?(Rollbar) + Rollbar.error("Error signature: #{@submitter.id}") if defined?(Rollbar) return render json: { error: "#{params[:type]} error, try to sign on another device" }, status: :unprocessable_content end end - attachment = Submitters.create_attachment!(submitter, params) + attachment = Submitters.create_attachment!(@submitter, params) - if params[:remember_signature] == 'true' && submitter.email.present? - cookies.encrypted[:signature_uuids] = build_new_cookie_signatures_json(submitter, attachment) + if params[:remember_signature] == 'true' && @submitter.email.present? + cookies.encrypted[:signature_uuids] = build_new_cookie_signatures_json(@submitter, attachment) end render json: attachment.as_json(only: %i[uuid created_at], methods: %i[url filename content_type]) diff --git a/app/controllers/submit_form_invite_controller.rb b/app/controllers/submit_form_invite_controller.rb index 413e2b9a..ac7f6db0 100644 --- a/app/controllers/submit_form_invite_controller.rb +++ b/app/controllers/submit_form_invite_controller.rb @@ -5,12 +5,12 @@ class SubmitFormInviteController < ApplicationController skip_authorization_check def create - submitter = Submitter.find_by!(slug: params[:submit_form_slug]) + @submitter = Submitter.find_by!(slug: params[:submit_form_slug]) - return head :unprocessable_content unless can_invite?(submitter) + return head :unprocessable_content unless can_invite?(@submitter) - invite_submitters = filter_invite_submitters(submitter, 'invite_by_uuid') - optional_invite_submitters = filter_invite_submitters(submitter, 'optional_invite_by_uuid') + invite_submitters = filter_invite_submitters(@submitter, 'invite_by_uuid') + optional_invite_submitters = filter_invite_submitters(@submitter, 'optional_invite_by_uuid') ApplicationRecord.transaction do (invite_submitters + optional_invite_submitters).each do |item| @@ -21,18 +21,18 @@ class SubmitFormInviteController < ApplicationController email = Submissions.normalize_email(attrs[:email]) - submitter.submission.submitters.create!(uuid: attrs[:uuid], email:, account_id: submitter.account_id) + @submitter.submission.submitters.create!(uuid: attrs[:uuid], email:, account_id: @submitter.account_id) - SubmissionEvents.create_with_tracking_data(submitter, 'invite_party', request, { uuid: submitter.uuid }) + SubmissionEvents.create_with_tracking_data(@submitter, 'invite_party', request, { uuid: @submitter.uuid }) end - submitter.submission.update!(submitters_order: :preserved) + @submitter.submission.update!(submitters_order: :preserved) end - submitter.submission.submitters.reload + @submitter.submission.submitters.reload - if invite_submitters.all? { |s| submitter.submission.submitters.any? { |e| e.uuid == s['uuid'] } } - Submitters::SubmitValues.call(submitter, ActionController::Parameters.new(completed: 'true'), request) + if invite_submitters.all? { |s| @submitter.submission.submitters.any? { |e| e.uuid == s['uuid'] } } + Submitters::SubmitValues.call(@submitter, ActionController::Parameters.new(completed: 'true'), request) head :ok else diff --git a/app/controllers/submit_form_metadata_controller.rb b/app/controllers/submit_form_metadata_controller.rb index 49bf8666..dcdeaa20 100644 --- a/app/controllers/submit_form_metadata_controller.rb +++ b/app/controllers/submit_form_metadata_controller.rb @@ -5,19 +5,19 @@ class SubmitFormMetadataController < ApplicationController skip_authorization_check def index - submitter = Submitter.find_by!(slug: params[:submit_form_slug]) + @submitter = Submitter.find_by!(slug: params[:submit_form_slug]) - return head :not_found if submitter.declined_at? || - submitter.completed_at? || - submitter.submission.archived_at? || - submitter.submission.expired? || - submitter.submission.template&.archived_at? || - submitter.account.archived_at? || - !Submitters::AuthorizedForForm.call(submitter, current_user, request) + return head :not_found if @submitter.declined_at? || + @submitter.completed_at? || + @submitter.submission.archived_at? || + @submitter.submission.expired? || + @submitter.submission.template&.archived_at? || + @submitter.account.archived_at? || + !Submitters::AuthorizedForForm.call(@submitter, current_user, request) - submission = submitter.submission + submission = @submitter.submission values = submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } - schema = Submissions.filtered_conditions_schema(submission, values:, include_submitter_uuid: submitter.uuid) + schema = Submissions.filtered_conditions_schema(submission, values:, include_submitter_uuid: @submitter.uuid) documents = schema.filter_map do |item| submission.schema_documents.find { |a| a.uuid == item['attachment_uuid'] } diff --git a/app/controllers/submit_form_values_controller.rb b/app/controllers/submit_form_values_controller.rb index affd37ba..1e14ce69 100644 --- a/app/controllers/submit_form_values_controller.rb +++ b/app/controllers/submit_form_values_controller.rb @@ -5,21 +5,21 @@ class SubmitFormValuesController < ApplicationController skip_authorization_check def index - submitter = Submitter.find_by!(slug: params[:submit_form_slug]) + @submitter = Submitter.find_by!(slug: params[:submit_form_slug]) - return render json: {} if submitter.completed_at? || - submitter.declined_at? || - submitter.submission.template&.archived_at? || - submitter.submission.archived_at? || - submitter.submission.expired? || - !Submitters::AuthorizedForForm.call(submitter, current_user, request) + return render json: {} if @submitter.completed_at? || + @submitter.declined_at? || + @submitter.submission.template&.archived_at? || + @submitter.submission.archived_at? || + @submitter.submission.expired? || + !Submitters::AuthorizedForForm.call(@submitter, current_user, request) - value = submitter.values[params['field_uuid']] - attachment = submitter.attachments.where(created_at: params[:after]..).find_by(uuid: value) if value.present? + value = @submitter.values[params['field_uuid']] + attachment = @submitter.attachments.where(created_at: params[:after]..).find_by(uuid: value) if value.present? render json: { value:, attachment: attachment&.as_json(only: %i[uuid created_at], methods: %i[url filename content_type]) - }, head: :ok + } end end diff --git a/app/controllers/templates_controller.rb b/app/controllers/templates_controller.rb index a6a5cbe8..2d612166 100644 --- a/app/controllers/templates_controller.rb +++ b/app/controllers/templates_controller.rb @@ -78,6 +78,8 @@ class TemplatesController < ApplicationController WebhookUrls.enqueue_events(@template, 'template.updated') + TemplateVersions.find_or_create_for(@template, author: current_user) if params[:revision] + head :ok end diff --git a/app/controllers/templates_versions_controller.rb b/app/controllers/templates_versions_controller.rb new file mode 100644 index 00000000..c9a67b8f --- /dev/null +++ b/app/controllers/templates_versions_controller.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class TemplatesVersionsController < ApplicationController + load_and_authorize_resource :template + + def index + versions = @template.template_versions.order(id: :desc).preload(:author) + + render json: versions.as_json(TemplateVersions::SERIALIZE_PARAMS) + end + + def show + version = @template.template_versions.find(params[:id]) + + render json: TemplateVersions.serialize(version) + end +end diff --git a/app/controllers/webhook_hmac_controller.rb b/app/controllers/webhook_hmac_controller.rb new file mode 100644 index 00000000..f0710b15 --- /dev/null +++ b/app/controllers/webhook_hmac_controller.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class WebhookHmacController < ApplicationController + load_and_authorize_resource :webhook_url, parent: false + + def show; end +end diff --git a/app/javascript/application.js b/app/javascript/application.js index ff617caa..af25f2b4 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -170,6 +170,8 @@ safeRegisterElement('template-builder', class extends HTMLElement { withLogo: this.dataset.withLogo !== 'false', withFieldsDetection: this.dataset.withFieldsDetection === 'true', withDetectExistingFields: this.dataset.withDetectExistingFields === 'true', + withRevisions: true, + withRevisionsMenu: this.dataset.withRevisionsMenu === 'true', editable: this.dataset.editable !== 'false', authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content, withCustomFields: true, diff --git a/app/javascript/submission_form/i18n.js b/app/javascript/submission_form/i18n.js index 09d2fb8f..2a265d18 100644 --- a/app/javascript/submission_form/i18n.js +++ b/app/javascript/submission_form/i18n.js @@ -136,7 +136,7 @@ const es = { verification_code_is_invalid: 'El código de verificación no es válido', sign_on_the_touchscreen: 'Firmar en pantalla táctil', scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Escanea el código QR con la aplicación de la cámara para abrir el formulario en el móvil y dibujar tu firma', - by_clicking_you_agree_to_the: 'Al hacer clic en "{button}", usted acepta el', + by_clicking_you_agree_to_the: 'Al hacer clic en "{button}", usted acepta la', electronic_signature_disclosure: 'Divulgación de Firma Electrónica', esignature_disclosure: 'Divulgación de eFirma', already_paid: 'Ya pagado', @@ -245,7 +245,7 @@ const it = { verification_code_is_invalid: 'Il codice di verifica non è valido', sign_on_the_touchscreen: 'Firma su schermo tattile', scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: "Scansiona il codice QR con l'app della fotocamera per aprire il modulo sul cellulare e disegnare la tua firma", - by_clicking_you_agree_to_the: 'Cliccando su "{button}", accetti il', + by_clicking_you_agree_to_the: 'Cliccando su "{button}", accetti la', electronic_signature_disclosure: 'Divulgazione della Firma Elettronica', esignature_disclosure: 'Divulgazione della eFirma', minimize: 'Minimizza', @@ -258,7 +258,7 @@ const it = { date: 'Data', number: 'Numero', image: 'Immagine', - pay: 'Pagamento', + pay: 'Paga', file: 'File', select: 'Seleziona', checkbox: 'Checkbox', @@ -574,7 +574,7 @@ const pl = { scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Zeskanuj kod QR za pomocą aplikacji aparatu, aby otworzyć formularz na telefonie i narysować swój podpis', by_clicking_you_agree_to_the: 'Klikając na "{button}", zgadzasz się na', electronic_signature_disclosure: 'Ujawnienie Elektronicznej Sygnatury', - esignature_disclosure: 'Ujawnienie ePodpisu', + esignature_disclosure: 'informację o e-podpisie', minimize: 'Zminimalizuj', text: 'Tekst', already_paid: 'Już zapłacono', @@ -642,7 +642,7 @@ const pl = { toggle_multiline_text: 'Przełącz Tekst Wielolinijkowy', email_has_been_sent: 'E-mail został wysłany', processing: 'Przetwarzanie', - pay_with_stripe: 'Płatność za pomocą Stripe', + pay_with_stripe: 'Zapłać za pomocą Stripe', reupload: 'Ponowne przesłanie', upload: 'Przesyłanie', files: 'Pliki', @@ -678,12 +678,12 @@ const uk = { authored_by: 'Автор', select_a_reason: 'Виберіть причину', value_is_invalid: 'Значення є неправильним', - verification_code_is_invalid: 'Код підтвердження є неправильним', + verification_code_is_invalid: 'Невірний код підтвердження', sign_on_the_touchscreen: 'Підписати на сенсорному екрані', scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Скануйте QR-код за допомогою програми камери, щоб відкрити форму на мобільному пристрої та намалювати свій підпис', by_clicking_you_agree_to_the: 'Натиснувши на "{button}", ви погоджуєтеся з', - electronic_signature_disclosure: 'Розголошення Електронного Підпису', - esignature_disclosure: 'Розголошення еПідпису', + electronic_signature_disclosure: 'інформацією про е-підпис', + esignature_disclosure: 'інформацією про е-підпис', minimize: 'Зменшити', text: 'Текст', already_paid: 'Вже оплачено', @@ -751,7 +751,7 @@ const uk = { toggle_multiline_text: 'Перемкнути Багаторядковий Текст', email_has_been_sent: 'Електронний лист був відправлений', processing: 'Обробка', - pay_with_strip: 'Сплатити за допомогою Stripe', + pay_with_stripe: 'Сплатити за допомогою Stripe', reupload: 'Перезавантажити', upload: 'Завантажити', files: 'Файли', @@ -774,7 +774,7 @@ const cs = { verify_id: 'Ověřit ID', identity_verification: 'Ověření identity', complete: 'Dokončit', - fill_all_required_fields_to_complete: 'Please complete all mandatory fields', + fill_all_required_fields_to_complete: 'Vyplňte povinná pole pro dokončení', sign_and_complete: 'Podepsat a dokončit', invite: 'Pozvat', email: 'E-mail', @@ -782,8 +782,8 @@ const cs = { reviewed: 'Zkontrolováno', other: 'Jiné', authored_by_me: 'Autorem jsem já', - approved_by: 'Schváleno kým', - reviewed_by: 'Zkontrolováno kým', + approved_by: 'Schválil(a)', + reviewed_by: 'Zkontroloval(a)', authored_by: 'Autorem', select_a_reason: 'Vyberte důvod', value_is_invalid: 'Hodnota je neplatná', @@ -792,7 +792,7 @@ const cs = { scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Naskenujte QR kód pomocí aplikace fotoaparátu, abyste otevřeli formulář na mobilním zařízení a nakreslili svůj podpis', by_clicking_you_agree_to_the: 'Kliknutím na "{button}" souhlasíte s', electronic_signature_disclosure: 'Zveřejněním Elektronického Podpisu', - esignature_disclosure: 'Zveřejnění ePodpisu', + esignature_disclosure: 'informací o e-podpisu', already_paid: 'Už zaplaceno', minimize: 'Minimalizovat', text: 'Text', @@ -860,7 +860,7 @@ const cs = { toggle_multiline_text: 'Přepnout Víceřádkový Text', email_has_been_sent: 'E-mail byl odeslán', processing: 'Zpracování', - pay_with_stripe: 'Zaplacení přes Stripe', + pay_with_stripe: 'Zaplatit přes Stripe', reupload: 'Znovu nahrát', upload: 'Nahrát', files: 'Soubory', @@ -890,18 +890,18 @@ const pt = { approved: 'Aprovado', reviewed: 'Revisado', other: 'Outro', - authored_by_me: 'Autorizado por mim', + authored_by_me: 'Escrito por mim', approved_by: 'Aprovado por', reviewed_by: 'Revisado por', - authored_by: 'Autorizado por', + authored_by: 'Escrito por', select_a_reason: 'Selecione um motivo', value_is_invalid: 'Valor é inválido', verification_code_is_invalid: 'Código de verificação é inválido', sign_on_the_touchscreen: 'Assinar na tela sensível', scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Escaneie o código QR com o aplicativo da câmera para abrir o formulário no celular e desenhar sua assinatura', - by_clicking_you_agree_to_the: 'Ao clicar em "{button}", você concorda com o', + by_clicking_you_agree_to_the: 'Ao clicar em "{button}", você concorda com a', electronic_signature_disclosure: 'Divulgação de Assinatura Eletrônica', - esignature_disclosure: 'Divulgação da eAssinatura', + esignature_disclosure: 'Informação sobre eAssinatura', already_paid: 'Já pago', minimize: 'Minimizar', text: 'Texto', @@ -970,7 +970,7 @@ const pt = { email_has_been_sent: 'Email enviado', processing: 'Processamento', pay_with_stripe: 'Pagar com Stripe', - reupload: 'Reenviar', + reupload: 'Recarregar', upload: 'Carregar', files: 'Arquivos', signature_is_too_small_or_simple_please_redraw: 'A assinatura é muito pequena ou simples. Por favor, redesenhe.', @@ -1010,8 +1010,8 @@ const he = { scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'סרוק את קוד ה-QR באמצעות אפליקציית המצלמה כדי לפתוח את הטופס במובייל ולצייר את החתימה שלך', by_clicking_you_agree_to_the: 'על ידי לחיצה על "{button}", אתה מסכים ל', electronic_signature_disclosure: 'חשיפת חתימה אלקטרונית', - esignature_disclosure: 'חשיפת ה-eחתימה', - minimize: 'לקטן', + esignature_disclosure: 'חשיפת חתימה אלקטרונית', + minimize: 'מזער', text: 'טקסט', already_paid: 'כבר שולם', signature: 'חתימה', @@ -1022,9 +1022,9 @@ const he = { number: 'מספר', image: 'תמונה', file: 'קובץ', - pay: 'תשלום', + pay: 'שלם', select: 'בחר', - checkbox: 'תיק בחירה', + checkbox: 'סימון', multiple: 'רב ערכים', radio: 'רדיו', cells: 'תאים', @@ -1051,9 +1051,9 @@ const he = { download: 'הורדה', clear: 'נקה', redraw: 'צייר מחדש', - draw_initials: 'צייר ציוני ראשי תיבות', + draw_initials: 'צייר ראשי תיבות', type_signature_here: 'הקלד חתימה כאן', - type_initial_here: 'הקלד ציוני ראשי תיבות כאן', + type_initial_here: 'הקלד ראשי תיבות כאן', form_has_been_completed: 'הטופס הושלם', document_has_been_signed: 'המסמך נחתם!', documents_have_been_signed: 'המסמכים נחתמו!', @@ -1063,7 +1063,7 @@ const he = { open_source_documents_software: 'תוכנה פתוחה למסמכים', verified_phone_number: 'אימות מספר טלפון', use_international_format: 'השתמש בפורמט בינלאומי: +1xxx', - six_digits_code: 'קוד משתמש שש ספרות', + six_digits_code: 'קוד שש ספרות', change_phone_number: 'שינוי מספר טלפון', sending: 'שולח', resend_code: 'שלח מחדש קוד', @@ -1072,7 +1072,7 @@ const he = { set_today: 'קבע היום', toggle_multiline_text: 'שנה בין טקסט במספר שורות לטקסט בשורה אחת', draw_signature: 'צייר חתימה', - type_initial: 'הקלד ציוני ראשי תיבות', + type_initial: 'הקלד ראשי תיבות', draw: 'צייר', type: 'הקלד', type_text: 'הקלד טקסט', @@ -1098,7 +1098,7 @@ const nl = { please_upload_an_image_file: 'Upload alstublieft een afbeeldingsbestand', must_be_characters_length: 'Moet {number} tekens lang zijn', complete_all_required_fields_to_proceed_with_identity_verification: 'Vul alle verplichte velden in om door te gaan met de identiteitsverificatie.', - verify_id: 'Verifiëren ID', + verify_id: 'ID verifiëren', identity_verification: 'Identiteitsverificatie', complete: 'Voltooien', fill_all_required_fields_to_complete: 'Vul alle verplichte velden in om te voltooien', @@ -1146,7 +1146,7 @@ const nl = { continue: 'Doorgaan', sign_now: 'Nu ondertekenen', type_here_: 'Typ hier...', - optional: 'Optioneel', + optional: 'optioneel', option: 'Optie', appears_on: 'Verschijnt op', page: 'Pagina', @@ -1170,11 +1170,11 @@ const nl = { powered_by: 'Aangedreven door', please_check_the_box_to_continue: 'Vink het vakje aan om door te gaan.', open_source_documents_software: 'Open source documenten software', - verified_phone_number: 'Geverifieerd telefoonnummer', + verified_phone_number: 'Telefoonnummer verifiëren', use_international_format: 'Gebruik internationaal formaat: +1xxx', six_digits_code: '6-cijferige code', change_phone_number: 'Wijzig telefoonnummer', - sending: 'Voltooien...', + sending: 'Verzenden...', resend_code: 'Code opnieuw verzenden', verification_code_has_been_resent: 'Verificatiecode is opnieuw verzonden via SMS', please_fill_all_required_fields: 'Vul alle verplichte velden in', @@ -1232,7 +1232,7 @@ const ar = { esignature_disclosure: 'كشف التوقيع الإلكتروني', text: 'نص', signature: 'توقيع', - initials: 'الاختصارات', + initials: 'حروف أولى', date: 'تاريخ', number: 'رقم', digitally_signed_by: 'تم التوقيع رقميًا بواسطة', @@ -1269,9 +1269,9 @@ const ar = { download: 'تحميل', clear: 'مسح', redraw: 'إعادة الرسم', - draw_initials: 'ارسم الاختصارات', + draw_initials: 'ارسم حروف أولى', type_signature_here: 'اكتب التوقيع هنا', - type_initial_here: 'اكتب الاختصارات هنا', + type_initial_here: 'اكتب حروف أولى هنا', form_has_been_completed: 'تم إكمال النموذج!', document_has_been_signed: 'تم توقيع الوثيقة!', documents_have_been_signed: 'تم توقيع الوثائق!', @@ -1290,13 +1290,13 @@ const ar = { set_today: 'تعيين اليوم', toggle_multiline_text: 'تبديل النصوص متعددة الأسطر', draw_signature: 'ارسم التوقيع', - type_initial: 'اكتب الاختصارات', + type_initial: 'اكتب حروف أولى', draw: 'ارسم', type: 'اكتب', type_text: 'اكتب نصًا', email_has_been_sent: 'تم إرسال البريد الإلكتروني', processing: 'جارٍ المعالجة', - pay_with_strip: 'الدفع بواسطة Stripe', + pay_with_stripe: 'الدفع بواسطة Stripe', reupload: 'إعادة التحميل', upload: 'تحميل', files: 'الملفات', @@ -1343,10 +1343,10 @@ const ko = { signature: '서명', initials: '이니셜', date: '날짜', - digitally_signed_by: '디지털 서명이 완료되었습니다', + digitally_signed_by: '디지털 서명자:', reason: '이유', number: '숫자', - pay: '급여', + pay: '결제', image: '이미지', take_photo: '사진 찍기', file: '파일', diff --git a/app/javascript/template_builder/area.vue b/app/javascript/template_builder/area.vue index 021f7dd7..e5c50aeb 100644 --- a/app/javascript/template_builder/area.vue +++ b/app/javascript/template_builder/area.vue @@ -50,6 +50,7 @@ @remove="$emit('remove')" @scroll-to="$emit('scroll-to', $event)" @add-custom-field="$emit('add-custom-field')" + @click-title="$emit('click-title')" />
+
{{ t('preferences') }} +
  • + +
  • @@ -618,7 +663,8 @@ import DocumentPreview from './preview' import DocumentControls from './controls' import MobileFields from './mobile_fields' import FieldSubmitter from './field_submitter' -import { IconPlus, IconUsersPlus, IconDeviceFloppy, IconChevronDown, IconEye, IconWritingSign, IconInnerShadowTop, IconInfoCircle, IconAdjustments, IconDownload } from '@tabler/icons-vue' +import RevisionsModal from './revisions_modal' +import { IconPlus, IconUsersPlus, IconDeviceFloppy, IconChevronDown, IconEye, IconWritingSign, IconInnerShadowTop, IconInfoCircle, IconAdjustments, IconDownload, IconHistory } from '@tabler/icons-vue' import { v4 } from 'uuid' import { ref, computed, toRaw, defineAsyncComponent } from 'vue' import * as i18n from './i18n' @@ -658,7 +704,9 @@ export default { IconDownload, IconAdjustments, IconEye, - IconDeviceFloppy + IconHistory, + IconDeviceFloppy, + RevisionsModal }, provide () { return { @@ -980,6 +1028,16 @@ export default { type: Boolean, required: false, default: false + }, + withRevisions: { + type: Boolean, + required: false, + default: false + }, + withRevisionsMenu: { + type: Boolean, + required: false, + default: false } }, data () { @@ -1002,7 +1060,10 @@ export default { drawOption: null, dragField: null, isDragFile: false, - isMathLoaded: false + isMathLoaded: false, + isRevisionsModalOpen: false, + revisions: [], + beforeRevisionSnapshot: null } }, computed: { @@ -1767,6 +1828,73 @@ export default { closeDropdown () { document.activeElement.blur() }, + preloadRevisions () { + this.loadRevisionsPromise ||= this.baseFetch(`/templates/${this.template.id}/versions`) + }, + openRevisionsModal () { + this.closeDropdown() + + this.loadRevisionsPromise ||= this.baseFetch(`/templates/${this.template.id}/versions`) + + this.loadRevisionsPromise.then(async (resp) => { + this.revisions = await resp.json() + + this.isRevisionsModalOpen = true + }).finally(() => { + this.loadRevisionsPromise = null + }) + }, + onRevisionApply (revision) { + this.beforeRevisionSnapshot = { + template: JSON.parse(JSON.stringify(this.template)), + dynamicDocuments: JSON.parse(JSON.stringify(this.dynamicDocuments)), + revision + } + + const { dynamic_documents: nextDynamicDocs = [], ...nextTemplate } = revision.data + + Object.assign(this.template, nextTemplate) + + this.dynamicDocuments.splice(0, this.dynamicDocuments.length, ...nextDynamicDocs) + + this.$nextTick(() => this.reloadDynamicDocumentContent()) + + this.isRevisionsModalOpen = false + }, + cancelRevision () { + Object.assign(this.template, this.beforeRevisionSnapshot.template) + + this.dynamicDocuments.splice(0, this.dynamicDocuments.length, ...this.beforeRevisionSnapshot.dynamicDocuments) + + this.beforeRevisionSnapshot = null + + this.$nextTick(() => this.reloadDynamicDocumentContent()) + }, + applyRevision () { + this.beforeRevisionSnapshot = null + + const dynamicDocumentRefs = this.documentRefs.filter((ref) => ref.isDynamic) + + dynamicDocumentRefs.forEach((ref) => ref.update()) + + this.rebuildVariablesSchema({ disable: false }) + + return Promise.all([this.save({ force: true }), ...dynamicDocumentRefs.map((ref) => ref.saveBody())]) + }, + reloadDynamicDocumentContent () { + this.documentRefs.forEach((ref) => { + if (ref.isDynamic) ref.reloadContent() + }) + }, + formatRevisionTime (string) { + return new Date(string).toLocaleString(this.locale || undefined, { + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: 'numeric', + minute: '2-digit' + }) + }, t (key) { return this.i18n[key] || i18n[this.language]?.[key] || i18n.en[key] || key }, @@ -3013,7 +3141,7 @@ export default { const dynamicDocumentSaves = dynamicDocumentRefs.map((ref) => ref.saveBody()) - Promise.all([this.save(), ...dynamicDocumentSaves]).then(() => { + Promise.all([this.save({ force: true, revision: this.withRevisions }), ...dynamicDocumentSaves]).then(() => { window.Turbo.visit(`/templates/${this.template.id}`) }).finally(() => { this.isSaving = false @@ -3244,9 +3372,13 @@ export default { } }) }, - save ({ force } = { force: false }) { + save ({ force = false, revision = false } = {}) { this.pendingFieldAttachmentUuids = [] + if (this.beforeRevisionSnapshot) { + this.beforeRevisionSnapshot = null + } + if (this.onChange) { this.onChange(this.template) } @@ -3272,7 +3404,8 @@ export default { submitters: this.template.submitters, fields: this.template.fields, variables_schema: this.template.variables_schema - } + }, + ...(revision ? { revision: true } : {}) }), headers: { 'Content-Type': 'application/json' } }).then(() => { diff --git a/app/javascript/template_builder/conditions_modal.vue b/app/javascript/template_builder/conditions_modal.vue index d8219ff6..0b17affc 100644 --- a/app/javascript/template_builder/conditions_modal.vue +++ b/app/javascript/template_builder/conditions_modal.vue @@ -6,7 +6,7 @@ class="absolute top-0 bottom-0 right-0 left-0" @click.prevent="$emit('close')" /> -