67 lines
4.3 KiB
JavaScript
67 lines
4.3 KiB
JavaScript
"use strict";
|
|
|
|
import { escapeHtml } from "../../cmap/cmap-utils.js";
|
|
|
|
/** Render wiki-specific HTML content for CMap editor items. */
|
|
export class CmapEditorPresentation {
|
|
constructor({ state, translate, canonicalPageReference, referenceIndex }) {
|
|
this.state = state;
|
|
this.translate = translate;
|
|
this.canonicalPageReference = canonicalPageReference;
|
|
this.referenceIndex = referenceIndex;
|
|
}
|
|
|
|
renderItem(record) {
|
|
const safeLabel = escapeHtml(record.label || "");
|
|
const safeSynopsis = escapeHtml(record.synopsis || "");
|
|
const safeKind = escapeHtml(record.kind || "concept");
|
|
const safeImageSource = escapeHtml(record.imageSource || "");
|
|
const image = safeImageSource ? `<img class="cmap-card-image" src="${safeImageSource}" alt="">` : "";
|
|
const aspects = (Array.isArray(record.aspects) ? record.aspects : [])
|
|
.map((aspect) => `<span class="cmap-card-aspect">${escapeHtml(aspect)}</span>`)
|
|
.join("");
|
|
const aspectList = aspects ? `<div class="cmap-card-aspects">${aspects}</div>` : "";
|
|
const personNames = (Array.isArray(record.tags) ? record.tags : [])
|
|
.filter((tag) => tag && typeof tag === "object" && tag.type === "person" && tag.value)
|
|
.map((tag) => String(tag.value));
|
|
const peopleLabel = personNames.length ?
|
|
this.translate("person-tags-title", "Responsibility/action: {people}").replace("{people}", personNames.join(", ")) : "";
|
|
const personTags = personNames.length ?
|
|
`<div class="cmap-card-people" title="${escapeHtml(peopleLabel)}"><span aria-hidden="true">👤</span> ${personNames.map(escapeHtml).join(" · ")}</div>` : "";
|
|
const usageCount = record.conceptId && record.kind !== "phrase" ?
|
|
this.conceptUsage(record) : null;
|
|
const usageLabel = usageCount === null ? "" :
|
|
this.translate("concept-usage-count", "{count} placements across all concept maps")
|
|
.replace("{count}", String(usageCount));
|
|
const usage = usageCount === null ? "" :
|
|
`<span class="cmap-card-usage" title="${escapeHtml(usageLabel)}">(${usageCount})</span>`;
|
|
const descriptionReference = this.canonicalPageReference(record.descriptionPageSlug || "");
|
|
const descriptionExists = Boolean(descriptionReference &&
|
|
this.state.pages.some((page) => page.slug === descriptionReference));
|
|
const descriptionButton = record.descriptionPageSlug ?
|
|
`<button type="button" class="rw-cmap-view-description ${descriptionExists ? "is-filled" : "is-empty"}" aria-label="${escapeHtml(this.translate("view-concept-description", "View concept description"))}">I</button>` : "";
|
|
const linkedTarget = record.pageSlug || record.cmapSlug || record.parentCmapLink;
|
|
const linkedButton = linkedTarget && record.kind !== "submap" ?
|
|
`<button type="button" class="rw-cmap-open-linked" title="${escapeHtml(this.translate("open-linked-item", "Open linked page or CMap"))}" aria-label="${escapeHtml(this.translate("open-linked-item", "Open linked page or CMap"))}">↗</button>` : "";
|
|
const externalButton = record.externalUrl ?
|
|
`<button type="button" class="rw-cmap-open-external" aria-label="${escapeHtml(this.translate("open-external-web-page", "Open external web page"))}">↗</button>` : "";
|
|
const linkedCmapClass = record.cmapSlug || record.parentCmapLink ? " cmap-card-linked-cmap" : "";
|
|
return `<div class="cmap-card cmap-card-${safeKind}${linkedCmapClass}">${descriptionButton}${linkedButton}${externalButton}${image}<div class="cmap-card-title">${safeLabel} ${usage}</div>${aspectList}${personTags}${safeSynopsis ? `<div class="cmap-card-synopsis">${safeSynopsis}</div>` : ""}</div>`;
|
|
}
|
|
|
|
conceptUsage(record) {
|
|
const maps = this.referenceIndex.byConceptId.get(record.conceptId);
|
|
const storedTotal = maps ? Array.from(maps.values()).reduce((sum, entry) =>
|
|
sum + (typeof entry === "number" ? entry : Number(entry.count) || 0), 0) : 0;
|
|
const editor = this.state.cmapPrototype?.editor;
|
|
if (!editor || !editor.containsItemRecord(record)) {
|
|
return Math.max(1, storedTotal || Number(record.usageCount) || 1);
|
|
}
|
|
const currentSlug = this.state.currentConceptMapSource?.slug ||
|
|
this.state.currentConceptMap?.slug || null;
|
|
const entry = currentSlug && maps ? maps.get(currentSlug) : null;
|
|
const current = typeof entry === "number" ? entry : Number(entry?.count) || 0;
|
|
return Math.max(1, storedTotal - current + (Number(record.usageCount) || 1));
|
|
}
|
|
}
|