mirror of
https://github.com/karatektus/pl_mail.git
synced 2026-10-25 01:54:10 +00:00
Settings → Profile opens the same file picker as attaching to a mail, in an avatar mode: albums, people, search and paging, one picture at a time, files that cannot be a picture greyed out. The choice is posted straight back to the profile form, and the connection is matched against the user's own. From Paperless-ngx the original is taken rather than the archived PDF. The setup wizard keeps its small grid for now. Co-authored-by: Flouss <15911342+Flouss@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
134 lines
7.5 KiB
Twig
134 lines
7.5 KiB
Twig
{#
|
|
integration/_row.html.twig — one file in the picker list.
|
|
|
|
The whole row selects, the way a whole tile does in the photo grid. Before, the
|
|
only target was a small "Copy" chip on the right, which meant the obvious
|
|
gesture — click the file you want — did nothing at all.
|
|
|
|
Copy and link stay mutually exclusive without any JavaScript by being two radios
|
|
in one group. The trick that avoids nesting a label inside a label: the row's
|
|
label is absolutely positioned over the whole row, the content above it is
|
|
pointer-events-none so clicks fall through, and the link chip sits above both
|
|
with its own label. So one tap picks copy, and the chip still picks link.
|
|
|
|
Link only appears where it is useful — the service can mint one, and the file is
|
|
either oversized or being hovered. Showing it permanently made every row look
|
|
like it needed a decision.
|
|
#}
|
|
{% if entry.isFolder %}
|
|
{% include 'integration/_folder_row.html.twig' with {
|
|
integration: integration,
|
|
entry: entry,
|
|
draftId: draftId,
|
|
mode: mode|default('attach'),
|
|
isMediaLibrary: isMediaLibrary|default(false),
|
|
} only %}
|
|
{% else %}
|
|
{# Only a *known* oversize blocks a copy — see the picker's docblock. #}
|
|
{% set knownTooBig = entry.size is not null and entry.size > maxBytes %}
|
|
{# Profile-picture mode only: a file that cannot become a picture. Shown, so
|
|
the page does not look empty, but not choosable. #}
|
|
{% set notAnImage = entry.id in refusedIds|default([]) %}
|
|
{% set canCopy = not knownTooBig and not notAnImage %}
|
|
{% set unattachable = not canCopy and not canLink %}
|
|
|
|
<li class="group relative flex items-center gap-3 px-3 py-2.5 -mx-2 rounded-xl
|
|
{{ unattachable ? 'opacity-50' : '' }}">
|
|
|
|
{% if canCopy %}
|
|
{# Covers the row. Everything else is layered above it. #}
|
|
<label class="absolute inset-0 cursor-pointer rounded-xl select-none touch-manipulation"
|
|
data-action="pointerdown->integration--integration-picker#tileDown click->integration--integration-picker#tileClick"
|
|
aria-label="{{ entry.name }}">
|
|
<input type="radio" class="peer sr-only"
|
|
name="mode[{{ entry.id }}]" value="copy"
|
|
data-integration--integration-picker-target="mode">
|
|
<span class="absolute inset-0 rounded-xl transition-colors
|
|
group-hover:bg-hover
|
|
peer-checked:bg-accent-soft
|
|
peer-checked:ring-2 peer-checked:ring-inset peer-checked:ring-accent"></span>
|
|
|
|
{# No tick here, unlike the grid: the ring plus the tinted fill
|
|
is unambiguous across a full-width row, and the only free
|
|
corner is where the link chip lives. #}
|
|
</label>
|
|
{% endif %}
|
|
|
|
{# pointer-events-none so a click on the name reaches the row label. #}
|
|
<div class="relative pointer-events-none flex items-center gap-3 flex-1 min-w-0">
|
|
{# A preview only where it tells you something. Asking for one per row
|
|
meant a 200-hit search fired 200 proxied requests, each making the
|
|
service render a document preview — 4.5 MB and half a minute for a
|
|
list of invoices, where the answer was always a red PDF square.
|
|
Pictures are worth previewing; a filename identifies the rest. #}
|
|
{% if canThumb and entry.mime is not null and (entry.mime starts with 'image/' or entry.mime starts with 'video/') %}
|
|
<img src="{{ path('app_integration_thumbnail', { id: integration.id, file: entry.id }) }}"
|
|
alt="" loading="lazy"
|
|
class="w-10 h-10 rounded-lg object-cover bg-sunken shrink-0">
|
|
{% else %}
|
|
{% set icon = 'fa-file' %}
|
|
{% set tint = 'text-ink-faint' %}
|
|
{% if entry.mime is not null %}
|
|
{% if entry.mime == 'application/pdf' %}
|
|
{% set icon = 'fa-file-pdf' %}{% set tint = 'text-red-400' %}
|
|
{% elseif entry.mime starts with 'image/' %}
|
|
{% set icon = 'fa-file-image' %}{% set tint = 'text-violet-400' %}
|
|
{% elseif entry.mime starts with 'video/' %}
|
|
{% set icon = 'fa-file-video' %}{% set tint = 'text-violet-400' %}
|
|
{% elseif entry.mime starts with 'audio/' %}
|
|
{% set icon = 'fa-file-audio' %}{% set tint = 'text-violet-400' %}
|
|
{% elseif entry.mime starts with 'text/' %}
|
|
{% set icon = 'fa-file-lines' %}
|
|
{% elseif 'zip' in entry.mime or 'compressed' in entry.mime or 'tar' in entry.mime %}
|
|
{% set icon = 'fa-file-zipper' %}{% set tint = 'text-amber-400' %}
|
|
{% elseif 'sheet' in entry.mime or 'excel' in entry.mime or 'csv' in entry.mime %}
|
|
{% set icon = 'fa-file-excel' %}{% set tint = 'text-emerald-400' %}
|
|
{% elseif 'word' in entry.mime or 'document' in entry.mime %}
|
|
{% set icon = 'fa-file-word' %}{% set tint = 'text-sky-400' %}
|
|
{% endif %}
|
|
{% endif %}
|
|
|
|
<span class="w-10 h-10 shrink-0 flex items-center justify-center rounded-lg bg-raised">
|
|
<i class="fa-solid {{ icon }} text-base {{ tint }}" aria-hidden="true"></i>
|
|
</span>
|
|
{% endif %}
|
|
|
|
<span class="flex-1 min-w-0">
|
|
<span class="block truncate text-sm text-ink">{{ entry.name }}</span>
|
|
<span class="block text-[11px] text-ink-faint tabular-nums">
|
|
{%- if entry.size is not null -%}
|
|
{{ entry.size > 1048576
|
|
? (entry.size / 1048576)|round(1) ~ ' MB'
|
|
: (entry.size / 1024)|round ~ ' KB' }}
|
|
{%- endif -%}
|
|
{%- if entry.size is not null and entry.modifiedAt %} · {% endif -%}
|
|
{%- if entry.modifiedAt %}{{ entry.modifiedAt|date('Y-m-d') }}{% endif -%}
|
|
</span>
|
|
</span>
|
|
</div>
|
|
|
|
{% if unattachable %}
|
|
<span class="relative shrink-0 text-[11px] text-ink-faint">
|
|
{{ (notAnImage ? 'integration.picker.not_an_image' : 'integration.picker.too_big')|trans }}
|
|
</span>
|
|
{% elseif canLink %}
|
|
{# Above the row label, so the chip wins the click it is under. #}
|
|
<label class="relative shrink-0 cursor-pointer
|
|
{{ knownTooBig ? '' : 'opacity-0 group-hover:opacity-100 focus-within:opacity-100' }}
|
|
transition-opacity">
|
|
<input type="radio" class="peer sr-only"
|
|
name="mode[{{ entry.id }}]" value="link"
|
|
data-integration--integration-picker-target="mode">
|
|
<span class="inline-flex items-center gap-1.5 h-7 px-2.5 rounded-lg text-[11px] font-medium
|
|
text-ink-faint bg-field border border-field
|
|
peer-checked:bg-accent peer-checked:text-accent-ink peer-checked:border-accent
|
|
peer-checked:opacity-100
|
|
transition-colors">
|
|
<i class="fa-solid fa-link text-[9px]" aria-hidden="true"></i>
|
|
{{ 'integration.picker.link'|trans }}
|
|
</span>
|
|
</label>
|
|
{% endif %}
|
|
</li>
|
|
{% endif %}
|