diff --git a/VERSION.properties b/VERSION.properties index 3bc40f27..19420751 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.4.2 +client.version=1.4.3 server.version=1.4.0 diff --git a/docs/Blockchain/15_TEXT_Attachments.md b/docs/Blockchain/15_TEXT_Attachments.md index 4cd0ca14..c043ed41 100644 --- a/docs/Blockchain/15_TEXT_Attachments.md +++ b/docs/Blockchain/15_TEXT_Attachments.md @@ -61,6 +61,8 @@ UI поддерживает три сценария: - выбрать файл из журнала файлов, загруженных в текущей браузерной сессии. Журнал хранится только в `sessionStorage` текущего браузера и не является частью блокчейна. +В настройках клиента есть отдельный экран журнала загрузок Arweave: пользователь может заранее загрузить файл в Arweave без создания сообщения, увидеть плитку с именем, размером, датой, `txId` и статусом доступности через gateway, а затем выбрать этот файл из истории при создании сообщения. +Если история очищена, уже созданные сообщения не меняются: в блокчейне остаются attach-блоки с `txId`. ## Отображение diff --git a/docs/Blockchain/CHANGELOG.md b/docs/Blockchain/CHANGELOG.md index a3a1c89a..4a68b503 100644 --- a/docs/Blockchain/CHANGELOG.md +++ b/docs/Blockchain/CHANGELOG.md @@ -1,5 +1,10 @@ # История изменений документации блокчейна +## 2026-07-31 13:12:24 +0400 +- Базовый коммит-ориентир: `c530627`. +- Для UI-вложений добавлен отдельный экран журнала загрузок Arweave в настройках: загрузка файла без создания сообщения, плитки истории с `txId`, размером, датой и статусом доступности через gateway. +- Уточнено, что журнал хранится только в `sessionStorage` текущего браузера и не влияет на уже записанные attach-блоки в блокчейне. + ## 2026-07-31 12:47:41 +0400 - Базовый коммит-ориентир: `f961947`. - Для `SHiNE:attach v=1` уточнено клиентское отображение вложений: горизонтальная карусель над текстом сообщения, счётчик позиции, просмотр image/video в модальном окне и скачивание файлов. diff --git a/shine-UI/js/app.js b/shine-UI/js/app.js index 403b7b2c..d8034140 100644 --- a/shine-UI/js/app.js +++ b/shine-UI/js/app.js @@ -59,6 +59,7 @@ import * as settingsView from './pages/settings-view.js'; import * as accessServersView from './pages/access-servers-view.js'; import * as developerSettingsView from './pages/developer-settings-view.js'; import * as serverSettingsView from './pages/server-settings-view.js?v=202606161240'; +import * as arweaveUploadsView from './pages/arweave-uploads-view.js'; import * as remoteAddBlockSessionView from './pages/remote-addblock-session-view.js?v=202606281300'; import * as deviceView from './pages/device-view.js?v=202606131435'; import * as connectDeviceView from './pages/connect-device-view.js?v=202606142055'; @@ -120,6 +121,7 @@ const routes = { 'access-servers-view': accessServersView, 'developer-settings-view': developerSettingsView, 'server-settings-view': serverSettingsView, + 'arweave-uploads-view': arweaveUploadsView, 'remote-addblock-session-view': remoteAddBlockSessionView, 'device-view': deviceView, 'connect-device-view': connectDeviceView, diff --git a/shine-UI/js/components/arweave-attachment-manager.js b/shine-UI/js/components/arweave-attachment-manager.js index abfa8bc3..c062abc6 100644 --- a/shine-UI/js/components/arweave-attachment-manager.js +++ b/shine-UI/js/components/arweave-attachment-manager.js @@ -1,4 +1,5 @@ import { + buildArweaveDataUrl, getArweaveUploadPrice, loadArweaveFileMetadata, sha256HexFromArrayBuffer, @@ -10,8 +11,9 @@ import { escapeHtml, formatBytes, normalizeAttachment } from '../services/attach const HISTORY_KEY = 'shine-ui-arweave-attachment-history-v1'; const MAX_HISTORY_ITEMS = 100; +const RECENT_UPLOAD_MS = 20 * 60 * 1000; -function readHistory(login) { +export function readArweaveAttachmentHistory(login) { try { const raw = sessionStorage.getItem(HISTORY_KEY); const parsed = raw ? JSON.parse(raw) : {}; @@ -23,7 +25,7 @@ function readHistory(login) { } } -function saveHistory(login, rows) { +export function saveArweaveAttachmentHistory(login, rows) { try { const key = String(login || '').trim().toLowerCase(); const raw = sessionStorage.getItem(HISTORY_KEY); @@ -35,20 +37,40 @@ function saveHistory(login, rows) { } } -function addToHistory(login, attachment) { +export function clearArweaveAttachmentHistory(login) { + saveArweaveAttachmentHistory(login, []); +} + +export function addArweaveAttachmentToHistory(login, attachment) { const item = normalizeAttachment(attachment); - const rows = readHistory(login).filter((row) => row.ar !== item.ar); + const rows = readArweaveAttachmentHistory(login).filter((row) => row.ar !== item.ar); rows.unshift(item); - saveHistory(login, rows); + saveArweaveAttachmentHistory(login, rows); return item; } -function formatDateTime(ms) { +export function formatArweaveHistoryDateTime(ms) { const value = Number(ms || 0); if (!Number.isFinite(value) || value <= 0) return '—'; return new Date(value).toLocaleString('ru-RU'); } +export async function getArweaveAttachmentAvailability({ gateway, attachment } = {}) { + const item = normalizeAttachment(attachment); + const url = buildArweaveDataUrl({ gateway, txId: item.ar }); + try { + const response = await fetch(url, { method: 'HEAD', cache: 'no-store' }); + if (response.ok) return { status: 'available', label: '✓ Доступен', url }; + } catch { + // gateway может временно не отвечать или блокировать HEAD; для UI это всё равно недоступно. + } + const ageMs = Date.now() - Number(item.uploadedAtMs || 0); + if (Number.isFinite(ageMs) && ageMs >= 0 && ageMs < RECENT_UPLOAD_MS) { + return { status: 'pending', label: '… Ещё обновляется', url }; + } + return { status: 'unavailable', label: '✕ Недоступен', url }; +} + function setText(node, text) { if (node) node.textContent = String(text || ''); } @@ -64,6 +86,7 @@ export function openArweaveAttachmentManager({ storagePwd, gateway, onSelect, + selectedTxIds = [], } = {}) { const cleanLogin = String(login || '').trim(); const cleanStoragePwd = String(storagePwd || '').trim(); @@ -82,6 +105,7 @@ export function openArweaveAttachmentManager({ let priceInfo = null; let balanceInfo = null; let autoOpenedFileDialog = false; + const selectedTxIdSet = new Set((Array.isArray(selectedTxIds) ? selectedTxIds : []).map((item) => String(item || '').trim()).filter(Boolean)); function selectedWallet() { return wallets.find((item) => String(item.id) === String(selectedWalletId)) || wallets[0] || null; @@ -95,7 +119,7 @@ export function openArweaveAttachmentManager({ } function finish(resolve, attachment) { - const item = addToHistory(cleanLogin, attachment); + const item = addArweaveAttachmentToHistory(cleanLogin, attachment); if (typeof onSelect === 'function') onSelect(item); close(resolve, item); } @@ -131,13 +155,14 @@ export function openArweaveAttachmentManager({ +

Для надёжности лучше сначала загрузить файл в Arweave, дождаться доступности в журнале, а потом добавить его из истории загрузок.

- +
@@ -313,29 +338,27 @@ export function openArweaveAttachmentManager({ }; const showHistory = () => { - const rows = readHistory(cleanLogin); + const rows = readArweaveAttachmentHistory(cleanLogin).slice().reverse(); root.innerHTML = `