SHA256
UI: добавить карусель вложений
This commit is contained in:
@@ -15,7 +15,8 @@ import { renderUserAvatar } from '../components/avatar-image.js';
|
||||
import { openArweaveAttachmentManager } from '../components/arweave-attachment-manager.js';
|
||||
import {
|
||||
composeMessageWithAttachments,
|
||||
createAttachmentListElement,
|
||||
createAttachmentCarouselElement,
|
||||
MAX_MESSAGE_ATTACHMENTS,
|
||||
parseMessageAttachments,
|
||||
} from '../services/attachment-format.js';
|
||||
import { loadProfileSnapshot } from '../services/user-profile-params.js';
|
||||
@@ -417,6 +418,10 @@ function openReplyModal({ onSubmit, navigate }) {
|
||||
|
||||
root.querySelector('#thread-reply-attach')?.addEventListener('click', async () => {
|
||||
try {
|
||||
if (attachments.length >= MAX_MESSAGE_ATTACHMENTS) {
|
||||
errorEl.textContent = `Можно прикрепить максимум ${MAX_MESSAGE_ATTACHMENTS} файлов.`;
|
||||
return;
|
||||
}
|
||||
const item = await openArweaveAttachmentManager({
|
||||
login: state.session.login,
|
||||
storagePwd: state.session.storagePwdInMemory,
|
||||
@@ -670,7 +675,10 @@ function renderNodeCard(node, heading, handlers, localNumber) {
|
||||
|
||||
card.append(authorTile);
|
||||
if (!isDeletedMessage && parsedText.attachments.length > 0) {
|
||||
card.append(createAttachmentListElement(parsedText.attachments, { gateway: state.entrySettings.arweaveServer }));
|
||||
card.append(createAttachmentCarouselElement(parsedText.attachments, {
|
||||
gateway: state.entrySettings.arweaveServer,
|
||||
messageTimestampMs: node?.createdAtMs,
|
||||
}));
|
||||
}
|
||||
card.append(body);
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@ import { renderUserAvatar } from '../components/avatar-image.js';
|
||||
import { openArweaveAttachmentManager } from '../components/arweave-attachment-manager.js';
|
||||
import {
|
||||
composeMessageWithAttachments,
|
||||
createAttachmentListElement,
|
||||
createAttachmentCarouselElement,
|
||||
MAX_MESSAGE_ATTACHMENTS,
|
||||
parseMessageAttachments,
|
||||
} from '../services/attachment-format.js';
|
||||
import { loadProfileSnapshot } from '../services/user-profile-params.js';
|
||||
@@ -392,6 +393,10 @@ function openReplyModal({ onSubmit, navigate }) {
|
||||
|
||||
root.querySelector('#reply-attach')?.addEventListener('click', async () => {
|
||||
try {
|
||||
if (attachments.length >= MAX_MESSAGE_ATTACHMENTS) {
|
||||
errorEl.textContent = `Можно прикрепить максимум ${MAX_MESSAGE_ATTACHMENTS} файлов.`;
|
||||
return;
|
||||
}
|
||||
const item = await openArweaveAttachmentManager({
|
||||
login: state.session.login,
|
||||
storagePwd: state.session.storagePwdInMemory,
|
||||
@@ -546,6 +551,10 @@ function openAddMessageModal({ channelName, onSubmit, navigate }) {
|
||||
|
||||
root.querySelector('#channel-message-attach')?.addEventListener('click', async () => {
|
||||
try {
|
||||
if (attachments.length >= MAX_MESSAGE_ATTACHMENTS) {
|
||||
errorEl.textContent = `Можно прикрепить максимум ${MAX_MESSAGE_ATTACHMENTS} файлов.`;
|
||||
return;
|
||||
}
|
||||
const item = await openArweaveAttachmentManager({
|
||||
login: state.session.login,
|
||||
storagePwd: state.session.storagePwdInMemory,
|
||||
@@ -1004,7 +1013,10 @@ function renderPostCard(post, {
|
||||
|
||||
card.append(authorTile);
|
||||
if (!isDeletedMessage && parsedBody.attachments.length > 0) {
|
||||
card.append(createAttachmentListElement(parsedBody.attachments, { gateway: state.entrySettings.arweaveServer }));
|
||||
card.append(createAttachmentCarouselElement(parsedBody.attachments, {
|
||||
gateway: state.entrySettings.arweaveServer,
|
||||
messageTimestampMs: post.timestampMs,
|
||||
}));
|
||||
}
|
||||
card.append(body);
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@ import { buildArweaveDataUrl, validateArweaveTxId, validateSha256Hex } from './a
|
||||
|
||||
const ATTACH_PREFIX = '<SHiNE:attach;';
|
||||
const ATTACH_BLOCK_RE = /^<SHiNE:attach;([^>]*)>\n?/u;
|
||||
export const MAX_MESSAGE_ATTACHMENTS = 10;
|
||||
const RECENT_UNAVAILABLE_MS = 20 * 60 * 1000;
|
||||
const IMAGE_EXTENSIONS = new Set(['apng', 'avif', 'bmp', 'gif', 'jpeg', 'jpg', 'png', 'svg', 'webp']);
|
||||
const VIDEO_EXTENSIONS = new Set(['avi', 'm4v', 'mkv', 'mov', 'mp4', 'mpeg', 'mpg', 'ogg', 'ogv', 'webm']);
|
||||
|
||||
export function escapeHtml(text) {
|
||||
return String(text || '')
|
||||
@@ -54,7 +58,9 @@ export function buildAttachmentBlock(attachment) {
|
||||
|
||||
export function composeMessageWithAttachments(text, attachments = []) {
|
||||
const cleanText = String(text || '').trim();
|
||||
const rows = (Array.isArray(attachments) ? attachments : []).map(buildAttachmentBlock);
|
||||
const rows = (Array.isArray(attachments) ? attachments : [])
|
||||
.slice(0, MAX_MESSAGE_ATTACHMENTS)
|
||||
.map(buildAttachmentBlock);
|
||||
if (!rows.length) return cleanText;
|
||||
return `${rows.join('\n')}${cleanText ? `\n${cleanText}` : ''}`;
|
||||
}
|
||||
@@ -98,35 +104,264 @@ export function parseMessageAttachments(rawText) {
|
||||
};
|
||||
}
|
||||
|
||||
export function createAttachmentListElement(attachments = [], { gateway = '', compact = false } = {}) {
|
||||
const items = Array.isArray(attachments) ? attachments : [];
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = `message-attachments${compact ? ' message-attachments--compact' : ''}`;
|
||||
items.forEach((raw) => {
|
||||
let item;
|
||||
export function getAttachmentExtension(name) {
|
||||
const clean = String(name || '').trim().toLowerCase();
|
||||
const dot = clean.lastIndexOf('.');
|
||||
if (dot < 0 || dot === clean.length - 1) return '';
|
||||
return clean.slice(dot + 1).replace(/[^a-z0-9]/g, '');
|
||||
}
|
||||
|
||||
export function getAttachmentKind(attachment = {}) {
|
||||
const ext = getAttachmentExtension(attachment.name);
|
||||
if (IMAGE_EXTENSIONS.has(ext)) return 'image';
|
||||
if (VIDEO_EXTENSIONS.has(ext)) return 'video';
|
||||
return 'file';
|
||||
}
|
||||
|
||||
function isRecentMessage(messageTimestampMs) {
|
||||
const ts = Number(messageTimestampMs || 0);
|
||||
if (!Number.isFinite(ts) || ts <= 0) return false;
|
||||
return Date.now() - ts < RECENT_UNAVAILABLE_MS;
|
||||
}
|
||||
|
||||
function createUnavailableBlock(messageTimestampMs) {
|
||||
const block = document.createElement('div');
|
||||
block.className = 'message-attachment-unavailable';
|
||||
block.textContent = isRecentMessage(messageTimestampMs)
|
||||
? 'Скорее всего файл недоступен, потому что сообщение было создано недавно и файл ещё не распространился в Arweave. Попробуйте повторить через несколько минут.'
|
||||
: 'Файл недоступен.';
|
||||
return block;
|
||||
}
|
||||
|
||||
function stopVideos(root) {
|
||||
root.querySelectorAll('video').forEach((video) => {
|
||||
try {
|
||||
item = normalizeAttachment(raw);
|
||||
video.pause();
|
||||
video.currentTime = 0;
|
||||
} catch {
|
||||
return;
|
||||
// Если браузер не даёт управлять видео, просто продолжаем перелистывание.
|
||||
}
|
||||
const url = buildArweaveDataUrl({ gateway, txId: item.ar });
|
||||
const link = document.createElement('a');
|
||||
link.className = 'message-attachment-card';
|
||||
link.href = url;
|
||||
link.target = '_blank';
|
||||
link.rel = 'noopener';
|
||||
link.title = 'Открыть файл в Arweave';
|
||||
link.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
});
|
||||
link.innerHTML = `
|
||||
<span class="message-attachment-icon" aria-hidden="true">📎</span>
|
||||
<span class="message-attachment-main">
|
||||
<span class="message-attachment-name">${escapeHtml(item.name)}</span>
|
||||
<span class="message-attachment-meta">${escapeHtml(formatBytes(item.size))} · ${escapeHtml(item.ar)}</span>
|
||||
</span>
|
||||
`;
|
||||
wrap.append(link);
|
||||
});
|
||||
}
|
||||
|
||||
function createDownloadLink(url, label = 'Скачать') {
|
||||
const link = document.createElement('a');
|
||||
link.className = 'message-attachment-download';
|
||||
link.href = url;
|
||||
link.target = '_blank';
|
||||
link.rel = 'noopener';
|
||||
link.download = '';
|
||||
link.textContent = label;
|
||||
link.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
});
|
||||
return link;
|
||||
}
|
||||
|
||||
function openAttachmentViewer({ item, url, kind }) {
|
||||
const root = document.getElementById('modal-root') || document.body;
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'modal attachment-viewer-modal';
|
||||
overlay.innerHTML = `
|
||||
<div class="attachment-viewer-card" role="dialog" aria-modal="true">
|
||||
<div class="attachment-viewer-head">
|
||||
<div class="attachment-viewer-title">${escapeHtml(item.name)}</div>
|
||||
<button class="icon-btn attachment-viewer-close" type="button" aria-label="Закрыть">×</button>
|
||||
</div>
|
||||
<div class="attachment-viewer-body"></div>
|
||||
<div class="attachment-viewer-actions"></div>
|
||||
</div>
|
||||
`;
|
||||
const close = () => {
|
||||
stopVideos(overlay);
|
||||
overlay.remove();
|
||||
};
|
||||
overlay.addEventListener('click', (event) => {
|
||||
if (event.target === overlay) close();
|
||||
});
|
||||
overlay.querySelector('.attachment-viewer-close')?.addEventListener('click', close);
|
||||
const body = overlay.querySelector('.attachment-viewer-body');
|
||||
if (kind === 'video') {
|
||||
const video = document.createElement('video');
|
||||
video.className = 'attachment-viewer-media';
|
||||
video.src = url;
|
||||
video.controls = true;
|
||||
video.autoplay = true;
|
||||
body.append(video);
|
||||
} else {
|
||||
const img = document.createElement('img');
|
||||
img.className = 'attachment-viewer-media';
|
||||
img.src = url;
|
||||
img.alt = item.name;
|
||||
body.append(img);
|
||||
}
|
||||
overlay.querySelector('.attachment-viewer-actions')?.append(createDownloadLink(url));
|
||||
root.append(overlay);
|
||||
}
|
||||
|
||||
function replaceWithUnavailable(slide, messageTimestampMs) {
|
||||
if (!slide || slide.dataset.unavailable === '1') return;
|
||||
slide.dataset.unavailable = '1';
|
||||
slide.replaceChildren(createUnavailableBlock(messageTimestampMs));
|
||||
}
|
||||
|
||||
function createFileCard({ item, url, messageTimestampMs, slide }) {
|
||||
const ext = getAttachmentExtension(item.name).toUpperCase() || 'FILE';
|
||||
const card = document.createElement('div');
|
||||
card.className = 'message-attachment-file-card';
|
||||
card.role = 'button';
|
||||
card.tabIndex = 0;
|
||||
card.title = 'Скачать файл';
|
||||
card.innerHTML = `
|
||||
<div class="message-attachment-file-ext">${escapeHtml(ext)}</div>
|
||||
<div class="message-attachment-file-main">
|
||||
<div class="message-attachment-name">${escapeHtml(item.name)}</div>
|
||||
<div class="message-attachment-meta">${escapeHtml(formatBytes(item.size))} · ${escapeHtml(item.ar)}</div>
|
||||
</div>
|
||||
`;
|
||||
const openFile = () => window.open(url, '_blank', 'noopener');
|
||||
card.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
openFile();
|
||||
});
|
||||
card.addEventListener('keydown', (event) => {
|
||||
if (event.key !== 'Enter' && event.key !== ' ') return;
|
||||
event.preventDefault();
|
||||
openFile();
|
||||
});
|
||||
card.append(createDownloadLink(url));
|
||||
void fetch(url, { method: 'HEAD', cache: 'no-store' })
|
||||
.then((response) => {
|
||||
if (!response.ok) replaceWithUnavailable(slide, messageTimestampMs);
|
||||
})
|
||||
.catch(() => replaceWithUnavailable(slide, messageTimestampMs));
|
||||
return card;
|
||||
}
|
||||
|
||||
function createMediaSlide({ item, url, kind, messageTimestampMs, slide }) {
|
||||
const frame = document.createElement('div');
|
||||
frame.className = `message-attachment-media-frame message-attachment-media-frame--${kind}`;
|
||||
frame.role = 'button';
|
||||
frame.tabIndex = 0;
|
||||
frame.title = kind === 'video' ? 'Открыть видео' : 'Открыть изображение';
|
||||
frame.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
openAttachmentViewer({ item, url, kind });
|
||||
});
|
||||
frame.addEventListener('keydown', (event) => {
|
||||
if (event.key !== 'Enter' && event.key !== ' ') return;
|
||||
event.preventDefault();
|
||||
openAttachmentViewer({ item, url, kind });
|
||||
});
|
||||
|
||||
if (kind === 'image') {
|
||||
const img = document.createElement('img');
|
||||
img.className = 'message-attachment-media';
|
||||
img.src = url;
|
||||
img.alt = item.name;
|
||||
img.loading = 'lazy';
|
||||
img.addEventListener('error', () => replaceWithUnavailable(slide, messageTimestampMs), { once: true });
|
||||
frame.append(img);
|
||||
} else {
|
||||
const video = document.createElement('video');
|
||||
video.className = 'message-attachment-media';
|
||||
video.src = url;
|
||||
video.preload = 'metadata';
|
||||
video.muted = true;
|
||||
video.playsInline = true;
|
||||
video.addEventListener('error', () => replaceWithUnavailable(slide, messageTimestampMs), { once: true });
|
||||
const play = document.createElement('span');
|
||||
play.className = 'message-attachment-play';
|
||||
play.textContent = '▶';
|
||||
frame.append(video, play);
|
||||
}
|
||||
|
||||
frame.append(createDownloadLink(url));
|
||||
return frame;
|
||||
}
|
||||
|
||||
export function createAttachmentCarouselElement(attachments = [], { gateway = '', messageTimestampMs = 0 } = {}) {
|
||||
const items = (Array.isArray(attachments) ? attachments : [])
|
||||
.slice(0, MAX_MESSAGE_ATTACHMENTS)
|
||||
.map((raw) => {
|
||||
try {
|
||||
return normalizeAttachment(raw);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = 'message-attachments message-attachment-carousel';
|
||||
wrap.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
});
|
||||
if (!items.length) return wrap;
|
||||
|
||||
let index = 0;
|
||||
let pointerStartX = 0;
|
||||
const viewport = document.createElement('div');
|
||||
viewport.className = 'message-attachment-carousel-viewport';
|
||||
const slide = document.createElement('div');
|
||||
slide.className = 'message-attachment-slide';
|
||||
const prev = document.createElement('button');
|
||||
prev.type = 'button';
|
||||
prev.className = 'message-attachment-arrow message-attachment-arrow--prev';
|
||||
prev.textContent = '‹';
|
||||
prev.setAttribute('aria-label', 'Предыдущее вложение');
|
||||
const next = document.createElement('button');
|
||||
next.type = 'button';
|
||||
next.className = 'message-attachment-arrow message-attachment-arrow--next';
|
||||
next.textContent = '›';
|
||||
next.setAttribute('aria-label', 'Следующее вложение');
|
||||
const counter = document.createElement('div');
|
||||
counter.className = 'message-attachment-counter';
|
||||
|
||||
const render = () => {
|
||||
stopVideos(wrap);
|
||||
const item = items[index];
|
||||
const kind = getAttachmentKind(item);
|
||||
const url = buildArweaveDataUrl({ gateway, txId: item.ar });
|
||||
slide.dataset.unavailable = '0';
|
||||
slide.replaceChildren();
|
||||
slide.append(kind === 'file'
|
||||
? createFileCard({ item, url, messageTimestampMs, slide })
|
||||
: createMediaSlide({ item, url, kind, messageTimestampMs, slide }));
|
||||
prev.disabled = index <= 0;
|
||||
next.disabled = index >= items.length - 1;
|
||||
counter.textContent = `${index + 1} из ${items.length}`;
|
||||
};
|
||||
|
||||
const move = (delta) => {
|
||||
const nextIndex = Math.max(0, Math.min(items.length - 1, index + delta));
|
||||
if (nextIndex === index) return;
|
||||
index = nextIndex;
|
||||
render();
|
||||
};
|
||||
|
||||
prev.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
move(-1);
|
||||
});
|
||||
next.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
move(1);
|
||||
});
|
||||
viewport.addEventListener('pointerdown', (event) => {
|
||||
pointerStartX = event.clientX;
|
||||
});
|
||||
viewport.addEventListener('pointerup', (event) => {
|
||||
const dx = event.clientX - pointerStartX;
|
||||
if (Math.abs(dx) < 40) return;
|
||||
move(dx < 0 ? 1 : -1);
|
||||
});
|
||||
|
||||
viewport.append(slide, prev, next);
|
||||
wrap.append(viewport, counter);
|
||||
render();
|
||||
return wrap;
|
||||
}
|
||||
|
||||
export function createAttachmentListElement(attachments = [], options = {}) {
|
||||
return createAttachmentCarouselElement(attachments, options);
|
||||
}
|
||||
|
||||
@@ -2182,6 +2182,165 @@ textarea.input {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.message-attachment-carousel {
|
||||
gap: 0.35rem;
|
||||
margin: 0.5rem 0 0.4rem;
|
||||
max-width: min(100%, 34rem);
|
||||
}
|
||||
|
||||
.message-attachment-carousel-viewport {
|
||||
align-items: center;
|
||||
background: linear-gradient(135deg, rgba(15, 23, 42, 0.05), rgba(20, 184, 166, 0.08));
|
||||
border: 1px solid rgba(15, 23, 42, 0.1);
|
||||
border-radius: 1.15rem;
|
||||
display: grid;
|
||||
min-height: 12rem;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
touch-action: pan-y;
|
||||
}
|
||||
|
||||
.message-attachment-slide {
|
||||
align-items: stretch;
|
||||
display: grid;
|
||||
min-height: 12rem;
|
||||
}
|
||||
|
||||
.message-attachment-arrow {
|
||||
align-items: center;
|
||||
background: rgba(15, 23, 42, 0.62);
|
||||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||
border-radius: 999px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
font-size: 1.85rem;
|
||||
height: 2.35rem;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 2.35rem;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.message-attachment-arrow:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.28;
|
||||
}
|
||||
|
||||
.message-attachment-arrow--prev {
|
||||
left: 0.55rem;
|
||||
}
|
||||
|
||||
.message-attachment-arrow--next {
|
||||
right: 0.55rem;
|
||||
}
|
||||
|
||||
.message-attachment-counter {
|
||||
color: #64748b;
|
||||
font-size: 0.78rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.message-attachment-media-frame {
|
||||
align-items: center;
|
||||
background: #0f172a;
|
||||
cursor: zoom-in;
|
||||
display: grid;
|
||||
min-height: 12rem;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message-attachment-media {
|
||||
display: block;
|
||||
height: min(52vh, 20rem);
|
||||
max-height: 20rem;
|
||||
object-fit: contain;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.message-attachment-play {
|
||||
align-items: center;
|
||||
background: rgba(15, 23, 42, 0.72);
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
border-radius: 999px;
|
||||
color: #fff;
|
||||
display: inline-flex;
|
||||
font-size: 1.7rem;
|
||||
height: 4rem;
|
||||
justify-content: center;
|
||||
left: 50%;
|
||||
padding-left: 0.18rem;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 4rem;
|
||||
}
|
||||
|
||||
.message-attachment-download {
|
||||
background: rgba(20, 184, 166, 0.95);
|
||||
border-radius: 999px;
|
||||
color: #fff;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 700;
|
||||
padding: 0.45rem 0.75rem;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
width: max-content;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.message-attachment-media-frame > .message-attachment-download {
|
||||
bottom: 0.75rem;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
}
|
||||
|
||||
.message-attachment-file-card {
|
||||
align-items: center;
|
||||
background: linear-gradient(135deg, rgba(240, 253, 250, 0.95), rgba(239, 246, 255, 0.95));
|
||||
color: #0f172a;
|
||||
display: grid;
|
||||
gap: 0.7rem;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.message-attachment-file-ext {
|
||||
align-items: center;
|
||||
background: #0f766e;
|
||||
border-radius: 0.9rem;
|
||||
color: #fff;
|
||||
display: inline-flex;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 800;
|
||||
height: 3rem;
|
||||
justify-content: center;
|
||||
max-width: 4rem;
|
||||
min-width: 3rem;
|
||||
padding: 0 0.45rem;
|
||||
}
|
||||
|
||||
.message-attachment-file-main {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.message-attachment-unavailable {
|
||||
align-items: center;
|
||||
background: linear-gradient(135deg, rgba(254, 242, 242, 0.96), rgba(255, 247, 237, 0.96));
|
||||
color: #7f1d1d;
|
||||
display: flex;
|
||||
font-weight: 700;
|
||||
justify-content: center;
|
||||
min-height: 12rem;
|
||||
padding: 1.25rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.message-attachment-card {
|
||||
align-items: center;
|
||||
background: linear-gradient(135deg, rgba(240, 253, 250, 0.95), rgba(239, 246, 255, 0.95));
|
||||
@@ -2224,6 +2383,80 @@ textarea.input {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.attachment-viewer-modal {
|
||||
z-index: 80;
|
||||
}
|
||||
|
||||
.attachment-viewer-card {
|
||||
background: rgba(15, 23, 42, 0.96);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 1.2rem;
|
||||
box-shadow: 0 24px 90px rgba(0, 0, 0, 0.45);
|
||||
color: #fff;
|
||||
display: grid;
|
||||
gap: 0.8rem;
|
||||
max-height: 92vh;
|
||||
max-width: min(94vw, 68rem);
|
||||
padding: 0.9rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.attachment-viewer-head,
|
||||
.attachment-viewer-actions {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 0.8rem;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.attachment-viewer-title {
|
||||
font-weight: 800;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.attachment-viewer-body {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
min-height: 12rem;
|
||||
}
|
||||
|
||||
.attachment-viewer-media {
|
||||
background: #020617;
|
||||
border-radius: 0.8rem;
|
||||
max-height: 76vh;
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.message-attachment-carousel {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.message-attachment-carousel-viewport,
|
||||
.message-attachment-slide,
|
||||
.message-attachment-media-frame,
|
||||
.message-attachment-unavailable {
|
||||
min-height: 10rem;
|
||||
}
|
||||
|
||||
.message-attachment-media {
|
||||
height: min(42vh, 16rem);
|
||||
}
|
||||
|
||||
.message-attachment-file-card {
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.message-attachment-file-card .message-attachment-download {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.ar-attachment-manager-card {
|
||||
max-width: min(92vw, 34rem);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user