Skip to content

Commit da44d51

Browse files
authored
Merge from docusealco/wip
2 parents ed46af8 + 73ce10b commit da44d51

25 files changed

Lines changed: 4169 additions & 65 deletions

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ jobs:
104104
bundle install --jobs 4 --retry 4
105105
yarn install
106106
sudo apt-get update
107-
sudo apt-get install libvips
107+
sudo apt-get install libvips liblept5
108108
- name: Run Brakeman
109109
run: bundle exec brakeman -q --exit-on-warn
110110

@@ -162,7 +162,7 @@ jobs:
162162
bundle install --jobs 4 --retry 4
163163
yarn install
164164
sudo apt-get update
165-
sudo apt-get install -y libvips
165+
sudo apt-get install -y libvips liblept5
166166
wget -O pdfium-linux.tgz "https://github.com/bblanchon/pdfium-binaries/releases/latest/download/pdfium-linux-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/').tgz"
167167
sudo tar -xzf pdfium-linux.tgz --strip-components=1 -C /usr/lib lib/libpdfium.so
168168
rm -f pdfium-linux.tgz

Dockerfile

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@ FROM ruby:4.0.5-alpine AS download
22

33
WORKDIR /fonts
44

5-
RUN apk --no-cache add wget && \
5+
RUN apk --no-cache add wget unzip && \
66
wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Regular.ttf && \
77
wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Bold.ttf && \
88
wget https://github.com/impallari/DancingScript/raw/master/fonts/DancingScript-Regular.otf && \
99
wget https://raw.githubusercontent.com/impallari/DancingScript/master/OFL.txt && \
1010
wget https://raw.githubusercontent.com/notofonts/noto-fonts/refs/heads/main/LICENSE && \
1111
wget -O /model.onnx "https://github.com/docusealco/fields-detection/releases/download/2.0.0/model_704_int8.onnx" && \
12-
wget -O pdfium-linux.tgz "https://github.com/bblanchon/pdfium-binaries/releases/latest/download/pdfium-linux-musl-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/').tgz" && \
12+
wget -O pdfium-linux.zip "https://github.com/docusealco/pdfium-binaries/releases/download/20260613/pdfium-musl-$(uname -m).zip" && \
13+
case "$(uname -m)" in \
14+
x86_64) echo "2c953ff72ee2dda07e7fc577e25841cc3d6464468a7c5adfaea574efcbc3b90b pdfium-linux.zip" ;; \
15+
aarch64) echo "23bbe287d2753fdb05741c7660647eb0ef0d2e4da2ce0722bfa9d9d455bd64e2 pdfium-linux.zip" ;; \
16+
esac | sha256sum -c - && \
1317
mkdir -p /pdfium-linux && \
14-
tar -xzf pdfium-linux.tgz -C /pdfium-linux
18+
unzip -q pdfium-linux.zip -d /pdfium-linux
1519

1620
FROM ruby:4.0.5-alpine AS webpack
1721

@@ -48,7 +52,7 @@ ENV OPENSSL_CONF=/etc/openssl_legacy.cnf
4852

4953
WORKDIR /app
5054

51-
RUN apk add --no-cache libpq vips redis onnxruntime && \
55+
RUN apk add --no-cache libpq vips redis onnxruntime leptonica && \
5256
rm -f /usr/bin/onnx_test_runner /usr/bin/onnxruntime_test
5357

5458
RUN addgroup -g 2000 docuseal && adduser -u 2000 -G docuseal -s /bin/sh -D -h /home/docuseal docuseal
@@ -82,7 +86,7 @@ COPY --chown=docuseal:docuseal .version ./public/version
8286

