diff --git a/core/templates/partials/compose-panel.html b/core/templates/partials/compose-panel.html index 31b36ba..1f7a312 100644 --- a/core/templates/partials/compose-panel.html +++ b/core/templates/partials/compose-panel.html @@ -1220,6 +1220,16 @@ display: grid; gap: 0.14rem; } + #{{ panel_id }} .compose-export-voice-col .radio { + border: 1px solid transparent; + border-radius: 6px; + padding: 0.1rem 0.22rem; + } + #{{ panel_id }} .compose-export-voice-col .radio.is-impossible-selected { + border-color: rgba(183, 37, 37, 0.6); + background: rgba(255, 232, 232, 0.9); + color: #8f1f1f; + } #{{ panel_id }} .compose-export-voice-title { margin: 0 0 0.08rem 0; font-size: 0.64rem; @@ -3282,26 +3292,6 @@ return picked.length ? picked : defaults; }; - const timeFieldNode = function () { - return exportFieldChecks.find(function (node) { - return String(node.value || "").trim() === "time"; - }) || null; - }; - - const syncExportFieldLocks = function () { - const format = String(exportFormat && exportFormat.value ? exportFormat.value : "plain"); - const timeNode = timeFieldNode(); - if (!timeNode) { - return; - } - if (format === "share") { - timeNode.checked = true; - timeNode.disabled = true; - } else { - timeNode.disabled = false; - } - }; - const orderedExportFields = function (fields) { const seen = new Set(); const input = Array.isArray(fields) ? fields.slice() : []; @@ -3335,17 +3325,72 @@ return String(match && match.value ? match.value : fallback || "").trim(); }; - const buildShareStatements = function (records) { + const isImpossibleVoiceChoice = function (direction, value) { + const key = String(value || "").trim().toLowerCase(); + const side = String(direction || "").trim().toLowerCase(); + if (side === "incoming") { + return key === "i said" || key === "you said"; + } + if (side === "outgoing") { + return key === "they said"; + } + return false; + }; + + const syncVoiceValidity = function () { + exportVoiceOut.forEach(function (node) { + const label = node.closest("label"); + if (!label) { + return; + } + label.classList.toggle( + "is-impossible-selected", + !!node.checked && isImpossibleVoiceChoice("outgoing", node.value) + ); + }); + exportVoiceIn.forEach(function (node) { + const label = node.closest("label"); + if (!label) { + return; + } + label.classList.toggle( + "is-impossible-selected", + !!node.checked && isImpossibleVoiceChoice("incoming", node.value) + ); + }); + }; + + const buildShareStatements = function (records, fields) { const outLabel = selectedVoiceLabel(exportVoiceOut, "I said"); const inLabel = selectedVoiceLabel(exportVoiceIn, "They said"); + const includeTime = fields.includes("time"); + const includeText = fields.includes("text"); + const metaFields = fields.filter(function (field) { + return field !== "text" && field !== "time"; + }); const lines = []; let previous = null; records.forEach(function (row) { const speaker = row.direction === "outgoing" ? "you" : "they"; const speakerLabel = speaker === "you" ? (outLabel + ": ") : (inLabel + ": "); - const timePrefix = row.time ? ("[" + row.time + "] ") : ""; + const timePrefix = includeTime && row.time ? ("[" + row.time + "] ") : ""; + const metaSuffix = metaFields + .map(function (field) { + const value = row[field]; + if (value === undefined || value === null || value === "") { + return ""; + } + return field + "=" + String(value); + }) + .filter(Boolean) + .join(" ยท "); + const textSegment = includeText ? String(row.text || "") : ""; if (!previous) { - lines.push(timePrefix + speakerLabel + row.text); + let line = timePrefix + speakerLabel + textSegment; + if (metaSuffix) { + line += " (" + metaSuffix + ")"; + } + lines.push(line.trim()); previous = row; return; } @@ -3354,7 +3399,11 @@ const phrasing = speaker === "you" ? ("After " + waitLabel + ", " + outLabel + ": ") : ("After " + waitLabel + ", " + inLabel + ": "); - lines.push(timePrefix + phrasing + row.text); + let line = timePrefix + phrasing + textSegment; + if (metaSuffix) { + line += " (" + metaSuffix + ")"; + } + lines.push(line.trim()); previous = row; }); return lines.join("\n"); @@ -3375,7 +3424,7 @@ return ""; } if (format === "share") { - return buildShareStatements(records); + return buildShareStatements(records, fields); } if (format === "jsonl") { return records.map(function (row) { @@ -3494,13 +3543,16 @@ }); } if (exportFormat) { - exportFormat.addEventListener("change", function () { - syncExportFieldLocks(); - updateExportBuffer(); - }); + exportFormat.addEventListener("change", updateExportBuffer); } exportVoiceOut.forEach(function (node) { node.addEventListener("change", updateExportBuffer); }); exportVoiceIn.forEach(function (node) { node.addEventListener("change", updateExportBuffer); }); + exportVoiceOut.forEach(function (node) { + node.addEventListener("change", syncVoiceValidity); + }); + exportVoiceIn.forEach(function (node) { + node.addEventListener("change", syncVoiceValidity); + }); exportFieldChecks.forEach(function (node) { node.addEventListener("change", updateExportBuffer); }); @@ -3537,7 +3589,7 @@ } }); } - syncExportFieldLocks(); + syncVoiceValidity(); updateExportBuffer(); };