diff --git a/app/javascript/application.js b/app/javascript/application.js
index acc47a64e..2e818759f 100644
--- a/app/javascript/application.js
+++ b/app/javascript/application.js
@@ -194,6 +194,8 @@ safeRegisterElement('template-builder', class extends HTMLElement {
document.addEventListener('template-builder:sync', this.onSync)
document.addEventListener('template-builder:scroll-to', this.onScrollTo)
document.addEventListener('template-builder:select-field', this.onSelectField)
+ document.addEventListener('template-builder:select-submitter', this.onSelectSubmitter)
+ document.addEventListener('template-builder:sync-custom-fields', this.onSyncCustomFields)
document.addEventListener('template-builder:draw-field', this.onDrawField)
document.addEventListener('turbo:before-cache', this.onBeforeCache)
@@ -201,6 +203,10 @@ safeRegisterElement('template-builder', class extends HTMLElement {
this.appElem.classList.add('md:h-screen')
+ if (this.dataset.nativeView) {
+ this.appElem.classList.add('min-w-0')
+ }
+
const template = reactive(JSON.parse(this.dataset.template))
this.app = createApp(TemplateBuilder, {
@@ -240,7 +246,7 @@ safeRegisterElement('template-builder', class extends HTMLElement {
acceptFileTypes: this.dataset.acceptFileTypes,
showTourStartForm: this.dataset.showTourStartForm === 'true',
nativePlatform: this.dataset.nativePlatform,
- view: this.dataset.view
+ nativeView: this.dataset.nativeView
})
this.component = this.app.mount(this.appElem)
@@ -266,14 +272,33 @@ safeRegisterElement('template-builder', class extends HTMLElement {
onSync = (e) => {
const { schema, documents, fields, submitters } = e.detail
+ const selectedAreas = this.component.selectedAreasRef.value.map((area) => {
+ const field = this.component.template.fields.find((f) => f.areas?.includes(area))
+
+ return field && { uuid: field.uuid, index: field.areas.indexOf(area) }
+ })
+ const selectedSubmitterUuid = this.component.selectedSubmitter?.uuid
Object.assign(this.component.template, { schema, documents, fields, submitters })
+
+ this.component.selectedAreasRef.value = selectedAreas.map((item) => item && fields.find((f) => f.uuid === item.uuid)?.areas?.[item.index]).filter(Boolean)
+ this.component.selectedSubmitter = submitters.find((s) => s.uuid === selectedSubmitterUuid) || submitters[0]
}
onScrollTo = (e) => {
this.component.scrollIntoDocument(e.detail)
}
+ onSyncCustomFields = (e) => {
+ this.component.customFields.splice(0, this.component.customFields.length, ...e.detail)
+ }
+
+ onSelectSubmitter = (e) => {
+ const submitter = this.component.template.submitters.find((s) => s.uuid === e.detail.uuid)
+
+ if (submitter) this.component.selectedSubmitter = submitter
+ }
+
onSelectField = (e) => {
const { uuid, attachment_uuid: attachmentUuid, page, x, y } = e.detail
const field = this.component.template.fields.find((f) => f.uuid === uuid)
@@ -284,9 +309,16 @@ safeRegisterElement('template-builder', class extends HTMLElement {
}
onDrawField = (e) => {
- const { uuid, type, option } = e.detail
+ const { uuid, type, option, custom_field_uuid: customFieldUuid } = e.detail
- if (uuid) {
+ if (customFieldUuid) {
+ const field = this.component.customFields.find((f) => f.uuid === customFieldUuid)
+
+ if (field) {
+ this.component.drawCustomField = field
+ this.component.showDrawField = true
+ }
+ } else if (uuid) {
const field = this.component.template.fields.find((f) => f.uuid === uuid)
if (field) {
@@ -338,6 +370,8 @@ safeRegisterElement('template-builder', class extends HTMLElement {
document.removeEventListener('template-builder:sync', this.onSync)
document.removeEventListener('template-builder:scroll-to', this.onScrollTo)
document.removeEventListener('template-builder:select-field', this.onSelectField)
+ document.removeEventListener('template-builder:select-submitter', this.onSelectSubmitter)
+ document.removeEventListener('template-builder:sync-custom-fields', this.onSyncCustomFields)
document.removeEventListener('template-builder:draw-field', this.onDrawField)
document.removeEventListener('turbo:before-cache', this.onBeforeCache)
diff --git a/app/javascript/template_builder/area_title.vue b/app/javascript/template_builder/area_title.vue
index 561188cc8..0b1b15e00 100644
--- a/app/javascript/template_builder/area_title.vue
+++ b/app/javascript/template_builder/area_title.vue
@@ -40,7 +40,7 @@
@blur="onNameBlur"
>{{ optionIndexText }} {{ (defaultField ? (defaultField.title || field.title || field.name) : field.name) || defaultName }}
+
+
+
+
+
+
+
+ The dots menu is retired in favor of the field context menu. Right-click the field to access field settings. Double-click the field to set a default value.
+
+
+
+
+ {{ t('remove') }}
+
+
+
+
@@ -53,7 +53,7 @@
@replace="onDocumentReplace"
@up="moveDocument(item, -1)"
@reorder="reorderFields"
- @edit="editModalDocumentUuid = item.attachment_uuid"
+ @edit="openDocumentsEditor(item)"
@down="moveDocument(item, 1)"
@change="save"
/>
@@ -119,29 +119,36 @@
:with-signature-id="withSignatureId"
:with-prefillable="withPrefillable"
:only-defined-fields="onlyDefinedFields"
+ :with-sticky-submitters="!!nativeView"
:editable="editable"
:scroll-on-edit="false"
@add-field="addField"
@set-draw="onDrawerSetDraw"
@remove-field="onRemoveField"
@remove-submitter="onRemoveSubmitter"
- @select-submitter="selectedSubmitter = $event"
+ @select-submitter="onDrawerSelectSubmitter"
@set-draw-type="onDrawerSetDrawType"
+ @set-draw-custom-field="onDrawerSetDrawCustomField"
@set-drag="dragField = $event"
@set-drag-placeholder="$refs.dragPlaceholder.dragPlaceholder = $event"
@drag-end="[dragField = null, $refs.dragPlaceholder.dragPlaceholder = null]"
- @change-submitter="selectedSubmitter = $event"
+ @change-submitter="onDrawerSelectSubmitter"
@scroll-to-area="onDrawerScrollToArea"
@rebuild-variables-schema="rebuildVariablesSchema"
/>
-
-
-
+
@@ -758,7 +759,7 @@
0
- },
- isRevisionsView () {
- return this.view === 'revisions'
- },
- isNativeView () {
- return this.isDocumentsView || this.isFieldsView || this.isRevisionsView
+ return !this.nativePlatform && !this.nativeView && this.sortedDocuments.length > 0
},
isMobile () {
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
@@ -1624,10 +1623,6 @@ export default {
this.selectedSubmitter = this.template.submitters[0]
},
mounted () {
- if (this.isRevisionsView) {
- this.openRevisionsModal()
- }
-
this.undoStack = [JSON.stringify(this.template)]
this.redoStack = []
@@ -2200,6 +2195,10 @@ export default {
this.loadRevisionsPromise ||= this.baseFetch(`/templates/${this.template.id}/versions`)
},
openRevisionsModal () {
+ if (window.webkit?.messageHandlers?.modal) {
+ return window.webkit.messageHandlers.modal.postMessage({ action: 'sheet', name: 'revisions', detent: 'medium' })
+ }
+
this.closeDropdown()
this.loadRevisionsPromise ||= this.baseFetch(`/templates/${this.template.id}/versions`)
@@ -2212,6 +2211,13 @@ export default {
this.loadRevisionsPromise = null
})
},
+ openDocumentsEditor (item) {
+ if (window.webkit?.messageHandlers?.modal) {
+ window.webkit.messageHandlers.modal.postMessage({ action: 'sheet', name: 'documents', viewport: 'fixed', uuid: item.attachment_uuid })
+ } else {
+ this.editModalDocumentUuid = item.attachment_uuid
+ }
+ },
onApplyRevisionEvent (e) {
this.onRevisionApply(e.detail)
},
@@ -2427,7 +2433,7 @@ export default {
window.webkit?.messageHandlers?.modal?.postMessage({ action: 'dispatch', name, detail: JSON.stringify(detail), dismiss: dismiss ? 'true' : 'false' })
},
onDrawerScrollTo (item) {
- if (this.isNativeView) {
+ if (this.nativeView) {
this.postNativeEvent('template-builder:scroll-to', { attachment_uuid: item.attachment_uuid }, true)
} else {
this.isDocumentsDrawerOpen = false
@@ -2435,7 +2441,7 @@ export default {
}
},
onDrawerSetDraw ({ field, option }) {
- if (this.isNativeView) {
+ if (this.nativeView) {
this.postNativeEvent('template-builder:draw-field', { uuid: field.uuid, option }, true)
} else {
this.isFieldsDrawerOpen = false
@@ -2444,15 +2450,31 @@ export default {
}
},
onDrawerSetDrawType (type) {
- if (this.isNativeView) {
+ if (this.nativeView) {
this.postNativeEvent('template-builder:draw-field', { type }, true)
} else {
this.isFieldsDrawerOpen = false
this.startFieldDraw({ type })
}
},
+ onDrawerSelectSubmitter (submitter) {
+ this.selectedSubmitter = submitter
+
+ if (this.nativeView) {
+ this.$nextTick(() => this.postNativeEvent('template-builder:select-submitter', { uuid: submitter.uuid }))
+ }
+ },
+ onDrawerSetDrawCustomField (field) {
+ if (this.nativeView) {
+ this.postNativeEvent('template-builder:draw-field', { custom_field_uuid: field.uuid }, true)
+ } else {
+ this.isFieldsDrawerOpen = false
+ this.drawCustomField = field
+ this.showDrawField = true
+ }
+ },
onDrawerScrollToArea (area) {
- if (this.isNativeView) {
+ if (this.nativeView) {
const field = this.template.fields.find((f) => (f.areas || []).includes(area))
this.postNativeEvent('template-builder:select-field', { uuid: field?.uuid, attachment_uuid: area.attachment_uuid, page: area.page, x: area.x, y: area.y }, true)
@@ -2465,7 +2487,7 @@ export default {
const { schema, documents, fields, submitters } = this.template
const detail = JSON.stringify({ schema, documents, fields, submitters })
- if (this.isNativeView) {
+ if (this.nativeView) {
window.webkit?.messageHandlers?.modal?.postMessage({ action: 'dispatch', name: 'template-builder:sync', detail, dismiss: 'false' })
} else {
window.webkit?.messageHandlers?.native?.postMessage({ type: 'drawer', op: 'sync', detail })
diff --git a/app/javascript/template_builder/conditions_modal.vue b/app/javascript/template_builder/conditions_modal.vue
index 0b17affc0..6ba5fe8cb 100644
--- a/app/javascript/template_builder/conditions_modal.vue
+++ b/app/javascript/template_builder/conditions_modal.vue
@@ -1,13 +1,17 @@
-
-
+
+
{{ t('condition') }} - {{ (defaultField ? (defaultField.title || item.title || item.name) : item.name) || buildDefaultName(item) }}
@@ -183,6 +187,11 @@ export default {
type: Array,
required: false,
default: () => []
+ },
+ inline: {
+ type: Boolean,
+ required: false,
+ default: false
}
},
emits: ['close', 'save'],
diff --git a/app/javascript/template_builder/custom_field.vue b/app/javascript/template_builder/custom_field.vue
index 213172612..80322d4a4 100644
--- a/app/javascript/template_builder/custom_field.vue
+++ b/app/javascript/template_builder/custom_field.vue
@@ -49,8 +49,8 @@
:field="field"
:with-condition="false"
:with-force-open="false"
- @click-description="isShowDescriptionModal = true"
- @click-formula="isShowFormulaModal = true"
+ @click-description="openDescriptionModal"
+ @click-formula="openFormulaModal"
/>
+
@@ -85,11 +93,27 @@
:with-areas="false"
:with-copy-to-all-pages="false"
:with-condition="false"
- @click-formula="isShowFormulaModal = true"
- @click-font="isShowFontModal = true"
- @click-description="isShowDescriptionModal = true"
+ @click-formula="openFormulaModal"
+ @click-font="openFontModal"
+ @click-description="openDescriptionModal"
@save="$emit('save')"
/>
+
+
+
+ {{ t('remove') }}
+
+
@@ -164,7 +189,7 @@ import FieldSettings from './field_settings'
import FormulaModal from './formula_modal'
import FontModal from './font_modal'
import DescriptionModal from './description_modal'
-import { IconTrashX, IconSettings, IconCheck } from '@tabler/icons-vue'
+import { IconTrashX, IconSettings, IconCheck, IconDotsVertical } from '@tabler/icons-vue'
import IconDrag from './icon_drag'
export default {
@@ -172,6 +197,7 @@ export default {
components: {
Contenteditable,
IconSettings,
+ IconDotsVertical,
IconCheck,
FieldSettings,
PaymentSettings,
@@ -182,7 +208,7 @@ export default {
IconTrashX,
FieldType
},
- inject: ['backgroundColor', 't'],
+ inject: ['backgroundColor', 't', 'isMobile'],
props: {
field: {
type: Object,
@@ -217,6 +243,9 @@ export default {
dropdownBgColor () {
return ['', null, 'transparent'].includes(this.backgroundColor) ? 'white' : this.backgroundColor
},
+ iconColorClass () {
+ return this.isMobile ? 'text-base-content' : 'text-transparent group-hover:text-base-content'
+ },
modalContainerEl () {
return this.$el.getRootNode().querySelector('#docuseal_modal_container')
}
@@ -230,6 +259,27 @@ export default {
}
},
methods: {
+ openFormulaModal () {
+ if (window.webkit?.messageHandlers?.modal) {
+ window.webkit.messageHandlers.modal.postMessage({ action: 'sheet', name: 'formula', custom_field_uuid: this.field.uuid })
+ } else {
+ this.isShowFormulaModal = true
+ }
+ },
+ openFontModal () {
+ if (window.webkit?.messageHandlers?.modal) {
+ window.webkit.messageHandlers.modal.postMessage({ action: 'sheet', name: 'font', detent: 'medium', custom_field_uuid: this.field.uuid })
+ } else {
+ this.isShowFontModal = true
+ }
+ },
+ openDescriptionModal () {
+ if (window.webkit?.messageHandlers?.modal) {
+ window.webkit.messageHandlers.modal.postMessage({ action: 'sheet', name: 'description', custom_field_uuid: this.field.uuid })
+ } else {
+ this.isShowDescriptionModal = true
+ }
+ },
buildDefaultName () {
return this.t('custom')
},
diff --git a/app/javascript/template_builder/description_modal.vue b/app/javascript/template_builder/description_modal.vue
index b387733f3..14bd31c57 100644
--- a/app/javascript/template_builder/description_modal.vue
+++ b/app/javascript/template_builder/description_modal.vue
@@ -1,13 +1,17 @@
-
-
+
+
{{ (defaultField ? (defaultField.title || field.title || field.name) : field.name) || buildDefaultName(field) }}
@@ -86,6 +90,11 @@ export default {
buildDefaultName: {
type: Function,
required: true
+ },
+ inline: {
+ type: Boolean,
+ required: false,
+ default: false
}
},
emits: ['close', 'save'],
diff --git a/app/javascript/template_builder/documents_editor_crop.vue b/app/javascript/template_builder/documents_editor_crop.vue
index 0df5f4faa..27998abb7 100644
--- a/app/javascript/template_builder/documents_editor_crop.vue
+++ b/app/javascript/template_builder/documents_editor_crop.vue
@@ -49,7 +49,7 @@
@@ -64,7 +64,7 @@
{{ t('crop_and_scan') }}
@@ -74,17 +74,15 @@
/>
-
+
{{ t('crop') }}
diff --git a/app/javascript/template_builder/documents_editor_modal.vue b/app/javascript/template_builder/documents_editor_modal.vue
index 1682bcdb5..6d243032b 100644
--- a/app/javascript/template_builder/documents_editor_modal.vue
+++ b/app/javascript/template_builder/documents_editor_modal.vue
@@ -1,19 +1,23 @@
-
+
{{ doc.name }}
@@ -174,7 +182,7 @@
@@ -183,6 +191,7 @@
:key="page.id"
:data-page-id="page.id"
class="relative min-w-0 cursor-pointer group"
+ :style="inline ? { scrollMarginTop: '86px' } : null"
draggable="true"
@click="selectedPageId = page.id"
@mousedown="onPageMousedown(page, $event)"
@@ -250,7 +259,8 @@
-
+
{{ t('crop') }}
@@ -441,6 +449,11 @@ export default {
type: String,
required: false,
default: 'image/*, application/pdf'
+ },
+ inline: {
+ type: Boolean,
+ required: false,
+ default: false
}
},
emits: ['close', 'saved'],
@@ -698,12 +711,14 @@ export default {
if (targetIndex < 0) {
if (docIndex > 0) {
this.layout[docIndex - 1].pages.push(page)
+ this.collapsedDocumentsIndex[this.layout[docIndex - 1].attachmentUuid] = false
} else {
this.layout[docIndex].pages.unshift(page)
}
} else if (targetIndex > this.layout[docIndex].pages.length) {
if (docIndex < this.layout.length - 1) {
this.layout[docIndex + 1].pages.unshift(page)
+ this.collapsedDocumentsIndex[this.layout[docIndex + 1].attachmentUuid] = false
} else {
this.layout[docIndex].pages.push(page)
}
@@ -712,6 +727,12 @@ export default {
}
this.pushUndo()
+ this.scrollIntoList(`[data-page-id="${page.id}"]`)
+ },
+ scrollIntoList (selector) {
+ this.$nextTick(() => {
+ this.$el.querySelector(selector)?.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
+ })
},
removePage () {
const position = this.selectedPosition
@@ -737,6 +758,7 @@ export default {
this.layout.splice(docIndex + direction, 0, doc)
this.pushUndo()
+ this.scrollIntoList(`[data-uuid="${doc.attachmentUuid}"]`)
},
mergeDocument (docIndex, direction) {
const doc = this.layout[docIndex]
diff --git a/app/javascript/template_builder/field.vue b/app/javascript/template_builder/field.vue
index 85f219708..7e645e79d 100644
--- a/app/javascript/template_builder/field.vue
+++ b/app/javascript/template_builder/field.vue
@@ -74,7 +74,7 @@
class="relative cursor-pointer"
:class="iconColorClass"
:title="t('formula')"
- @click="isShowFormulaModal = true"
+ @click="openFormulaModal"
>
-
+
@@ -237,7 +241,7 @@ export default {
IconTrashX,
IconChevronUp
},
- inject: ['t', 'save'],
+ inject: ['t', 'save', 'isMobile'],
props: {
submitters: {
type: Array,
diff --git a/app/javascript/template_builder/fields.vue b/app/javascript/template_builder/fields.vue
index e10745025..aa791ca97 100644
--- a/app/javascript/template_builder/fields.vue
+++ b/app/javascript/template_builder/fields.vue
@@ -172,6 +172,7 @@
@click="setFieldsTab('custom')"
>{{ t('custom') }}
+
+
+
+
+
+ {{ t('add_fields') }}
+
+
+
diff --git a/app/javascript/template_builder/page.vue b/app/javascript/template_builder/page.vue
index 1248cfe7f..5c16362c5 100644
--- a/app/javascript/template_builder/page.vue
+++ b/app/javascript/template_builder/page.vue
@@ -1,7 +1,7 @@
@@ -115,11 +115,11 @@
/>
@@ -295,6 +295,13 @@ export default {
}
},
methods: {
+ openConditionsModal () {
+ if (window.webkit?.messageHandlers?.modal) {
+ window.webkit.messageHandlers.modal.postMessage({ action: 'sheet', name: 'conditions', detent: 'medium', attachment_uuid: this.item.attachment_uuid })
+ } else {
+ this.isShowConditionsModal = true
+ }
+ },
upload: Upload.methods.upload,
buildDefaultName: Field.methods.buildDefaultName,
closeDropdown () {
diff --git a/app/javascript/template_builder/revisions_modal.vue b/app/javascript/template_builder/revisions_modal.vue
index f2c1cee10..ffe33ac3b 100644
--- a/app/javascript/template_builder/revisions_modal.vue
+++ b/app/javascript/template_builder/revisions_modal.vue
@@ -5,7 +5,7 @@
class="absolute top-0 bottom-0 right-0 left-0"
@click.prevent="$emit('close')"
/>
-
+
<% if local_assigns[:with_template] %>
<% template = submission.template %>
-
+
<%= svg_icon('file_text', class: 'w-4 h-4 mt-0.5 flex-shrink-0') %>
diff --git a/app/views/templates/_title.html.erb b/app/views/templates/_title.html.erb
index 7f3f80339..aa1567a25 100644
--- a/app/views/templates/_title.html.erb
+++ b/app/views/templates/_title.html.erb
@@ -115,6 +115,11 @@
<% end %>
+ <% if !template.archived_at? && can?(:update, template) && @template.account_id == current_account.id %>
+
+
+
+ <% end %>
<% if can?(:destroy, template) %>
<% if template.archived_at? %>
diff --git a/app/views/templates/edit.html.erb b/app/views/templates/edit.html.erb
index 4f04aaf91..6c527eb21 100644
--- a/app/views/templates/edit.html.erb
+++ b/app/views/templates/edit.html.erb
@@ -1,4 +1,4 @@
-<% content_for(:native_title, params[:view] == 'revisions' ? t('revisions') : @template.name) if turbo_native_app? %>
+<% content_for(:native_title, @template.name) if turbo_native_app? %>
<% if !turbo_native_app? && (current_user.created_at > 2.weeks.ago || params[:tour] == 'true') %>
<% user_config = current_user.user_configs.find_or_initialize_by(key: UserConfig::SHOW_APP_TOUR) %>
<% @show_tour_start_form = user_config.new_record? && !params.key?(:tour) %>
@@ -10,4 +10,4 @@
<% account_configs = current_account.account_configs.where(key: [AccountConfig::TEMPLATE_CUSTOM_FIELDS_KEY, AccountConfig::TEMPLATE_DATE_FORMATS_KEY]).to_a %>
<% custom_fields = account_configs.find { |e| e.key == AccountConfig::TEMPLATE_CUSTOM_FIELDS_KEY }&.value || [] %>
<% date_formats = account_configs.find { |e| e.key == AccountConfig::TEMPLATE_DATE_FORMATS_KEY }&.value || [] %>
-
+