Merge from docusealco/wip
This commit is contained in:
+2
-1
@@ -48,7 +48,8 @@ ENV OPENSSL_CONF=/etc/openssl_legacy.cnf
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apk add --no-cache libpq vips redis onnxruntime
|
RUN apk add --no-cache libpq vips redis onnxruntime && \
|
||||||
|
rm -f /usr/bin/onnx_test_runner /usr/bin/onnxruntime_test
|
||||||
|
|
||||||
RUN addgroup -g 2000 docuseal && adduser -u 2000 -G docuseal -s /bin/sh -D -h /home/docuseal docuseal
|
RUN addgroup -g 2000 docuseal && adduser -u 2000 -G docuseal -s /bin/sh -D -h /home/docuseal docuseal
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ gem 'faraday'
|
|||||||
gem 'faraday-follow_redirects'
|
gem 'faraday-follow_redirects'
|
||||||
gem 'google-cloud-storage', require: false
|
gem 'google-cloud-storage', require: false
|
||||||
gem 'hexapdf'
|
gem 'hexapdf'
|
||||||
gem 'image_processing'
|
|
||||||
gem 'jwt', require: false
|
gem 'jwt', require: false
|
||||||
gem 'lograge'
|
gem 'lograge'
|
||||||
gem 'numo-narray-alt', require: false
|
gem 'numo-narray-alt', require: false
|
||||||
|
|||||||
+4
-10
@@ -264,9 +264,6 @@ GEM
|
|||||||
strscan (>= 3.1.2)
|
strscan (>= 3.1.2)
|
||||||
i18n (1.14.8)
|
i18n (1.14.8)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
image_processing (1.14.0)
|
|
||||||
mini_magick (>= 4.9.5, < 6)
|
|
||||||
ruby-vips (>= 2.0.17, < 3)
|
|
||||||
io-console (0.8.2)
|
io-console (0.8.2)
|
||||||
irb (1.18.0)
|
irb (1.18.0)
|
||||||
pp (>= 0.6.0)
|
pp (>= 0.6.0)
|
||||||
@@ -274,7 +271,7 @@ GEM
|
|||||||
rdoc (>= 4.0.0)
|
rdoc (>= 4.0.0)
|
||||||
reline (>= 0.4.2)
|
reline (>= 0.4.2)
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
json (2.19.5)
|
json (2.19.7)
|
||||||
jwt (3.2.0)
|
jwt (3.2.0)
|
||||||
base64
|
base64
|
||||||
language_server-protocol (3.17.0.5)
|
language_server-protocol (3.17.0.5)
|
||||||
@@ -308,8 +305,6 @@ GEM
|
|||||||
marcel (1.1.0)
|
marcel (1.1.0)
|
||||||
matrix (0.4.3)
|
matrix (0.4.3)
|
||||||
method_source (1.1.0)
|
method_source (1.1.0)
|
||||||
mini_magick (5.3.1)
|
|
||||||
logger
|
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
minitest (6.0.6)
|
minitest (6.0.6)
|
||||||
drb (~> 2.0)
|
drb (~> 2.0)
|
||||||
@@ -434,7 +429,7 @@ GEM
|
|||||||
erb
|
erb
|
||||||
psych (>= 4.0.0)
|
psych (>= 4.0.0)
|
||||||
tsort
|
tsort
|
||||||
redis-client (0.28.0)
|
redis-client (0.29.0)
|
||||||
connection_pool
|
connection_pool
|
||||||
regexp_parser (2.11.3)
|
regexp_parser (2.11.3)
|
||||||
reline (0.6.3)
|
reline (0.6.3)
|
||||||
@@ -516,12 +511,12 @@ GEM
|
|||||||
rack-proxy (>= 0.6.1)
|
rack-proxy (>= 0.6.1)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
semantic_range (>= 2.3.0)
|
semantic_range (>= 2.3.0)
|
||||||
sidekiq (8.1.2)
|
sidekiq (8.1.6)
|
||||||
connection_pool (>= 3.0.0)
|
connection_pool (>= 3.0.0)
|
||||||
json (>= 2.16.0)
|
json (>= 2.16.0)
|
||||||
logger (>= 1.7.0)
|
logger (>= 1.7.0)
|
||||||
rack (>= 3.2.0)
|
rack (>= 3.2.0)
|
||||||
redis-client (>= 0.26.0)
|
redis-client (>= 0.29.0)
|
||||||
signet (0.21.0)
|
signet (0.21.0)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
faraday (>= 0.17.5, < 3.a)
|
faraday (>= 0.17.5, < 3.a)
|
||||||
@@ -624,7 +619,6 @@ DEPENDENCIES
|
|||||||
foreman
|
foreman
|
||||||
google-cloud-storage
|
google-cloud-storage
|
||||||
hexapdf
|
hexapdf
|
||||||
image_processing
|
|
||||||
jwt
|
jwt
|
||||||
letter_opener_web
|
letter_opener_web
|
||||||
lograge
|
lograge
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ module Api
|
|||||||
@submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
@submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
||||||
|
|
||||||
unless can_upload?(@submitter)
|
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
|
return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_content
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -33,9 +31,11 @@ module Api
|
|||||||
return render json: { error: "#{params[:type]} error, try to sign on another device" },
|
return render json: { error: "#{params[:type]} error, try to sign on another device" },
|
||||||
status: :unprocessable_content
|
status: :unprocessable_content
|
||||||
end
|
end
|
||||||
|
|
||||||
|
metadata = { analyzed: true, identified: true, width: image.width, height: image.height }
|
||||||
end
|
end
|
||||||
|
|
||||||
attachment = Submitters.create_attachment!(@submitter, file)
|
attachment = Submitters.create_attachment!(@submitter, file, metadata:)
|
||||||
|
|
||||||
if params[:remember_signature] == 'true' && @submitter.email.present?
|
if params[:remember_signature] == 'true' && @submitter.email.present?
|
||||||
cookies.encrypted[:signature_uuids] = build_new_cookie_signatures_json(@submitter, attachment)
|
cookies.encrypted[:signature_uuids] = build_new_cookie_signatures_json(@submitter, attachment)
|
||||||
|
|||||||
@@ -9,20 +9,7 @@ module Api
|
|||||||
|
|
||||||
templates = paginate(templates.preload(:author, folder: :parent_folder))
|
templates = paginate(templates.preload(:author, folder: :parent_folder))
|
||||||
|
|
||||||
schema_documents =
|
schema_documents, dynamic_documents, preview_image_attachments = preload_relations(templates)
|
||||||
ActiveStorage::Attachment.where(record_id: templates.map(&:id),
|
|
||||||
record_type: 'Template',
|
|
||||||
name: :documents,
|
|
||||||
uuid: templates.flat_map { |t| t.schema.pluck('attachment_uuid') })
|
|
||||||
.preload(:blob)
|
|
||||||
|
|
||||||
preview_image_attachments =
|
|
||||||
ActiveStorage::Attachment.joins(:blob)
|
|
||||||
.where(blob: { filename: ['0.png', '0.jpg'] })
|
|
||||||
.where(record_id: schema_documents.map(&:id),
|
|
||||||
record_type: 'ActiveStorage::Attachment',
|
|
||||||
name: :preview_images)
|
|
||||||
.preload(:blob)
|
|
||||||
|
|
||||||
expires_at = Accounts.link_expires_at(current_account)
|
expires_at = Accounts.link_expires_at(current_account)
|
||||||
|
|
||||||
@@ -30,6 +17,7 @@ module Api
|
|||||||
data: templates.map do |t|
|
data: templates.map do |t|
|
||||||
Templates::SerializeForApi.call(t,
|
Templates::SerializeForApi.call(t,
|
||||||
schema_documents: schema_documents.select { |e| e.record_id == t.id },
|
schema_documents: schema_documents.select { |e| e.record_id == t.id },
|
||||||
|
dynamic_documents:,
|
||||||
preview_image_attachments:,
|
preview_image_attachments:,
|
||||||
expires_at:)
|
expires_at:)
|
||||||
end,
|
end,
|
||||||
@@ -88,6 +76,41 @@ module Api
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def preload_relations(templates)
|
||||||
|
schema_documents =
|
||||||
|
ActiveStorage::Attachment.where(record_id: templates.map(&:id),
|
||||||
|
record_type: 'Template',
|
||||||
|
name: :documents,
|
||||||
|
uuid: templates.flat_map { |t| t.schema.pluck('attachment_uuid') })
|
||||||
|
.preload(:blob)
|
||||||
|
|
||||||
|
dynamic_document_uuids =
|
||||||
|
templates.flat_map { |t| t.schema.select { |item| item['dynamic'] }.pluck('attachment_uuid') }
|
||||||
|
|
||||||
|
dynamic_documents =
|
||||||
|
if dynamic_document_uuids.present?
|
||||||
|
DynamicDocument.where(template: templates.map(&:id))
|
||||||
|
.where(uuid: dynamic_document_uuids)
|
||||||
|
.preload(current_version: { document_attachment: :blob })
|
||||||
|
.select(:id, :uuid, :template_id, :sha1, :created_at, :updated_at)
|
||||||
|
else
|
||||||
|
DynamicDocument.none
|
||||||
|
end
|
||||||
|
|
||||||
|
preview_attachment_ids =
|
||||||
|
schema_documents.map(&:id) + dynamic_documents.filter_map { |d| d.current_version&.document_attachment&.id }
|
||||||
|
|
||||||
|
preview_image_attachments =
|
||||||
|
ActiveStorage::Attachment.joins(:blob)
|
||||||
|
.where(blob: { filename: ['0.png', '0.jpg'] })
|
||||||
|
.where(record_id: preview_attachment_ids,
|
||||||
|
record_type: 'ActiveStorage::Attachment',
|
||||||
|
name: :preview_images)
|
||||||
|
.preload(:blob)
|
||||||
|
|
||||||
|
[schema_documents, dynamic_documents, preview_image_attachments]
|
||||||
|
end
|
||||||
|
|
||||||
def filter_templates(templates, params)
|
def filter_templates(templates, params)
|
||||||
templates = Templates.search(current_user, templates, params[:q])
|
templates = Templates.search(current_user, templates, params[:q])
|
||||||
templates = params[:archived].in?(['true', true]) ? templates.archived : templates.active
|
templates = params[:archived].in?(['true', true]) ? templates.archived : templates.active
|
||||||
|
|||||||
@@ -101,11 +101,18 @@ class StartFormController < ApplicationController
|
|||||||
def load_resubmit_submitter
|
def load_resubmit_submitter
|
||||||
@resubmit_submitter =
|
@resubmit_submitter =
|
||||||
if params[:resubmit].present? && !params[:resubmit].in?([true, 'true'])
|
if params[:resubmit].present? && !params[:resubmit].in?([true, 'true'])
|
||||||
Submitter.find_by(slug: params[:resubmit])
|
submitter = Submitter.find_by(slug: params[:resubmit])
|
||||||
|
|
||||||
|
submitter if submitter && can_resubmit?(submitter)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def can_resubmit?(submitter)
|
||||||
|
submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false
|
||||||
|
end
|
||||||
|
|
||||||
def authorize_start!
|
def authorize_start!
|
||||||
|
return redirect_to submit_form_path(@resubmit_submitter.slug) if @resubmit_submitter && @template.archived_at?
|
||||||
return redirect_to start_form_path(@template.slug) if @template.archived_at?
|
return redirect_to start_form_path(@template.slug) if @template.archived_at?
|
||||||
|
|
||||||
return if @resubmit_submitter
|
return if @resubmit_submitter
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class TemplatesController < ApplicationController
|
class TemplatesController < ApplicationController
|
||||||
TEMPLATE_FIELDS = %i[id author_id folder_id external_id name slug
|
|
||||||
schema fields submitters variables_schema preferences
|
|
||||||
shared_link source archived_at created_at updated_at].freeze
|
|
||||||
|
|
||||||
load_and_authorize_resource :template
|
load_and_authorize_resource :template
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@@ -31,19 +27,7 @@ class TemplatesController < ApplicationController
|
|||||||
def new; end
|
def new; end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
ActiveRecord::Associations::Preloader.new(
|
@template_data = Templates.serialize_for_builder(@template)
|
||||||
records: [@template],
|
|
||||||
associations: [{ schema_documents: [:blob, { preview_images_attachments: :blob }] }]
|
|
||||||
).call
|
|
||||||
|
|
||||||
@template_data =
|
|
||||||
@template.as_json(only: TEMPLATE_FIELDS).merge(
|
|
||||||
documents: @template.schema_documents.as_json(
|
|
||||||
only: %i[id uuid],
|
|
||||||
methods: %i[metadata signed_key],
|
|
||||||
include: { preview_images: { only: %i[id], methods: %i[url metadata filename] } }
|
|
||||||
)
|
|
||||||
).to_json
|
|
||||||
|
|
||||||
render :edit, layout: 'plain'
|
render :edit, layout: 'plain'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -16,7 +16,12 @@ class TemplatesDetectFieldsController < ApplicationController
|
|||||||
page_number = params[:page].presence&.to_i
|
page_number = params[:page].presence&.to_i
|
||||||
|
|
||||||
documents.each do |document|
|
documents.each do |document|
|
||||||
io = StringIO.new(document.download)
|
io =
|
||||||
|
if document.image?
|
||||||
|
StringIO.new(document.preview_images.joins(:blob).find_by(blob: { filename: ['0.png', '0.jpg'] }).download)
|
||||||
|
else
|
||||||
|
StringIO.new(document.download)
|
||||||
|
end
|
||||||
|
|
||||||
Templates::DetectFields.call(io, attachment: document, page_number:) do |(attachment_uuid, page, fields)|
|
Templates::DetectFields.call(io, attachment: document, page_number:) do |(attachment_uuid, page, fields)|
|
||||||
sse.write({ attachment_uuid:, page:, fields: })
|
sse.write({ attachment_uuid:, page:, fields: })
|
||||||
|
|||||||
@@ -4,18 +4,7 @@ class TemplatesPreviewController < ApplicationController
|
|||||||
load_and_authorize_resource :template
|
load_and_authorize_resource :template
|
||||||
|
|
||||||
def show
|
def show
|
||||||
ActiveRecord::Associations::Preloader.new(
|
@template_data = Templates.serialize_for_builder(@template)
|
||||||
records: [@template],
|
|
||||||
associations: [{ schema_documents: { preview_images_attachments: :blob } }]
|
|
||||||
).call
|
|
||||||
|
|
||||||
@template_data =
|
|
||||||
@template.as_json.merge(
|
|
||||||
documents: @template.schema_documents.as_json(
|
|
||||||
methods: %i[metadata signed_key],
|
|
||||||
include: { preview_images: { methods: %i[url metadata filename] } }
|
|
||||||
)
|
|
||||||
).to_json
|
|
||||||
|
|
||||||
render :show, layout: 'plain'
|
render :show, layout: 'plain'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ class TemplatesUploadsController < ApplicationController
|
|||||||
|
|
||||||
layout 'plain'
|
layout 'plain'
|
||||||
|
|
||||||
def show; end
|
def show
|
||||||
|
redirect_to root_path if params[:url].blank?
|
||||||
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
url_params = create_file_params_from_url if params[:url].present?
|
url_params = create_file_params_from_url if params[:url].present?
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ import GoogleDriveFilePicker from './elements/google_drive_file_picker'
|
|||||||
import OpenModal from './elements/open_modal'
|
import OpenModal from './elements/open_modal'
|
||||||
import BarChart from './elements/bar_chart'
|
import BarChart from './elements/bar_chart'
|
||||||
import FieldCondition from './elements/field_condition'
|
import FieldCondition from './elements/field_condition'
|
||||||
|
import ConfirmUpload from './elements/confirm_upload'
|
||||||
|
|
||||||
import * as TurboInstantClick from './lib/turbo_instant_click'
|
import * as TurboInstantClick from './lib/turbo_instant_click'
|
||||||
|
|
||||||
@@ -146,6 +147,7 @@ safeRegisterElement('google-drive-file-picker', GoogleDriveFilePicker)
|
|||||||
safeRegisterElement('open-modal', OpenModal)
|
safeRegisterElement('open-modal', OpenModal)
|
||||||
safeRegisterElement('bar-chart', BarChart)
|
safeRegisterElement('bar-chart', BarChart)
|
||||||
safeRegisterElement('field-condition', FieldCondition)
|
safeRegisterElement('field-condition', FieldCondition)
|
||||||
|
safeRegisterElement('confirm-upload', ConfirmUpload)
|
||||||
|
|
||||||
safeRegisterElement('template-builder', class extends HTMLElement {
|
safeRegisterElement('template-builder', class extends HTMLElement {
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
@@ -197,10 +199,35 @@ safeRegisterElement('template-builder', class extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSubmit = (e) => {
|
onSubmit = (e) => {
|
||||||
if (e.detail.success && e.detail?.formSubmission?.formElement?.id === 'submitters_form') {
|
if (e.detail.success) {
|
||||||
e.detail.fetchResponse.response.json().then((data) => {
|
if (e.detail?.formSubmission?.formElement?.id === 'submitters_form') {
|
||||||
this.component.template.submitters = data.submitters
|
e.detail.fetchResponse.response.json().then((data) => {
|
||||||
})
|
this.component.template.submitters = data.submitters
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.detail?.formSubmission?.formElement?.action?.endsWith('/prefillable_fields')) {
|
||||||
|
e.detail.fetchResponse.response.text().then((data) => {
|
||||||
|
const doc = new DOMParser().parseFromString(data, 'text/html')
|
||||||
|
const fragment = doc.querySelector('turbo-stream template').content
|
||||||
|
|
||||||
|
const prefillableUuidsIndex = {}
|
||||||
|
|
||||||
|
fragment.querySelectorAll('[name="field_uuid"]').forEach((field) => {
|
||||||
|
prefillableUuidsIndex[field.value] = true
|
||||||
|
})
|
||||||
|
|
||||||
|
this.component.template.fields.forEach((field) => {
|
||||||
|
if (prefillableUuidsIndex[field.uuid]) {
|
||||||
|
field.prefillable = true
|
||||||
|
field.readonly = true
|
||||||
|
} else if (field.prefillable) {
|
||||||
|
delete field.prefillable
|
||||||
|
delete field.readonly
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { target, targetable } from '@github/catalyst/lib/targetable'
|
||||||
|
|
||||||
|
export default targetable(class extends HTMLElement {
|
||||||
|
static [target.static] = [
|
||||||
|
'prompt',
|
||||||
|
'processing',
|
||||||
|
'logo'
|
||||||
|
]
|
||||||
|
|
||||||
|
connectedCallback () {
|
||||||
|
this.form.addEventListener('submit', this.onSubmit)
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback () {
|
||||||
|
this.form.removeEventListener('submit', this.onSubmit)
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit = () => {
|
||||||
|
this.prompt.classList.add('hidden')
|
||||||
|
this.processing.classList.remove('hidden')
|
||||||
|
this.logo.classList.add('animate-bounce')
|
||||||
|
}
|
||||||
|
|
||||||
|
get form () {
|
||||||
|
return this.querySelector('form')
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
v-if="withToday"
|
v-if="withToday"
|
||||||
|
type="button"
|
||||||
class="btn btn-outline btn-sm !normal-case font-normal set-current-date-button"
|
class="btn btn-outline btn-sm !normal-case font-normal set-current-date-button"
|
||||||
@click.prevent="[setCurrentDate(), $emit('focus')]"
|
@click.prevent="[setCurrentDate(), $emit('focus')]"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -95,7 +95,7 @@
|
|||||||
v-else
|
v-else
|
||||||
id="complete_form_button"
|
id="complete_form_button"
|
||||||
class="btn btn-sm btn-neutral text-white px-4 w-full flex justify-center"
|
class="btn btn-sm btn-neutral text-white px-4 w-full flex justify-center"
|
||||||
form="steps_form"
|
form="complete_form"
|
||||||
type="submit"
|
type="submit"
|
||||||
name="completed"
|
name="completed"
|
||||||
value="true"
|
value="true"
|
||||||
@@ -120,7 +120,7 @@
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="complete-button btn btn-sm btn-neutral text-white px-4"
|
class="complete-button btn btn-sm btn-neutral text-white px-4"
|
||||||
form="steps_form"
|
form="complete_form"
|
||||||
type="submit"
|
type="submit"
|
||||||
name="completed"
|
name="completed"
|
||||||
value="true"
|
value="true"
|
||||||
@@ -138,6 +138,14 @@
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
|
<form
|
||||||
|
v-if="!isCompleted && !isInvite"
|
||||||
|
id="complete_form"
|
||||||
|
class="hidden"
|
||||||
|
:action="submitPath"
|
||||||
|
method="post"
|
||||||
|
@submit.prevent="submitStep"
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
v-if="!isFormVisible"
|
v-if="!isFormVisible"
|
||||||
id="expand_form_button"
|
id="expand_form_button"
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
class="btn btn-outline btn-sm reupload-button"
|
class="btn btn-outline btn-sm reupload-button"
|
||||||
@click.prevent="remove"
|
@click.prevent="remove"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
{{ error }}
|
{{ error }}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
class="base-button w-full flex justify-center submit-form-button"
|
class="base-button w-full flex justify-center submit-form-button"
|
||||||
@click="restartKba"
|
@click="restartKba"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
<div v-else>
|
<div v-else>
|
||||||
<button
|
<button
|
||||||
v-if="sessionId"
|
v-if="sessionId"
|
||||||
|
type="button"
|
||||||
disabled
|
disabled
|
||||||
class="base-button w-full modal-save-button"
|
class="base-button w-full modal-save-button"
|
||||||
>
|
>
|
||||||
@@ -47,6 +48,7 @@
|
|||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
:id="field.uuid"
|
:id="field.uuid"
|
||||||
|
type="button"
|
||||||
class="btn bg-[#7B73FF] text-white hover:bg-[#0A2540] text-lg w-full"
|
class="btn bg-[#7B73FF] text-white hover:bg-[#0A2540] text-lg w-full"
|
||||||
:class="{ disabled: isCreatingCheckout }"
|
:class="{ disabled: isCreatingCheckout }"
|
||||||
:disabled="isCreatingCheckout"
|
:disabled="isCreatingCheckout"
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ class DynamicDocument < ApplicationRecord
|
|||||||
|
|
||||||
has_many :versions, class_name: 'DynamicDocumentVersion', dependent: :destroy
|
has_many :versions, class_name: 'DynamicDocumentVersion', dependent: :destroy
|
||||||
|
|
||||||
|
has_one :current_version, class_name: 'DynamicDocumentVersion',
|
||||||
|
primary_key: %i[id sha1],
|
||||||
|
foreign_key: %i[dynamic_document_id sha1],
|
||||||
|
dependent: :destroy,
|
||||||
|
inverse_of: :dynamic_document
|
||||||
|
|
||||||
attribute :fields, :json
|
attribute :fields, :json
|
||||||
|
|
||||||
before_validation :set_sha1
|
before_validation :set_sha1
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class Template < ApplicationRecord
|
|||||||
has_many :dynamic_document_versions, through: :dynamic_documents, source: :versions
|
has_many :dynamic_document_versions, through: :dynamic_documents, source: :versions
|
||||||
|
|
||||||
has_many :schema_dynamic_documents, lambda { |e|
|
has_many :schema_dynamic_documents, lambda { |e|
|
||||||
where(uuid: e.schema.select { |e| e['dynamic'] }.pluck('attachment_uuid'))
|
where(uuid: e.schema.select { |item| item['dynamic'] }.pluck('attachment_uuid'))
|
||||||
}, class_name: 'DynamicDocument', dependent: :destroy, inverse_of: :template
|
}, class_name: 'DynamicDocument', dependent: :destroy, inverse_of: :template
|
||||||
|
|
||||||
scope :active, -> { where(archived_at: nil) }
|
scope :active, -> { where(archived_at: nil) }
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<% if signature %>
|
<% if signature %>
|
||||||
<div class="flex justify-center mb-4 relative">
|
<div class="flex justify-center mb-4 relative">
|
||||||
<%= button_to button_title(title: t('remove'), disabled_with: t('removing')), user_signature_path, method: :delete, class: 'right-0 top-0 absolute link' %>
|
<%= button_to button_title(title: t('remove'), disabled_with: t('removing')), user_signature_path, method: :delete, class: 'right-0 top-0 absolute link' %>
|
||||||
<img src="<%= signature.url %>" style="max-height: 200px; width: auto" width="<%= signature.metadata['width'] %>" height="<%= signature.metadata['height'] %>">
|
<img src="<%= signature.url %>" style="height: 130px; width: 100%; object-fit: contain;">
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<a href="<%= edit_user_signature_path %>" data-turbo-frame="modal" class="base-button w-full">
|
<a href="<%= edit_user_signature_path %>" data-turbo-frame="modal" class="base-button w-full">
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
<% if initials %>
|
<% if initials %>
|
||||||
<div class="flex justify-center mb-4 relative">
|
<div class="flex justify-center mb-4 relative">
|
||||||
<%= button_to button_title(title: t('remove'), disabled_with: t('removing')), user_initials_path, method: :delete, class: 'right-0 top-0 absolute link' %>
|
<%= button_to button_title(title: t('remove'), disabled_with: t('removing')), user_initials_path, method: :delete, class: 'right-0 top-0 absolute link' %>
|
||||||
<img src="<%= initials.url %>" style="max-height: 200px; width: auto" width="<%= initials.metadata['width'] %>" height="<%= initials.metadata['height'] %>">
|
<img src="<%= initials.url %>" style="height: 130px; width: 100%; object-fit: contain;">
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<a href="<%= edit_user_initials_path %>" data-turbo-frame="modal" class="base-button w-full">
|
<a href="<%= edit_user_initials_path %>" data-turbo-frame="modal" class="base-button w-full">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<a target="_blank" href="<%= Docuseal::GITHUB_URL %>" rel="noopener noreferrer nofollow" class="relative flex items-center rounded-full px-2 py-0.5 text-xs leading-4 mt-1 text-base-content border border-base-300 tooltip tooltip-bottom" data-tip="Give a star on GitHub">
|
<a target="_blank" href="<%= Docuseal::GITHUB_URL %>" rel="noopener noreferrer nofollow" class="relative flex items-center rounded-full px-2 py-0.5 text-xs leading-4 mt-1 text-base-content border border-base-300 tooltip tooltip-bottom" data-tip="Give a star on GitHub">
|
||||||
<span class="flex items-center justify-between space-x-0.5 font-medium">
|
<span class="flex items-center justify-between space-x-0.5 font-medium">
|
||||||
<%= svg_icon('start', class: 'h-3 w-3') %>
|
<%= svg_icon('start', class: 'h-3 w-3') %>
|
||||||
<span>16k</span>
|
<span>17k</span>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -266,10 +266,12 @@
|
|||||||
<div dir="auto">
|
<div dir="auto">
|
||||||
<% if field['type'].in?(%w[signature initials]) %>
|
<% if field['type'].in?(%w[signature initials]) %>
|
||||||
<div class="w-full bg-base-300 py-1">
|
<div class="w-full bg-base-300 py-1">
|
||||||
<img class="object-contain mx-auto" style="max-height: <%= field['type'] == 'signature' ? 100 : 50 %>px" height="<%= attachments_index[value].metadata['height'] %>" width="<%= attachments_index[value].metadata['width'] %>" src="<%= attachments_index[value].url %>" loading="lazy" alt="<%= field['name'] || field['title'] || field['type'] %>">
|
<% img_height = attachments_index[value].metadata['height'] %>
|
||||||
|
<img class="object-contain mx-auto" style="<%= 'max-' if img_height %>height: <%= field['type'] == 'signature' ? 100 : 50 %>px" height="<%= img_height %>" width="<%= attachments_index[value].metadata['width'] %>" src="<%= attachments_index[value].url %>" loading="lazy" alt="<%= field['name'] || field['title'] || field['type'] %>">
|
||||||
</div>
|
</div>
|
||||||
<% elsif field['type'].in?(['image', 'stamp', 'kba']) && attachments_index[value].image? %>
|
<% elsif field['type'].in?(['image', 'stamp', 'kba']) && attachments_index[value].image? %>
|
||||||
<img class="object-contain mx-auto max-h-28" style="max-height: 200px" height="<%= attachments_index[value].metadata['height'] %>" width="<%= attachments_index[value].metadata['width'] %>" src="<%= attachments_index[value].url %>" loading="lazy" alt="<%= field['name'] || field['title'] || field['type'] %>">
|
<% img_height = attachments_index[value].metadata['height'] %>
|
||||||
|
<img class="object-contain mx-auto" style="<%= 'max-' if img_height %>height: 200px" height="<%= img_height %>" width="<%= attachments_index[value].metadata['width'] %>" src="<%= attachments_index[value].url %>" loading="lazy" alt="<%= field['name'] || field['title'] || field['type'] %>">
|
||||||
<% elsif field['type'].in?(['file', 'payment', 'image']) %>
|
<% elsif field['type'].in?(['file', 'payment', 'image']) %>
|
||||||
<div class="flex flex-col justify-center">
|
<div class="flex flex-col justify-center">
|
||||||
<% Array.wrap(value).each do |val| %>
|
<% Array.wrap(value).each do |val| %>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap gap-2 w-full md:w-fit md:justify-between md:flex-none md:pt-1">
|
<div class="flex flex-wrap gap-2 w-full md:w-fit md:justify-between md:flex-none md:pt-1">
|
||||||
<% if !template.archived_at? && can?(:destroy, template) %>
|
<% if !template.archived_at? && can?(:destroy, template) %>
|
||||||
<%= button_to button_title(title: t('archive'), disabled_with: t('archiving'), title_class: 'inline', icon: svg_icon('archive', class: 'w-6 h-6')), template_path(template), class: 'btn btn-outline btn-sm w-full', form_class: 'flex-1', method: :delete, data: { turbo_confirm: t('are_you_sure_') } %>
|
<%= button_to button_title(title: t('archive'), disabled_with: t('archiving')[..-4], title_class: 'inline', icon: svg_icon('archive', class: 'w-6 h-6')), template_path(template), class: 'btn btn-outline btn-sm w-full', form_class: 'flex-1', method: :delete %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:create, current_account.templates.new(author: current_user)) %>
|
<% if can?(:create, current_account.templates.new(author: current_user)) %>
|
||||||
<%= link_to new_template_clone_path(template), class: 'btn btn-outline btn-sm flex-1', data: { turbo_frame: :modal } do %>
|
<%= link_to new_template_clone_path(template), class: 'btn btn-outline btn-sm flex-1', data: { turbo_frame: :modal } do %>
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% if template.archived_at? %>
|
<% if template.archived_at? %>
|
||||||
<% if can?(:create, template) %>
|
<% if can?(:create, template) %>
|
||||||
<%= button_to button_title(title: t('restore'), disabled_with: t('restoring'), icon: svg_icon('rotate', class: 'w-6 h-6')), template_restore_index_path(template), class: 'btn btn-outline btn-sm flex-1' %>
|
<%= button_to button_title(title: t('restore'), disabled_with: t('restoring')[..-4], icon: svg_icon('rotate', class: 'w-6 h-6')), template_restore_index_path(template), class: 'btn btn-outline btn-sm flex-1' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= link_to template_preview_path(template), class: 'btn btn-outline btn-sm flex-1' do %>
|
<%= link_to template_preview_path(template), class: 'btn btn-outline btn-sm flex-1' do %>
|
||||||
<span class="flex items-center justify-center space-x-2">
|
<span class="flex items-center justify-center space-x-2">
|
||||||
|
|||||||
@@ -6,4 +6,4 @@
|
|||||||
<%= button_to nil, user_configs_path, method: :post, params: { user_config: { key: UserConfig::SHOW_APP_TOUR, value: true } }, class: 'hidden', id: 'start_tour_button' %>
|
<%= button_to nil, user_configs_path, method: :post, params: { user_config: { key: UserConfig::SHOW_APP_TOUR, value: true } }, class: 'hidden', id: 'start_tour_button' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<template-builder class="grid" data-template="<%= @template_data %>" data-custom-fields="<%= (current_account.account_configs.find_or_initialize_by(key: AccountConfig::TEMPLATE_CUSTOM_FIELDS_KEY).value || []).to_json %>" data-with-sign-yourself-button="<%= !@template.archived_at? %>" data-with-fields-detection="true" data-with-send-button="<%= !@template.archived_at? && can?(:create, @template.submissions.new(account: current_account)) %>" data-with-revisions-menu="<%= @template.template_versions.exists? %>" data-locale="<%= I18n.locale %>" data-show-tour-start-form="<%= @show_tour_start_form %>"></template-builder>
|
<template-builder class="grid" data-template="<%= @template_data.to_json %>" data-custom-fields="<%= (current_account.account_configs.find_or_initialize_by(key: AccountConfig::TEMPLATE_CUSTOM_FIELDS_KEY).value || []).to_json %>" data-with-sign-yourself-button="<%= !@template.archived_at? %>" data-with-fields-detection="true" data-with-send-button="<%= !@template.archived_at? && can?(:create, @template.submissions.new(account: current_account)) %>" data-with-revisions-menu="<%= @template.template_versions.exists? %>" data-locale="<%= I18n.locale %>" data-show-tour-start-form="<%= @show_tour_start_form %>"></template-builder>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<template-builder class="grid" data-editable="false" data-with-sign-yourself-button="<%= !@template.archived_at? %>" data-with-send-button="<%= !@template.archived_at? && can?(:create, @template.submissions.new(account: current_account)) %>" data-template="<%= @template_data %>"></template-builder>
|
<template-builder class="grid" data-editable="false" data-with-sign-yourself-button="<%= !@template.archived_at? %>" data-with-send-button="<%= !@template.archived_at? && can?(:create, @template.submissions.new(account: current_account)) %>" data-template="<%= @template_data.to_json %>"></template-builder>
|
||||||
|
|||||||
@@ -1,18 +1,30 @@
|
|||||||
<div class="h-screen">
|
<confirm-upload>
|
||||||
<div
|
<div class="h-screen">
|
||||||
class="text-center p-8 h-full flex items-center justify-center">
|
<div class="text-center p-8 h-full flex items-center justify-center">
|
||||||
<div>
|
<div class="space-y-4">
|
||||||
<%= render 'shared/logo', width: 50, height: 50, class: 'mx-auto animate-bounce' %>
|
<div data-target="confirm-upload.logo" class="mx-auto">
|
||||||
<span>
|
<%= render 'shared/logo', width: 50, height: 50, class: 'mx-auto' %>
|
||||||
<%= t('processing') %>...
|
</div>
|
||||||
</span>
|
<div data-target="confirm-upload.processing" class="hidden">
|
||||||
|
<span><%= t('processing') %>...</span>
|
||||||
|
</div>
|
||||||
|
<div data-target="confirm-upload.prompt">
|
||||||
|
<p class="text-lg">
|
||||||
|
<%= t('open_file_from') %>
|
||||||
|
<a href="<%= params[:url] %>" target="_blank" class="link" rel="noopener noreferrer nofollow">
|
||||||
|
<%= params[:filename].presence || params[:url] %>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<div class="flex items-center justify-center gap-2 mt-4">
|
||||||
|
<%= link_to t('cancel'), root_path, class: 'white-button w-44' %>
|
||||||
|
<%= form_for '', url: templates_upload_path, method: :post do |f| %>
|
||||||
|
<input type="hidden" name="url" value="<%= params[:url] %>">
|
||||||
|
<input type="hidden" name="filename" value="<%= params[:filename] %>">
|
||||||
|
<%= f.button button_title(title: t('open')), class: 'base-button w-44' %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</confirm-upload>
|
||||||
<submit-form>
|
|
||||||
<%= form_for '', url: templates_upload_path, method: :post, class: 'hidden' do %>
|
|
||||||
<button type="submit"></button>
|
|
||||||
<input name="url" value="<%= params[:url] %>">
|
|
||||||
<input name="filename" value="<%= params[:filename] %>">
|
|
||||||
<% end %>
|
|
||||||
</submit-form>
|
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ module DocuSeal
|
|||||||
|
|
||||||
config.active_storage.draw_routes = ENV['MULTITENANT'] != 'true'
|
config.active_storage.draw_routes = ENV['MULTITENANT'] != 'true'
|
||||||
|
|
||||||
|
config.active_storage.analyzers = []
|
||||||
|
|
||||||
|
config.active_storage.variant_processor = :disabled
|
||||||
|
|
||||||
config.active_storage.content_types_to_serve_as_binary += %w[
|
config.active_storage.content_types_to_serve_as_binary += %w[
|
||||||
application/javascript
|
application/javascript
|
||||||
text/javascript
|
text/javascript
|
||||||
|
|||||||
@@ -937,6 +937,9 @@ en: &en
|
|||||||
your_email_address_has_been_changed: Your email address has been changed
|
your_email_address_has_been_changed: Your email address has been changed
|
||||||
the_email_address_for_your_account_has_been_changed_to_new_email: The email address for your account has been changed to %{new_email}.
|
the_email_address_for_your_account_has_been_changed_to_new_email: The email address for your account has been changed to %{new_email}.
|
||||||
if_you_did_not_make_this_change_please_contact_us_by_replying_to_this_email: If you did not make this change, please contact us by replying to this email.
|
if_you_did_not_make_this_change_please_contact_us_by_replying_to_this_email: If you did not make this change, please contact us by replying to this email.
|
||||||
|
open_file_from: 'Open file from'
|
||||||
|
cancel: Cancel
|
||||||
|
open: Open
|
||||||
devise:
|
devise:
|
||||||
confirmations:
|
confirmations:
|
||||||
confirmed: Your email address has been successfully confirmed.
|
confirmed: Your email address has been successfully confirmed.
|
||||||
@@ -1987,6 +1990,9 @@ es: &es
|
|||||||
your_email_address_has_been_changed: Tu dirección de correo electrónico ha sido cambiada
|
your_email_address_has_been_changed: Tu dirección de correo electrónico ha sido cambiada
|
||||||
the_email_address_for_your_account_has_been_changed_to_new_email: La dirección de correo electrónico de tu cuenta ha sido cambiada a %{new_email}.
|
the_email_address_for_your_account_has_been_changed_to_new_email: La dirección de correo electrónico de tu cuenta ha sido cambiada a %{new_email}.
|
||||||
if_you_did_not_make_this_change_please_contact_us_by_replying_to_this_email: Si no realizaste este cambio, contáctanos respondiendo a este correo electrónico.
|
if_you_did_not_make_this_change_please_contact_us_by_replying_to_this_email: Si no realizaste este cambio, contáctanos respondiendo a este correo electrónico.
|
||||||
|
open_file_from: 'Abrir archivo desde'
|
||||||
|
cancel: Cancelar
|
||||||
|
open: Abrir
|
||||||
devise:
|
devise:
|
||||||
confirmations:
|
confirmations:
|
||||||
confirmed: Tu dirección de correo electrónico ha sido confirmada correctamente.
|
confirmed: Tu dirección de correo electrónico ha sido confirmada correctamente.
|
||||||
@@ -3037,6 +3043,9 @@ it: &it
|
|||||||
your_email_address_has_been_changed: Il tuo indirizzo email è stato modificato
|
your_email_address_has_been_changed: Il tuo indirizzo email è stato modificato
|
||||||
the_email_address_for_your_account_has_been_changed_to_new_email: L'indirizzo email del tuo account è stato modificato in %{new_email}.
|
the_email_address_for_your_account_has_been_changed_to_new_email: L'indirizzo email del tuo account è stato modificato in %{new_email}.
|
||||||
if_you_did_not_make_this_change_please_contact_us_by_replying_to_this_email: Se non hai effettuato questa modifica, contattaci rispondendo a questa email.
|
if_you_did_not_make_this_change_please_contact_us_by_replying_to_this_email: Se non hai effettuato questa modifica, contattaci rispondendo a questa email.
|
||||||
|
open_file_from: 'Aprire file da'
|
||||||
|
cancel: Annulla
|
||||||
|
open: Apri
|
||||||
devise:
|
devise:
|
||||||
confirmations:
|
confirmations:
|
||||||
confirmed: Il tuo indirizzo email è stato confermato con successo.
|
confirmed: Il tuo indirizzo email è stato confermato con successo.
|
||||||
@@ -4084,6 +4093,9 @@ fr: &fr
|
|||||||
your_email_address_has_been_changed: Votre adresse e-mail a été modifiée
|
your_email_address_has_been_changed: Votre adresse e-mail a été modifiée
|
||||||
the_email_address_for_your_account_has_been_changed_to_new_email: "L'adresse e-mail de votre compte a été modifiée en %{new_email}."
|
the_email_address_for_your_account_has_been_changed_to_new_email: "L'adresse e-mail de votre compte a été modifiée en %{new_email}."
|
||||||
if_you_did_not_make_this_change_please_contact_us_by_replying_to_this_email: Si vous n'avez pas effectué ce changement, veuillez nous contacter en répondant à cet e-mail.
|
if_you_did_not_make_this_change_please_contact_us_by_replying_to_this_email: Si vous n'avez pas effectué ce changement, veuillez nous contacter en répondant à cet e-mail.
|
||||||
|
open_file_from: 'Ouvrir le fichier depuis'
|
||||||
|
cancel: Annuler
|
||||||
|
open: Ouvrir
|
||||||
devise:
|
devise:
|
||||||
confirmations:
|
confirmations:
|
||||||
confirmed: Votre adresse e-mail a été confirmée avec succès.
|
confirmed: Votre adresse e-mail a été confirmée avec succès.
|
||||||
@@ -5134,6 +5146,9 @@ pt: &pt
|
|||||||
your_email_address_has_been_changed: Seu endereço de e-mail foi alterado
|
your_email_address_has_been_changed: Seu endereço de e-mail foi alterado
|
||||||
the_email_address_for_your_account_has_been_changed_to_new_email: O endereço de e-mail da sua conta foi alterado para %{new_email}.
|
the_email_address_for_your_account_has_been_changed_to_new_email: O endereço de e-mail da sua conta foi alterado para %{new_email}.
|
||||||
if_you_did_not_make_this_change_please_contact_us_by_replying_to_this_email: Se você não fez essa alteração, entre em contato conosco respondendo a este e-mail.
|
if_you_did_not_make_this_change_please_contact_us_by_replying_to_this_email: Se você não fez essa alteração, entre em contato conosco respondendo a este e-mail.
|
||||||
|
open_file_from: 'Abrir arquivo de'
|
||||||
|
cancel: Cancelar
|
||||||
|
open: Abrir
|
||||||
devise:
|
devise:
|
||||||
confirmations:
|
confirmations:
|
||||||
confirmed: Seu endereço de e-mail foi confirmado com sucesso.
|
confirmed: Seu endereço de e-mail foi confirmado com sucesso.
|
||||||
@@ -6184,6 +6199,9 @@ de: &de
|
|||||||
your_email_address_has_been_changed: Ihre E-Mail-Adresse wurde geändert
|
your_email_address_has_been_changed: Ihre E-Mail-Adresse wurde geändert
|
||||||
the_email_address_for_your_account_has_been_changed_to_new_email: Die E-Mail-Adresse Ihres Kontos wurde in %{new_email} geändert.
|
the_email_address_for_your_account_has_been_changed_to_new_email: Die E-Mail-Adresse Ihres Kontos wurde in %{new_email} geändert.
|
||||||
if_you_did_not_make_this_change_please_contact_us_by_replying_to_this_email: Wenn Sie diese Änderung nicht vorgenommen haben, kontaktieren Sie uns bitte, indem Sie auf diese E-Mail antworten.
|
if_you_did_not_make_this_change_please_contact_us_by_replying_to_this_email: Wenn Sie diese Änderung nicht vorgenommen haben, kontaktieren Sie uns bitte, indem Sie auf diese E-Mail antworten.
|
||||||
|
open_file_from: 'Datei öffnen von'
|
||||||
|
cancel: Abbrechen
|
||||||
|
open: Öffnen
|
||||||
devise:
|
devise:
|
||||||
confirmations:
|
confirmations:
|
||||||
confirmed: Ihre E-Mail-Adresse wurde erfolgreich bestätigt.
|
confirmed: Ihre E-Mail-Adresse wurde erfolgreich bestätigt.
|
||||||
@@ -7635,6 +7653,9 @@ nl: &nl
|
|||||||
your_email_address_has_been_changed: Uw e-mailadres is gewijzigd
|
your_email_address_has_been_changed: Uw e-mailadres is gewijzigd
|
||||||
the_email_address_for_your_account_has_been_changed_to_new_email: Het e-mailadres van uw account is gewijzigd naar %{new_email}.
|
the_email_address_for_your_account_has_been_changed_to_new_email: Het e-mailadres van uw account is gewijzigd naar %{new_email}.
|
||||||
if_you_did_not_make_this_change_please_contact_us_by_replying_to_this_email: Als u deze wijziging niet hebt aangebracht, neem dan contact met ons op door op deze e-mail te antwoorden.
|
if_you_did_not_make_this_change_please_contact_us_by_replying_to_this_email: Als u deze wijziging niet hebt aangebracht, neem dan contact met ons op door op deze e-mail te antwoorden.
|
||||||
|
open_file_from: 'Bestand openen van'
|
||||||
|
cancel: Annuleren
|
||||||
|
open: Openen
|
||||||
devise:
|
devise:
|
||||||
confirmations:
|
confirmations:
|
||||||
confirmed: Uw e-mailadres is succesvol bevestigd.
|
confirmed: Uw e-mailadres is succesvol bevestigd.
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Accounts
|
module Accounts
|
||||||
LINK_EXPIRES_AT = 40.minutes
|
LINK_EXPIRES_AT = ENV.fetch('FILE_URLS_EXPIRE_MINUTES', '40').to_i.minutes
|
||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ module LoadBmp
|
|||||||
bands = header_data[:bpp] / 8
|
bands = header_data[:bpp] / 8
|
||||||
end
|
end
|
||||||
|
|
||||||
image = Vips::Image.new_from_memory(final_pixel_data, header_data[:width], header_data[:height], bands, :uchar)
|
image = Vips::Image.new_from_memory_copy(final_pixel_data, header_data[:width], header_data[:height], bands, :uchar)
|
||||||
|
|
||||||
image = image.flip(:vertical) if header_data[:orientation] == -1
|
image = image.flip(:vertical) if header_data[:orientation] == -1
|
||||||
|
|
||||||
|
|||||||
+5
-3
@@ -43,7 +43,9 @@ module LoadIco
|
|||||||
|
|
||||||
raise ArgumentError, 'Unable to load' unless image_data_bytes && image_data_bytes.bytesize == best_entry[:size]
|
raise ArgumentError, 'Unable to load' unless image_data_bytes && image_data_bytes.bytesize == best_entry[:size]
|
||||||
|
|
||||||
return Vips::Image.new_from_buffer(image_data_bytes, '') if image_data_bytes.start_with?(PNG_SIGNATURE)
|
if image_data_bytes.start_with?(PNG_SIGNATURE)
|
||||||
|
return ImageUtils.load_vips(image_data_bytes, content_type: 'image/png')
|
||||||
|
end
|
||||||
|
|
||||||
image = load_image_entry(image_data_bytes, best_entry[:width], best_entry[:height])
|
image = load_image_entry(image_data_bytes, best_entry[:width], best_entry[:height])
|
||||||
|
|
||||||
@@ -200,13 +202,13 @@ module LoadIco
|
|||||||
|
|
||||||
return nil unless pixel_data_string.bytesize == expected_bytes && expected_bytes.positive?
|
return nil unless pixel_data_string.bytesize == expected_bytes && expected_bytes.positive?
|
||||||
|
|
||||||
Vips::Image.new_from_memory(
|
Vips::Image.new_from_memory_copy(
|
||||||
pixel_data_string,
|
pixel_data_string,
|
||||||
dib_width,
|
dib_width,
|
||||||
image_pixel_height,
|
image_pixel_height,
|
||||||
4,
|
4,
|
||||||
:uchar
|
:uchar
|
||||||
)
|
).copy(interpretation: :srgb)
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics
|
# rubocop:enable Metrics
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'puma/plugin'
|
require 'puma/plugin'
|
||||||
|
require 'redis_client'
|
||||||
|
|
||||||
# rubocop:disable Metrics
|
# rubocop:disable Metrics
|
||||||
Puma::Plugin.create do
|
Puma::Plugin.create do
|
||||||
@@ -68,7 +69,7 @@ Puma::Plugin.create do
|
|||||||
|
|
||||||
break
|
break
|
||||||
rescue RedisClient::CannotConnectError
|
rescue RedisClient::CannotConnectError
|
||||||
raise('Unable to connect to redis') if attempt > 10
|
raise('Unable to connect to redis') if attempt > 30
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+4
-2
@@ -162,7 +162,7 @@ module Submissions
|
|||||||
return email.downcase.sub(/@gmail?\z/i, '@gmail.com') if email.match?(/@gmail?\z/i)
|
return email.downcase.sub(/@gmail?\z/i, '@gmail.com') if email.match?(/@gmail?\z/i)
|
||||||
|
|
||||||
return email.downcase if email.include?(',') ||
|
return email.downcase if email.include?(',') ||
|
||||||
email.match?(/\.(?:gob|om|mm|cm|et|mo|nz|za|ie|ed\.jp)\z/i) ||
|
email.match?(/\.(?:gob(?:\.\w+)?|om|mm|cm|et|mo|nz|za|ie|ed\.jp)\z/i) ||
|
||||||
email.exclude?('.')
|
email.exclude?('.')
|
||||||
|
|
||||||
fixed_email = EmailTypo.call(email.delete_prefix('<'))
|
fixed_email = EmailTypo.call(email.delete_prefix('<'))
|
||||||
@@ -175,7 +175,9 @@ module Submissions
|
|||||||
return email.downcase if domain == fixed_domain
|
return email.downcase if domain == fixed_domain
|
||||||
return email.downcase if fixed_domain.match?(/\Agmail\.(?!com\z)/i)
|
return email.downcase if fixed_domain.match?(/\Agmail\.(?!com\z)/i)
|
||||||
|
|
||||||
if DidYouMean::Levenshtein.distance(domain, fixed_domain) > 3
|
threshold = fixed_domain.start_with?('hotmail.') ? 2 : 3
|
||||||
|
|
||||||
|
if DidYouMean::Levenshtein.distance(domain, fixed_domain) > threshold
|
||||||
Rails.logger.info("Skipped email fix #{domain}")
|
Rails.logger.info("Skipped email fix #{domain}")
|
||||||
|
|
||||||
return email.downcase
|
return email.downcase
|
||||||
|
|||||||
@@ -129,6 +129,8 @@ module Submissions
|
|||||||
with_submitter_timezone = configs.find { |c| c.key == AccountConfig::WITH_SUBMITTER_TIMEZONE_KEY }&.value == true
|
with_submitter_timezone = configs.find { |c| c.key == AccountConfig::WITH_SUBMITTER_TIMEZONE_KEY }&.value == true
|
||||||
with_timestamp_seconds = configs.find { |c| c.key == AccountConfig::WITH_TIMESTAMP_SECONDS_KEY }&.value == true
|
with_timestamp_seconds = configs.find { |c| c.key == AccountConfig::WITH_TIMESTAMP_SECONDS_KEY }&.value == true
|
||||||
|
|
||||||
|
file_links_expire_at = Accounts.link_expires_at(submission.account) if with_file_links
|
||||||
|
|
||||||
timezone = account.timezone
|
timezone = account.timezone
|
||||||
timezone = last_submitter.timezone || account.timezone if with_submitter_timezone
|
timezone = last_submitter.timezone || account.timezone if with_submitter_timezone
|
||||||
|
|
||||||
@@ -408,7 +410,7 @@ module Submissions
|
|||||||
|
|
||||||
link =
|
link =
|
||||||
if with_file_links
|
if with_file_links
|
||||||
ActiveStorage::Blob.proxy_url(attachment.blob)
|
ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: file_links_expire_at)
|
||||||
else
|
else
|
||||||
r.submissions_preview_url(submission.slug, **Docuseal.default_url_options)
|
r.submissions_preview_url(submission.slug, **Docuseal.default_url_options)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ module Submissions
|
|||||||
with_signature_id_reason =
|
with_signature_id_reason =
|
||||||
configs.find { |c| c.key == AccountConfig::WITH_SIGNATURE_ID_REASON_KEY }&.value != false
|
configs.find { |c| c.key == AccountConfig::WITH_SIGNATURE_ID_REASON_KEY }&.value != false
|
||||||
|
|
||||||
|
file_links_expire_at = Accounts.link_expires_at(submission.account) if with_file_links
|
||||||
|
|
||||||
pdfs_index = GenerateResultAttachments.build_pdfs_index(submission, flatten: is_flatten,
|
pdfs_index = GenerateResultAttachments.build_pdfs_index(submission, flatten: is_flatten,
|
||||||
incremental: is_rotate_incremental)
|
incremental: is_rotate_incremental)
|
||||||
|
|
||||||
@@ -42,7 +44,8 @@ module Submissions
|
|||||||
GenerateResultAttachments.fill_submitter_fields(s, submission.account, pdfs_index,
|
GenerateResultAttachments.fill_submitter_fields(s, submission.account, pdfs_index,
|
||||||
with_signature_id:, is_flatten:, with_headings: index.zero?,
|
with_signature_id:, is_flatten:, with_headings: index.zero?,
|
||||||
with_submitter_timezone:, with_file_links:,
|
with_submitter_timezone:, with_file_links:,
|
||||||
with_signature_id_reason:, with_timestamp_seconds:)
|
file_links_expire_at:, with_signature_id_reason:,
|
||||||
|
with_timestamp_seconds:)
|
||||||
end
|
end
|
||||||
|
|
||||||
template = submission.template
|
template = submission.template
|
||||||
|
|||||||
@@ -151,6 +151,8 @@ module Submissions
|
|||||||
with_signature_id_reason =
|
with_signature_id_reason =
|
||||||
configs.find { |c| c.key == AccountConfig::WITH_SIGNATURE_ID_REASON_KEY }&.value != false
|
configs.find { |c| c.key == AccountConfig::WITH_SIGNATURE_ID_REASON_KEY }&.value != false
|
||||||
|
|
||||||
|
file_links_expire_at = Accounts.link_expires_at(submitter.account) if with_file_links
|
||||||
|
|
||||||
pdfs_index = build_pdfs_index(submitter.submission, submitter:, flatten: is_flatten,
|
pdfs_index = build_pdfs_index(submitter.submission, submitter:, flatten: is_flatten,
|
||||||
incremental: is_rotate_incremental)
|
incremental: is_rotate_incremental)
|
||||||
|
|
||||||
@@ -198,12 +200,14 @@ module Submissions
|
|||||||
with_submitter_timezone:,
|
with_submitter_timezone:,
|
||||||
with_file_links:,
|
with_file_links:,
|
||||||
with_timestamp_seconds:,
|
with_timestamp_seconds:,
|
||||||
with_signature_id_reason:)
|
with_signature_id_reason:,
|
||||||
|
file_links_expire_at:)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fill_submitter_fields(submitter, account, pdfs_index, with_signature_id:, is_flatten:, with_headings: nil,
|
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_submitter_timezone: false, with_signature_id_reason: true,
|
||||||
with_timestamp_seconds: false, with_file_links: nil)
|
with_timestamp_seconds: false, with_file_links: nil,
|
||||||
|
file_links_expire_at: Accounts.link_expires_at(account))
|
||||||
cell_layouters = Hash.new do |hash, valign|
|
cell_layouters = Hash.new do |hash, valign|
|
||||||
hash[valign] = HexaPDF::Layout::TextLayouter.new(text_valign: valign.to_sym, text_align: :center)
|
hash[valign] = HexaPDF::Layout::TextLayouter.new(text_valign: valign.to_sym, text_align: :center)
|
||||||
end
|
end
|
||||||
@@ -516,7 +520,7 @@ module Submissions
|
|||||||
|
|
||||||
url =
|
url =
|
||||||
if with_file_links
|
if with_file_links
|
||||||
ActiveStorage::Blob.proxy_url(attachment.blob)
|
ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: file_links_expire_at)
|
||||||
else
|
else
|
||||||
r.submissions_preview_url(submission.slug, **Docuseal.default_url_options)
|
r.submissions_preview_url(submission.slug, **Docuseal.default_url_options)
|
||||||
end
|
end
|
||||||
@@ -743,7 +747,7 @@ module Submissions
|
|||||||
pdf.trailer.info[:Creator] = info_creator
|
pdf.trailer.info[:Creator] = info_creator
|
||||||
|
|
||||||
if Docuseal.pdf_format == 'pdf/a-3b'
|
if Docuseal.pdf_format == 'pdf/a-3b'
|
||||||
pdf.task(:pdfa, level: '3b')
|
pdfa_listener = pdf.task(:pdfa, level: '3b')
|
||||||
pdf.config['font.map'] = PDFA_FONT_MAP
|
pdf.config['font.map'] = PDFA_FONT_MAP
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -759,12 +763,14 @@ module Submissions
|
|||||||
|
|
||||||
begin
|
begin
|
||||||
pdf.sign(io, write_options: { validate: false }, **sign_params)
|
pdf.sign(io, write_options: { validate: false }, **sign_params)
|
||||||
rescue HexaPDF::Error, NoMethodError => e
|
rescue HexaPDF::Error, NoMethodError, TypeError => e
|
||||||
Rollbar.error(e) if defined?(Rollbar)
|
Rollbar.error(e) if defined?(Rollbar)
|
||||||
|
|
||||||
|
pdf.instance_variable_get(:@listeners)[:complete_objects].delete(pdfa_listener) if pdfa_listener
|
||||||
|
|
||||||
begin
|
begin
|
||||||
pdf.sign(io, write_options: { validate: false, incremental: false }, **sign_params)
|
pdf.sign(io, write_options: { validate: false, incremental: false }, **sign_params)
|
||||||
rescue HexaPDF::Error
|
rescue HexaPDF::Error, TypeError
|
||||||
pdf.validate(auto_correct: true)
|
pdf.validate(auto_correct: true)
|
||||||
pdf.sign(io, write_options: { validate: false, incremental: false }, **sign_params)
|
pdf.sign(io, write_options: { validate: false, incremental: false }, **sign_params)
|
||||||
end
|
end
|
||||||
|
|||||||
+3
-2
@@ -122,7 +122,7 @@ module Submitters
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_attachment!(submitter, file)
|
def create_attachment!(submitter, file, metadata: {})
|
||||||
raise ParamsError, 'file param is missing' if file.blank?
|
raise ParamsError, 'file param is missing' if file.blank?
|
||||||
|
|
||||||
extension = File.extname(file.original_filename).delete_prefix('.').downcase
|
extension = File.extname(file.original_filename).delete_prefix('.').downcase
|
||||||
@@ -133,7 +133,8 @@ module Submitters
|
|||||||
|
|
||||||
blob = ActiveStorage::Blob.create_and_upload!(io: file.tap(&:rewind).open,
|
blob = ActiveStorage::Blob.create_and_upload!(io: file.tap(&:rewind).open,
|
||||||
filename: file.original_filename,
|
filename: file.original_filename,
|
||||||
content_type: file.content_type)
|
content_type: file.content_type,
|
||||||
|
metadata:)
|
||||||
|
|
||||||
ActiveStorage::Attachment.create!(blob:, name: 'attachments', record: submitter)
|
ActiveStorage::Attachment.create!(blob:, name: 'attachments', record: submitter)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,12 +6,6 @@ module Submitters
|
|||||||
HEIGHT = 200
|
HEIGHT = 200
|
||||||
LRM = "\u200E"
|
LRM = "\u200E"
|
||||||
|
|
||||||
TRANSPARENT_PIXEL = "\x89PNG\r\n\u001A\n\u0000\u0000\u0000\rIHDR\u0000" \
|
|
||||||
"\u0000\u0000\u0001\u0000\u0000\u0000\u0001\b\u0004" \
|
|
||||||
"\u0000\u0000\u0000\xB5\u001C\f\u0002\u0000\u0000\u0000" \
|
|
||||||
"\vIDATx\xDAc\xFC_\u000F\u0000\u0002\x83\u0001\x804\xC3ڨ" \
|
|
||||||
"\u0000\u0000\u0000\u0000IEND\xAEB`\x82"
|
|
||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
def call(submitter, with_logo: true)
|
def call(submitter, with_logo: true)
|
||||||
@@ -42,14 +36,15 @@ module Submitters
|
|||||||
if with_logo
|
if with_logo
|
||||||
ImageUtils.load_vips(load_logo(submitter).read)
|
ImageUtils.load_vips(load_logo(submitter).read)
|
||||||
else
|
else
|
||||||
Vips::Image.new_from_buffer(TRANSPARENT_PIXEL, '').resize(WIDTH)
|
Vips::Image.black(WIDTH, WIDTH, bands: 4).copy(interpretation: :srgb)
|
||||||
end
|
end
|
||||||
|
|
||||||
logo = logo.resize([WIDTH / logo.width.to_f, HEIGHT / logo.height.to_f].min)
|
logo = logo.resize([WIDTH / logo.width.to_f, HEIGHT / logo.height.to_f].min)
|
||||||
|
logo = logo.copy(interpretation: :srgb) if logo.interpretation == :multiband
|
||||||
|
|
||||||
base_layer = Vips::Image.black(WIDTH, HEIGHT).new_from_image([255, 255, 255]).copy(interpretation: :srgb)
|
base_layer = Vips::Image.black(WIDTH, HEIGHT).new_from_image([255, 255, 255, 255]).copy(interpretation: :srgb)
|
||||||
|
|
||||||
opacity_layer = Vips::Image.new_from_buffer(TRANSPARENT_PIXEL, '').resize(WIDTH)
|
opacity_layer = Vips::Image.black(WIDTH, HEIGHT).new_from_image([255, 255, 255, 127]).copy(interpretation: :srgb)
|
||||||
|
|
||||||
text = build_text_image(submitter)
|
text = build_text_image(submitter)
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ module Submitters
|
|||||||
|
|
||||||
text_mask = Vips::Image.black(text_image.width, text_image.height)
|
text_mask = Vips::Image.black(text_image.width, text_image.height)
|
||||||
|
|
||||||
text_mask.bandjoin(text_image).copy(interpretation: :b_w).write_to_buffer('.png')
|
image = text_mask.bandjoin(text_image).copy(interpretation: :b_w)
|
||||||
|
|
||||||
|
[image.write_to_buffer('.png'), image.width, image.height]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ module Submitters
|
|||||||
end
|
end
|
||||||
|
|
||||||
def find_or_create_blob_from_text(account, text, type)
|
def find_or_create_blob_from_text(account, text, type)
|
||||||
data = Submitters::GenerateFontImage.call(text, font: type)
|
data, width, height = Submitters::GenerateFontImage.call(text, font: type)
|
||||||
|
|
||||||
checksum = Digest::MD5.base64digest(data)
|
checksum = Digest::MD5.base64digest(data)
|
||||||
|
|
||||||
@@ -268,7 +268,9 @@ module Submitters
|
|||||||
|
|
||||||
blob || ActiveStorage::Blob.create_and_upload!(
|
blob || ActiveStorage::Blob.create_and_upload!(
|
||||||
io: StringIO.new(data),
|
io: StringIO.new(data),
|
||||||
filename: "#{type}.png"
|
filename: "#{type}.png",
|
||||||
|
content_type: 'image/png',
|
||||||
|
metadata: { analyzed: true, identified: true, width:, height: }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
module Templates
|
module Templates
|
||||||
COLOR_REGEXP = /\A(#(?:[0-9a-f]{3}|[0-9a-f]{6})|[a-z]+)\z/i
|
COLOR_REGEXP = /\A(#(?:[0-9a-f]{3}|[0-9a-f]{6})|[a-z]+)\z/i
|
||||||
|
|
||||||
|
TEMPLATE_BUILDER_FIELDS = %i[id author_id folder_id external_id name slug
|
||||||
|
schema fields submitters variables_schema preferences
|
||||||
|
shared_link source archived_at created_at updated_at].freeze
|
||||||
|
|
||||||
EXPIRATION_DURATIONS = {
|
EXPIRATION_DURATIONS = {
|
||||||
one_day: 1.day,
|
one_day: 1.day,
|
||||||
two_days: 2.days,
|
two_days: 2.days,
|
||||||
@@ -91,4 +95,16 @@ module Templates
|
|||||||
Time.current + EXPIRATION_DURATIONS[default_expire_at_duration]
|
Time.current + EXPIRATION_DURATIONS[default_expire_at_duration]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def serialize_for_builder(template)
|
||||||
|
data = template.as_json(only: TEMPLATE_BUILDER_FIELDS)
|
||||||
|
|
||||||
|
data['documents'] = template.schema_documents.preload(:blob, { preview_images_attachments: :blob }).as_json(
|
||||||
|
only: %i[id uuid],
|
||||||
|
methods: %i[metadata signed_key],
|
||||||
|
include: { preview_images: { only: %i[id], methods: %i[url metadata filename] } }
|
||||||
|
)
|
||||||
|
|
||||||
|
data
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ module Templates
|
|||||||
split_page: false, aspect_ratio: false, padding: nil, page_number: nil)
|
split_page: false, aspect_ratio: false, padding: nil, page_number: nil)
|
||||||
return [[], nil] if page_number && page_number != 0
|
return [[], nil] if page_number && page_number != 0
|
||||||
|
|
||||||
image = ImageUtils.load_vips(io.read, content_type: attachment.content_type)
|
image = ImageUtils.load_vips(io.read)
|
||||||
|
|
||||||
fields = inference.call(image, confidence:, nms:, nmm:, split_page:,
|
fields = inference.call(image, confidence:, nms:, nmm:, split_page:,
|
||||||
temperature:, aspect_ratio:, padding:)
|
temperature:, aspect_ratio:, padding:)
|
||||||
@@ -193,7 +193,7 @@ module Templates
|
|||||||
|
|
||||||
data, width, height = page.render_to_bitmap(size_key => size)
|
data, width, height = page.render_to_bitmap(size_key => size)
|
||||||
|
|
||||||
Vips::Image.new_from_memory(data, width, height, 4, :uchar)
|
Vips::Image.new_from_memory_copy(data, width, height, 4, :uchar)
|
||||||
end
|
end
|
||||||
|
|
||||||
def sort_fields(fields, y_threshold: 0.01)
|
def sort_fields(fields, y_threshold: 0.01)
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ module Templates
|
|||||||
|
|
||||||
bytes, width, height = doc_page.render_to_bitmap(width: MAX_WIDTH)
|
bytes, width, height = doc_page.render_to_bitmap(width: MAX_WIDTH)
|
||||||
|
|
||||||
image = Vips::Image.new_from_memory(bytes, width, height, 4, :uchar)
|
image = Vips::Image.new_from_memory_copy(bytes, width, height, 4, :uchar)
|
||||||
|
|
||||||
Concurrent::Promise.execute(executor: pool) { build_and_upload_blob(image, page_number) }
|
Concurrent::Promise.execute(executor: pool) { build_and_upload_blob(image, page_number) }
|
||||||
ensure
|
ensure
|
||||||
@@ -201,7 +201,7 @@ module Templates
|
|||||||
|
|
||||||
doc_page.close
|
doc_page.close
|
||||||
|
|
||||||
image = Vips::Image.new_from_memory(bytes, width, height, 4, :uchar)
|
image = Vips::Image.new_from_memory_copy(bytes, width, height, 4, :uchar)
|
||||||
|
|
||||||
blob = build_and_upload_blob(image, page_number, PREVIEW_FORMAT)
|
blob = build_and_upload_blob(image, page_number, PREVIEW_FORMAT)
|
||||||
|
|
||||||
|
|||||||
@@ -14,27 +14,25 @@ module Templates
|
|||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
def call(template, schema_documents: template.schema_documents.preload(:blob), preview_image_attachments: nil,
|
def call(template, schema_documents: template.schema_documents.preload(:blob), dynamic_documents: nil,
|
||||||
expires_at: Accounts.link_expires_at(Account.new(id: template.account_id)))
|
preview_image_attachments: nil, expires_at: Accounts.link_expires_at(Account.new(id: template.account_id)))
|
||||||
json = template.as_json(SERIALIZE_PARAMS)
|
json = template.as_json(SERIALIZE_PARAMS)
|
||||||
|
|
||||||
preview_image_attachments ||=
|
dynamic_documents ||= preload_dynamic_documents(template)
|
||||||
ActiveStorage::Attachment.joins(:blob)
|
|
||||||
.where(blob: { filename: ['0.jpg', '0.png'] })
|
preview_image_attachments ||= preload_preview_image_attachments(schema_documents, dynamic_documents)
|
||||||
.where(record_id: schema_documents.map(&:id),
|
|
||||||
record_type: 'ActiveStorage::Attachment',
|
|
||||||
name: :preview_images)
|
|
||||||
.preload(:blob)
|
|
||||||
|
|
||||||
json['documents'] = template.schema.filter_map do |item|
|
json['documents'] = template.schema.filter_map do |item|
|
||||||
attachment = schema_documents.find { |e| e.uuid == item['attachment_uuid'] }
|
if item['dynamic']
|
||||||
|
dynamic_document = dynamic_documents.find { |e| e.uuid == item['attachment_uuid'] }
|
||||||
|
|
||||||
unless attachment
|
attachment = dynamic_document.current_version&.document_attachment
|
||||||
Rollbar.error("Documents missing: #{template.id}") if defined?(Rollbar)
|
|
||||||
|
|
||||||
next
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
attachment ||= schema_documents.find { |e| e.uuid == item['attachment_uuid'] }
|
||||||
|
|
||||||
|
next unless attachment
|
||||||
|
|
||||||
first_page_blob = preview_image_attachments.find { |e| e.record_id == attachment.id }&.blob
|
first_page_blob = preview_image_attachments.find { |e| e.record_id == attachment.id }&.blob
|
||||||
first_page_blob ||= attachment.preview_images.joins(:blob).find_by(blob: { filename: ['0.jpg', '0.png'] })&.blob
|
first_page_blob ||= attachment.preview_images.joins(:blob).find_by(blob: { filename: ['0.jpg', '0.png'] })&.blob
|
||||||
|
|
||||||
@@ -49,5 +47,26 @@ module Templates
|
|||||||
|
|
||||||
json
|
json
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def preload_dynamic_documents(template)
|
||||||
|
return DynamicDocument.none if template.schema.none? { |item| item['dynamic'] }
|
||||||
|
|
||||||
|
template.schema_dynamic_documents
|
||||||
|
.preload(current_version: { document_attachment: :blob })
|
||||||
|
.select(:id, :uuid, :template_id, :sha1, :created_at, :updated_at)
|
||||||
|
end
|
||||||
|
|
||||||
|
def preload_preview_image_attachments(schema_documents, dynamic_documents)
|
||||||
|
record_ids =
|
||||||
|
schema_documents.map(&:id) +
|
||||||
|
dynamic_documents.filter_map { |d| d.current_version&.document_attachment&.id }
|
||||||
|
|
||||||
|
ActiveStorage::Attachment.joins(:blob)
|
||||||
|
.where(blob: { filename: ['0.jpg', '0.png'] })
|
||||||
|
.where(record_id: record_ids,
|
||||||
|
record_type: 'ActiveStorage::Attachment',
|
||||||
|
name: :preview_images)
|
||||||
|
.preload(:blob)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -42,9 +42,7 @@ RSpec.describe 'Template' do
|
|||||||
|
|
||||||
it 'archives a template' do
|
it 'archives a template' do
|
||||||
expect do
|
expect do
|
||||||
accept_confirm('Are you sure?') do
|
click_button 'Archive'
|
||||||
click_button 'Archive'
|
|
||||||
end
|
|
||||||
end.to change { Template.active.count }.by(-1)
|
end.to change { Template.active.count }.by(-1)
|
||||||
|
|
||||||
expect(page).to have_content('Template has been archived')
|
expect(page).to have_content('Template has been archived')
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe 'Templates Upload' do
|
||||||
|
let!(:account) { create(:account) }
|
||||||
|
let!(:user) { create(:user, account:) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sign_in(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when url param is present' do
|
||||||
|
let(:file_url) { 'https://example.com/document.pdf' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_request(:get, file_url).to_return(
|
||||||
|
body: Rails.root.join('spec/fixtures/sample-document.pdf').read,
|
||||||
|
headers: { 'Content-Type' => 'application/pdf' }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows a confirm page and creates template on submit' do
|
||||||
|
visit "/new?url=#{CGI.escape(file_url)}"
|
||||||
|
|
||||||
|
expect(page).to have_text('Open file from')
|
||||||
|
expect(page).to have_link('example.com/document.pdf')
|
||||||
|
|
||||||
|
click_button 'Open'
|
||||||
|
|
||||||
|
expect(Template.last.name).to eq('document')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when url param is missing' do
|
||||||
|
it 'redirects to root' do
|
||||||
|
visit '/new'
|
||||||
|
|
||||||
|
expect(page).to have_current_path(root_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user