8387
COPY --chown=docuseal:docuseal --from=download /fonts/GoNotoKurrent-Regular.ttf /fonts/GoNotoKurrent-Bold.ttf /fonts/DancingScript-Regular.otf /fonts/OFL.txt /fonts/LICENSE /fonts/
8488
COPY --from=download /pdfium-linux/lib/libpdfium.so /usr/lib/libpdfium.so
85-
COPY --from=download /pdfium-linux/licenses/pdfium.txt /usr/lib/libpdfium-LICENSE.txt
89+
COPY --from=download /pdfium-linux/licenses/ /usr/lib/libpdfium-licenses/
8690
COPY --chown=docuseal:docuseal --from=download /model.onnx /app/tmp/model.onnx
8791
COPY --chown=docuseal:docuseal --from=webpack /app/public/packs ./public/packs
8892

Gemfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ GEM
313313
multi_json (1.19.1)
314314
net-http (0.9.1)
315315
uri (>= 0.11.1)
316-
net-imap (0.6.4)
316+
net-imap (0.6.4.1)
317317
date
318318
net-protocol
319319
net-pop (0.1.2)
@@ -373,7 +373,7 @@ GEM
373373
date
374374
stringio
375375
public_suffix (7.0.5)
376-
puma (7.2.0)
376+
puma (8.0.2)
377377
nio4r (~> 2.0)
378378
racc (1.8.1)
379379
rack (3.2.6)

app/controllers/api/submissions_controller.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ def create
5454

5555
return render json: { error: 'Template not found' }, status: :unprocessable_content if @template.nil?
5656

57+
if @template.archived_at?
58+
Rollbar.warning("Archived template submission: #{@template.id}") if defined?(Rollbar)
59+
60+
return render json: { error: 'Template has been archived' }, status: :unprocessable_content
61+
end
62+
5763
if @template.fields.blank?
5864
Rollbar.warning("Template does not contain fields: #{@template.id}") if defined?(Rollbar)
5965

app/controllers/submissions_controller.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ def new
3838
end
3939

4040
def create
41+
return redirect_to template_path(@template), alert: I18n.t('template_has_been_archived') if @template.archived_at?
42+
4143
save_template_message(@template, params) if params[:save_message] == '1'
4244

4345
[params.delete(:subject), params.delete(:body)] if params[:is_custom_message] != '1'
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# frozen_string_literal: true
2+
3+
class TemplateDocumentsCropController < ApplicationController
4+
load_and_authorize_resource :template
5+
before_action :load_attachment
6+
7+
rescue_from Leptonica::LeptonicaError do
8+
render json: { error: I18n.t(:unable_to_save) }, status: :unprocessable_content
9+
end
10+
11+
def index
12+
render json: { corners: Leptonica.detect_document_corners(@attachment.download) }
13+
end
14+
15+
def create
16+
authorize!(:update, @template)
17+
18+
document = Templates::CreateDocumentCrop.call(@template, @attachment, crop_params)
19+
20+
render json: {
21+
document: document.as_json(
22+
methods: %i[metadata signed_key],
23+
include: {
24+
preview_images: { methods: %i[url metadata filename] }
25+
}
26+
)
27+
}
28+
end
29+
30+
private
31+
32+
def load_attachment
33+
@attachment = @template.documents_attachments.find_by!(uuid: params[:attachment_uuid])
34+
end
35+
36+
def crop_params
37+
params.permit(:scan, :rotate, :flip_h, :flip_v, corners: [%i[x y]])
38+
end
39+
end
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# frozen_string_literal: true
2+
3+
class TemplateDocumentsModifyController < ApplicationController
4+
load_and_authorize_resource :template
5+
6+
def create
7+
authorize!(:update, @template)
8+
9+
documents_layout =
10+
params.require(:documents).map do |item|
11+
item.permit(:attachment_uuid,
12+
pages: [:attachment_uuid, :page, :rotate,
13+
{ redact: [%i[x y w h]], replaced_page: %i[attachment_uuid page] }]).to_h
14+
end
15+
16+
Templates::ModifyDocuments.call(@template, documents_layout)
17+
18+
render json: {
19+
schema: @template.schema,
20+
fields: @template.fields,
21+
submitters: @template.submitters,
22+
documents: @template.schema_documents.reload.preload(:blob, preview_images_attachments: :blob).as_json(
23+
methods: %i[metadata signed_key],
24+
include: {
25+
preview_images: { methods: %i[url metadata filename] }
26+
}
27+
)
28+
}
29+
rescue Templates::ModifyDocuments::InvalidLayout
30+
render json: { error: I18n.t(:unable_to_save) }, status: :unprocessable_content
31+
end
32+
end
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# frozen_string_literal: true
2+
3+
class TemplateDocumentsPageObjectsController < ApplicationController
4+
load_and_authorize_resource :template
5+
6+
def index
7+
attachment = @template.documents_attachments.find_by!(uuid: params[:attachment_uuid])
8+
9+
render json: Templates::ModifyDocuments.page_objects(attachment, params[:page].to_i)
10+
end
11+
end

