// ===== Globals ===== let userPublicKey = ''; let userAddress = ''; let userName = ''; let isAuthenticated = false; let allNames = []; let authStatus = 'idle'; let namesStatus = 'idle'; let allResults = []; let metadataArray = []; // Pagination let currentPage = 1; let itemsPerPage = 10; let totalResults = 0; let totalSize = 0; let currentServiceFilter = 'ALL'; // 'ALL' means no service filter const infoDetails = ` Click the identifier to "delete" content.
(This will replace it with a blank file.)

Click the file select icon to "edit" content.
(This will replace it with a selected file.)`; // Data sets for chips-based filtering let masterResults = []; // full unfiltered list for current name let filteredResults = []; // results after chips selection let selectedServices = new Set(); // inclusion set; empty => show all let serviceCounts = {}; // { service: count } // ===== DOM & UI helpers ===== const contentPage = document.getElementById('content-page'); const authButton = document.getElementById('auth-button'); const nameSwitcherEl = document.getElementById('name-switcher'); const nameSelectEl = document.getElementById('name-select'); const loadingOverlay = document.getElementById('loading-overlay'); function showSpinner(){ if (loadingOverlay) loadingOverlay.style.display = 'flex'; } function hideSpinner(){ if (loadingOverlay) loadingOverlay.style.display = 'none'; } function updateAuthUI() { if (isAuthenticated) { authButton.style.display = 'none'; nameSwitcherEl.style.display = 'flex'; nameSelectEl.innerHTML = ''; for (const n of allNames) { const opt = document.createElement('option'); opt.value = n.name; opt.textContent = n.name || '(no name)'; if (n.name === userName) opt.selected = true; nameSelectEl.appendChild(opt); } } else { authButton.style.display = 'inline-block'; nameSwitcherEl.style.display = 'none'; } authButton.disabled = authStatus === 'loading'; authButton.textContent = authStatus === 'loading' ? 'Authenticating…' : 'Authenticate'; } // Wire listeners authButton.addEventListener('click', () => { if (authStatus === 'loading') return; accountLogin(); }); nameSelectEl.addEventListener('change', (e) => { switchActiveName(e.target.value); }); document.getElementById('items-per-page-dropdown').addEventListener('change', async function() { showSpinner(); contentPage.style.display = "none"; try { itemsPerPage = parseInt(this.value, 10); currentPage = 1; await fetchPage(); } finally { contentPage.style.display = "block"; hideSpinner(); } }); updateAuthUI(); // ===== QDN Preview helpers ===== function buildQdnParams(base) { const p = { ...base }; if (!p.identifier || p.identifier === '' || p.identifier === 'default') delete p.identifier; return p; } function b64ToBytes(b64) { const bin = atob(b64); const len = bin.length; const bytes = new Uint8Array(len); for (let i=0;i { overlay.style.display='none'; }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') overlay.style.display='none'; }); document.body.appendChild(overlay); } overlay.querySelector('img').src = dataUrl; overlay.style.display = 'flex'; } // ===== Auth & Names ===== async function accountLogin() { try { showSpinner(); authStatus = 'loading'; updateAuthUI(); const account = await qortalRequest({ action: "GET_USER_ACCOUNT" }); contentPage.style.display = "none"; document.getElementById('account-details').innerHTML = 'Loading...'; userAddress = account.address ? account.address : 'Address unavailable'; userPublicKey = account.publicKey ? account.publicKey : 'Public key unavailable'; let names = []; if (userAddress && userAddress !== 'Address unavailable') { try { namesStatus = 'loading'; const res = await qortalRequest({ action: "GET_ACCOUNT_NAMES", address: userAddress }); names = Array.isArray(res) ? res : []; allNames = names.map(n => ({ name: typeof n?.name === 'string' ? n.name : '', owner: typeof n?.owner === 'string' ? n.owner : userAddress })); let primary = ''; try { const maybePrimary = await qortalRequest({ action: "GET_PRIMARY_NAME", address: userAddress }); primary = typeof maybePrimary === 'string' ? maybePrimary : ''; } catch {} if (primary) userName = primary; else if (allNames.length > 0 && allNames[0].name) userName = allNames[0].name; else userName = 'Name unavailable'; namesStatus = 'succeeded'; } catch { allNames = [{ name: '', owner: userAddress }]; userName = 'Name unavailable'; namesStatus = 'failed'; } } else { allNames = [{ name: '', owner: userAddress }]; userName = 'Name unavailable'; namesStatus = 'failed'; } isAuthenticated = true; document.getElementById('info-details').innerHTML = infoDetails; document.getElementById('account-details').innerHTML = `${userAddress}
${userName}`; updateAuthUI(); currentServiceFilter = 'ALL'; currentPage = 1; await loadAllResults(); await fetchPage(); contentPage.style.display = "block"; authStatus = 'succeeded'; updateAuthUI(); hideSpinner(); } catch (error) { console.error('Error fetching account details:', error); authStatus = 'failed'; updateAuthUI(); document.getElementById('account-details').innerHTML = `Error fetching account details: ${error}`; hideSpinner(); } } async function switchActiveName(newName) { if (!newName || newName === userName) return; showSpinner(); contentPage.style.display = "none"; userName = newName; currentServiceFilter = 'ALL'; currentPage = 1; document.getElementById('account-details').innerHTML = `${userAddress}
${userName}`; updateAuthUI(); try { await loadAllResults(); await fetchPage(); } finally { contentPage.style.display = "block"; hideSpinner(); } } async function loadAllResults() { // Fetch all resources for the active name, including metadata, unfiltered const resp = await fetch(`/arbitrary/resources/search?name=${encodeURIComponent(userName)}&includemetadata=true&exactmatchnames=true&mode=ALL`); if (!resp.ok) throw new Error('Failed loading resources'); masterResults = await resp.json(); // Sort newest first (created/updated) masterResults.sort((a, b) => (b.updated || b.created || 0) - (a.updated || a.created || 0)); // Build counts serviceCounts = {}; for (const r of masterResults) { const svc = r.service || 'UNKNOWN'; serviceCounts[svc] = (serviceCounts[svc] || 0) + 1; } // Reset selection (none selected => show all) selectedServices = new Set(); applyServiceFilter(); buildServiceChips(); } function buildServiceChips() { const container = document.getElementById('service-chips-container'); if (!container) return; container.innerHTML = ''; const services = Object.keys(serviceCounts).sort(); services.forEach(svc => { const chip = document.createElement('div'); chip.className = 'chip' + (selectedServices.has(svc) ? ' selected' : ''); chip.setAttribute('data-service', svc); const label = document.createElement('span'); label.className = 'label'; label.textContent = svc; const count = document.createElement('span'); count.className = 'count'; count.textContent = serviceCounts[svc]; chip.appendChild(label); chip.appendChild(count); chip.addEventListener('click', async () => { // Toggle selection if (selectedServices.has(svc)) selectedServices.delete(svc); else selectedServices.add(svc); // Visual state chip.classList.toggle('selected'); // Apply filter + re-render showSpinner(); contentPage.style.display = "none"; try { applyServiceFilter(); currentPage = 1; await fetchPage(); } finally { contentPage.style.display = "block"; hideSpinner(); } }); container.appendChild(chip); }); } function applyServiceFilter() { if (selectedServices.size === 0) { filteredResults = masterResults.slice(); } else { filteredResults = masterResults.filter(r => selectedServices.has(r.service)); } totalResults = filteredResults.length; totalSize = filteredResults.reduce((acc, r) => acc + (r.size || 0), 0); } // ===== Data fetchers ===== async function fetchPage() { try { if (!userName || userName === 'Name unavailable') return; // Client-side pagination using filteredResults const contentDetails = document.getElementById('content-details'); contentDetails.innerHTML = '

Loading...

'; const start = (currentPage - 1) * itemsPerPage; const pageItems = filteredResults.slice(start, start + itemsPerPage); buildContentTable(pageItems); } catch (error) { console.error('Error fetching page:', error); document.getElementById('content-details').innerHTML = `

