deletion works
This commit is contained in:
@@ -15,6 +15,33 @@ const SETTINGS_VERSION = 2;
|
||||
const COIN_ORDER = ["pp", "gp", "sp", "cp"];
|
||||
const ENCOUNTER_FLAG = "pf1EncounterHistory";
|
||||
const DAMAGE_DEALT_FLAG = "pf1DamageDealtHistory";
|
||||
const DEBUG_LOGS = false;
|
||||
const HISTORY_PAGE_OPTIONS = [10, 20, 50, "all"];
|
||||
|
||||
const DAMAGE_ICON_MAP = {
|
||||
slashing: { icon: "ra ra-sword", color: "#e3c000" },
|
||||
piercing: { icon: "ra ra-spear-head", color: "#2c7be5" },
|
||||
bludgeoning: { icon: "ra ra-large-hammer", color: "#e03131" },
|
||||
fire: { icon: "ra ra-fire", color: "#f76707" },
|
||||
cold: { icon: "ra ra-snowflake", color: "#3bc9db" },
|
||||
electricity: { icon: "ra ra-lightning-bolt", color: "#f0c419" },
|
||||
acid: { icon: "ra ra-round-bottom-flask", color: "#2f9e44" },
|
||||
sonic: { icon: "ra ra-megaphone", color: "#22b8cf" },
|
||||
force: { icon: "ra ra-crystal-ball", color: "#845ef7" },
|
||||
negative: { icon: "ra ra-skull", color: "#7950f2" },
|
||||
positive: { icon: "ra ra-sun", color: "#fab005" },
|
||||
healing: { icon: "ra ra-health", color: "#4caf50" },
|
||||
precision: { icon: "ra ra-target-arrows", color: "#000" },
|
||||
nonlethal: { icon: "ra ra-hand", color: "#000" },
|
||||
untyped: { icon: "ra ra-uncertainty", color: "#666" },
|
||||
};
|
||||
|
||||
const log = {
|
||||
debug: (...args) => DEBUG_LOGS && console.debug("[GowlersTracking]", ...args),
|
||||
info: (...args) => console.info("[GowlersTracking]", ...args),
|
||||
warn: (...args) => console.warn("[GowlersTracking]", ...args),
|
||||
error: (...args) => console.error("[GowlersTracking]", ...args),
|
||||
};
|
||||
|
||||
const STAT_CONFIGS = {
|
||||
hp: {
|
||||
@@ -116,12 +143,12 @@ function setActiveHistoryTab(actorId, tabId) {
|
||||
|
||||
// Global tab switching function for history dialog
|
||||
window.switchHistoryTab = function(tabId, el = null) {
|
||||
console.log("[GowlersTracking] Tab switched to:", tabId);
|
||||
log.debug("Tab switched to:", tabId);
|
||||
|
||||
// Find the root element using the data-history-root attribute
|
||||
const root = el?.closest?.('[data-history-root]') ?? document.querySelector('[data-history-root]');
|
||||
if (!root) {
|
||||
console.warn("[GowlersTracking] History root element not found");
|
||||
log.warn("History root element not found");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -139,16 +166,16 @@ window.switchHistoryTab = function(tabId, el = null) {
|
||||
|
||||
if (activeButton) {
|
||||
activeButton.classList.add('active');
|
||||
console.log("[GowlersTracking] Tab button activated:", tabId);
|
||||
log.debug("Tab button activated:", tabId);
|
||||
} else {
|
||||
console.warn("[GowlersTracking] Tab button not found for:", tabId);
|
||||
log.warn("Tab button not found for:", tabId);
|
||||
}
|
||||
|
||||
if (activePanel) {
|
||||
activePanel.style.display = 'block';
|
||||
console.log("[GowlersTracking] Tab panel displayed:", tabId);
|
||||
log.debug("Tab panel displayed:", tabId);
|
||||
} else {
|
||||
console.warn("[GowlersTracking] Tab panel not found for:", tabId);
|
||||
log.warn("Tab panel not found for:", tabId);
|
||||
}
|
||||
|
||||
const actorId = root?.getAttribute('data-history-root');
|
||||
@@ -500,16 +527,15 @@ async function initializeModule() {
|
||||
}
|
||||
}
|
||||
|
||||
// Log detailed inspection for debugging
|
||||
console.log("[GowlersTracking] Message inspection for damage details:");
|
||||
console.log("[GowlersTracking] - pf1Flags keys:", Object.keys(pf1Flags));
|
||||
console.log("[GowlersTracking] - Full metadata:", JSON.stringify(metadata, null, 2));
|
||||
console.log("[GowlersTracking] - message.rolls:", message.rolls?.length > 0 ? "Present" : "None");
|
||||
console.log("[GowlersTracking] - message.content length:", message.content?.length ?? 0);
|
||||
|
||||
// Log HTML content snippet for analysis
|
||||
if (DEBUG_LOGS) {
|
||||
log.debug("Message inspection for damage details:");
|
||||
log.debug(" - pf1Flags keys:", Object.keys(pf1Flags));
|
||||
log.debug(" - Full metadata:", JSON.stringify(metadata, null, 2));
|
||||
log.debug(" - message.rolls:", message.rolls?.length > 0 ? "Present" : "None");
|
||||
log.debug(" - message.content length:", message.content?.length ?? 0);
|
||||
if (message.content) {
|
||||
console.log("[GowlersTracking] - HTML preview (first 1000 chars):", message.content.substring(0, 1000));
|
||||
log.debug(" - HTML preview (first 1000 chars):", message.content.substring(0, 1000));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -982,7 +1008,7 @@ function buildXpBreakdownTooltip(actor, xpEntry) {
|
||||
|
||||
function buildHistoryContent(actor, tabArg) {
|
||||
const initialTab = tabArg ?? getActiveHistoryTab(actor.id, "hp"); // Explicitly capture the tab parameter
|
||||
console.log("[GowlersTracking] buildHistoryContent called with initialTab:", initialTab);
|
||||
log.debug("buildHistoryContent called with initialTab:", initialTab);
|
||||
setActiveHistoryTab(actor.id, initialTab);
|
||||
const canConfigure = game.user?.isGM;
|
||||
const configs = [
|
||||
@@ -994,7 +1020,7 @@ function buildHistoryContent(actor, tabArg) {
|
||||
{ label: "Timestamp", render: (entry) => formatDate(entry.timestamp) },
|
||||
{ label: "HP", render: (entry) => entry.value },
|
||||
{
|
||||
label: "Δ",
|
||||
label: "Delta",
|
||||
render: (entry) => entry.diff,
|
||||
getTitle: (entry) => entry.damageBreakdown ? `${entry.damageBreakdown}` : ""
|
||||
},
|
||||
@@ -1100,15 +1126,15 @@ function buildHistoryContent(actor, tabArg) {
|
||||
currentPage = totalPages;
|
||||
setHistoryPageState(actor.id, cfg.id, currentPage, rowsPerPage);
|
||||
}
|
||||
const selectOptions = [10, 20, 50, "all"]
|
||||
const selectOptions = HISTORY_PAGE_OPTIONS
|
||||
.map((value) => `<option value="${value}" ${String(value) === String(rowsPerPage) ? "selected" : ""}>${value === "all" ? "All" : `${value} rows`}</option>`)
|
||||
.join("");
|
||||
return `<div class="history-panel tab ${isActive}" data-history-panel="${cfg.id}" data-page="${currentPage}" data-page-size="${rowsPerPage}" style="display:${display}">
|
||||
${renderHistoryTable(entries, cfg.columns, cfg.id, rowsPerPage, currentPage)}
|
||||
<div class="history-panel-pagination" style="display: flex; gap: 8px; align-items: center; justify-content: center; margin-top: 12px; padding-top: 8px; border-top: 1px solid #e0e0e0;">
|
||||
<button type="button" data-action="history-page" data-direction="prev" onclick="window.historyPageNav(this, 'prev')" style="padding: 4px 8px; cursor: pointer;" ${totalPages <= 1 ? 'disabled' : ''}>« Prev</button>
|
||||
<button type="button" data-action="history-page" data-direction="prev" onclick="window.historyPageNav(this, 'prev')" style="padding: 4px 8px; cursor: pointer;" ${totalPages <= 1 ? 'disabled' : ''}>< Prev</button>
|
||||
<span data-page-info style="font-size: 0.85em; min-width: 100px; text-align: center;">Page ${currentPage} / ${Math.max(1, totalPages)}</span>
|
||||
<button type="button" data-action="history-page" data-direction="next" onclick="window.historyPageNav(this, 'next')" style="padding: 4px 8px; cursor: pointer;" ${totalPages <= 1 ? 'disabled' : ''}>Next »</button>
|
||||
<button type="button" data-action="history-page" data-direction="next" onclick="window.historyPageNav(this, 'next')" style="padding: 4px 8px; cursor: pointer;" ${totalPages <= 1 ? 'disabled' : ''}>Next ></button>
|
||||
<select data-history-page-size onchange="window.historyChangePageSize(this)" style="padding: 2px 4px;">
|
||||
${selectOptions}
|
||||
</select>
|
||||
@@ -1185,23 +1211,23 @@ async function recordHistoryEntry(actor, statId, previous, nextValue, userId, op
|
||||
|
||||
const diffValue = config.diff(previous, nextValue);
|
||||
|
||||
// COMPREHENSIVE DEBUG LOGGING FOR DAMAGE REPORTING
|
||||
console.log("[GowlersTracking] ===== recordHistoryEntry DEBUG =====");
|
||||
console.log("[GowlersTracking] statId:", statId);
|
||||
console.log("[GowlersTracking] Previous:", previous, "Next:", nextValue, "Diff:", diffValue);
|
||||
console.log("[GowlersTracking] Actor:", actor.name, "(" + actor.id + ")");
|
||||
console.log("[GowlersTracking] userId:", userId);
|
||||
console.log("[GowlersTracking] Full options object:", options);
|
||||
console.log("[GowlersTracking] Full change object:", change);
|
||||
if (DEBUG_LOGS) {
|
||||
log.debug("===== recordHistoryEntry DEBUG =====");
|
||||
log.debug("statId:", statId);
|
||||
log.debug("Previous:", previous, "Next:", nextValue, "Diff:", diffValue);
|
||||
log.debug("Actor:", actor.name, "(" + actor.id + ")");
|
||||
log.debug("userId:", userId);
|
||||
log.debug("Full options object:", options);
|
||||
log.debug("Full change object:", change);
|
||||
|
||||
// Log all keys in options for inspection
|
||||
if (Object.keys(options).length > 0) {
|
||||
console.log("[GowlersTracking] Options keys:", Object.keys(options));
|
||||
log.debug("Options keys:", Object.keys(options));
|
||||
for (const key of Object.keys(options)) {
|
||||
console.log(`[GowlersTracking] options.${key}:`, options[key]);
|
||||
log.debug(`options.${key}:`, options[key]);
|
||||
}
|
||||
}
|
||||
console.log("[GowlersTracking] ===== end debug =====");
|
||||
log.debug("===== end debug =====");
|
||||
}
|
||||
|
||||
// Determine encounter ID: use active combat, or if none, check if combat just ended
|
||||
let encounterId = game.combat?.id ?? null;
|
||||
@@ -1239,28 +1265,29 @@ async function recordHistoryEntry(actor, statId, previous, nextValue, userId, op
|
||||
else if (options?.encounter?.id) encounterId = options.encounter.id;
|
||||
else if (options?.encounter?._id) encounterId = options.encounter._id;
|
||||
|
||||
// XP debug logging for data inspection
|
||||
if (DEBUG_LOGS) {
|
||||
try {
|
||||
console.log("[GowlersTracking][XP DEBUG] -----");
|
||||
console.log("[GowlersTracking][XP DEBUG] encounterId:", encounterId, "manualXp:", manualXp, "hasExplicitEncounter:", hasExplicitEncounter);
|
||||
console.log("[GowlersTracking][XP DEBUG] options keys:", Object.keys(options || {}));
|
||||
console.log("[GowlersTracking][XP DEBUG] change keys:", Object.keys(change || {}));
|
||||
if (options) console.log("[GowlersTracking][XP DEBUG] options JSON:", JSON.stringify(options, null, 2));
|
||||
if (change) console.log("[GowlersTracking][XP DEBUG] change JSON keys:", Object.keys(change));
|
||||
if (change?.system?.details?.xp) console.log("[GowlersTracking][XP DEBUG] change.system.details.xp:", change.system.details.xp);
|
||||
if (change?.system?.attributes?.hp) console.log("[GowlersTracking][XP DEBUG] change.system.attributes.hp (truncated):", JSON.stringify(change.system.attributes.hp, null, 2).substring(0, 500));
|
||||
log.debug("[XP DEBUG] -----");
|
||||
log.debug("[XP DEBUG] encounterId:", encounterId, "manualXp:", manualXp, "hasExplicitEncounter:", hasExplicitEncounter);
|
||||
log.debug("[XP DEBUG] options keys:", Object.keys(options || {}));
|
||||
log.debug("[XP DEBUG] change keys:", Object.keys(change || {}));
|
||||
if (options) log.debug("[XP DEBUG] options JSON:", JSON.stringify(options, null, 2));
|
||||
if (change) log.debug("[XP DEBUG] change JSON keys:", Object.keys(change));
|
||||
if (change?.system?.details?.xp) log.debug("[XP DEBUG] change.system.details.xp:", change.system.details.xp);
|
||||
if (change?.system?.attributes?.hp) log.debug("[XP DEBUG] change.system.attributes.hp (truncated):", JSON.stringify(change.system.attributes.hp, null, 2).substring(0, 500));
|
||||
const pf1Flags = actor.getFlag("pf1") ?? {};
|
||||
if (pf1Flags && Object.keys(pf1Flags).length) console.log("[GowlersTracking][XP DEBUG] actor pf1 flags keys:", Object.keys(pf1Flags));
|
||||
if (game.combat) console.log("[GowlersTracking][XP DEBUG] active combat flags:", game.combat.flags ?? "(none)");
|
||||
if (pf1Flags && Object.keys(pf1Flags).length) log.debug("[XP DEBUG] actor pf1 flags keys:", Object.keys(pf1Flags));
|
||||
if (game.combat) log.debug("[XP DEBUG] active combat flags:", game.combat.flags ?? "(none)");
|
||||
const encounterFlag = actor.getFlag(FLAG_SCOPE, ENCOUNTER_FLAG) ?? [];
|
||||
console.log("[GowlersTracking][XP DEBUG] actor encounter flag count:", encounterFlag.length);
|
||||
log.debug("[XP DEBUG] actor encounter flag count:", encounterFlag.length);
|
||||
if (encounterFlag.length && encounterId) {
|
||||
const encounterEntry = encounterFlag.find((e) => e.encounterID === encounterId);
|
||||
console.log("[GowlersTracking][XP DEBUG] matched encounter entry:", encounterEntry ?? "(not found)");
|
||||
log.debug("[XP DEBUG] matched encounter entry:", encounterEntry ?? "(not found)");
|
||||
}
|
||||
console.log("[GowlersTracking][XP DEBUG] -----");
|
||||
log.debug("[XP DEBUG] -----");
|
||||
} catch (e) {
|
||||
console.warn("[GowlersTracking][XP DEBUG] logging failed:", e);
|
||||
log.warn("[XP DEBUG] logging failed:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1584,30 +1611,20 @@ function isNonlethalType(type) {
|
||||
return String(type ?? "").toLowerCase() === "nonlethal";
|
||||
}
|
||||
|
||||
function getPrimaryDamageType(part) {
|
||||
return (part?.types && part.types[0]) || part?.customTypes?.[0] || part?.materials?.[0] || "untyped";
|
||||
}
|
||||
|
||||
function getDamageIconMeta(type) {
|
||||
return DAMAGE_ICON_MAP[type?.toLowerCase?.()] ?? DAMAGE_ICON_MAP.untyped;
|
||||
}
|
||||
|
||||
function formatDamagePartsWithIcons(parts) {
|
||||
if (!Array.isArray(parts) || !parts.length) return "";
|
||||
const iconMap = {
|
||||
slashing: { icon: "ra ra-sword", color: "#e3c000" },
|
||||
piercing: { icon: "ra ra-spear-head", color: "#2c7be5" },
|
||||
bludgeoning: { icon: "ra ra-large-hammer", color: "#e03131" },
|
||||
fire: { icon: "ra ra-fire", color: "#f76707" },
|
||||
cold: { icon: "ra ra-snowflake", color: "#3bc9db" },
|
||||
electricity: { icon: "ra ra-lightning-bolt", color: "#f0c419" },
|
||||
acid: { icon: "ra ra-round-bottom-flask", color: "#2f9e44" },
|
||||
sonic: { icon: "ra ra-megaphone", color: "#22b8cf" },
|
||||
force: { icon: "ra ra-crystal-ball", color: "#845ef7" },
|
||||
negative: { icon: "ra ra-skull", color: "#7950f2" },
|
||||
positive: { icon: "ra ra-sun", color: "#fab005" },
|
||||
healing: { icon: "ra ra-health", color: "#4caf50" },
|
||||
precision: { icon: "ra ra-target-arrows", color: "#000" },
|
||||
nonlethal: { icon: "ra ra-hand", color: "#000" },
|
||||
untyped: { icon: "ra ra-uncertainty", color: "#666" },
|
||||
};
|
||||
return parts
|
||||
.map((p) => {
|
||||
const baseType = (p.types && p.types[0]) || p.customTypes?.[0] || p.materials?.[0] || "untyped";
|
||||
const mapEntry = iconMap[baseType?.toLowerCase?.()] ?? iconMap.untyped;
|
||||
const icon = `<i class="${mapEntry.icon}" style="color:${mapEntry.color};"></i>`;
|
||||
const meta = getDamageIconMeta(getPrimaryDamageType(p));
|
||||
const icon = `<i class="${meta.icon}" style="color:${meta.color};"></i>`;
|
||||
const amt = Number.isFinite(p.total) ? p.total : p.formula ?? "?";
|
||||
return `<span style="display:inline-flex; align-items:center; gap:4px; margin-right:6px;">${icon}<span>${amt}</span></span>`;
|
||||
})
|
||||
@@ -1615,27 +1632,10 @@ function formatDamagePartsWithIcons(parts) {
|
||||
}
|
||||
|
||||
function renderDamageBar(composition = [], total = 0) {
|
||||
const iconMap = {
|
||||
slashing: { icon: "ra ra-sword", color: "#e3c000" },
|
||||
piercing: { icon: "ra ra-spear-head", color: "#2c7be5" },
|
||||
bludgeoning: { icon: "ra ra-large-hammer", color: "#e03131" },
|
||||
fire: { icon: "ra ra-fire", color: "#f76707" },
|
||||
cold: { icon: "ra ra-snowflake", color: "#3bc9db" },
|
||||
electricity: { icon: "ra ra-lightning-bolt", color: "#f0c419" },
|
||||
acid: { icon: "ra ra-round-bottom-flask", color: "#2f9e44" },
|
||||
sonic: { icon: "ra ra-megaphone", color: "#22b8cf" },
|
||||
force: { icon: "ra ra-crystal-ball", color: "#845ef7" },
|
||||
negative: { icon: "ra ra-skull", color: "#7950f2" },
|
||||
positive: { icon: "ra ra-sun", color: "#fab005" },
|
||||
healing: { icon: "ra ra-health", color: "#4caf50" },
|
||||
precision: { icon: "ra ra-target-arrows", color: "#000" },
|
||||
nonlethal: { icon: "ra ra-hand", color: "#000" },
|
||||
untyped: { icon: "ra ra-uncertainty", color: "#666" },
|
||||
};
|
||||
if (!total || !composition.length) return "";
|
||||
const segments = composition.map((c) => {
|
||||
const pct = Math.max(2, Math.round((c.value / total) * 100));
|
||||
const entry = iconMap[c.type?.toLowerCase?.()] ?? iconMap.untyped;
|
||||
const entry = getDamageIconMeta(c.type);
|
||||
return `<div style="width:${pct}%; background:${entry.color}; height:10px; display:flex; align-items:center; justify-content:center;">
|
||||
<i class="${entry.icon}" style="color:#fff; font-size:10px;"></i>
|
||||
</div>`;
|
||||
@@ -2353,16 +2353,7 @@ class TrackingLedgerConfig extends FormApplication {
|
||||
defaultYes: false,
|
||||
});
|
||||
if (!confirmed) return;
|
||||
try {
|
||||
const actors = collectAllActorDocuments();
|
||||
const tokens = collectAllTokens();
|
||||
for (const actor of actors) await clearDocumentHistory(actor);
|
||||
for (const token of tokens) await clearDocumentHistory(token);
|
||||
ui.notifications?.info?.("History cleared for all actors.");
|
||||
} catch (err) {
|
||||
console.error("[GowlersTracking] Failed to clear all histories", err);
|
||||
ui.notifications?.error?.("Failed to clear all histories; see console.");
|
||||
}
|
||||
await clearAllHistoriesWithProgress();
|
||||
});
|
||||
|
||||
html.find("[data-action=\"clear-history\"]").on("click", async (event) => {
|
||||
@@ -2417,6 +2408,71 @@ async function clearDocumentHistory(doc) {
|
||||
}
|
||||
}
|
||||
|
||||
async function clearAllHistoriesWithProgress() {
|
||||
try {
|
||||
const targets = new Map();
|
||||
|
||||
// Core actors in the directory
|
||||
for (const actor of collectAllActorDocuments()) {
|
||||
const key = `actor:${actor.uuid ?? actor.id}`;
|
||||
if (!targets.has(key)) targets.set(key, { doc: actor, label: actor.name ?? actor.id });
|
||||
}
|
||||
|
||||
// Tokens on scenes (covers unlinked NPCs) and their embedded actor documents
|
||||
for (const token of collectAllTokens()) {
|
||||
const tokenKey = `token:${token.uuid ?? token.id}:${token.parent?.uuid ?? "scene"}`;
|
||||
if (!targets.has(tokenKey)) targets.set(tokenKey, { doc: token, label: token.name ?? token.id });
|
||||
|
||||
const tokenActor = token.actor;
|
||||
if (tokenActor) {
|
||||
const actorKey = `actor:${tokenActor.uuid ?? tokenActor.id}`;
|
||||
if (!targets.has(actorKey)) targets.set(actorKey, { doc: tokenActor, label: tokenActor.name ?? tokenActor.id });
|
||||
}
|
||||
}
|
||||
|
||||
const entries = Array.from(targets.values());
|
||||
if (!entries.length) {
|
||||
ui.notifications?.info?.("No actor or token histories found to clear.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Simple progress dialog
|
||||
let progressEl = null;
|
||||
let statusEl = null;
|
||||
const dialog = new Dialog({
|
||||
title: "Clearing Histories",
|
||||
content: `
|
||||
<div style="display:flex; flex-direction:column; gap:8px; padding:4px;">
|
||||
<progress value="0" max="${entries.length}" style="width:100%;"></progress>
|
||||
<div data-status style="font-size:12px;">Starting...</div>
|
||||
</div>
|
||||
`,
|
||||
buttons: {},
|
||||
close: () => {},
|
||||
render: (html) => {
|
||||
progressEl = html[0].querySelector("progress");
|
||||
statusEl = html[0].querySelector("[data-status]");
|
||||
},
|
||||
});
|
||||
dialog.render(true);
|
||||
|
||||
let completed = 0;
|
||||
for (const { doc, label } of entries) {
|
||||
if (statusEl) statusEl.textContent = `Clearing: ${label}`;
|
||||
await clearDocumentHistory(doc);
|
||||
completed += 1;
|
||||
if (progressEl) progressEl.value = completed;
|
||||
}
|
||||
|
||||
if (statusEl) statusEl.textContent = "Done.";
|
||||
setTimeout(() => dialog.close(), 500);
|
||||
ui.notifications?.info?.("History cleared for all actors and tokens.");
|
||||
} catch (err) {
|
||||
console.error("[GowlersTracking] Failed to clear all histories", err);
|
||||
ui.notifications?.error?.("Failed to clear all histories; see console.");
|
||||
}
|
||||
}
|
||||
|
||||
function collectAllActorDocuments() {
|
||||
const actors = new Map();
|
||||
for (const a of game.actors.contents ?? []) {
|
||||
@@ -2584,7 +2640,7 @@ function sendChatNotification(statId, actor, previous, nextValue, entry) {
|
||||
<div style="font-family: var(--font-primary); font-size: 12px; line-height: 1.4;">
|
||||
<div style="font-weight: bold; margin-bottom: 4px;">${title} Update</div>
|
||||
<div><strong>Actor:</strong> ${actor.name}</div>
|
||||
<div><strong>Value:</strong> ${prevText} → ${nextText} (${entry.diff})</div>
|
||||
<div><strong>Value:</strong> ${prevText} -> ${nextText} (${entry.diff})</div>
|
||||
<div><strong>Source:</strong> ${source}</div>
|
||||
<div><strong>Encounter:</strong> ${encounter}</div>
|
||||
${detailHtml}
|
||||
|
||||
Reference in New Issue
Block a user