app/javascript/submission_form/area.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ export default {
528528
return this.formatDate(
529529
this.modelValue === '{{date}}' ? new Date() : new Date(this.modelValue),
530530
this.field.preferences?.format || (this.locale.endsWith('-US') ? 'MM/DD/YYYY' : 'DD/MM/YYYY'),
531-
{ withTimePlaceholders: this.modelValue === '{{date}}' }
531+
{ withTimePlaceholders: this.modelValue === '{{date}}', utc: this.modelValue !== '{{date}}' }
532532
)
533533
} catch {
534534
return this.modelValue
@@ -651,7 +651,7 @@ export default {
651651
return number
652652
}
653653
},
654-
formatDate (date, format, { withTimePlaceholders = false } = {}) {
654+
formatDate (date, format, { withTimePlaceholders = false, utc = true } = {}) {
655655
const monthFormats = { M: 'numeric', MM: '2-digit', MMM: 'short', MMMM: 'long' }
656656
const dayFormats = { D: 'numeric', DD: '2-digit' }
657657
const yearFormats = { YYYY: 'numeric', YYY: 'numeric', YY: '2-digit' }
@@ -673,7 +673,7 @@ export default {
673673
if (format.match(/m+/)) opts.minute = minuteFormats[format.match(/m+/)[0]]
674674
if (format.match(/s+/)) opts.second = secondFormats[format.match(/s+/)[0]]
675675
if (/z/.test(format)) opts.timeZoneName = 'short'
676-
if (!hasTime) opts.timeZone = 'UTC'
676+
if (!hasTime && utc) opts.timeZone = 'UTC'
677677
678678
const partTypes = {
679679
M: 'month',

app/javascript/template_builder/builder.vue

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@
320320
@replace="onDocumentReplace"
321321
@up="moveDocument(item, -1)"
322322
@reorder="reorderFields"
323+
@edit="editModalDocumentUuid = item.attachment_uuid"
323324
@down="moveDocument(item, 1)"
324325
@change="save"
325326
/>
@@ -363,6 +364,7 @@
363364
isMobile ? 'overflow-y-auto' : 'overflow-y-hidden md:overflow-y-auto',
364365
zoomLevel > 1 ? 'overflow-x-auto' : 'overflow-x-hidden'
365366
]"
367+
@wheel="onPagesWheel"
366368
>
367369
<div
368370
ref="documents"
@@ -684,6 +686,17 @@
684686
@close="isRevisionsModalOpen = false"
685687
@apply="onRevisionApply"
686688
/>
689+
<DocumentsEditorModal
690+
v-if="editModalDocumentUuid"
691+
:template="template"
692+
:authenticity-token="authenticityToken"
693+
:accept-file-types="acceptFileTypes"
694+
:base-url="baseUrl"
695+
:page-preview-format="pagePreviewFormat"
696+
:scroll-to-attachment-uuid="editModalDocumentUuid"
697+
@saved="onDocumentsModified"
698+
@close="editModalDocumentUuid = null"
699+
/>
687700
</div>
688701
</div>
689702
</template>
@@ -703,6 +716,7 @@ import DocumentControls from './controls'
703716
import MobileFields from './mobile_fields'
704717
import FieldSubmitter from './field_submitter'
705718
import RevisionsModal from './revisions_modal'
719+
import DocumentsEditorModal from './documents_editor_modal'
706720
import { IconPlus, IconUsersPlus, IconDeviceFloppy, IconChevronDown, IconEye, IconWritingSign, IconInnerShadowTop, IconInfoCircle, IconAdjustments, IconDownload, IconHistory, IconX } from '@tabler/icons-vue'
707721
import { v4 } from 'uuid'
708722
import { ref, computed, toRaw, defineAsyncComponent } from 'vue'
@@ -746,7 +760,8 @@ export default {
746760
IconHistory,
747761
IconDeviceFloppy,
748762
IconX,
749-
RevisionsModal
763+
RevisionsModal,
764+
DocumentsEditorModal
750765
},
751766
provide () {
752767
return {
@@ -1102,6 +1117,7 @@ export default {
11021117
isDragFile: false,
11031118
isMathLoaded: false,
11041119
isRevisionsModalOpen: false,
1120+
editModalDocumentUuid: null,
11051121
revisions: [],
11061122
beforeRevisionSnapshot: null,
11071123
zoomLevel: 1
@@ -1354,17 +1370,13 @@ export default {
13541370
this.pendingFieldAttachmentUuids.push(item.attachment_uuid)
13551371
}
13561372
})
1357-
1358-
this.$refs.pagesContainer.addEventListener('wheel', this.onPagesWheel, { passive: false })
13591373
},
13601374
unmounted () {
13611375
document.removeEventListener('keyup', this.onKeyUp)
13621376
window.removeEventListener('keydown', this.onKeyDown)
13631377

13641378
window.removeEventListener('resize', this.onWindowResize)
13651379
window.removeEventListener('dragleave', this.onWindowDragLeave)
1366-
1367-
this.$refs.pagesContainer.removeEventListener('wheel', this.onPagesWheel)
13681380
},
13691381
beforeUpdate () {
13701382
this.documentRefs = []
@@ -1681,7 +1693,7 @@ export default {
16811693

16821694
ref.x = e.clientX - ref.offsetX
16831695
ref.y = e.clientY - ref.offsetY
1684-
} else if (e.dataTransfer?.types?.includes('Files')) {
1696+
} else if (e.dataTransfer?.types?.includes('Files') && !this.editModalDocumentUuid) {
16851697
this.isDragFile = true
16861698
}
16871699
},
@@ -2124,6 +2136,10 @@ export default {
21242136
}
21252137
},
21262138
onKeyDown (event) {
2139+
if (this.editModalDocumentUuid) {
2140+
return
2141+
}
2142+
21272143
if (event.key === 'Tab' && document.activeElement === document.body) {
21282144
event.stopImmediatePropagation()
21292145
event.preventDefault()
@@ -3142,6 +3158,22 @@ export default {
31423158
onDocumentsReplaceAndTemplateClone (template) {
31433159
window.Turbo.visit(`/templates/${template.id}/edit`)
31443160
},
3161+
onDocumentsModified (data) {
3162+
this.template.schema = data.schema
3163+
this.template.fields = data.fields
3164+
this.template.submitters = data.submitters
3165+
this.template.documents = data.documents
3166+
3167+
this.selectedAreasRef.value = []
3168+
3169+
if (!this.template.submitters.find((s) => s.uuid === this.selectedSubmitter?.uuid)) {
3170+
this.selectedSubmitter = this.template.submitters[0]
3171+
}
3172+
3173+
this.editModalDocumentUuid = null
3174+
3175+
this.save()
3176+
},
31453177
moveDocument (item, direction) {
31463178
const currentIndex = this.template.schema.indexOf(item)
31473179

0 commit comments

Comments
 (0)