diff --git a/module.json b/module.json index f7ba39a..55052a4 100644 --- a/module.json +++ b/module.json @@ -2,7 +2,7 @@ "id": "kosmos-storage-audit", "title": "Kosmos Storage Audit", "description": "Analyzes media references and risky storage locations across Foundry data and public roots.", - "version": "0.0.12", + "version": "0.0.13", "compatibility": { "minimum": "13", "verified": "13" diff --git a/scripts/apps/audit-report-app.js b/scripts/apps/audit-report-app.js index 9eda157..9a14d00 100644 --- a/scripts/apps/audit-report-app.js +++ b/scripts/apps/audit-report-app.js @@ -34,7 +34,7 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV async _prepareContext() { const findings = this.#analysis?.findings ?? []; const visibleFindings = this.#showAll ? findings : findings.filter(f => f.severity !== "info"); - const groupedFindings = groupFindings(visibleFindings); + const groupedFindings = await enrichGroupedFindings(groupFindings(visibleFindings)); return { loading: this.#loading, hasAnalysis: !!this.#analysis, @@ -43,7 +43,7 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV progress: this.#progress, summary: this.#summarize(this.#analysis), groupedFindings, - findings: visibleFindings + findings: await enrichFindings(visibleFindings) }; } @@ -87,13 +87,6 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV _replaceHTML(result, content) { content.replaceChildren(result); - for (const link of content.querySelectorAll(".content-link[data-uuid]")) { - link.addEventListener("click", event => { - event.preventDefault(); - event.stopPropagation(); - void openSourceUuid(link.dataset.uuid); - }); - } } async runAnalysis() { @@ -384,7 +377,7 @@ function renderFindingList(findings, hasAnalysis, loading, showAll, showRaw) {

${escapeHtml(finding.reason)}

${localize("KSA.Field.Target")}
${escapeHtml(finding.target.locator ?? `${finding.target.storage}:${finding.target.path}`)}
- ${finding.source ? `
${localize("KSA.Field.Source")}
${renderSourceLink(finding.source)}
` : ""} + ${finding.source ? `
${localize("KSA.Field.Source")}
${finding.source.renderedSource ?? renderPlainSourceLabel(finding.source)}
` : ""}

${escapeHtml(finding.recommendation)}

@@ -400,7 +393,7 @@ function renderFindingList(findings, hasAnalysis, loading, showAll, showRaw) { function renderSampleSources(sources) { if (!sources.length) return ""; - const rows = sources.map(source => `
  • ${renderSourceLink(source)}
  • `).join(""); + const rows = sources.map(source => `
  • ${source.renderedSource ?? renderPlainSourceLabel(source)}
  • `).join(""); return `
    ${localize("KSA.Section.Samples")}
    `; } @@ -550,43 +543,79 @@ function renderLocalizedCodeText(key, data, codeValues) { return escapeHtml(text).replace(/@@CODE:([^@]+)@@/g, "$1"); } -function renderSourceLink(source) { - const trail = Array.isArray(source.sourceTrail) && source.sourceTrail.length ? source.sourceTrail : null; - if (trail) { - return `${trail.map(renderTrailNode).join('->')}`; - } - - const label = source.sourceName ? `${source.sourceLabel} (${source.sourceName})` : source.sourceLabel; - if (!source.sourceUuid) return escapeHtml(label); - return renderUuidLink(source.sourceUuid, label); -} - -async function openSourceUuid(uuid) { - if (!uuid) return; - const document = await fromUuid(uuid); - if (document?.sheet) { - document.sheet.render(true, { focus: true }); - return; - } - if (document?.parent?.sheet) { - document.parent.sheet.render(true, { focus: true }); - return; - } - ui.notifications.warn(format("KSA.Notify.OpenSourceFailed", { uuid })); -} - -function renderTrailNode(node) { - if (!node?.uuid) return escapeHtml(node?.label ?? ""); - return renderUuidLink(node.uuid, node.label ?? node.uuid); -} - -function renderUuidLink(uuid, label) { - return `${escapeHtml(label)}`; -} - function buildSourceKey(source) { const trailKey = Array.isArray(source.sourceTrail) && source.sourceTrail.length ? source.sourceTrail.map(node => node.uuid ?? node.label ?? "").join(">") : ""; return `${source.sourceUuid ?? ""}|${trailKey}|${source.sourceLabel ?? ""}`; } + +async function enrichGroupedFindings(groupedFindings) { + return { + brokenReferences: await enrichGroupedSources(groupedFindings.brokenReferences), + nonPackageToPackage: await enrichGroupedSources(groupedFindings.nonPackageToPackage), + orphans: groupedFindings.orphans + }; +} + +async function enrichGroupedSources(groups) { + const enriched = []; + for (const group of groups) { + const sources = []; + for (const source of group.sources) { + sources.push({ + ...source, + renderedSource: await renderSourceHtml(source) + }); + } + enriched.push({ ...group, sources }); + } + return enriched; +} + +async function enrichFindings(findings) { + const enriched = []; + for (const finding of findings) { + if (!finding.source) { + enriched.push(finding); + continue; + } + enriched.push({ + ...finding, + source: { + ...finding.source, + renderedSource: await renderSourceHtml(finding.source) + } + }); + } + return enriched; +} + +async function renderSourceHtml(source) { + const markup = buildSourceMarkup(source); + if (!markup) return renderPlainSourceLabel(source); + return TextEditor.enrichHTML(markup); +} + +function buildSourceMarkup(source) { + const trail = Array.isArray(source.sourceTrail) && source.sourceTrail.length ? source.sourceTrail : null; + if (trail?.length) { + return trail.map(node => renderTrailMarkup(node)).join(" -> "); + } + if (!source.sourceUuid) return null; + return renderTrailMarkup({ + uuid: source.sourceUuid, + label: source.sourceName ? `${source.sourceLabel} (${source.sourceName})` : source.sourceLabel + }); +} + +function renderTrailMarkup(node) { + if (!node?.uuid) return foundry.utils.escapeHTML(node?.label ?? ""); + const label = foundry.utils.escapeHTML(node.label ?? node.uuid); + return `@UUID[${node.uuid}]{${label}}`; +} + +function renderPlainSourceLabel(source) { + const label = source.sourceName ? `${source.sourceLabel} (${source.sourceName})` : source.sourceLabel; + return escapeHtml(label); +} diff --git a/scripts/core/analyzer.js b/scripts/core/analyzer.js index 857326d..180ba87 100644 --- a/scripts/core/analyzer.js +++ b/scripts/core/analyzer.js @@ -27,7 +27,8 @@ export async function analyzeStorage({ listFiles, listSources, onProgress, i18n sourceScope: source.sourceScope, sourceLabel: source.sourceLabel, sourceName: source.sourceName, - sourceUuid: source.sourceUuid + sourceUuid: source.sourceUuid, + resolveSourceTrail: source.resolveSourceTrail }); references.push(...extracted); referenceCount += extracted.length; diff --git a/styles/audit.css b/styles/audit.css index fccea1b..a74411f 100644 --- a/styles/audit.css +++ b/styles/audit.css @@ -126,6 +126,7 @@ min-width: 0; width: 100%; max-width: 18rem; + justify-self: end; } .storage-audit__actions .button { @@ -279,25 +280,11 @@ overflow-wrap: anywhere; } -.storage-audit__trail { - display: inline-flex; - align-items: center; - flex-wrap: wrap; - gap: 0.2rem; -} - -.storage-audit__trail .content-link { - white-space: nowrap; -} - -.storage-audit__trail-separator { - opacity: 0.65; -} - @media (max-width: 1100px) { .storage-audit__actions { width: 100%; max-width: none; + justify-self: stretch; } }