UI: добавить вложения Arweave в каналы

This commit is contained in:
AidarKC
2026-07-30 11:04:50 +04:00
parent c7684d6fe1
commit e9e6628b21
10 changed files with 974 additions and 29 deletions
+81 -11
View File
@@ -18,6 +18,12 @@ import {
} from '../services/channels-ux.js';
import { navigateBack } from '../router.js';
import { renderUserAvatar } from '../components/avatar-image.js';
import { openArweaveAttachmentManager } from '../components/arweave-attachment-manager.js';
import {
composeMessageWithAttachments,
createAttachmentListElement,
parseMessageAttachments,
} from '../services/attachment-format.js';
import { loadProfileSnapshot } from '../services/user-profile-params.js';
import {
extractLoginFromBlockchainName,
@@ -306,6 +312,25 @@ function bindSubmitOnPlainEnter(textarea, submit) {
});
}
function renderDraftAttachments(container, attachments) {
if (!container) return;
container.innerHTML = '';
(Array.isArray(attachments) ? attachments : []).forEach((item, index) => {
const button = document.createElement('button');
button.type = 'button';
button.className = 'draft-attachment-chip';
button.textContent = `${item.name} · ${item.ar}`;
button.title = 'Нажмите, чтобы убрать вложение';
button.addEventListener('click', () => {
const ok = window.confirm('Отменить вложение?');
if (!ok) return;
attachments.splice(index, 1);
renderDraftAttachments(container, attachments);
});
container.append(button);
});
}
function openReplyModal({ onSubmit, navigate }) {
const root = document.getElementById('modal-root');
root.innerHTML = `
@@ -313,6 +338,8 @@ function openReplyModal({ onSubmit, navigate }) {
<div class="modal-card stack">
<h3 class="modal-title">Ответ</h3>
<textarea id="reply-text" class="input" rows="5" maxlength="2000" placeholder="Текст ответа"></textarea>
<div class="draft-attachments" id="reply-attachments"></div>
<button class="secondary-btn attachment-trigger-btn" id="reply-attach" type="button" aria-label="Добавить вложение" title="Добавить вложение">▣ 📎</button>
<div class="meta-muted inline-error" id="reply-error"></div>
<div class="form-actions-grid">
<button class="secondary-btn" id="reply-cancel" type="button">Отмена</button>
@@ -323,14 +350,17 @@ function openReplyModal({ onSubmit, navigate }) {
`;
const textEl = root.querySelector('#reply-text');
const attachmentsEl = root.querySelector('#reply-attachments');
const errorEl = root.querySelector('#reply-error');
const submitEl = root.querySelector('#reply-submit');
const attachments = [];
let inFlight = false;
const setBusy = (busy) => {
inFlight = !!busy;
submitEl.disabled = inFlight;
if (textEl) textEl.disabled = inFlight;
root.querySelector('#reply-attach')?.toggleAttribute('disabled', inFlight);
submitEl.textContent = inFlight ? 'Отправляем...' : 'Отправить';
};
@@ -343,8 +373,8 @@ function openReplyModal({ onSubmit, navigate }) {
if (inFlight) return;
const text = String(textEl?.value || '').trim();
if (!text) {
errorEl.textContent = 'Введите текст ответа.';
if (!text && attachments.length === 0) {
errorEl.textContent = 'Введите текст ответа или добавьте вложение.';
return;
}
@@ -352,7 +382,7 @@ function openReplyModal({ onSubmit, navigate }) {
errorEl.textContent = '';
try {
await onSubmit(text);
await onSubmit(composeMessageWithAttachments(text, attachments));
close();
} catch (error) {
setBusy(false);
@@ -360,6 +390,21 @@ function openReplyModal({ onSubmit, navigate }) {
}
});
root.querySelector('#reply-attach')?.addEventListener('click', async () => {
try {
const item = await openArweaveAttachmentManager({
login: state.session.login,
storagePwd: state.session.storagePwdInMemory,
gateway: state.entrySettings.arweaveServer,
});
if (!item) return;
attachments.push(item);
renderDraftAttachments(attachmentsEl, attachments);
} catch (error) {
errorEl.textContent = toUserMessage(error, 'Не удалось добавить вложение.');
}
});
bindSubmitOnPlainEnter(textEl, () => submitEl?.click());
if (textEl) textEl.focus();
@@ -447,6 +492,8 @@ function openAddMessageModal({ channelName, onSubmit, navigate }) {
<h3 class="modal-title">Новое сообщение в канале</h3>
<p class="meta-muted">${channelName}</p>
<textarea id="channel-message-text" class="input" rows="6" maxlength="2000" placeholder="Текст сообщения"></textarea>
<div class="draft-attachments" id="channel-message-attachments"></div>
<button class="secondary-btn attachment-trigger-btn" id="channel-message-attach" type="button" aria-label="Добавить вложение" title="Добавить вложение">▣ 📎</button>
<div class="meta-muted inline-error" id="channel-message-error"></div>
<div class="form-actions-grid">
<button class="secondary-btn" id="channel-message-cancel" type="button">Отмена</button>
@@ -457,14 +504,17 @@ function openAddMessageModal({ channelName, onSubmit, navigate }) {
`;
const textEl = root.querySelector('#channel-message-text');
const attachmentsEl = root.querySelector('#channel-message-attachments');
const errorEl = root.querySelector('#channel-message-error');
const submitEl = root.querySelector('#channel-message-submit');
const attachments = [];
let inFlight = false;
const setBusy = (busy) => {
inFlight = !!busy;
submitEl.disabled = inFlight;
if (textEl) textEl.disabled = inFlight;
root.querySelector('#channel-message-attach')?.toggleAttribute('disabled', inFlight);
submitEl.textContent = inFlight ? 'Отправляем...' : 'Отправить';
};
@@ -477,8 +527,8 @@ function openAddMessageModal({ channelName, onSubmit, navigate }) {
if (inFlight) return;
const body = String(textEl?.value || '').trim();
if (!body) {
errorEl.textContent = 'Введите текст сообщения.';
if (!body && attachments.length === 0) {
errorEl.textContent = 'Введите текст сообщения или добавьте вложение.';
return;
}
@@ -486,7 +536,7 @@ function openAddMessageModal({ channelName, onSubmit, navigate }) {
errorEl.textContent = '';
try {
await onSubmit(body);
await onSubmit(composeMessageWithAttachments(body, attachments));
close();
} catch (error) {
setBusy(false);
@@ -494,6 +544,21 @@ function openAddMessageModal({ channelName, onSubmit, navigate }) {
}
});
root.querySelector('#channel-message-attach')?.addEventListener('click', async () => {
try {
const item = await openArweaveAttachmentManager({
login: state.session.login,
storagePwd: state.session.storagePwdInMemory,
gateway: state.entrySettings.arweaveServer,
});
if (!item) return;
attachments.push(item);
renderDraftAttachments(attachmentsEl, attachments);
} catch (error) {
errorEl.textContent = toUserMessage(error, 'Не удалось добавить вложение.');
}
});
bindSubmitOnPlainEnter(textEl, () => submitEl?.click());
if (textEl) textEl.focus();
@@ -535,7 +600,7 @@ function openMessageHistoryModal({ versions = [], title = 'История изм
});
}
function openEditMessageModal({ initialText = '', onSave, onDelete }) {
function openEditMessageModal({ initialText = '', allowEmptyText = false, onSave, onDelete }) {
const root = document.getElementById('modal-root');
root.innerHTML = `
<div class="modal" id="edit-message-modal">
@@ -563,7 +628,7 @@ function openEditMessageModal({ initialText = '', onSave, onDelete }) {
root.querySelector('#edit-message-cancel')?.addEventListener('click', close);
root.querySelector('#edit-message-save')?.addEventListener('click', async () => {
const value = String(textEl?.value || '').trim();
if (!value) {
if (!value && !allowEmptyText) {
errorEl.textContent = 'Введите текст сообщения.';
return;
}
@@ -932,11 +997,15 @@ function renderPostCard(post, {
});
const isDeletedMessage = String(post.body || '').trim().toLowerCase() === 'удалено';
const parsedBody = parseMessageAttachments(post.body);
const body = document.createElement('p');
body.className = `channel-message-body${isDeletedMessage ? ' channel-message-body--deleted' : ''}`;
body.textContent = isDeletedMessage ? 'Сообщение удалено' : post.body;
body.textContent = isDeletedMessage ? 'Сообщение удалено' : parsedBody.text;
card.append(authorTile, body);
if (!isDeletedMessage && parsedBody.attachments.length > 0) {
card.append(createAttachmentListElement(parsedBody.attachments, { gateway: state.entrySettings.arweaveServer }));
}
const refKey = messageRefKey(post.messageRef);
if (refKey) {
@@ -1048,8 +1117,9 @@ function renderPostCard(post, {
event.stopPropagation();
animatePress(event.currentTarget);
openEditMessageModal({
initialText: String(post.body || '').trim() === 'удалено' ? '' : post.body,
onSave: async (nextText) => onEdit(post.messageRef, nextText, { isDelete: false }),
initialText: String(post.body || '').trim() === 'удалено' ? '' : parsedBody.text,
allowEmptyText: parsedBody.attachments.length > 0,
onSave: async (nextText) => onEdit(post.messageRef, composeMessageWithAttachments(nextText, parsedBody.attachments), { isDelete: false }),
onDelete: async () => onEdit(post.messageRef, '', { isDelete: true }),
});
});