mirror of
https://github.com/bal-spec/sillytavern-character-memory.git
synced 2026-04-28 03:39:44 +00:00
feat: replace inline convert preview with interactive popup dialog
Convert tool now uses the same callGenericPopup pattern as consolidation: - Side-by-side panes (original file vs editable memory cards) - Full card editing (add/delete/edit blocks and bullets) - Re-run with LLM toggle, version stack, and undo - Output destination controls inside the dialog - inApiCall guard prevents concurrent dialogs - dialogClosed flag cancels stale re-run callbacks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3e89637592
commit
5cb444e55e
3 changed files with 281 additions and 114 deletions
301
index.js
301
index.js
|
|
@ -56,7 +56,7 @@ function getMemoryFileName() {
|
|||
let inApiCall = false;
|
||||
let lastExtractionResult = null;
|
||||
let consolidationBackup = null;
|
||||
let convertPreviewResult = null; // { blocks, warnings, sourceContent }
|
||||
// convertPreviewResult removed — conversion state now lives in the dialog closure
|
||||
let lastExtractionTime = 0; // session-only, resets on page load
|
||||
|
||||
// ============ Activity Log ============
|
||||
|
|
@ -834,9 +834,68 @@ async function convertWithLLM(content, charName) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parse the selected source file and show before/after preview.
|
||||
* Build the HTML for the conversion preview dialog.
|
||||
*/
|
||||
function buildConversionDialog(sourceContent, formatLabel, method, convertedBlocks, editingSet, useLLM) {
|
||||
const afterCount = countMemories(convertedBlocks);
|
||||
const hasEditing = editingSet.size > 0;
|
||||
const memoryFileName = getMemoryFileName();
|
||||
|
||||
return `<div class="charMemory_consolidationDialog">
|
||||
<div class="charMemory_consolidationStats" id="charMemory_convStats">
|
||||
Detected: ${escapeHtml(formatLabel)} • Method: <span id="charMemory_convMethod">${escapeHtml(method)}</span> • Result: <span id="charMemory_convAfterCount">${afterCount}</span> memories in <span id="charMemory_convBlockCount">${convertedBlocks.length}</span> block(s)
|
||||
</div>
|
||||
<div class="charMemory_consolidationToolbar">
|
||||
<label class="checkbox_label" style="margin-right:8px;white-space:nowrap;">
|
||||
<input type="checkbox" id="charMemory_convDialogLLM" ${useLLM ? 'checked' : ''} />
|
||||
<span>Use LLM</span>
|
||||
</label>
|
||||
<input type="button" id="charMemory_rerunConversion" class="menu_button" value="Re-run" title="Re-parse the source file with current settings" />
|
||||
<input type="button" id="charMemory_undoConvRerun" class="menu_button" value="Undo" title="Revert to previous version" disabled />
|
||||
<span id="charMemory_convRerunSpinner" style="display:none;">Working...</span>
|
||||
</div>
|
||||
<div class="charMemory_consolidationPanes">
|
||||
<div class="charMemory_consolidationPane">
|
||||
<h4>Original File</h4>
|
||||
<div class="charMemory_consolidationContent"><pre class="charMemory_convertSourcePre">${escapeHtml(sourceContent)}</pre></div>
|
||||
</div>
|
||||
<div class="charMemory_consolidationPane">
|
||||
<h4>Converted Memories</h4>
|
||||
<div class="charMemory_consolidationContent" id="charMemory_convEditorPane">${renderConsolidatedCards(convertedBlocks, editingSet)}</div>
|
||||
<button class="charMemory_editorAddBlock menu_button ${hasEditing ? '' : 'charMemory_editorAddBlock--hidden'}" id="charMemory_convAddBlock"><i class="fa-solid fa-plus fa-xs"></i> Add Block</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="charMemory_convOutputSection">
|
||||
<div class="charMemory_convertWarning">
|
||||
<i class="fa-solid fa-triangle-exclamation fa-sm"></i>
|
||||
The original file will <b>not</b> be deleted. Hide or remove it from the Data Bank to avoid duplicate memories.
|
||||
</div>
|
||||
<div class="charMemory_convDestRow">
|
||||
<small><b>Output to:</b></small>
|
||||
<label class="radio_label">
|
||||
<input type="radio" name="charMemory_convDest" value="auto" checked />
|
||||
<span>CharMemory file (${escapeHtml(memoryFileName)})</span>
|
||||
</label>
|
||||
<label class="radio_label">
|
||||
<input type="radio" name="charMemory_convDest" value="custom" />
|
||||
<span>Custom:</span>
|
||||
<input type="text" id="charMemory_convCustomName" class="text_pole" placeholder="my-memories.md" style="flex:1;max-width:200px;" disabled />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the selected source file and show an interactive conversion preview dialog.
|
||||
* The dialog uses the same editable-card pattern as the consolidation feature.
|
||||
*/
|
||||
async function previewConversion() {
|
||||
if (inApiCall) {
|
||||
toastr.warning('An API call is already in progress.', 'CharMemory');
|
||||
return;
|
||||
}
|
||||
|
||||
const fileUrl = $('#charMemory_convertSource').val();
|
||||
if (!fileUrl) {
|
||||
toastr.warning('Select a source file first.', 'CharMemory');
|
||||
|
|
@ -870,6 +929,7 @@ async function previewConversion() {
|
|||
let result;
|
||||
|
||||
try {
|
||||
inApiCall = true;
|
||||
if (useLLM && format !== 'memory_tags') {
|
||||
const charName = getCharacterName() || 'Character';
|
||||
toastr.info('Sending to LLM for restructuring...', 'CharMemory', { timeOut: 3000 });
|
||||
|
|
@ -881,40 +941,204 @@ async function previewConversion() {
|
|||
console.error(LOG_PREFIX, 'Conversion failed:', err);
|
||||
toastr.error(`Conversion failed: ${err.message || 'Unknown error'}`, 'CharMemory');
|
||||
return;
|
||||
} finally {
|
||||
inApiCall = false;
|
||||
}
|
||||
|
||||
convertPreviewResult = { ...result, sourceContent };
|
||||
|
||||
// Populate preview UI
|
||||
$('#charMemory_convertFormat').text(formatLabels[format] || format);
|
||||
$('#charMemory_convertMethod').text(useLLM && format !== 'memory_tags' ? 'LLM' : 'Heuristic');
|
||||
$('#charMemory_convertResultCount').text(`${countMemories(result.blocks)} memories in ${result.blocks.length} block(s)`);
|
||||
|
||||
const beforeText = sourceContent.length > 1500 ? sourceContent.substring(0, 1500) + '\n...(truncated)' : sourceContent;
|
||||
const afterText = serializeMemories(result.blocks);
|
||||
const afterTruncated = afterText.length > 1500 ? afterText.substring(0, 1500) + '\n...(truncated)' : afterText;
|
||||
|
||||
$('#charMemory_convertBefore').text(beforeText);
|
||||
$('#charMemory_convertAfter').text(afterTruncated);
|
||||
$('#charMemory_convertPreviewArea').show();
|
||||
|
||||
for (const w of result.warnings) {
|
||||
toastr.warning(w, 'CharMemory');
|
||||
}
|
||||
|
||||
// memory_tags format needs no conversion — heuristic already warned the user
|
||||
if (format === 'memory_tags') {
|
||||
$('#charMemory_convertExecute').prop('disabled', true);
|
||||
} else {
|
||||
$('#charMemory_convertExecute').prop('disabled', false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write converted memories to the chosen destination.
|
||||
*/
|
||||
async function executeConversion() {
|
||||
if (!convertPreviewResult || convertPreviewResult.blocks.length === 0) {
|
||||
toastr.warning('No memories to convert. Run Preview first.', 'CharMemory');
|
||||
if (result.blocks.length === 0) {
|
||||
toastr.warning('No memories could be extracted from the file.', 'CharMemory');
|
||||
return;
|
||||
}
|
||||
|
||||
// === Editor state (lives in closure, survives popup DOM lifecycle) ===
|
||||
let editorBlocks = result.blocks.map(b => ({ ...b, bullets: [...b.bullets] }));
|
||||
const versionStack = [];
|
||||
const editingSet = new Set();
|
||||
let destType = 'auto';
|
||||
let destCustomName = '';
|
||||
let dialogClosed = false; // cancellation flag for in-flight re-run callbacks
|
||||
const cloneBlocks = (blocks) => blocks.map(b => ({ ...b, bullets: [...b.bullets] }));
|
||||
|
||||
const refreshEditor = () => {
|
||||
$('#charMemory_convEditorPane').html(renderConsolidatedCards(editorBlocks, editingSet));
|
||||
$('#charMemory_convAfterCount').text(countMemories(editorBlocks));
|
||||
$('#charMemory_convBlockCount').text(editorBlocks.length);
|
||||
$('#charMemory_convAddBlock').toggleClass('charMemory_editorAddBlock--hidden', editingSet.size === 0);
|
||||
};
|
||||
|
||||
// Build and show dialog
|
||||
const formatLabel = formatLabels[format] || format;
|
||||
const method = useLLM && format !== 'memory_tags' ? 'LLM' : 'Heuristic';
|
||||
const dialogHtml = buildConversionDialog(sourceContent, formatLabel, method, editorBlocks, editingSet, useLLM && format !== 'memory_tags');
|
||||
const popup = callGenericPopup(dialogHtml, POPUP_TYPE.CONFIRM, '', { wide: true, allowVerticalScrolling: true });
|
||||
|
||||
// === Editor event delegation (same card classes as consolidation, different namespaces) ===
|
||||
|
||||
$(document).off('click.charMemoryConvToggle').on('click.charMemoryConvToggle', '.charMemory_editorToggleEdit', function () {
|
||||
const bi = Number($(this).data('block'));
|
||||
if (editingSet.has(bi)) editingSet.delete(bi);
|
||||
else editingSet.add(bi);
|
||||
refreshEditor();
|
||||
});
|
||||
|
||||
$(document).off('input.charMemoryConvBullet').on('input.charMemoryConvBullet', '.charMemory_editorBulletInput', function () {
|
||||
const bi = Number($(this).data('block'));
|
||||
const bui = Number($(this).data('bullet'));
|
||||
if (editorBlocks[bi]) editorBlocks[bi].bullets[bui] = $(this).val();
|
||||
});
|
||||
|
||||
$(document).off('input.charMemoryConvTheme').on('input.charMemoryConvTheme', '.charMemory_editorThemeInput', function () {
|
||||
const bi = Number($(this).data('block'));
|
||||
if (editorBlocks[bi]) editorBlocks[bi].chat = $(this).val();
|
||||
});
|
||||
|
||||
$(document).off('click.charMemoryConvDelBullet').on('click.charMemoryConvDelBullet', '.charMemory_editorDeleteBullet', function () {
|
||||
const bi = Number($(this).data('block'));
|
||||
const bui = Number($(this).data('bullet'));
|
||||
if (editorBlocks[bi]) {
|
||||
editorBlocks[bi].bullets.splice(bui, 1);
|
||||
if (editorBlocks[bi].bullets.length === 0) {
|
||||
editorBlocks.splice(bi, 1);
|
||||
reindexEditingSet(editingSet, bi);
|
||||
}
|
||||
refreshEditor();
|
||||
}
|
||||
});
|
||||
|
||||
$(document).off('click.charMemoryConvDelBlock').on('click.charMemoryConvDelBlock', '.charMemory_editorDeleteBlock', function () {
|
||||
const bi = Number($(this).data('block'));
|
||||
editorBlocks.splice(bi, 1);
|
||||
reindexEditingSet(editingSet, bi);
|
||||
refreshEditor();
|
||||
});
|
||||
|
||||
$(document).off('click.charMemoryConvAddBullet').on('click.charMemoryConvAddBullet', '.charMemory_editorAddBullet', function () {
|
||||
const bi = Number($(this).data('block'));
|
||||
if (editorBlocks[bi]) {
|
||||
editorBlocks[bi].bullets.push('');
|
||||
refreshEditor();
|
||||
$(`#charMemory_convEditorPane .charMemory_editorCard[data-block="${bi}"] .charMemory_editorBulletInput:last`).focus();
|
||||
}
|
||||
});
|
||||
|
||||
$(document).off('click.charMemoryConvAddBlock').on('click.charMemoryConvAddBlock', '#charMemory_convAddBlock', function () {
|
||||
const now = new Date();
|
||||
const timestamp = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
|
||||
const newIdx = editorBlocks.length;
|
||||
editorBlocks.push({ chat: 'New Group', date: timestamp, bullets: [''] });
|
||||
editingSet.add(newIdx);
|
||||
refreshEditor();
|
||||
$('#charMemory_convEditorPane .charMemory_editorCard:last .charMemory_editorBulletInput:last').focus();
|
||||
});
|
||||
|
||||
// === Destination controls (state captured in closure for after popup closes) ===
|
||||
|
||||
$(document).off('change.charMemoryConvDest').on('change.charMemoryConvDest', 'input[name="charMemory_convDest"]', function () {
|
||||
destType = $(this).val();
|
||||
$('#charMemory_convCustomName').prop('disabled', destType !== 'custom');
|
||||
});
|
||||
|
||||
$(document).off('input.charMemoryConvCustom').on('input.charMemoryConvCustom', '#charMemory_convCustomName', function () {
|
||||
destCustomName = $(this).val();
|
||||
});
|
||||
|
||||
// === Re-run ===
|
||||
|
||||
$('#charMemory_rerunConversion').off('click').on('click', async () => {
|
||||
if (inApiCall) return;
|
||||
const currentBlocks = cloneBlocks(editorBlocks);
|
||||
const llmChecked = $('#charMemory_convDialogLLM').prop('checked');
|
||||
|
||||
$('#charMemory_convRerunSpinner').show();
|
||||
$('#charMemory_rerunConversion').prop('disabled', true);
|
||||
$('#charMemory_convEditorPane').addClass('charMemory_editorDisabled');
|
||||
|
||||
let newResult;
|
||||
try {
|
||||
inApiCall = true;
|
||||
if (llmChecked && format !== 'memory_tags') {
|
||||
const charName = getCharacterName() || 'Character';
|
||||
newResult = await convertWithLLM(sourceContent, charName);
|
||||
} else {
|
||||
newResult = convertHeuristic(sourceContent, format);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(LOG_PREFIX, 'Re-run conversion failed:', err);
|
||||
toastr.error(`Re-run failed: ${err.message || 'Unknown error'}`, 'CharMemory');
|
||||
newResult = null;
|
||||
} finally {
|
||||
inApiCall = false;
|
||||
}
|
||||
|
||||
// Bail out if the dialog was closed while the LLM call was in flight
|
||||
if (dialogClosed) return;
|
||||
|
||||
$('#charMemory_convRerunSpinner').hide();
|
||||
$('#charMemory_rerunConversion').prop('disabled', false);
|
||||
$('#charMemory_convEditorPane').removeClass('charMemory_editorDisabled');
|
||||
|
||||
if (newResult && newResult.blocks.length > 0) {
|
||||
versionStack.push(currentBlocks);
|
||||
$('#charMemory_undoConvRerun').prop('disabled', false);
|
||||
editorBlocks = newResult.blocks.map(b => ({ ...b, bullets: [...b.bullets] }));
|
||||
editingSet.clear();
|
||||
refreshEditor();
|
||||
for (const w of newResult.warnings) {
|
||||
toastr.warning(w, 'CharMemory');
|
||||
}
|
||||
const newMethod = llmChecked && format !== 'memory_tags' ? 'LLM' : 'Heuristic';
|
||||
$('#charMemory_convMethod').text(newMethod);
|
||||
}
|
||||
});
|
||||
|
||||
// === Undo ===
|
||||
|
||||
$('#charMemory_undoConvRerun').off('click').on('click', () => {
|
||||
if (versionStack.length === 0) return;
|
||||
editorBlocks = versionStack.pop();
|
||||
editingSet.clear();
|
||||
refreshEditor();
|
||||
if (versionStack.length === 0) $('#charMemory_undoConvRerun').prop('disabled', true);
|
||||
});
|
||||
|
||||
// === Wait for dialog Accept/Cancel ===
|
||||
|
||||
const confirmed = await popup;
|
||||
dialogClosed = true;
|
||||
|
||||
// Clean up all event delegation
|
||||
$(document).off('click.charMemoryConvToggle');
|
||||
$(document).off('input.charMemoryConvBullet');
|
||||
$(document).off('input.charMemoryConvTheme');
|
||||
$(document).off('click.charMemoryConvDelBullet');
|
||||
$(document).off('click.charMemoryConvDelBlock');
|
||||
$(document).off('click.charMemoryConvAddBullet');
|
||||
$(document).off('click.charMemoryConvAddBlock');
|
||||
$(document).off('change.charMemoryConvDest');
|
||||
$(document).off('input.charMemoryConvCustom');
|
||||
|
||||
if (!confirmed) {
|
||||
logActivity('Conversion cancelled by user');
|
||||
return;
|
||||
}
|
||||
|
||||
// === Save converted memories ===
|
||||
|
||||
const cleanBlocks = editorBlocks
|
||||
.map(b => ({ ...b, bullets: b.bullets.filter(bullet => bullet.trim() !== '') }))
|
||||
.filter(b => b.bullets.length > 0);
|
||||
|
||||
if (cleanBlocks.length === 0) {
|
||||
toastr.warning('No memories to save.', 'CharMemory');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -925,10 +1149,10 @@ async function executeConversion() {
|
|||
return;
|
||||
}
|
||||
|
||||
const destType = $('input[name="charMemory_convertDest"]:checked').val();
|
||||
// destType and destCustomName are captured from closure (updated by event handlers)
|
||||
let destFileName;
|
||||
if (destType === 'custom') {
|
||||
destFileName = $('#charMemory_convertCustomName').val().trim();
|
||||
destFileName = destCustomName.trim();
|
||||
if (!destFileName) {
|
||||
toastr.warning('Enter a filename for custom output.', 'CharMemory');
|
||||
return;
|
||||
|
|
@ -944,17 +1168,15 @@ async function executeConversion() {
|
|||
existingBlocks = parseMemories(existingContent);
|
||||
}
|
||||
|
||||
const allBlocks = [...existingBlocks, ...convertPreviewResult.blocks];
|
||||
const allBlocks = [...existingBlocks, ...cleanBlocks];
|
||||
await writeMemoriesForCharacter(serializeMemories(allBlocks), avatar, destFileName);
|
||||
|
||||
const count = countMemories(convertPreviewResult.blocks);
|
||||
const count = countMemories(cleanBlocks);
|
||||
toastr.success(`Converted ${count} memories to ${destFileName}. Remember to hide or remove the original file from Data Bank to avoid duplicates.`, 'CharMemory', { timeOut: 8000 });
|
||||
logActivity(`Converted ${count} memories from Data Bank file to ${destFileName}`);
|
||||
|
||||
// Reset preview and source selection
|
||||
$('#charMemory_convertPreviewArea').hide();
|
||||
// Reset source dropdown
|
||||
$('#charMemory_convertSource').val('');
|
||||
convertPreviewResult = null;
|
||||
updateStatusDisplay();
|
||||
}
|
||||
|
||||
|
|
@ -1030,8 +1252,6 @@ function populateConvertSourceDropdown() {
|
|||
$select.append($opt);
|
||||
}
|
||||
|
||||
// Show auto-name in output destination
|
||||
$('#charMemory_convertAutoName').text(memoryFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -4290,11 +4510,6 @@ function setupListeners() {
|
|||
|
||||
// Convert tool
|
||||
$('#charMemory_convertPreview').off('click').on('click', () => previewConversion());
|
||||
$('#charMemory_convertExecute').off('click').on('click', () => executeConversion());
|
||||
$('#charMemory_convertCancel').off('click').on('click', () => {
|
||||
$('#charMemory_convertPreviewArea').hide();
|
||||
convertPreviewResult = null;
|
||||
});
|
||||
$('#charMemory_restoreConvertPrompt').off('click').on('click', () => {
|
||||
$('#charMemory_convertPrompt').val(defaultConversionPrompt);
|
||||
extension_settings[MODULE_NAME].conversionPrompt = '';
|
||||
|
|
@ -4304,10 +4519,6 @@ function setupListeners() {
|
|||
extension_settings[MODULE_NAME].conversionPrompt = $(this).val();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$('input[name="charMemory_convertDest"]').off('change').on('change', function () {
|
||||
$('#charMemory_convertCustomName').prop('disabled', $(this).val() !== 'custom');
|
||||
});
|
||||
|
||||
$('#charMemory_refreshDiag').off('click').on('click', function () {
|
||||
captureDiagnostics();
|
||||
toastr.info('Diagnostics refreshed.', 'CharMemory');
|
||||
|
|
|
|||
|
|
@ -134,45 +134,7 @@
|
|||
</div>
|
||||
|
||||
<div class="charMemory_buttonRow">
|
||||
<input type="button" id="charMemory_convertPreview" class="menu_button" value="Preview Conversion" title="Parse the selected file and show a before/after preview" />
|
||||
</div>
|
||||
|
||||
<div id="charMemory_convertPreviewArea" style="display:none;">
|
||||
<div class="charMemory_convertMeta">
|
||||
<small><b>Detected format:</b> <span id="charMemory_convertFormat">—</span></small>
|
||||
<small><b>Parse method:</b> <span id="charMemory_convertMethod">—</span></small>
|
||||
<small><b>Result:</b> <span id="charMemory_convertResultCount">—</span></small>
|
||||
</div>
|
||||
<div class="charMemory_convertColumns">
|
||||
<div class="charMemory_convertColumn">
|
||||
<small><b>Before</b></small>
|
||||
<div id="charMemory_convertBefore" class="charMemory_convertBox"></div>
|
||||
</div>
|
||||
<div class="charMemory_convertColumn">
|
||||
<small><b>After</b></small>
|
||||
<div id="charMemory_convertAfter" class="charMemory_convertBox"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="charMemory_convertWarning">
|
||||
<i class="fa-solid fa-triangle-exclamation fa-sm"></i>
|
||||
The original file will <b>not</b> be deleted. Hide or remove it from the Data Bank to avoid duplicate memories.
|
||||
</div>
|
||||
<div class="charMemory_statusRow">
|
||||
<small><b>Output to:</b></small>
|
||||
<label class="radio_label">
|
||||
<input type="radio" name="charMemory_convertDest" value="auto" checked />
|
||||
<span>CharMemory file (<span id="charMemory_convertAutoName">—</span>)</span>
|
||||
</label>
|
||||
<label class="radio_label">
|
||||
<input type="radio" name="charMemory_convertDest" value="custom" />
|
||||
<span>Custom filename:</span>
|
||||
<input type="text" id="charMemory_convertCustomName" class="text_pole" placeholder="my-memories.md" style="flex:1;" disabled />
|
||||
</label>
|
||||
</div>
|
||||
<div class="charMemory_buttonRow">
|
||||
<input type="button" id="charMemory_convertExecute" class="menu_button" value="Convert" title="Write converted memories to the chosen destination" />
|
||||
<input type="button" id="charMemory_convertCancel" class="menu_button" value="Cancel" title="Discard preview and start over" />
|
||||
</div>
|
||||
<input type="button" id="charMemory_convertPreview" class="menu_button" value="Preview Conversion" title="Parse the selected file and show an interactive before/after preview" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
54
style.css
54
style.css
|
|
@ -704,36 +704,6 @@
|
|||
}
|
||||
|
||||
/* Convert tool */
|
||||
.charMemory_convertMeta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
margin-bottom: 8px;
|
||||
padding: 6px 8px;
|
||||
background: var(--SmartThemeBorderColor, rgba(128, 128, 128, 0.1));
|
||||
border-radius: 4px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.charMemory_convertColumns {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.charMemory_convertBox {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
padding: 6px 8px;
|
||||
font-size: 0.8em;
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
background: var(--black30, rgba(0, 0, 0, 0.15));
|
||||
border: 1px solid var(--SmartThemeBorderColor, rgba(128, 128, 128, 0.2));
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.charMemory_convertWarning {
|
||||
padding: 6px 8px;
|
||||
margin-bottom: 8px;
|
||||
|
|
@ -742,3 +712,27 @@
|
|||
background: rgba(232, 163, 61, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Convert dialog (popup) */
|
||||
.charMemory_convertSourcePre {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 0.8em;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
font-family: var(--monoFontFamily, monospace);
|
||||
}
|
||||
|
||||
.charMemory_convOutputSection {
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid var(--SmartThemeBorderColor, rgba(128, 128, 128, 0.2));
|
||||
}
|
||||
|
||||
.charMemory_convDestRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue