adjust native

This commit is contained in:
Pete Matsyburka committed 2026-09-23 11:00:17 +03:00
1 parent a86f896694
commit d35538e2ba
17 files changed
+121 -96

No files matched your search

+2 -53
View File
@@ -1,5 +1,5 @@
import '@hotwired/turbo-rails'
import './elements/native_bridge'
import './native'
import { createApp, reactive } from 'vue'
import TemplateBuilder from './template_builder/builder'
@@ -81,63 +81,12 @@ document.addEventListener('keyup', (e) => {
}
})
if (window.webkit?.messageHandlers?.native) {
const staleSnapshots = new Set()
let isRestoring = false
window.Turbo.session.view.clearSnapshotCache = function () {
this.snapshotCache.keys.forEach((key) => staleSnapshots.add(key))
}
document.addEventListener('turbo:submit-end', (e) => {
if (e.detail.success && !e.detail.formSubmission.isSafe) {
window.webkit.messageHandlers.native.postMessage({ type: 'stale' })
}
})
document.addEventListener('native:pull-to-refresh', (event) => {
const { session } = window.Turbo
const url = new URL(document.baseURI)
url.searchParams.delete('limit')
session.history.replace(url, session.history.restorationIdentifier)
session.view.lastRenderedLocation = url
event.detail.result = new Promise((resolve) => {
document.addEventListener('turbo:render', () => resolve(true), { once: true })
document.addEventListener('turbo:fetch-request-error', () => resolve(true), { once: true })
session.refresh(document.baseURI)
})
})
document.addEventListener('native:refresh', () => {
window.Turbo.session.refresh(document.baseURI)
})
document.addEventListener('native:turbo-stream', (e) => {
if (!e.detail.body?.trimStart().startsWith('<turbo-stream')) return
window.Turbo.renderStreamMessage(e.detail.body)
})
document.addEventListener('turbo:visit', (e) => {
isRestoring = e.detail.action === 'restore'
})
document.addEventListener('turbo:load', () => {
if (staleSnapshots.delete(window.location.href.split('#')[0]) && isRestoring && window.location.pathname !== '/search') {
setTimeout(() => window.Turbo.session.refresh(document.baseURI))
}
})
}
document.addEventListener('turbo:before-fetch-response', (event) => {
if (event.detail.fetchResponse.header('content-disposition')?.includes('attachment')) {
event.preventDefault()
}
})
document.addEventListener('turbo:submit-end', async (event) => {
const resp = event.detail?.formSubmission?.result?.fetchResponse?.response
+1 -3
View File
@@ -1,5 +1,3 @@
let counter = 0
export default class extends HTMLElement {
connectedCallback () {
const bridge = window.webkit?.messageHandlers?.native
@@ -10,7 +8,7 @@ export default class extends HTMLElement {
if (!target && !this.dataset.native) return
this.nativeId ||= `native-action-${++counter}`
this.nativeId ||= `native-action-${Math.random().toString(32).split('.')[1]}`
this.id = this.nativeId
if (target) target.dataset.nativeId = this.nativeId
+1 -3
View File
@@ -1,5 +1,3 @@
let counter = 0
export default class extends HTMLElement {
connectedCallback () {
if (window.webkit?.messageHandlers?.native) {
@@ -28,7 +26,7 @@ export default class extends HTMLElement {
const items = [...this.querySelectorAll('native-menu-action')].map((action) => {
const target = action.querySelector('a, button, [role="button"]') || action.firstElementChild
target.dataset.nativeId ||= `native-menu-${++counter}`
target.dataset.nativeId ||= `native-menu-${Math.random().toString(32).split('.')[1]}`
return {
id: target.dataset.nativeId,
+2 -1
View File
@@ -1,4 +1,4 @@
import './elements/native_bridge'
import './native'
import { createApp, reactive } from 'vue'
@@ -45,6 +45,7 @@ safeRegisterElement('submission-form', class extends HTMLElement {
withSignatureId: this.dataset.withSignatureId === 'true',
requireSigningReason: this.dataset.requireSigningReason === 'true',
withConfetti: this.dataset.withConfetti === 'true',
nativePlatform: this.dataset.nativePlatform,
withFieldLabels: this.dataset.withFieldLabels !== 'false',
withDisclosure: this.dataset.withDisclosure === 'true',
reuseSignature: this.dataset.reuseSignature !== 'false',
@@ -35,7 +35,7 @@ if (handlers?.native && window === window.top) {
reportPageFlash()
document.addEventListener('click', (event) => {
const link = event.target.closest?.('a[href]')
const link = event.target.closest('a[href]')
if (!link) return
@@ -52,7 +52,7 @@ if (handlers?.native && window === window.top) {
document.addEventListener('click', (event) => {
if (document.querySelector('native-modal')) return
const link = event.target.closest?.('a[data-turbo-frame="modal"], a[data-turbo-frame="drawer"]')
const link = event.target.closest('a[data-turbo-frame="modal"], a[data-turbo-frame="drawer"]')
if (!link?.href) return
@@ -72,3 +72,55 @@ if (handlers?.native && window === window.top) {
event.detail.result = rect?.height ? rect.bottom - document.documentElement.getBoundingClientRect().top : null
})
}
if (handlers?.native && window.Turbo) {
const staleSnapshots = new Set()
let isRestoring = false
window.Turbo.session.view.clearSnapshotCache = function () {
this.snapshotCache.keys.forEach((key) => staleSnapshots.add(key))
}
document.addEventListener('turbo:submit-end', (e) => {
if (e.detail.success && !e.detail.formSubmission.isSafe) {
handlers.native.postMessage({ type: 'stale' })
}
})
document.addEventListener('native:pull-to-refresh', (event) => {
const { session } = window.Turbo
const url = new URL(document.baseURI)
url.searchParams.delete('limit')
session.history.replace(url, session.history.restorationIdentifier)
session.view.lastRenderedLocation = url
event.detail.result = new Promise((resolve) => {
document.addEventListener('turbo:render', () => resolve(true), { once: true })
document.addEventListener('turbo:fetch-request-error', () => resolve(true), { once: true })
session.refresh(document.baseURI)
})
})
document.addEventListener('native:refresh', () => {
window.Turbo.session.refresh(document.baseURI)
})
document.addEventListener('native:turbo-stream', (e) => {
if (!e.detail.body?.trimStart().startsWith('<turbo-stream')) return
window.Turbo.renderStreamMessage(e.detail.body)
})
document.addEventListener('turbo:visit', (e) => {
isRestoring = e.detail.action === 'restore'
})
document.addEventListener('turbo:load', () => {
if (staleSnapshots.delete(window.location.href.split('#')[0]) && isRestoring && window.location.pathname !== '/search') {
setTimeout(() => window.Turbo.session.refresh(document.baseURI))
}
})
}
+2 -2
View File
@@ -126,7 +126,7 @@ export default {
IconLogin,
IconDownload
},
inject: ['baseUrl', 't'],
inject: ['baseUrl', 't', 'nativePlatform'],
props: {
submitterSlug: {
type: String,
@@ -196,7 +196,7 @@ export default {
},
computed: {
isNativeApp () {
return !!window.webkit?.messageHandlers?.download
return !!this.nativePlatform && !!window.webkit?.messageHandlers?.download
},
isWebView () {
return /webview|wv|ip((?!.*Safari)|(?=.*like Safari))/i.test(window.navigator.userAgent)
+9 -1
View File
@@ -774,6 +774,7 @@ export default {
baseUrl: this.baseUrl,
scrollIntoArea: this.scrollIntoArea,
scrollIntoField: this.scrollIntoField,
nativePlatform: this.nativePlatform,
t: this.t
}
},
@@ -909,6 +910,11 @@ export default {
required: false,
default: false
},
nativePlatform: {
type: String,
required: false,
default: ''
},
autoscrollFields: {
type: Boolean,
required: false,
@@ -1767,7 +1773,9 @@ export default {
this.isCompleted = true
this.isFormVisible = true
window.webkit?.messageHandlers?.native?.postMessage({ type: 'stale' })
if (this.nativePlatform) {
window.webkit?.messageHandlers?.native?.postMessage({ type: 'stale' })
}
if (resp?.text) {
const respData = await resp.text()
@@ -376,7 +376,7 @@ export default {
IconSignature,
IconArrowsDiagonalMinimize2
},
inject: ['baseUrl', 't'],
inject: ['baseUrl', 't', 'nativePlatform'],
props: {
field: {
type: Object,
@@ -580,10 +580,14 @@ export default {
},
methods: {
lockSwipe () {
window.webkit?.messageHandlers?.native?.postMessage({ type: 'swipe', op: 'lock' })
if (this.nativePlatform) {
window.webkit?.messageHandlers?.native?.postMessage({ type: 'swipe', op: 'lock' })
}
},
unlockSwipe () {
window.webkit?.messageHandlers?.native?.postMessage({ type: 'swipe', op: 'unlock' })
if (this.nativePlatform) {
window.webkit?.messageHandlers?.native?.postMessage({ type: 'swipe', op: 'unlock' })
}
},
setCanvasSize () {
const canvas = this.$refs.canvas
@@ -272,7 +272,7 @@ export default {
FieldSubmitter,
IconX
},
inject: ['t', 'isMobile'],
inject: ['t', 'isMobile', 'nativePlatform'],
props: {
template: {
type: Object,
@@ -387,28 +387,28 @@ export default {
},
methods: {
openFormulaModal () {
if (window.webkit?.messageHandlers?.modal) {
if (this.nativePlatform && window.webkit?.messageHandlers?.modal) {
window.webkit.messageHandlers.modal.postMessage({ action: 'sheet', name: 'formula', detent: 'medium', uuid: this.field.uuid })
} else {
this.isShowFormulaModal = true
}
},
openFontModal () {
if (window.webkit?.messageHandlers?.modal) {
if (this.nativePlatform && window.webkit?.messageHandlers?.modal) {
window.webkit.messageHandlers.modal.postMessage({ action: 'sheet', name: 'font', detent: 'medium', uuid: this.field.uuid })
} else {
this.isShowFontModal = true
}
},
openConditionsModal () {
if (window.webkit?.messageHandlers?.modal) {
if (this.nativePlatform && window.webkit?.messageHandlers?.modal) {
window.webkit.messageHandlers.modal.postMessage({ action: 'sheet', name: 'conditions', detent: 'medium', uuid: this.field.uuid })
} else {
this.isShowConditionsModal = true
}
},
openDescriptionModal () {
if (window.webkit?.messageHandlers?.modal) {
if (this.nativePlatform && window.webkit?.messageHandlers?.modal) {
window.webkit.messageHandlers.modal.postMessage({ action: 'sheet', name: 'description', detent: 'medium', uuid: this.field.uuid })
} else {
this.isShowDescriptionModal = true
+6 -4
View File
@@ -1929,7 +1929,7 @@ export default {
download () {
const download = window.webkit?.messageHandlers?.download
if (download) return download.postMessage({ src: `/templates/${this.template.id}/documents` })
if (this.nativePlatform && download) return download.postMessage({ src: `/templates/${this.template.id}/documents` })
this.isDownloading = true
@@ -2203,7 +2203,7 @@ export default {
this.loadRevisionsPromise ||= this.baseFetch(`/templates/${this.template.id}/versions`)
},
openRevisionsModal () {
if (window.webkit?.messageHandlers?.modal) {
if (this.nativePlatform && window.webkit?.messageHandlers?.modal) {
return window.webkit.messageHandlers.modal.postMessage({ action: 'sheet', name: 'revisions', detent: 'medium' })
}
@@ -2220,7 +2220,7 @@ export default {
})
},
openDocumentsEditor (item) {
if (window.webkit?.messageHandlers?.modal) {
if (this.nativePlatform && 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
@@ -2438,7 +2438,9 @@ export default {
ref?.$el.scrollIntoView({ behavior: 'smooth', block: 'start' })
},
postNativeEvent (name, detail, dismiss = false) {
window.webkit?.messageHandlers?.modal?.postMessage({ action: 'dispatch', name, detail: JSON.stringify(detail), dismiss: dismiss ? 'true' : 'false' })
if (this.nativePlatform) {
window.webkit?.messageHandlers?.modal?.postMessage({ action: 'dispatch', name, detail: JSON.stringify(detail), dismiss: dismiss ? 'true' : 'false' })
}
},
onDrawerScrollTo (item) {
if (this.nativeView) {
@@ -209,7 +209,7 @@ export default {
IconTrashX,
FieldType
},
inject: ['backgroundColor', 't', 'isMobile'],
inject: ['backgroundColor', 't', 'isMobile', 'nativePlatform'],
props: {
field: {
type: Object,
@@ -261,21 +261,21 @@ export default {
},
methods: {
openFormulaModal () {
if (window.webkit?.messageHandlers?.modal) {
if (this.nativePlatform && window.webkit?.messageHandlers?.modal) {
window.webkit.messageHandlers.modal.postMessage({ action: 'sheet', name: 'formula', detent: 'medium', custom_field_uuid: this.field.uuid })
} else {
this.isShowFormulaModal = true
}
},
openFontModal () {
if (window.webkit?.messageHandlers?.modal) {
if (this.nativePlatform && 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) {
if (this.nativePlatform && window.webkit?.messageHandlers?.modal) {
window.webkit.messageHandlers.modal.postMessage({ action: 'sheet', name: 'description', detent: 'medium', custom_field_uuid: this.field.uuid })
} else {
this.isShowDescriptionModal = true
+7 -2
View File
@@ -38,7 +38,12 @@
v-if="withDescription"
class="text-sm"
>
<span class="font-medium">{{ t('click_to_upload') }}</span> {{ t('or_drag_and_drop_files') }}
<template v-if="isMobile">
{{ t('click_to_upload') }}
</template>
<template v-else>
<span class="font-medium">{{ t('click_to_upload') }}</span> {{ t('or_drag_and_drop_files') }}
</template>
</div>
<button
v-if="withGoogleDrive"
@@ -83,7 +88,7 @@ export default {
IconFiles,
IconBrandGoogleDrive
},
inject: ['baseFetch', 't'],
inject: ['baseFetch', 't', 'isMobile'],
props: {
templateId: {
type: [Number, String],
+5 -5
View File
@@ -424,7 +424,7 @@ export default {
IconMathFunction,
FieldType
},
inject: ['template', 'backgroundColor', 'selectedAreasRef', 't', 'locale', 'getFieldTypeIndex', 'dateFormats', 'isMobile'],
inject: ['template', 'backgroundColor', 'selectedAreasRef', 't', 'locale', 'getFieldTypeIndex', 'dateFormats', 'isMobile', 'nativePlatform'],
props: {
field: {
type: Object,
@@ -559,28 +559,28 @@ export default {
this.$emit('save')
},
openFormulaModal () {
if (window.webkit?.messageHandlers?.modal) {
if (this.nativePlatform && window.webkit?.messageHandlers?.modal) {
window.webkit.messageHandlers.modal.postMessage({ action: 'sheet', name: 'formula', detent: 'medium', uuid: this.field.uuid })
} else {
this.isShowFormulaModal = true
}
},
openFontModal () {
if (window.webkit?.messageHandlers?.modal) {
if (this.nativePlatform && window.webkit?.messageHandlers?.modal) {
window.webkit.messageHandlers.modal.postMessage({ action: 'sheet', name: 'font', detent: 'medium', uuid: this.field.uuid })
} else {
this.isShowFontModal = true
}
},
openConditionsModal () {
if (window.webkit?.messageHandlers?.modal) {
if (this.nativePlatform && window.webkit?.messageHandlers?.modal) {
window.webkit.messageHandlers.modal.postMessage({ action: 'sheet', name: 'conditions', detent: 'medium', uuid: this.field.uuid })
} else {
this.isShowConditionsModal = true
}
},
openDescriptionModal () {
if (window.webkit?.messageHandlers?.modal) {
if (this.nativePlatform && window.webkit?.messageHandlers?.modal) {
window.webkit.messageHandlers.modal.postMessage({ action: 'sheet', name: 'description', detent: 'medium', uuid: this.field.uuid })
} else {
this.isShowDescriptionModal = true
+3 -1
View File
@@ -874,7 +874,9 @@ export default {
this.save()
window.webkit?.messageHandlers?.flash?.postMessage({ style: 'notice', message: this.t('fields_detected').replace('{count}', (data.fields || fields).length) })
if (this.nativePlatform) {
window.webkit?.messageHandlers?.flash?.postMessage({ style: 'notice', message: this.t('fields_detected').replace('{count}', (data.fields || fields).length) })
}
break
} else if (data.fields) {
@@ -93,7 +93,7 @@ export default {
RevisionsModal,
IconInnerShadowTop
},
inject: ['t', 'template', 'locale', 'baseFetch', 'getFieldTypeIndex', 'save'],
inject: ['t', 'template', 'locale', 'baseFetch', 'getFieldTypeIndex', 'save', 'nativePlatform'],
props: {
defaultFields: {
type: Array,
@@ -203,7 +203,9 @@ export default {
document.body.style.overflow = ''
},
close () {
window.webkit?.messageHandlers?.modal?.postMessage({ action: 'closeSheet' })
if (this.nativePlatform) {
window.webkit?.messageHandlers?.modal?.postMessage({ action: 'closeSheet' })
}
},
onSave () {
if (this.modal?.custom_field_uuid) {
@@ -222,7 +224,9 @@ export default {
this.customFields.splice(0, this.customFields.length, ...fields)
window.webkit?.messageHandlers?.modal?.postMessage({ action: 'dispatch', name: 'template-builder:sync-custom-fields', detail: JSON.stringify(fields), dismiss: 'false' })
if (this.nativePlatform) {
window.webkit?.messageHandlers?.modal?.postMessage({ action: 'dispatch', name: 'template-builder:sync-custom-fields', detail: JSON.stringify(fields), dismiss: 'false' })
}
})
},
loadRevisions () {
@@ -233,7 +237,9 @@ export default {
})
},
applyRevision (revision) {
window.webkit?.messageHandlers?.modal?.postMessage({ action: 'dispatch', name: 'template-builder:apply-revision', detail: JSON.stringify(revision), dismiss: 'true' })
if (this.nativePlatform) {
window.webkit?.messageHandlers?.modal?.postMessage({ action: 'dispatch', name: 'template-builder:apply-revision', detail: JSON.stringify(revision), dismiss: 'true' })
}
},
onDocumentsSaved (data) {
this.$emit('documents-modified', data)
+2 -2
View File
@@ -211,7 +211,7 @@ export default {
IconBolt,
IconPencil
},
inject: ['t', 'getFieldTypeIndex', 'baseFetch', 'isMobile'],
inject: ['t', 'getFieldTypeIndex', 'baseFetch', 'isMobile', 'nativePlatform'],
props: {
item: {
type: Object,
@@ -290,7 +290,7 @@ export default {
},
methods: {
openConditionsModal () {
if (window.webkit?.messageHandlers?.modal) {
if (this.nativePlatform && 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
@@ -2,4 +2,4 @@
<% data_fields = Submissions.filtered_conditions_fields(submitter).to_json %>
<% invite_submitters = (submitter.submission.template_submitters || submitter.submission.template.submitters).select { |s| s['invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] } }.to_json %>
<% optional_invite_submitters = (submitter.submission.template_submitters || submitter.submission.template.submitters).select { |s| s['optional_invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] } }.to_json %>
<submission-form data-attribution="<%= !turbo_native_app? %>" data-is-demo="<%= Docuseal.demo? %>" data-schema="<%= schema.to_json %>" data-reuse-signature="<%= configs[:reuse_signature] %>" data-require-signing-reason="<%= configs[:require_signing_reason] %>" data-with-signature-id="<%= configs[:with_signature_id] %>" data-with-field-labels="<%= configs[:with_field_labels] %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'].presence || submitter.submission.template&.preferences&.dig('completed_redirect_url') %>" data-completed-message="<%= (configs[:completed_message]&.compact_blank.presence || submitter.submission.template&.preferences&.dig('completed_message') || {}).to_json %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.preferences.key?('go_to_last') ? submitter.preferences['go_to_last'] : submitter.opened_at? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Docuseal.multitenant? || Accounts.can_send_emails?(submitter.submission.account) %>" data-optional-invite-submitters="<%= optional_invite_submitters %>" data-invite-submitters="<%= invite_submitters %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>" data-signature-text="<%= params[:signature] %>" data-previous-signature-value="<%= local_assigns[:signature_attachment]&.uuid %>" data-remember-signature="<%= configs[:prefill_signature] %>" data-dry-run="<%= local_assigns[:dry_run] %>" data-expand="<%= local_assigns[:expand] %>" data-scroll-padding="<%= local_assigns[:scroll_padding] %>" data-language="<%= I18n.locale.to_s.split('-').first %>"></submission-form>
<submission-form data-native-platform="<%= turbo_native_ios? ? 'ios' : (turbo_native_android? ? 'android' : '') %>" data-attribution="<%= !turbo_native_app? %>" data-is-demo="<%= Docuseal.demo? %>" data-schema="<%= schema.to_json %>" data-reuse-signature="<%= configs[:reuse_signature] %>" data-require-signing-reason="<%= configs[:require_signing_reason] %>" data-with-signature-id="<%= configs[:with_signature_id] %>" data-with-field-labels="<%= configs[:with_field_labels] %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'].presence || submitter.submission.template&.preferences&.dig('completed_redirect_url') %>" data-completed-message="<%= (configs[:completed_message]&.compact_blank.presence || submitter.submission.template&.preferences&.dig('completed_message') || {}).to_json %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.preferences.key?('go_to_last') ? submitter.preferences['go_to_last'] : submitter.opened_at? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Docuseal.multitenant? || Accounts.can_send_emails?(submitter.submission.account) %>" data-optional-invite-submitters="<%= optional_invite_submitters %>" data-invite-submitters="<%= invite_submitters %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>" data-signature-text="<%= params[:signature] %>" data-previous-signature-value="<%= local_assigns[:signature_attachment]&.uuid %>" data-remember-signature="<%= configs[:prefill_signature] %>" data-dry-run="<%= local_assigns[:dry_run] %>" data-expand="<%= local_assigns[:expand] %>" data-scroll-padding="<%= local_assigns[:scroll_padding] %>" data-language="<%= I18n.locale.to_s.split('-').first %>"></submission-form>