Error: ${error.message}

`; } } // ===== Table & pagination ===== function buildContentTable(results) { const contentDetailsDiv = document.getElementById('content-details'); const contentSummaryDiv = document.getElementById('content-summary'); if (results.length === 0) { contentDetailsDiv.innerHTML = '

No results found.

'; contentSummaryDiv.innerHTML = ''; document.getElementById('pagination-top').innerHTML = ''; document.getElementById('pagination-bottom').innerHTML = ''; return; } results.sort((a, b) => (b.updated || b.created) - (a.updated || a.created)); let tableHtml = ''; tableHtml += ``; metadataArray = []; for (const result of results) { const identifier = (result.identifier === undefined) ? 'default' : result.identifier; let createdString = new Date(result.created).toLocaleString(); if (isNaN(new Date(result.created))) createdString = 'Unknown'; let updatedString = new Date(result.updated).toLocaleString(); if (isNaN(new Date(result.updated))) updatedString = 'Never'; const sizeString = formatSize(result.size || 0); let metadataKeys = ''; let metadataIndex = -1; if (result.metadata) { metadataIndex = metadataArray.length; metadataArray.push(result.metadata); metadataKeys = Object.keys(result.metadata).join(', '); } tableHtml += ``; } tableHtml += `
Service Identifier Metadata Preview Size Created / Updated
${result.service} Edit Delete ${identifier} ${generatePreviewHTML(result, userName, identifier)} ${sizeString} ${createdString}
${updatedString}
`; const startItem = (currentPage - 1) * itemsPerPage + 1; const endItem = Math.min(startItem + itemsPerPage - 1, totalResults); contentSummaryDiv.innerHTML = `

