SHA256
UI: добавить просмотр данных блокчейна сообщения
This commit is contained in:
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
client.version=1.4.8
|
client.version=1.4.9
|
||||||
server.version=1.4.0
|
server.version=1.4.0
|
||||||
|
|||||||
@@ -113,6 +113,12 @@
|
|||||||
- В `11_TEXT_Blocks.md` зафиксировано, что запись `TEXT_REPOST` временно не используется до будущей реализации.
|
- В `11_TEXT_Blocks.md` зафиксировано, что запись `TEXT_REPOST` временно не используется до будущей реализации.
|
||||||
- В `docs/API/04_Add_Block_to_Blockchain_API.md` добавлен код отказа `repost_disabled`.
|
- В `docs/API/04_Add_Block_to_Blockchain_API.md` добавлен код отказа `repost_disabled`.
|
||||||
|
|
||||||
|
## 2026-07-31 17:40:31 +0400
|
||||||
|
- Базовый коммит-ориентир: `d640dd6`.
|
||||||
|
- Формат блокчейна не менялся.
|
||||||
|
- В UI каналов добавлен технический просмотр данных сообщения из уже полученного ответа сервера: цепочка автора, номер записи, хэш, тип, время, текст, public key/подпись при наличии и сырой JSON-блок.
|
||||||
|
- Удалённые сообщения в UI теперь отображаются компактной строкой с переходом в историю версий.
|
||||||
|
|
||||||
## 2026-05-21 19:05:00 +0300
|
## 2026-05-21 19:05:00 +0300
|
||||||
- Базовый коммит-ориентир: `5344c42`.
|
- Базовый коммит-ориентир: `5344c42`.
|
||||||
- Добавлен новый TEXT-подтип `TEXT_REPOST (subType=30)`:
|
- Добавлен новый TEXT-подтип `TEXT_REPOST (subType=30)`:
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { markArweaveAttachmentPlaced, openArweaveAttachmentManager } from '../co
|
|||||||
import {
|
import {
|
||||||
composeMessageWithAttachments,
|
composeMessageWithAttachments,
|
||||||
createAttachmentCarouselElement,
|
createAttachmentCarouselElement,
|
||||||
|
escapeHtml,
|
||||||
MAX_MESSAGE_ATTACHMENTS,
|
MAX_MESSAGE_ATTACHMENTS,
|
||||||
parseMessageAttachments,
|
parseMessageAttachments,
|
||||||
} from '../services/attachment-format.js';
|
} from '../services/attachment-format.js';
|
||||||
@@ -329,6 +330,91 @@ function bindSubmitOnPlainEnter(textarea, submit) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setActionTitle(button, label) {
|
||||||
|
if (!button) return;
|
||||||
|
button.title = label;
|
||||||
|
button.setAttribute('aria-label', label);
|
||||||
|
const labelEl = button.querySelector('.channel-action-label');
|
||||||
|
if (labelEl) labelEl.textContent = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyTextToClipboard(text) {
|
||||||
|
if (navigator?.clipboard?.writeText) {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const ta = document.createElement('textarea');
|
||||||
|
ta.value = text;
|
||||||
|
ta.setAttribute('readonly', '');
|
||||||
|
ta.style.position = 'fixed';
|
||||||
|
ta.style.opacity = '0';
|
||||||
|
document.body.append(ta);
|
||||||
|
ta.select();
|
||||||
|
const ok = document.execCommand('copy');
|
||||||
|
ta.remove();
|
||||||
|
return !!ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildBlockchainDetails({ target, authorLogin, timestampMs, text, raw, localNumber, msgSubType }) {
|
||||||
|
const source = raw && typeof raw === 'object' ? raw : {};
|
||||||
|
return {
|
||||||
|
authorLogin,
|
||||||
|
authorBlockchainName: target?.blockchainName || source.authorBlockchainName || '',
|
||||||
|
blockNumber: target?.blockNumber ?? source?.messageRef?.blockNumber ?? '',
|
||||||
|
blockHash: target?.blockHash || source?.messageRef?.blockHash || '',
|
||||||
|
localNumber,
|
||||||
|
msgSubType: msgSubType ?? source.msgSubType ?? '',
|
||||||
|
createdAtMs: timestampMs || source.createdAtMs || '',
|
||||||
|
text: String(text || ''),
|
||||||
|
signature: source.signature || source.blockSignature || source.authorSignature || 'нет в ответе сервера',
|
||||||
|
publicKey: source.publicKey || source.authorPublicKey || 'нет в ответе сервера',
|
||||||
|
raw,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function openBlockchainDetailsModal(details) {
|
||||||
|
const root = document.getElementById('modal-root');
|
||||||
|
const rawText = JSON.stringify(details.raw || details, null, 2);
|
||||||
|
root.innerHTML = `
|
||||||
|
<div class="modal" id="thread-blockchain-details-modal">
|
||||||
|
<div class="modal-card stack blockchain-details-card">
|
||||||
|
<h3 class="modal-title">Данные блокчейна сообщения</h3>
|
||||||
|
<p class="meta-muted">Это технические данные записи SHiNE. По ним можно увидеть цепочку автора, номер записи, хэш и подпись, если она есть в ответе сервера.</p>
|
||||||
|
<div class="blockchain-details-grid">
|
||||||
|
<span>Автор</span><strong>${escapeHtml(details.authorLogin)}</strong>
|
||||||
|
<span>Блокчейн</span><code>${escapeHtml(details.authorBlockchainName)}</code>
|
||||||
|
<span>Номер записи</span><code>${escapeHtml(details.blockNumber)}</code>
|
||||||
|
<span>Хэш</span><code>${escapeHtml(details.blockHash)}</code>
|
||||||
|
<span>Тип</span><code>${escapeHtml(details.msgSubType)}</code>
|
||||||
|
<span>Время</span><code>${escapeHtml(details.createdAtMs ? new Date(Number(details.createdAtMs)).toLocaleString('ru-RU') : '—')}</code>
|
||||||
|
<span>Public key</span><code>${escapeHtml(details.publicKey)}</code>
|
||||||
|
<span>Подпись</span><code>${escapeHtml(details.signature)}</code>
|
||||||
|
</div>
|
||||||
|
<label class="field-label" for="thread-blockchain-details-text">Текст записи</label>
|
||||||
|
<textarea class="input" id="thread-blockchain-details-text" rows="4" readonly>${escapeHtml(details.text)}</textarea>
|
||||||
|
<pre class="blockchain-raw-block" id="thread-blockchain-raw-block" hidden>${escapeHtml(rawText)}</pre>
|
||||||
|
<div class="form-actions-grid">
|
||||||
|
<button class="secondary-btn" id="thread-blockchain-details-copy" type="button">Скопировать</button>
|
||||||
|
<button class="secondary-btn" id="thread-blockchain-details-raw" type="button">Показать сырой блок</button>
|
||||||
|
</div>
|
||||||
|
<button class="secondary-btn" id="thread-blockchain-details-close" type="button">Закрыть</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
root.querySelector('#thread-blockchain-details-close')?.addEventListener('click', () => {
|
||||||
|
root.innerHTML = '';
|
||||||
|
});
|
||||||
|
root.querySelector('#thread-blockchain-details-copy')?.addEventListener('click', async () => {
|
||||||
|
await copyTextToClipboard(rawText);
|
||||||
|
showToast('Данные блокчейна скопированы');
|
||||||
|
});
|
||||||
|
root.querySelector('#thread-blockchain-details-raw')?.addEventListener('click', () => {
|
||||||
|
const rawEl = root.querySelector('#thread-blockchain-raw-block');
|
||||||
|
if (!rawEl) return;
|
||||||
|
rawEl.hidden = !rawEl.hidden;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function resolveNodeText(node) {
|
function resolveNodeText(node) {
|
||||||
return firstNonEmptyText(
|
return firstNonEmptyText(
|
||||||
node?.text,
|
node?.text,
|
||||||
@@ -671,18 +757,35 @@ function renderNodeCard(node, heading, handlers, localNumber) {
|
|||||||
authorTile.append(avatar, authorBlock);
|
authorTile.append(avatar, authorBlock);
|
||||||
|
|
||||||
const isDeletedMessage = String(text || '').trim().toLowerCase() === 'удалено';
|
const isDeletedMessage = String(text || '').trim().toLowerCase() === 'удалено';
|
||||||
const body = document.createElement('p');
|
|
||||||
body.className = `channel-message-body${isDeletedMessage ? ' channel-message-body--deleted' : ''}`;
|
|
||||||
body.textContent = isDeletedMessage ? 'Сообщение удалено' : parsedText.text;
|
|
||||||
|
|
||||||
|
if (isDeletedMessage) {
|
||||||
|
card.classList.add('channel-message-card--deleted-compact');
|
||||||
|
const deleted = document.createElement('button');
|
||||||
|
deleted.type = 'button';
|
||||||
|
deleted.className = 'deleted-message-pill';
|
||||||
|
deleted.textContent = `Удалённое сообщение от ${author}`;
|
||||||
|
deleted.title = 'Открыть историю изменений';
|
||||||
|
deleted.addEventListener('click', (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
openMessageHistoryModal({
|
||||||
|
title: `История #${localNumber}`,
|
||||||
|
versions,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
card.append(deleted);
|
||||||
|
} else {
|
||||||
card.append(authorTile);
|
card.append(authorTile);
|
||||||
if (!isDeletedMessage && parsedText.attachments.length > 0) {
|
if (parsedText.attachments.length > 0) {
|
||||||
card.append(createAttachmentCarouselElement(parsedText.attachments, {
|
card.append(createAttachmentCarouselElement(parsedText.attachments, {
|
||||||
gateway: state.entrySettings.arweaveServer,
|
gateway: state.entrySettings.arweaveServer,
|
||||||
messageTimestampMs: node?.createdAtMs,
|
messageTimestampMs: node?.createdAtMs,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
const body = document.createElement('p');
|
||||||
|
body.className = 'channel-message-body';
|
||||||
|
body.textContent = parsedText.text;
|
||||||
card.append(body);
|
card.append(body);
|
||||||
|
}
|
||||||
|
|
||||||
const target = buildTargetFromNode(node);
|
const target = buildTargetFromNode(node);
|
||||||
const refKey = messageRefKey(target);
|
const refKey = messageRefKey(target);
|
||||||
@@ -709,6 +812,7 @@ function renderNodeCard(node, heading, handlers, localNumber) {
|
|||||||
<span class="channel-action-label">${isPending ? 'Лайк...' : 'Лайк'}</span>
|
<span class="channel-action-label">${isPending ? 'Лайк...' : 'Лайк'}</span>
|
||||||
<span class="channel-action-counter">${likes}</span>
|
<span class="channel-action-counter">${likes}</span>
|
||||||
`;
|
`;
|
||||||
|
setActionTitle(likeButton, isPending ? 'Лайк...' : 'Лайк');
|
||||||
likeButton.disabled = isPending;
|
likeButton.disabled = isPending;
|
||||||
likeButton.addEventListener('click', async (event) => {
|
likeButton.addEventListener('click', async (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@@ -720,6 +824,7 @@ function renderNodeCard(node, heading, handlers, localNumber) {
|
|||||||
}
|
}
|
||||||
await longPressFeel(event.currentTarget, 130);
|
await longPressFeel(event.currentTarget, 130);
|
||||||
likeButton.disabled = true;
|
likeButton.disabled = true;
|
||||||
|
setActionTitle(likeButton, 'Лайк...');
|
||||||
try {
|
try {
|
||||||
await handlers.onToggleLike(target, isLiked ? 'unlike' : 'like');
|
await handlers.onToggleLike(target, isLiked ? 'unlike' : 'like');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -740,6 +845,7 @@ function renderNodeCard(node, heading, handlers, localNumber) {
|
|||||||
<span class="channel-action-label">Ответить</span>
|
<span class="channel-action-label">Ответить</span>
|
||||||
<span class="channel-action-counter">${replies}</span>
|
<span class="channel-action-counter">${replies}</span>
|
||||||
`;
|
`;
|
||||||
|
setActionTitle(replyButton, 'Ответить');
|
||||||
replyButton.addEventListener('click', (event) => {
|
replyButton.addEventListener('click', (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
animatePress(event.currentTarget);
|
animatePress(event.currentTarget);
|
||||||
@@ -756,6 +862,7 @@ function renderNodeCard(node, heading, handlers, localNumber) {
|
|||||||
<span class="channel-action-icon" aria-hidden="true">↗</span>
|
<span class="channel-action-icon" aria-hidden="true">↗</span>
|
||||||
<span class="channel-action-label">Отправить</span>
|
<span class="channel-action-label">Отправить</span>
|
||||||
`;
|
`;
|
||||||
|
setActionTitle(shareButton, 'Отправить');
|
||||||
shareButton.addEventListener('click', async (event) => {
|
shareButton.addEventListener('click', async (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
animatePress(event.currentTarget);
|
animatePress(event.currentTarget);
|
||||||
@@ -773,6 +880,7 @@ function renderNodeCard(node, heading, handlers, localNumber) {
|
|||||||
<span class="channel-action-icon" aria-hidden="true">↪</span>
|
<span class="channel-action-icon" aria-hidden="true">↪</span>
|
||||||
<span class="channel-action-label">Оригинал</span>
|
<span class="channel-action-label">Оригинал</span>
|
||||||
`;
|
`;
|
||||||
|
setActionTitle(originalButton, 'Оригинал');
|
||||||
originalButton.addEventListener('click', (event) => {
|
originalButton.addEventListener('click', (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const ok = window.confirm('Перейти к оригинальному сообщению?');
|
const ok = window.confirm('Перейти к оригинальному сообщению?');
|
||||||
@@ -787,6 +895,27 @@ function renderNodeCard(node, heading, handlers, localNumber) {
|
|||||||
});
|
});
|
||||||
actions.append(originalButton);
|
actions.append(originalButton);
|
||||||
}
|
}
|
||||||
|
const detailsButton = document.createElement('button');
|
||||||
|
detailsButton.type = 'button';
|
||||||
|
detailsButton.className = 'channel-action-item';
|
||||||
|
detailsButton.innerHTML = `
|
||||||
|
<span class="channel-action-icon" aria-hidden="true">⛓</span>
|
||||||
|
<span class="channel-action-label">Данные блокчейна</span>
|
||||||
|
`;
|
||||||
|
setActionTitle(detailsButton, 'Данные блокчейна');
|
||||||
|
detailsButton.addEventListener('click', (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
openBlockchainDetailsModal(buildBlockchainDetails({
|
||||||
|
target,
|
||||||
|
authorLogin: author,
|
||||||
|
timestampMs: node?.createdAtMs,
|
||||||
|
text,
|
||||||
|
raw: node,
|
||||||
|
localNumber,
|
||||||
|
msgSubType,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
actions.append(detailsButton);
|
||||||
if (isOwnMessage) {
|
if (isOwnMessage) {
|
||||||
const editButton = document.createElement('button');
|
const editButton = document.createElement('button');
|
||||||
editButton.type = 'button';
|
editButton.type = 'button';
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { markArweaveAttachmentPlaced, openArweaveAttachmentManager } from '../co
|
|||||||
import {
|
import {
|
||||||
composeMessageWithAttachments,
|
composeMessageWithAttachments,
|
||||||
createAttachmentCarouselElement,
|
createAttachmentCarouselElement,
|
||||||
|
escapeHtml,
|
||||||
MAX_MESSAGE_ATTACHMENTS,
|
MAX_MESSAGE_ATTACHMENTS,
|
||||||
parseMessageAttachments,
|
parseMessageAttachments,
|
||||||
} from '../services/attachment-format.js';
|
} from '../services/attachment-format.js';
|
||||||
@@ -313,6 +314,91 @@ function bindSubmitOnPlainEnter(textarea, submit) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setActionTitle(button, label) {
|
||||||
|
if (!button) return;
|
||||||
|
button.title = label;
|
||||||
|
button.setAttribute('aria-label', label);
|
||||||
|
const labelEl = button.querySelector('.channel-action-label');
|
||||||
|
if (labelEl) labelEl.textContent = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyTextToClipboard(text) {
|
||||||
|
if (navigator?.clipboard?.writeText) {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const ta = document.createElement('textarea');
|
||||||
|
ta.value = text;
|
||||||
|
ta.setAttribute('readonly', '');
|
||||||
|
ta.style.position = 'fixed';
|
||||||
|
ta.style.opacity = '0';
|
||||||
|
document.body.append(ta);
|
||||||
|
ta.select();
|
||||||
|
const ok = document.execCommand('copy');
|
||||||
|
ta.remove();
|
||||||
|
return !!ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildBlockchainDetails({ messageRef, authorLogin, timestampMs, text, raw, localNumber, msgSubType }) {
|
||||||
|
const source = raw && typeof raw === 'object' ? raw : {};
|
||||||
|
return {
|
||||||
|
authorLogin,
|
||||||
|
authorBlockchainName: messageRef?.blockchainName || source.authorBlockchainName || '',
|
||||||
|
blockNumber: messageRef?.blockNumber ?? source?.messageRef?.blockNumber ?? '',
|
||||||
|
blockHash: messageRef?.blockHash || source?.messageRef?.blockHash || '',
|
||||||
|
localNumber,
|
||||||
|
msgSubType: msgSubType ?? source.msgSubType ?? '',
|
||||||
|
createdAtMs: timestampMs || source.createdAtMs || '',
|
||||||
|
text: String(text || ''),
|
||||||
|
signature: source.signature || source.blockSignature || source.authorSignature || 'нет в ответе сервера',
|
||||||
|
publicKey: source.publicKey || source.authorPublicKey || 'нет в ответе сервера',
|
||||||
|
raw,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function openBlockchainDetailsModal(details) {
|
||||||
|
const root = document.getElementById('modal-root');
|
||||||
|
const rawText = JSON.stringify(details.raw || details, null, 2);
|
||||||
|
root.innerHTML = `
|
||||||
|
<div class="modal" id="blockchain-details-modal">
|
||||||
|
<div class="modal-card stack blockchain-details-card">
|
||||||
|
<h3 class="modal-title">Данные блокчейна сообщения</h3>
|
||||||
|
<p class="meta-muted">Это технические данные записи SHiNE. По ним можно увидеть цепочку автора, номер записи, хэш и подпись, если она есть в ответе сервера.</p>
|
||||||
|
<div class="blockchain-details-grid">
|
||||||
|
<span>Автор</span><strong>${escapeHtml(details.authorLogin)}</strong>
|
||||||
|
<span>Блокчейн</span><code>${escapeHtml(details.authorBlockchainName)}</code>
|
||||||
|
<span>Номер записи</span><code>${escapeHtml(details.blockNumber)}</code>
|
||||||
|
<span>Хэш</span><code>${escapeHtml(details.blockHash)}</code>
|
||||||
|
<span>Тип</span><code>${escapeHtml(details.msgSubType)}</code>
|
||||||
|
<span>Время</span><code>${escapeHtml(details.createdAtMs ? new Date(Number(details.createdAtMs)).toLocaleString('ru-RU') : '—')}</code>
|
||||||
|
<span>Public key</span><code>${escapeHtml(details.publicKey)}</code>
|
||||||
|
<span>Подпись</span><code>${escapeHtml(details.signature)}</code>
|
||||||
|
</div>
|
||||||
|
<label class="field-label" for="blockchain-details-text">Текст записи</label>
|
||||||
|
<textarea class="input" id="blockchain-details-text" rows="4" readonly>${escapeHtml(details.text)}</textarea>
|
||||||
|
<pre class="blockchain-raw-block" id="blockchain-raw-block" hidden>${escapeHtml(rawText)}</pre>
|
||||||
|
<div class="form-actions-grid">
|
||||||
|
<button class="secondary-btn" id="blockchain-details-copy" type="button">Скопировать</button>
|
||||||
|
<button class="secondary-btn" id="blockchain-details-raw" type="button">Показать сырой блок</button>
|
||||||
|
</div>
|
||||||
|
<button class="secondary-btn" id="blockchain-details-close" type="button">Закрыть</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
root.querySelector('#blockchain-details-close')?.addEventListener('click', () => {
|
||||||
|
root.innerHTML = '';
|
||||||
|
});
|
||||||
|
root.querySelector('#blockchain-details-copy')?.addEventListener('click', async () => {
|
||||||
|
await copyTextToClipboard(rawText);
|
||||||
|
showToast('Данные блокчейна скопированы');
|
||||||
|
});
|
||||||
|
root.querySelector('#blockchain-details-raw')?.addEventListener('click', () => {
|
||||||
|
const rawEl = root.querySelector('#blockchain-raw-block');
|
||||||
|
if (!rawEl) return;
|
||||||
|
rawEl.hidden = !rawEl.hidden;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function renderDraftAttachments(container, attachments) {
|
function renderDraftAttachments(container, attachments) {
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
@@ -693,6 +779,7 @@ function mapApiMessageToPost(message, selector, localNumber) {
|
|||||||
repliesCount: Number(message?.repliesCount || 0),
|
repliesCount: Number(message?.repliesCount || 0),
|
||||||
timestampMs: resolveMessageTimestampMs(message),
|
timestampMs: resolveMessageTimestampMs(message),
|
||||||
messageRef,
|
messageRef,
|
||||||
|
rawMessage: message,
|
||||||
msgSubType: Number(message?.msgSubType || 0),
|
msgSubType: Number(message?.msgSubType || 0),
|
||||||
targetRef: message?.targetBlockchainName && Number.isFinite(Number(message?.targetBlockNumber))
|
targetRef: message?.targetBlockchainName && Number.isFinite(Number(message?.targetBlockNumber))
|
||||||
? {
|
? {
|
||||||
@@ -1011,18 +1098,35 @@ function renderPostCard(post, {
|
|||||||
|
|
||||||
const isDeletedMessage = String(post.body || '').trim().toLowerCase() === 'удалено';
|
const isDeletedMessage = String(post.body || '').trim().toLowerCase() === 'удалено';
|
||||||
const parsedBody = parseMessageAttachments(post.body);
|
const parsedBody = parseMessageAttachments(post.body);
|
||||||
const body = document.createElement('p');
|
|
||||||
body.className = `channel-message-body${isDeletedMessage ? ' channel-message-body--deleted' : ''}`;
|
|
||||||
body.textContent = isDeletedMessage ? 'Сообщение удалено' : parsedBody.text;
|
|
||||||
|
|
||||||
|
if (isDeletedMessage) {
|
||||||
|
card.classList.add('channel-message-card--deleted-compact');
|
||||||
|
const deleted = document.createElement('button');
|
||||||
|
deleted.type = 'button';
|
||||||
|
deleted.className = 'deleted-message-pill';
|
||||||
|
deleted.textContent = `Удалённое сообщение от ${post.authorLogin}`;
|
||||||
|
deleted.title = 'Открыть историю изменений';
|
||||||
|
deleted.addEventListener('click', (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
openMessageHistoryModal({
|
||||||
|
title: `История #${post.localNumber}`,
|
||||||
|
versions: post.versions,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
card.append(deleted);
|
||||||
|
} else {
|
||||||
card.append(authorTile);
|
card.append(authorTile);
|
||||||
if (!isDeletedMessage && parsedBody.attachments.length > 0) {
|
if (parsedBody.attachments.length > 0) {
|
||||||
card.append(createAttachmentCarouselElement(parsedBody.attachments, {
|
card.append(createAttachmentCarouselElement(parsedBody.attachments, {
|
||||||
gateway: state.entrySettings.arweaveServer,
|
gateway: state.entrySettings.arweaveServer,
|
||||||
messageTimestampMs: post.timestampMs,
|
messageTimestampMs: post.timestampMs,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
const body = document.createElement('p');
|
||||||
|
body.className = 'channel-message-body';
|
||||||
|
body.textContent = parsedBody.text;
|
||||||
card.append(body);
|
card.append(body);
|
||||||
|
}
|
||||||
|
|
||||||
const refKey = messageRefKey(post.messageRef);
|
const refKey = messageRefKey(post.messageRef);
|
||||||
if (refKey) {
|
if (refKey) {
|
||||||
@@ -1048,6 +1152,7 @@ function renderPostCard(post, {
|
|||||||
<span class="channel-action-label">${isPending ? 'Лайк...' : 'Лайк'}</span>
|
<span class="channel-action-label">${isPending ? 'Лайк...' : 'Лайк'}</span>
|
||||||
<span class="channel-action-counter">${post.likesCount || 0}</span>
|
<span class="channel-action-counter">${post.likesCount || 0}</span>
|
||||||
`;
|
`;
|
||||||
|
setActionTitle(likeButton, isPending ? 'Лайк...' : 'Лайк');
|
||||||
likeButton.disabled = isPending;
|
likeButton.disabled = isPending;
|
||||||
likeButton.addEventListener('click', async (event) => {
|
likeButton.addEventListener('click', async (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@@ -1059,8 +1164,7 @@ function renderPostCard(post, {
|
|||||||
}
|
}
|
||||||
await longPressFeel(event.currentTarget, 130);
|
await longPressFeel(event.currentTarget, 130);
|
||||||
likeButton.disabled = true;
|
likeButton.disabled = true;
|
||||||
const labelEl = likeButton.querySelector('.channel-action-label');
|
setActionTitle(likeButton, 'Лайк...');
|
||||||
if (labelEl) labelEl.textContent = 'Лайк...';
|
|
||||||
await onToggleLike(post.messageRef, isLiked ? 'unlike' : 'like', { likeButton });
|
await onToggleLike(post.messageRef, isLiked ? 'unlike' : 'like', { likeButton });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1072,6 +1176,7 @@ function renderPostCard(post, {
|
|||||||
<span class="channel-action-label">Ответить</span>
|
<span class="channel-action-label">Ответить</span>
|
||||||
<span class="channel-action-counter">${post.repliesCount || 0}</span>
|
<span class="channel-action-counter">${post.repliesCount || 0}</span>
|
||||||
`;
|
`;
|
||||||
|
setActionTitle(replyButton, 'Ответить');
|
||||||
replyButton.addEventListener('click', (event) => {
|
replyButton.addEventListener('click', (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
animatePress(event.currentTarget);
|
animatePress(event.currentTarget);
|
||||||
@@ -1091,6 +1196,7 @@ function renderPostCard(post, {
|
|||||||
<span class="channel-action-icon" aria-hidden="true">↗</span>
|
<span class="channel-action-icon" aria-hidden="true">↗</span>
|
||||||
<span class="channel-action-label">Отправить</span>
|
<span class="channel-action-label">Отправить</span>
|
||||||
`;
|
`;
|
||||||
|
setActionTitle(shareButton, 'Отправить');
|
||||||
shareButton.addEventListener('click', async (event) => {
|
shareButton.addEventListener('click', async (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
animatePress(event.currentTarget);
|
animatePress(event.currentTarget);
|
||||||
@@ -1107,6 +1213,7 @@ function renderPostCard(post, {
|
|||||||
<span class="channel-action-icon" aria-hidden="true">↪</span>
|
<span class="channel-action-icon" aria-hidden="true">↪</span>
|
||||||
<span class="channel-action-label">Оригинал</span>
|
<span class="channel-action-label">Оригинал</span>
|
||||||
`;
|
`;
|
||||||
|
setActionTitle(originalBtn, 'Оригинал');
|
||||||
originalBtn.addEventListener('click', (event) => {
|
originalBtn.addEventListener('click', (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const ownerLogin = extractLoginFromBlockchainName(post.targetRef.blockchainName);
|
const ownerLogin = extractLoginFromBlockchainName(post.targetRef.blockchainName);
|
||||||
@@ -1121,6 +1228,27 @@ function renderPostCard(post, {
|
|||||||
});
|
});
|
||||||
actions.append(originalBtn);
|
actions.append(originalBtn);
|
||||||
}
|
}
|
||||||
|
const detailsButton = document.createElement('button');
|
||||||
|
detailsButton.type = 'button';
|
||||||
|
detailsButton.className = 'channel-action-item';
|
||||||
|
detailsButton.innerHTML = `
|
||||||
|
<span class="channel-action-icon" aria-hidden="true">⛓</span>
|
||||||
|
<span class="channel-action-label">Данные блокчейна</span>
|
||||||
|
`;
|
||||||
|
setActionTitle(detailsButton, 'Данные блокчейна');
|
||||||
|
detailsButton.addEventListener('click', (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
openBlockchainDetailsModal(buildBlockchainDetails({
|
||||||
|
messageRef: post.messageRef,
|
||||||
|
authorLogin: post.authorLogin,
|
||||||
|
timestampMs: post.timestampMs,
|
||||||
|
text: post.body,
|
||||||
|
raw: post.rawMessage,
|
||||||
|
localNumber: post.localNumber,
|
||||||
|
msgSubType: post.msgSubType,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
actions.append(detailsButton);
|
||||||
if (post.isOwnMessage) {
|
if (post.isOwnMessage) {
|
||||||
const editButton = document.createElement('button');
|
const editButton = document.createElement('button');
|
||||||
editButton.type = 'button';
|
editButton.type = 'button';
|
||||||
|
|||||||
@@ -3878,6 +3878,36 @@ textarea.input {
|
|||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.channel-message-card--deleted-compact {
|
||||||
|
gap: 7px;
|
||||||
|
padding-top: 9px;
|
||||||
|
padding-bottom: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleted-message-pill {
|
||||||
|
appearance: none;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid rgba(255, 160, 120, 0.28);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: rgba(70, 31, 25, 0.45);
|
||||||
|
color: rgba(255, 216, 190, 0.9);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.2;
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleted-message-pill:hover,
|
||||||
|
.deleted-message-pill:focus-visible {
|
||||||
|
border-color: rgba(255, 196, 142, 0.55);
|
||||||
|
background: rgba(98, 42, 31, 0.58);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
.channel-message-time {
|
.channel-message-time {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: rgba(255, 255, 255, 0.48);
|
color: rgba(255, 255, 255, 0.48);
|
||||||
@@ -3919,12 +3949,15 @@ textarea.input {
|
|||||||
appearance: none;
|
appearance: none;
|
||||||
border: 0;
|
border: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
position: relative;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
|
min-width: 24px;
|
||||||
color: rgba(255, 255, 255, 0.55);
|
color: rgba(255, 255, 255, 0.55);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: color 0.18s ease, text-shadow 0.18s ease, transform 0.18s ease;
|
transition: color 0.18s ease, text-shadow 0.18s ease, transform 0.18s ease;
|
||||||
@@ -3954,9 +3987,37 @@ textarea.input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.channel-action-label {
|
.channel-action-label {
|
||||||
font-size: 12px;
|
position: absolute;
|
||||||
line-height: 1;
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-action-item:hover::after,
|
||||||
|
.channel-action-item:focus-visible::after {
|
||||||
|
content: attr(title);
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: calc(100% + 8px);
|
||||||
|
z-index: 30;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
max-width: 180px;
|
||||||
|
padding: 5px 8px;
|
||||||
|
border: 1px solid rgba(255, 220, 140, 0.24);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(20, 20, 24, 0.94);
|
||||||
|
color: rgba(255, 244, 210, 0.96);
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.15;
|
||||||
letter-spacing: 0.01em;
|
letter-spacing: 0.01em;
|
||||||
|
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.35);
|
||||||
|
pointer-events: none;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-edited-marker {
|
.message-edited-marker {
|
||||||
@@ -3995,6 +4056,45 @@ textarea.input {
|
|||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blockchain-details-card {
|
||||||
|
max-width: min(620px, calc(100vw - 28px));
|
||||||
|
}
|
||||||
|
|
||||||
|
.blockchain-details-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: max-content minmax(0, 1fr);
|
||||||
|
gap: 8px 12px;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: 14px;
|
||||||
|
background: rgba(255, 255, 255, 0.035);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blockchain-details-grid span {
|
||||||
|
color: rgba(255, 255, 255, 0.58);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blockchain-details-grid code,
|
||||||
|
.blockchain-details-grid strong {
|
||||||
|
min-width: 0;
|
||||||
|
color: rgba(255, 244, 210, 0.96);
|
||||||
|
font-size: 12px;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blockchain-raw-block {
|
||||||
|
max-height: 280px;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: rgba(0, 0, 0, 0.32);
|
||||||
|
color: rgba(255, 255, 255, 0.82);
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.45;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
.thread-summary {
|
.thread-summary {
|
||||||
color: #efd9a4;
|
color: #efd9a4;
|
||||||
border-color: rgba(212, 171, 90, 0.36);
|
border-color: rgba(212, 171, 90, 0.36);
|
||||||
|
|||||||
Reference in New Issue
Block a user