make template editor header native

This commit is contained in:
Alex Turchyn authored and Pete Matsyburka committed 2026-09-23 10:59:54 +03:00
1 parent 9abdb68bc5
commit d36f40cafd
4 files changed
+188 -136

No files matched your search

+2 -1
View File
@@ -209,7 +209,8 @@ safeRegisterElement('template-builder', class extends HTMLElement {
withDownload: true, withDownload: true,
currencies: (this.dataset.currencies || '').split(',').filter(Boolean), currencies: (this.dataset.currencies || '').split(',').filter(Boolean),
acceptFileTypes: this.dataset.acceptFileTypes, acceptFileTypes: this.dataset.acceptFileTypes,
showTourStartForm: this.dataset.showTourStartForm === 'true' showTourStartForm: this.dataset.showTourStartForm === 'true',
nativeApp: this.dataset.nativeApp === 'true'
}) })
this.component = this.app.mount(this.appElem) this.component = this.app.mount(this.appElem)
+176 -125
View File
@@ -79,7 +79,7 @@
v-if="$slots.buttons || withTitle" v-if="$slots.buttons || withTitle"
id="title_container" id="title_container"
class="flex justify-between py-1.5 items-center pr-4 top-0 z-10 title-container" class="flex justify-between py-1.5 items-center pr-4 top-0 z-10 title-container"
:class="{ sticky: withStickySubmitters || isBreakpointLg }" :class="{ sticky: withStickySubmitters || isBreakpointLg, hidden: nativeApp }"
:style="{ backgroundColor }" :style="{ backgroundColor }"
> >
<div class="flex items-center space-x-3"> <div class="flex items-center space-x-3">
@@ -104,30 +104,56 @@
name="buttons" name="buttons"
/> />
<template v-else> <template v-else>
<form <native-action
v-if="withSignYourselfButton && undefinedSubmitters.length < 2 && (!template.variables_schema || Object.keys(template.variables_schema).length === 0)" v-if="withSignYourselfButton && undefinedSubmitters.length < 2 && (!template.variables_schema || Object.keys(template.variables_schema).length === 0)"
target="_blank" :data-label="t('sign_yourself')"
data-turbo="false" data-icon="writing"
class="inline"
method="post"
:action="`/start_form_self/${template.id}`"
@submit="maybeShowErrorTemplateAlert"
> >
<input <form
type="hidden" target="_blank"
name="_method" data-turbo="false"
value="put" class="inline"
autocomplete="off" method="post"
:action="`/start_form_self/${template.id}`"
@submit="maybeShowErrorTemplateAlert"
> >
<input <input
type="hidden" type="hidden"
name="authenticity_token" name="_method"
:value="authenticityToken" value="put"
autocomplete="off" autocomplete="off"
> >
<button <input
type="hidden"
name="authenticity_token"
:value="authenticityToken"
autocomplete="off"
>
<button
class="btn btn-primary btn-ghost text-base hidden md:flex"
type="submit"
>
<IconWritingSign
width="22"
class="inline"
/>
<span class="hidden md:inline">
{{ t('sign_yourself') }}
</span>
</button>
</form>
</native-action>
<native-action
v-else-if="withSignYourselfButton"
:data-label="t('sign_yourself')"
data-icon="writing"
>
<a
id="sign_yourself_button"
:href="`/templates/${template.id}/submissions/new?selfsign=true`"
class="btn btn-primary btn-ghost text-base hidden md:flex" class="btn btn-primary btn-ghost text-base hidden md:flex"
type="submit" data-turbo-frame="modal"
@click="maybeShowErrorTemplateAlert"
> >
<IconWritingSign <IconWritingSign
width="22" width="22"
@@ -136,64 +162,60 @@
<span class="hidden md:inline"> <span class="hidden md:inline">
{{ t('sign_yourself') }} {{ t('sign_yourself') }}
</span> </span>
</button> </a>
</form> </native-action>
<a <native-action
v-else-if="withSignYourselfButton"
id="sign_yourself_button"
:href="`/templates/${template.id}/submissions/new?selfsign=true`"
class="btn btn-primary btn-ghost text-base hidden md:flex"
data-turbo-frame="modal"
@click="maybeShowErrorTemplateAlert"
>
<IconWritingSign
width="22"
class="inline"
/>
<span class="hidden md:inline">
{{ t('sign_yourself') }}
</span>
</a>
<a
v-if="withSendButton" v-if="withSendButton"
id="send_button" :data-label="t('send')"
:href="`/templates/${template.id}/submissions/new?with_link=true`" data-icon="users_plus"
:data-turbo-frame="isMobile && isBreakpointLg ? '_top' : 'modal'" data-placement="bar"
class="white-button md:!px-6"
@click="maybeShowErrorTemplateAlert"
> >
<IconUsersPlus <a
width="20" id="send_button"
class="inline" :href="`/templates/${template.id}/submissions/new?with_link=true`"
/> :data-turbo-frame="!nativeApp && isMobile && isBreakpointLg ? '_top' : 'modal'"
<span class="hidden md:inline"> class="white-button md:!px-6"
{{ t('send') }} @click="maybeShowErrorTemplateAlert"
</span> >
</a> <IconUsersPlus
width="20"
class="inline"
/>
<span class="hidden md:inline">
{{ t('send') }}
</span>
</a>
</native-action>
<span <span
v-if="editable" v-if="editable"
id="save_button_container" id="save_button_container"
class="flex" class="flex"
> >
<button <native-action
class="base-button !rounded-r-none !pr-2" :data-label="t('save')"
:class="{ disabled: isSaving }" data-icon="device_floppy"
v-bind="isSaving ? { disabled: true } : {}" data-placement="bar"
@click.prevent="onSaveClick"
> >
<IconInnerShadowTop <button
v-if="isSaving" class="base-button !rounded-r-none !pr-2"
width="22" :class="{ disabled: isSaving }"
class="animate-spin" v-bind="isSaving ? { disabled: true } : {}"
/> @click.prevent="onSaveClick"
<IconDeviceFloppy >
v-else <IconInnerShadowTop
width="22" v-if="isSaving"
/> width="22"
<span class="hidden md:inline"> class="animate-spin"
{{ t('save') }} />
</span> <IconDeviceFloppy
</button> v-else
width="22"
/>
<span class="hidden md:inline">
{{ t('save') }}
</span>
</button>
</native-action>
<div <div
class="dropdown dropdown-end" class="dropdown dropdown-end"
:class="{ 'dropdown-open': isDownloading || isPreviewLoading }" :class="{ 'dropdown-open': isDownloading || isPreviewLoading }"
@@ -211,69 +233,89 @@
class="dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box text-right" class="dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box text-right"
> >
<li> <li>
<a <native-action
:href="`/templates/${template.id}/form`" :data-label="t('save_and_preview')"
data-turbo="false" data-icon="eye"
class="flex items-center justify-center space-x-2"
@click.exact="isPreviewLoading = true"
> >
<IconInnerShadowTop <a
v-if="isPreviewLoading" :href="`/templates/${template.id}/form`"
class="animate-spin w-6 h-6 flex-shrink-0" data-turbo="false"
/> class="flex items-center justify-center space-x-2"
<IconEye @click.exact="isPreviewLoading = true"
v-else >
class="w-6 h-6 flex-shrink-0" <IconInnerShadowTop
/> v-if="isPreviewLoading"
<span class="whitespace-nowrap">{{ t('save_and_preview') }}</span> class="animate-spin w-6 h-6 flex-shrink-0"
</a> />
<IconEye
v-else
class="w-6 h-6 flex-shrink-0"
/>
<span class="whitespace-nowrap">{{ t('save_and_preview') }}</span>
</a>
</native-action>
</li> </li>
<li> <li>
<a <native-action
:href="`/templates/${template.id}/preferences`" :data-label="t('preferences')"
:data-turbo-frame="isMobile && isBreakpointLg ? '_top' : 'modal'" data-icon="adjustments_horizontal"
class="flex space-x-2"
@click="closeDropdown"
> >
<IconAdjustments class="w-6 h-6 flex-shrink-0" /> <a
<span class="whitespace-nowrap">{{ t('preferences') }}</span> :href="`/templates/${template.id}/preferences`"
</a> :data-turbo-frame="!nativeApp && isMobile && isBreakpointLg ? '_top' : 'modal'"
class="flex space-x-2"
@click="closeDropdown"
>
<IconAdjustments class="w-6 h-6 flex-shrink-0" />
<span class="whitespace-nowrap">{{ t('preferences') }}</span>
</a>
</native-action>
</li> </li>
<li v-if="withRevisionsMenu"> <li v-if="withRevisionsMenu">
<button <native-action
class="flex space-x-2" :data-label="t('revisions')"
@click.prevent="openRevisionsModal" data-icon="history"
@mouseenter="preloadRevisions"
> >
<span class="w-6 h-6 flex-shrink-0 flex items-center justify-center"> <button
<IconHistory class="w-5 h-5" /> class="flex space-x-2"
</span> @click.prevent="openRevisionsModal"
<span class="whitespace-nowrap">{{ t('revisions') }}</span> @mouseenter="preloadRevisions"
</button> >
<span class="w-6 h-6 flex-shrink-0 flex items-center justify-center">
<IconHistory class="w-5 h-5" />
</span>
<span class="whitespace-nowrap">{{ t('revisions') }}</span>
</button>
</native-action>
</li> </li>
<li v-if="withDownload"> <li v-if="withDownload">
<button <native-action
class="flex space-x-2" :data-label="t('download')"
:disabled="isDownloading" data-icon="download"
@click.stop.prevent="download"
> >
<IconInnerShadowTop <button
v-if="isDownloading" class="flex space-x-2"
class="animate-spin w-6 h-6 flex-shrink-0" :disabled="isDownloading"
/> @click.stop.prevent="download"
<IconDownload >
v-else <IconInnerShadowTop
class="w-6 h-6 flex-shrink-0" v-if="isDownloading"
/> class="animate-spin w-6 h-6 flex-shrink-0"
<span />
v-if="isDownloading" <IconDownload
class="whitespace-nowrap" v-else
>{{ t('downloading_') }}</span> class="w-6 h-6 flex-shrink-0"
<span />
v-else <span
class="whitespace-nowrap" v-if="isDownloading"
>{{ t('download') }}</span> class="whitespace-nowrap"
</button> >{{ t('downloading_') }}</span>
<span
v-else
class="whitespace-nowrap"
>{{ t('download') }}</span>
</button>
</native-action>
</li> </li>
</ul> </ul>
</div> </div>
@@ -297,7 +339,7 @@
<div <div
id="main_container" id="main_container"
class="flex main-container" class="flex main-container"
:class="$slots.buttons || withTitle ? (isMobile ? 'max-h-[calc(100%_-_60px)]' : 'md:max-h-[calc(100%_-_60px)]') : (isMobile ? 'max-h-[100%]' : 'md:max-h-[100%]')" :class="($slots.buttons || withTitle) && !nativeApp ? (isMobile ? 'max-h-[calc(100%_-_60px)]' : 'md:max-h-[calc(100%_-_60px)]') : (isMobile ? 'max-h-[100%]' : 'md:max-h-[100%]')"
> >
<div <div
v-if="withDocumentsList" v-if="withDocumentsList"
@@ -1008,6 +1050,11 @@ export default {
required: false, required: false,
default: true default: true
}, },
nativeApp: {
type: Boolean,
required: false,
default: false
},
withFieldsSearch: { withFieldsSearch: {
type: Boolean, type: Boolean,
required: false, required: false,
@@ -1665,6 +1712,10 @@ export default {
this.debouncedSave() this.debouncedSave()
}, },
download () { download () {
const download = window.webkit?.messageHandlers?.download
if (download) return download.postMessage({ src: `/templates/${this.template.id}/documents` })
this.isDownloading = true this.isDownloading = true
this.baseFetch(`/templates/${this.template.id}/documents`).then(async (response) => { this.baseFetch(`/templates/${this.template.id}/documents`).then(async (response) => {
+9 -9
View File
@@ -78,8 +78,8 @@
<% end %> <% end %>
<ul class="z-10 dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box text-right"> <ul class="z-10 dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box text-right">
<% if show_combined_download %> <% if show_combined_download %>
<native-action data-label="<%= t('download_combined_pdf') %>" data-icon="download"> <li>
<li> <native-action data-label="<%= t('download_combined_pdf') %>" data-icon="download">
<download-button role="button" tabindex="0" data-src="<%= local_assigns[:is_preview] ? (@sig_submitter ? submit_form_documents_path(@sig_submitter.slug, { sig: params[:sig], combined: true }.compact) : submissions_preview_download_index_path(@submission.slug, combined: true)) : submission_download_index_path(@submission, combined: true) %>" class="flex items-center"> <download-button role="button" tabindex="0" data-src="<%= local_assigns[:is_preview] ? (@sig_submitter ? submit_form_documents_path(@sig_submitter.slug, { sig: params[:sig], combined: true }.compact) : submissions_preview_download_index_path(@submission.slug, combined: true)) : submission_download_index_path(@submission, combined: true) %>" class="flex items-center">
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton"> <span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
<%= svg_icon('download', class: 'w-6 h-6 flex-shrink-0') %> <%= svg_icon('download', class: 'w-6 h-6 flex-shrink-0') %>
@@ -90,18 +90,18 @@
<span><%= t('downloading') %></span> <span><%= t('downloading') %></span>
</span> </span>
</download-button> </download-button>
</li> </native-action>
</native-action> </li>
<% end %> <% end %>
<% if show_unarchive %> <% if show_unarchive %>
<native-action data-label="<%= t('unarchive') %>" data-icon="rotate"> <li>
<li> <native-action data-label="<%= t('unarchive') %>" data-icon="rotate">
<button form="submission_unarchive_form"> <button form="submission_unarchive_form">
<%= button_title(title: t('unarchive'), disabled_with: t('unarchive'), icon: svg_icon('rotate', class: 'w-6 h-6 flex-shrink-0')) %> <%= button_title(title: t('unarchive'), disabled_with: t('unarchive'), icon: svg_icon('rotate', class: 'w-6 h-6 flex-shrink-0')) %>
</button> </button>
<%= button_to '', submission_unarchive_index_path(@submission), form: { id: 'submission_unarchive_form' }, form_class: 'hidden' %> </native-action>
</li> <%= button_to '', submission_unarchive_index_path(@submission), form: { id: 'submission_unarchive_form' }, form_class: 'hidden' %>
</native-action> </li>
<% end %> <% end %>
</ul> </ul>
</div> </div>
+1 -1
View File
@@ -10,4 +10,4 @@
<% account_configs = current_account.account_configs.where(key: [AccountConfig::TEMPLATE_CUSTOM_FIELDS_KEY, AccountConfig::TEMPLATE_DATE_FORMATS_KEY]).to_a %> <% 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 || [] %> <% 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 || [] %> <% date_formats = account_configs.find { |e| e.key == AccountConfig::TEMPLATE_DATE_FORMATS_KEY }&.value || [] %>
<template-builder class="grid" data-template="<%= @template_data.to_json %>" data-custom-fields="<%= custom_fields.to_json %>" data-date-formats="<%= date_formats.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 data-native-app="<%= turbo_native_app? %>" class="grid" data-template="<%= @template_data.to_json %>" data-custom-fields="<%= custom_fields.to_json %>" data-date-formats="<%= date_formats.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>