${startItem}-${endItem} of ${totalResults} results

Total Size: ${formatSize(totalSize)}

`; const paginationHTML = buildPaginationControls(); document.getElementById('pagination-top').innerHTML = paginationHTML; document.getElementById('pagination-bottom').innerHTML = paginationHTML; contentDetailsDiv.innerHTML = tableHtml; initPreviews(); document.querySelectorAll('.clickable-delete').forEach(el => { el.addEventListener('click', function() { deleteContent(this.getAttribute('data-service'), this.getAttribute('data-identifier')); }); }); document.querySelectorAll('.clickable-edit').forEach(el => { el.addEventListener('click', function() { editContent(this.getAttribute('data-service'), this.getAttribute('data-identifier')); }); }); document.querySelectorAll('.clickable-metadata').forEach(el => { el.addEventListener('click', function() { const idx = parseInt(this.getAttribute('data-metadata-index'), 10); if (!isNaN(idx) && idx >= 0) openMetadataDialog(metadataArray[idx]); else alert('No metadata available.'); }); }); addPaginationEventHandlers(); } function buildPaginationControls() { const totalPages = Math.ceil(totalResults / itemsPerPage); if (totalPages <= 1) return ''; let html = ''; if (currentPage > 1) { html += `««`; html += `«`; } else { html += `«««`; } for (let p = 1; p <= totalPages; p++) { html += (p === currentPage) ? `${p}` : `${p}`; } if (currentPage < totalPages) { html += `»`; html += `»»`; } else { html += `»»»`; } return html; } function addPaginationEventHandlers() { document.querySelectorAll('.pagination-link').forEach(link => { link.addEventListener('click', async function() { const newPage = parseInt(this.getAttribute('data-page'), 10); if (!isNaN(newPage)) { showSpinner(); contentPage.style.display = "none"; try { currentPage = newPage; await fetchPage(); } finally { contentPage.style.display = "block"; hideSpinner(); } } }); }); } function formatSize(size) { if (size > (1024*1024*1024*1024)) return (size / (1024*1024*1024*1024)).toFixed(2) + ' TB'; if (size > (1024*1024*1024)) return (size / (1024*1024*1024)).toFixed(2) + ' GB'; if (size > (1024*1024)) return (size / (1024*1024)).toFixed(2) + ' MB'; if (size > 1024) return (size / 1024).toFixed(2) + ' KB'; return (size || 0) + ' B'; } // ===== Preview rendering ===== function generatePreviewHTML(result, userName, identifier) { const safeName = (result.name || userName || '').replace(/"/g, '"'); const safeService = (result.service || '').replace(/"/g, '"'); const safeIdent = (identifier || 'default').replace(/"/g, '"'); return `
0% Loaded
`; } function initPreviews() { const holders = document.querySelectorAll('.preview-holder'); holders.forEach((el) => { const svc = (el.getAttribute('data-service') || '').toUpperCase(); const ident = el.getAttribute('data-identifier') || 'default'; const nm = el.getAttribute('data-name') || userName; loadPreviewInto(el, { service: svc, identifier: ident, name: nm }); }); } function isPrivateService(service) { const s = (service || '').toUpperCase(); return s.endsWith('_PRIVATE') || ['QCHAT_ATTACHMENT_PRIVATE','ATTACHMENT_PRIVATE','FILE_PRIVATE','IMAGE_PRIVATE','VIDEO_PRIVATE','AUDIO_PRIVATE','VOICE_PRIVATE','DOCUMENT_PRIVATE','MAIL_PRIVATE','MESSAGE_PRIVATE'].includes(s); } function getBaseServiceKind(service) { const s = (service || '').toUpperCase(); if (s.includes('IMAGE') || s.includes('THUMBNAIL')) return 'image'; if (s.includes('VIDEO')) return 'video'; if (s.includes('AUDIO') || s.includes('VOICE') || s.includes('PODCAST')) return 'audio'; if (s.includes('DOCUMENT') || s.includes('BLOG') || s.includes('COMMENT') || s.includes('JSON') || s.includes('CODE')) return 'text'; if (s.includes('FILE') || s.includes('ATTACHMENT')) return 'file'; return 'file'; } async function waitForResourceReady({ name, service, identifier, initialBuild = true, timeoutMs = 60000, intervalMs = 800, onProgress }) { const start = Date.now(); if (initialBuild) { try { await qortalRequest(buildQdnParams({ action: 'GET_QDN_RESOURCE_STATUS', name, service, identifier, build: true })); } catch (e) { /* ignore */ } } while (true) { try { const status = await qortalRequest(buildQdnParams({ action: 'GET_QDN_RESOURCE_STATUS', name, service, identifier })); let percent = 0; if (typeof status?.percentLoaded === 'number') percent = status.percentLoaded; else if (status?.localChunkCount && status?.totalChunkCount) percent = Math.floor((status.localChunkCount / status.totalChunkCount) * 100); if (onProgress && Number.isFinite(percent)) onProgress(Math.max(0, Math.min(100, Math.floor(percent)))); const ready = status && (status.status === 'READY' || percent >= 100 || (status.localChunkCount && status.totalChunkCount && status.localChunkCount >= status.totalChunkCount)); if (ready) return status; } catch (e) { /* ignore transient */ } if (Date.now() - start > timeoutMs) throw new Error('Resource not ready (timeout)'); await new Promise(r => setTimeout(r, intervalMs)); } } async function loadPreviewInto(container, ctx) { const set = (el) => { container.innerHTML=''; container.appendChild(el); }; const looksHtml = (txt) => /<(?:!doctype|html|head|body|div|p|span|img|video|audio|iframe|section|article)/i.test(txt); try { // Wait until built; update percent text while polling await waitForResourceReady({ name: ctx.name, service: ctx.service, identifier: ctx.identifier, initialBuild: true, onProgress: (pct) => { container.textContent = `${pct}% Loaded`; } }); const service = ctx.service; const identifier = ctx.identifier; const name = ctx.name; const lower = service.toLowerCase(); const baseKind = getBaseServiceKind(service); const isPriv = isPrivateService(service); // Text-like services (public and private) const textServices = ['blog','blog_post','blog_comment','document','game','json','code']; const isText = textServices.some(t => lower.includes(t)) || baseKind === 'text'; if (isPriv) { // Private path: always fetch base64, then decrypt via DECRYPT_DATA const encB64 = await qortalRequest(buildQdnParams({ action:'FETCH_QDN_RESOURCE', name, service, identifier, encoding:'base64', rebuild:false })); const decB64 = await qortalRequest({ action:'DECRYPT_DATA', encryptedData: encB64 }); if (isText) { // Decode to UTF-8 string const bin = atob(decB64); const bytes = new Uint8Array(bin.length); for (let i=0;i openImageOverlayFromDataUrl(url)); set(img); return; } if (baseKind === 'video') { const video = document.createElement('video'); video.controls = true; video.className = 'preview-video'; video.src = url; set(video); return; } if (baseKind === 'audio') { const audio = document.createElement('audio'); audio.controls = true; audio.className = 'preview-audio'; audio.src = url; set(audio); return; } const a = document.createElement('a'); a.textContent = 'Open'; a.href = url; a.target = '_blank'; a.rel = 'noopener'; set(a); return; } } else { // Public path (existing behavior) if (isText) { let resp; try { resp = await qortalRequest(buildQdnParams({ action:'FETCH_QDN_RESOURCE', name, service, identifier, rebuild: false })); } catch (e) { const b64 = await qortalRequest(buildQdnParams({ action:'FETCH_QDN_RESOURCE', name, service, identifier, encoding:'base64', rebuild:false })); const bin = atob(b64); const bytes = new Uint8Array(bin.length); for (let i=0;i Blob URL const b64 = await qortalRequest(buildQdnParams({ action:'FETCH_QDN_RESOURCE', name, service, identifier, encoding:'base64', rebuild:false })); const bytes = b64ToBytes(b64); const mime = detectMimeFromBytes(bytes) || (baseKind==='image' ? 'image/*' : baseKind==='video' ? 'video/mp4' : baseKind==='audio' ? 'audio/mpeg' : 'application/octet-stream'); const blob = new Blob([bytes], { type: mime }); const url = URL.createObjectURL(blob); if (baseKind === 'image') { const img = document.createElement('img'); img.src = url; img.alt = identifier; img.className = 'preview-image'; img.addEventListener('click', () => openImageOverlayFromDataUrl(url)); set(img); return; } if (baseKind === 'video') { const video = document.createElement('video'); video.controls = true; video.className = 'preview-video'; video.src = url; set(video); return; } if (baseKind === 'audio') { const audio = document.createElement('audio'); audio.controls = true; audio.className = 'preview-audio'; audio.src = url; set(audio); return; } const a = document.createElement('a'); a.textContent = 'Open'; a.href = url; a.target = '_blank'; a.rel = 'noopener'; set(a); return; } } catch (err) { console.error('Preview load error for', ctx, err); container.textContent = 'Preview unavailable'; } } // ===== Delete/Edit/Metadata/Publish (from user's file, preserved) ===== async function deleteContent(service, identifier) { try { if (!userName || userName === 'Name unavailable') { return; } showPublishModal("Please wait..."); // Fetch existing metadata let existingMetadata = {}; try { const metadataResponse = await fetch(`/arbitrary/resources/search?name=${userName}&service=${service}&identifier=${identifier}&includemetadata=true&exactmatchnames=true&mode=ALL`); if (metadataResponse.ok) { const metadataResults = await metadataResponse.json(); if (metadataResults.length > 0 && metadataResults[0].metadata) { existingMetadata = metadataResults[0].metadata; } } } catch (err) { console.error('Error fetching existing metadata:', err); } const emptyFile = new Blob([], { type: 'application/octet-stream' }); const deleteIdent = (identifier === 'default') ? '' : identifier; // Prepare the publish parameters, including existing metadata if available const publishParams = { action: "PUBLISH_QDN_RESOURCE", name: userName, service: service, identifier: deleteIdent, file: emptyFile }; // List of metadata fields to delete const metadataFields = ['filename', 'title', 'description']; // Add existing metadata fields to publishParams if they exist for (const field of metadataFields) { if (existingMetadata[field]) { publishParams[field] = "deleted"; } } if (existingMetadata["category"]) { publishParams["category"] = "UNCATEGORIZED"; } if (existingMetadata["tags"]) { publishParams["tag1"] = "deleted"; } // Proceed with publishing using publishWithFeedback try { const response = await publishWithFeedback(publishParams); console.log('Content deleted successfully'); } catch (error) { console.error('Error deleting content:', error); } } catch (error) { console.error('Error deleting content:', error); } } async function editContent(service, identifier) { try { if (!userName || userName === 'Name unavailable') { return; } showPublishModal("Please wait..."); // Fetch existing metadata let existingMetadata = {}; try { const metadataResponse = await fetch(`/arbitrary/resources/search?name=${userName}&service=${service}&identifier=${identifier}&includemetadata=true&exactmatchnames=true&mode=ALL`); if (metadataResponse.ok) { const metadataResults = await metadataResponse.json(); if (metadataResults.length > 0 && metadataResults[0].metadata) { existingMetadata = metadataResults[0].metadata; } } } catch (err) { console.error('Error fetching existing metadata:', err); } const editIdent = (identifier === 'default') ? '' : identifier; // Prepare the publish parameters const publishParams = { action: "PUBLISH_QDN_RESOURCE", name: userName, service: service, identifier: editIdent, // 'file' will be added below after obtaining the edited or selected file }; const textServices = ['BLOG', 'BLOG_POST', 'BLOG_COMMENT', 'DOCUMENT']; if (textServices.includes(service)) { // For text types, fetch the current content let contentUrl = `/arbitrary/${service}/${userName}/${identifier}`; let content = ''; try { const contentResponse = await fetch(contentUrl); if (contentResponse.ok) { content = await contentResponse.text(); } else { content = 'Error fetching content'; } } catch (err) { content = 'Error fetching content'; } // Open a modal dialog to edit the content let editedContent = await openTextEditorDialog(content); if (editedContent === null) { // User cancelled return; } // Create a new Blob with the edited content const editedFile = new Blob([editedContent], { type: 'text/plain' }); publishParams.file = editedFile; // Add the edited file to publishParams } else { closePublishModal(); // For other types, prompt the user to select a new file const input = document.createElement('input'); input.type = 'file'; input.click(); const selectedFilePromise = new Promise((resolve, reject) => { input.onchange = (event) => { const file = event.target.files[0]; resolve(file); }; input.onerror = reject; }); const selectedFile = await selectedFilePromise; publishParams.file = selectedFile; // Add the selected file to publishParams } // Open metadata editor dialog let updatedMetadata = await openMetadataEditorDialog(existingMetadata); if (updatedMetadata === null) { // User cancelled return; } // Update 'publishParams' with 'updatedMetadata' const metadataFields = ['filename', 'title', 'description', 'category']; for (const field of metadataFields) { if (updatedMetadata[field]) { publishParams[field] = updatedMetadata[field]; } else { delete publishParams[field]; } } // Handle tags if (updatedMetadata['tags']) { const tagsArray = updatedMetadata['tags'].split(',').map(tag => tag.trim()).filter(tag => tag); for (let i = 1; i <= 5; i++) { if (tagsArray[i - 1]) { publishParams[`tag${i}`] = tagsArray[i - 1]; } else { delete publishParams[`tag${i}`]; } } } else { // Remove tags if none provided for (let i = 1; i <= 5; i++) { delete publishParams[`tag${i}`]; } } // Proceed with publishing using publishWithFeedback try { const response = await publishWithFeedback(publishParams); console.log('Content edited successfully'); // Optionally, refresh the content display // fetchContent(); } catch (error) { console.error('Error editing content:', error); } } catch (error) { console.error('Error editing content:', error); } } function openTextEditorDialog(content) { return new Promise((resolve, reject) => { // Create the modal overlay const modalOverlay = document.createElement('div'); modalOverlay.style.position = 'fixed'; modalOverlay.style.top = '0'; modalOverlay.style.left = '0'; modalOverlay.style.width = '100%'; modalOverlay.style.height = '100%'; modalOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; modalOverlay.style.display = 'flex'; modalOverlay.style.justifyContent = 'center'; modalOverlay.style.alignItems = 'center'; modalOverlay.style.zIndex = '1000'; // Create the modal content container const modalContent = document.createElement('div'); modalContent.style.backgroundColor = '#2d3749'; // Use background color from main content modalContent.style.color = '#c9d2d9'; // Use text color from your CSS modalContent.style.padding = '20px'; modalContent.style.borderRadius = '25px'; // Match border radius from your CSS modalContent.style.maxWidth = '600px'; modalContent.style.width = '90%'; modalContent.style.fontFamily = "'Lexend', sans-serif"; // Use the same font modalContent.style.lineHeight = '1.6'; // Consistent line height // Create the textarea for editing const textarea = document.createElement('textarea'); textarea.style.width = '100%'; textarea.style.height = '300px'; textarea.style.backgroundColor = '#3d4452'; // Use background color from main content textarea.style.color = '#c9d2d9'; // Use text color from your CSS textarea.value = content; // Create the button container const buttonContainer = document.createElement('div'); buttonContainer.style.textAlign = 'right'; buttonContainer.style.marginTop = '10px'; // Create the Save and Cancel buttons const saveButton = document.createElement('button'); saveButton.textContent = 'Save'; const cancelButton = document.createElement('button'); cancelButton.textContent = 'Cancel'; cancelButton.style.marginRight = '10px'; buttonContainer.appendChild(cancelButton); buttonContainer.appendChild(saveButton); modalContent.appendChild(textarea); modalContent.appendChild(buttonContainer); modalOverlay.appendChild(modalContent); document.body.appendChild(modalOverlay); // Event listeners for the buttons cancelButton.addEventListener('click', () => { document.body.removeChild(modalOverlay); closePublishModal(); resolve(null); }); saveButton.addEventListener('click', () => { const editedContent = textarea.value; document.body.removeChild(modalOverlay); resolve(editedContent); }); }); } function openMetadataDialog(metadata) { // Create the modal overlay const modalOverlay = document.createElement('div'); modalOverlay.style.position = 'fixed'; modalOverlay.style.top = '0'; modalOverlay.style.left = '0'; modalOverlay.style.width = '100%'; modalOverlay.style.height = '100%'; modalOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; modalOverlay.style.display = 'flex'; modalOverlay.style.justifyContent = 'center'; modalOverlay.style.alignItems = 'center'; modalOverlay.style.zIndex = '1000'; // Create the modal content container const modalContent = document.createElement('div'); modalContent.style.backgroundColor = '#2d3749'; // Use background color from main content modalContent.style.color = '#c9d2d9'; // Use text color from your CSS modalContent.style.padding = '20px'; modalContent.style.borderRadius = '25px'; // Match border radius from your CSS modalContent.style.maxWidth = '600px'; modalContent.style.width = '90%'; modalContent.style.fontFamily = "'Lexend', sans-serif"; // Use the same font modalContent.style.lineHeight = '1.6'; // Consistent line height // Create the content display const contentDiv = document.createElement('div'); contentDiv.style.maxHeight = '400px'; contentDiv.style.overflowY = 'auto'; // Build the metadata display for (let key in metadata) { const keyElement = document.createElement('strong'); keyElement.textContent = key + ': '; keyElement.style.color = '#ffffff'; // Make keys stand out const valueElement = document.createElement('span'); valueElement.textContent = metadata[key]; const lineBreak = document.createElement('br'); contentDiv.appendChild(keyElement); contentDiv.appendChild(valueElement); contentDiv.appendChild(lineBreak); } // Create the Close button const closeButton = document.createElement('button'); closeButton.textContent = 'Close'; closeButton.style.marginTop = '10px'; modalContent.appendChild(contentDiv); modalContent.appendChild(closeButton); modalOverlay.appendChild(modalContent); document.body.appendChild(modalOverlay); // Event listener for the Close button closeButton.addEventListener('click', () => { document.body.removeChild(modalOverlay); }); } function openMetadataEditorDialog(existingMetadata) { return new Promise((resolve, reject) => { // Create the modal overlay const modalOverlay = document.createElement('div'); modalOverlay.style.position = 'fixed'; modalOverlay.style.top = '0'; modalOverlay.style.left = '0'; modalOverlay.style.width = '100%'; modalOverlay.style.height = '100%'; modalOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; modalOverlay.style.display = 'flex'; modalOverlay.style.justifyContent = 'center'; modalOverlay.style.alignItems = 'center'; modalOverlay.style.zIndex = '1000'; // Create the modal content container const modalContent = document.createElement('div'); modalContent.style.backgroundColor = '#2d3749'; modalContent.style.color = '#c9d2d9'; modalContent.style.padding = '20px'; modalContent.style.borderRadius = '25px'; modalContent.style.maxWidth = '600px'; modalContent.style.width = '90%'; modalContent.style.fontFamily = "'Lexend', sans-serif"; modalContent.style.lineHeight = '1.6'; // Create the form const form = document.createElement('form'); const fields = ['filename', 'title', 'description', 'category', 'tags']; // Define the categories const categories = [ { value: '', display: '' }, { value: 'ART', display: 'Art and Design' }, { value: 'AUTOMOTIVE', display: 'Automotive' }, { value: 'BEAUTY', display: 'Beauty' }, { value: 'BOOKS', display: 'Books and Reference' }, { value: 'BUSINESS', display: 'Business' }, { value: 'COMMUNICATIONS', display: 'Communications' }, { value: 'CRYPTOCURRENCY', display: 'Cryptocurrency and Blockchain' }, { value: 'CULTURE', display: 'Culture' }, { value: 'DATING', display: 'Dating' }, { value: 'DESIGN', display: 'Design' }, { value: 'ENTERTAINMENT', display: 'Entertainment' }, { value: 'EVENTS', display: 'Events' }, { value: 'FAITH', display: 'Faith and Religion' }, { value: 'FASHION', display: 'Fashion' }, { value: 'FINANCE', display: 'Finance' }, { value: 'FOOD', display: 'Food and Drink' }, { value: 'GAMING', display: 'Gaming' }, { value: 'GEOGRAPHY', display: 'Geography' }, { value: 'HEALTH', display: 'Health' }, { value: 'HISTORY', display: 'History' }, { value: 'HOME', display: 'Home' }, { value: 'KNOWLEDGE', display: 'Knowledge Share' }, { value: 'LANGUAGE', display: 'Language' }, { value: 'LIFESTYLE', display: 'Lifestyle' }, { value: 'MANUFACTURING', display: 'Manufacturing' }, { value: 'MAPS', display: 'Maps and Navigation' }, { value: 'MUSIC', display: 'Music' }, { value: 'NEWS', display: 'News' }, { value: 'OTHER', display: 'Other' }, { value: 'PETS', display: 'Pets' }, { value: 'PHILOSOPHY', display: 'Philosophy' }, { value: 'PHOTOGRAPHY', display: 'Photography' }, { value: 'POLITICS', display: 'Politics' }, { value: 'PRODUCE', display: 'Products and Services' }, { value: 'PRODUCTIVITY', display: 'Productivity' }, { value: 'PSYCHOLOGY', display: 'Psychology' }, { value: 'QORTAL', display: 'Qortal' }, { value: 'SCIENCE', display: 'Science' }, { value: 'SELF_CARE', display: 'Self Care' }, { value: 'SELF_SUFFICIENCY', display: 'Self-Sufficiency and Homesteading' }, { value: 'SHOPPING', display: 'Shopping' }, { value: 'SOCIAL', display: 'Social' }, { value: 'SOFTWARE', display: 'Software' }, { value: 'SPIRITUALITY', display: 'Spirituality' }, { value: 'SPORTS', display: 'Sports' }, { value: 'STORYTELLING', display: 'Storytelling' }, { value: 'TECHNOLOGY', display: 'Technology' }, { value: 'TOOLS', display: 'Tools' }, { value: 'TRAVEL', display: 'Travel' }, { value: 'UNCATEGORIZED', display: 'Uncategorized' }, { value: 'VIDEO', display: 'Video' }, { value: 'WEATHER', display: 'Weather' }, ]; fields.forEach(field => { const label = document.createElement('label'); label.textContent = field.charAt(0).toUpperCase() + field.slice(1) + ':'; label.style.display = 'block'; label.style.marginTop = '10px'; let input; if (field === 'category') { // Create a select element for category input = document.createElement('select'); input.name = field; input.style.width = '100%'; input.style.padding = '5px'; input.style.marginTop = '5px'; // Add options to the select element categories.forEach(category => { const option = document.createElement('option'); option.value = category.value; option.textContent = category.display; input.appendChild(option); }); // Set the selected value if it exists in existingMetadata if (existingMetadata[field]) { input.value = existingMetadata[field]; } else { input.value = ''; // Default to blank line } } else { // Create an input element for other fields input = document.createElement('input'); input.type = 'text'; input.name = field; input.style.width = '100%'; input.style.padding = '5px'; input.style.marginTop = '5px'; if (existingMetadata[field]) { if (field === 'tags' && Array.isArray(existingMetadata[field])) { input.value = existingMetadata[field].join(', '); } else { input.value = existingMetadata[field]; } } else { input.placeholder = field.charAt(0).toUpperCase() + field.slice(1); } } label.appendChild(input); form.appendChild(label); }); // Create the button container const buttonContainer = document.createElement('div'); buttonContainer.style.textAlign = 'right'; buttonContainer.style.marginTop = '20px'; // Create the Save and Cancel buttons const saveButton = document.createElement('button'); saveButton.textContent = 'Save'; saveButton.type = 'submit'; saveButton.style.marginLeft = '10px'; const cancelButton = document.createElement('button'); cancelButton.textContent = 'Cancel'; cancelButton.type = 'button'; buttonContainer.appendChild(cancelButton); buttonContainer.appendChild(saveButton); form.appendChild(buttonContainer); modalContent.appendChild(form); modalOverlay.appendChild(modalContent); document.body.appendChild(modalOverlay); // Event listeners cancelButton.addEventListener('click', (event) => { event.preventDefault(); document.body.removeChild(modalOverlay); closePublishModal(); resolve(null); }); form.addEventListener('submit', (event) => { event.preventDefault(); // Collect the metadata const formData = new FormData(form); const updatedMetadata = {}; fields.forEach(field => { const value = formData.get(field); if (value) { updatedMetadata[field] = value; } }); document.body.removeChild(modalOverlay); resolve(updatedMetadata); }); }); } let publishModal = null; function showPublishModal(message) { if (!publishModal) { // Create the modal publishModal = document.createElement('div'); publishModal.style.position = 'fixed'; publishModal.style.top = '0'; publishModal.style.left = '0'; publishModal.style.width = '100%'; publishModal.style.height = '100%'; publishModal.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; publishModal.style.display = 'flex'; publishModal.style.justifyContent = 'center'; publishModal.style.alignItems = 'center'; publishModal.style.zIndex = '1000'; // Create the modal content container const modalContent = document.createElement('div'); modalContent.style.backgroundColor = '#2d3749'; // Use background color from main content modalContent.style.padding = '20px'; modalContent.style.borderRadius = '5px'; modalContent.style.maxWidth = '400px'; modalContent.style.width = '90%'; modalContent.style.textAlign = 'center'; // Create the message element const messageElement = document.createElement('p'); messageElement.id = 'publish-modal-message'; messageElement.textContent = message; modalContent.appendChild(messageElement); publishModal.appendChild(modalContent); document.body.appendChild(publishModal); } else { // Update the message const messageElement = publishModal.querySelector('#publish-modal-message'); messageElement.textContent = message; // Remove any buttons (Retry/Cancel) if they exist const buttons = publishModal.querySelector('#publish-modal-buttons'); if (buttons) { buttons.remove(); } } if (message == "Publish TX submitted! Confirmation needed.") { // Create buttons container const buttonsContainer = document.createElement('div'); buttonsContainer.id = 'publish-modal-buttons'; buttonsContainer.style.marginTop = '20px'; // Create Close button const closeButton = document.createElement('button'); closeButton.textContent = 'Close'; closeButton.addEventListener('click', () => { closePublishModal(); }); buttonsContainer.appendChild(closeButton); // Append button to modal content const modalContent = publishModal.firstChild; modalContent.appendChild(buttonsContainer); } } function closePublishModal() { if (publishModal) { document.body.removeChild(publishModal); publishModal = null; } } function showPublishErrorModal(errorMessage, onRetry, onCancel) { if (publishModal) { // Update the message const messageElement = publishModal.querySelector('#publish-modal-message'); messageElement.textContent = errorMessage; // Remove any existing buttons const existingButtons = publishModal.querySelector('#publish-modal-buttons'); if (existingButtons) { existingButtons.remove(); } // Create buttons container const buttonsContainer = document.createElement('div'); buttonsContainer.id = 'publish-modal-buttons'; buttonsContainer.style.marginTop = '20px'; // Create Retry button const retryButton = document.createElement('button'); retryButton.textContent = 'Retry'; retryButton.style.marginRight = '10px'; retryButton.addEventListener('click', () => { onRetry(); }); // Create Cancel button const cancelButton = document.createElement('button'); cancelButton.textContent = 'Cancel'; cancelButton.addEventListener('click', () => { onCancel(); }); buttonsContainer.appendChild(retryButton); buttonsContainer.appendChild(cancelButton); // Append buttons to modal content const modalContent = publishModal.firstChild; modalContent.appendChild(buttonsContainer); } } async function publishWithFeedback(publishParams) { return new Promise(async (resolve, reject) => { async function attemptPublish() { try { // Show modal with "Attempting to publish, please wait..." showPublishModal("Attempting to publish, please wait..."); const response = await qortalRequest(publishParams); // Close modal resolve(response); showPublishModal("Publish TX submitted! Confirmation needed."); } catch (error) { // Update modal to show error message and Retry/Cancel buttons showPublishErrorModal(`Publishing failed: ${error.message}`, () => { // On Retry attemptPublish(); }, () => { // On Cancel closePublishModal(); reject(error); }); } } await attemptPublish(); }); }