fix sync in between text area and modal

This commit is contained in:
u80861711 2025-09-04 16:39:47 +02:00
parent fa33c94944
commit 6dddd5d529
2 changed files with 103 additions and 41 deletions

View file

@ -11,38 +11,78 @@
const i18n = getContext('i18n'); const i18n = getContext('i18n');
export let banners: any[] = []; export let banners: any[] = [];
let sortable: any = null; let sortable: any = null;
let bannerListElement: HTMLDivElement | null = null; let bannerListElement: HTMLDivElement | null = null;
// dynamic languages from config
$: LANGS = Array.isArray($config.features.translation_languages)
? $config.features.translation_languages
: ['de']; // fallback if missing
// reactive UI language code // reactive UI language code
$: langCode = $i18n?.language?.split('-')[0] || 'de'; $: langCode = $i18n?.language?.split('-')[0] || 'de';
// dynamic languages from config
$: LANGS = Array.isArray($config.features.translation_languages)
? [...new Set([...$config.features.translation_languages, langCode])]
: [langCode, 'de'];
// contentObjs stores parsed content objects keyed by banner.id // contentObjs stores parsed content objects keyed by banner.id
let contentObjs: Record<string, Record<string, string>> = {}; let contentObjs: Record<string, Record<string, string>> = {};
// Create a helper function to get/set content safely
function getContent(bannerId: string, lang: string): string {
ensureContentStructure(bannerId);
return contentObjs[bannerId][lang] || '';
}
function setContent(bannerId: string, lang: string, value: string) {
ensureContentStructure(bannerId);
contentObjs[bannerId][lang] = value;
contentObjs = { ...contentObjs };
// Update the corresponding banner
const bannerIndex = banners.findIndex(b => b.id === bannerId);
if (bannerIndex !== -1) {
banners[bannerIndex].content = safeStringify(contentObjs[bannerId]);
banners = [...banners];
// Sync to modal if open
if (showBannerModal && editingBannerIndex === bannerIndex) {
newBanner.content[lang] = value;
newBanner = { ...newBanner };
}
}
}
function ensureContentStructure(bannerId: string) {
if (!contentObjs[bannerId]) {
const banner = banners.find(b => b.id === bannerId);
contentObjs[bannerId] = banner ? parseContentToObj(banner.content) : {};
}
// Ensure all required languages exist
const allLangs = [...new Set([...LANGS, langCode])];
for (const lang of allLangs) {
if (!contentObjs[bannerId][lang]) {
contentObjs[bannerId][lang] = '';
}
}
}
// Initialize/Sync contentObjs with banners // Initialize/Sync contentObjs with banners
$: if (banners) { $: if (banners && banners.length > 0) {
for (const b of banners) { for (const b of banners) {
if (!b) continue; if (!b) continue;
const id = b.id ?? (b.id = crypto?.randomUUID ? crypto.randomUUID() : String(Date.now())); const id = b.id ?? (b.id = crypto?.randomUUID ? crypto.randomUUID() : String(Date.now()));
if (!contentObjs[id]) {
contentObjs[id] = parseContentToObj(b.content); ensureContentStructure(id);
} else {
// Sync from banner.content if it has changed
const currentString = safeStringify(contentObjs[id]); const currentString = safeStringify(contentObjs[id]);
const incomingString = typeof b.content === 'string' const incomingString = typeof b.content === 'string'
? b.content ? b.content
: safeStringify(parseContentToObj(b.content)); : safeStringify(parseContentToObj(b.content));
if (incomingString !== currentString) { if (incomingString !== currentString && incomingString !== '{}') {
contentObjs[id] = parseContentToObj(b.content); contentObjs[id] = parseContentToObj(b.content);
} ensureContentStructure(id); // Re-ensure after parsing
} }
} }
@ -63,8 +103,9 @@
parsed = { de: content || '' }; parsed = { de: content || '' };
} }
// ensure all languages from config exist // keep current languages and check that LANGS are included
for (const lang of LANGS) { const allLangs = [...new Set([...LANGS, langCode, ...Object.keys(parsed)])];
for (const lang of allLangs) {
if (parsed[lang] == null) parsed[lang] = ''; if (parsed[lang] == null) parsed[lang] = '';
} }
return parsed; return parsed;
@ -90,33 +131,48 @@
} }
} }
function handleInlineChange(e: any, idx: number, id: string) {
const val = e?.detail ?? e?.target?.value ?? '';
if (!contentObjs[id]) contentObjs[id] = Object.fromEntries(LANGS.map(l => [l, '']));
contentObjs[id][langCode] = val;
contentObjs = { ...contentObjs };
banners[idx].content = safeStringify(contentObjs[id]);
banners = [...banners];
}
let showBannerModal = false; let showBannerModal = false;
let editingBannerIndex: number | null = null; let editingBannerIndex: number | null = null;
let newBanner: { id: string; content: Record<string, string> } = { id: '', content: {} }; let newBanner: { id: string; content: Record<string, string>; workspaces: string[] } = { id: '', content: {}, workspaces: [] };
function openEditModal(idx: number) { function openEditModal(idx: number) {
editingBannerIndex = idx; editingBannerIndex = idx;
const b = banners[idx]; const b = banners[idx];
if (!b) return; if (!b) return;
const id = b.id; const id = b.id;
if (!contentObjs[id]) contentObjs[id] = parseContentToObj(b.content); const workspaces = b.workspaces || [];
newBanner = { id, content: { ...contentObjs[id] } };
ensureContentStructure(id);
// Copy current contentObjs to newBanner
newBanner = {
id,
content: JSON.parse(JSON.stringify(contentObjs[id])), // Deep copy of current state
workspaces: [...workspaces]
};
showBannerModal = true; showBannerModal = true;
} }
function syncModalToInline(changedLang: string) {
if (editingBannerIndex !== null) {
const id = banners[editingBannerIndex].id;
ensureContentStructure(id);
contentObjs[id][changedLang] = newBanner.content[changedLang] || '';
contentObjs = { ...contentObjs };
// Update banner content
banners[editingBannerIndex].content = safeStringify(contentObjs[id]);
banners = [...banners];
}
}
function closeModal() { function closeModal() {
showBannerModal = false; showBannerModal = false;
editingBannerIndex = null; editingBannerIndex = null;
newBanner = { id: '', content: Object.fromEntries(LANGS.map(l => [l, ''])) }; newBanner = { id: '', content: Object.fromEntries(LANGS.map(l => [l, ''])), workspaces:[] };
} }
function saveModal() { function saveModal() {
@ -133,6 +189,8 @@
if (editingBannerIndex != null) { if (editingBannerIndex != null) {
banners[editingBannerIndex].content = safeStringify(newBanner.content); banners[editingBannerIndex].content = safeStringify(newBanner.content);
banners[editingBannerIndex].workspaces = newBanner.workspaces;
contentObjs[newBanner.id] = { ...newBanner.content }; contentObjs[newBanner.id] = { ...newBanner.content };
contentObjs = { ...contentObjs }; contentObjs = { ...contentObjs };
banners = [...banners]; banners = [...banners];
@ -163,14 +221,17 @@
<option value="success" class="text-gray-900">{$i18n.t('Success')}</option> <option value="success" class="text-gray-900">{$i18n.t('Success')}</option>
</select> </select>
<Textarea <!-- Use getter/setter approach instead of direct binding -->
className="mr-2 text-xs w-full bg-transparent outline-hidden resize-none" <textarea
class="mr-2 text-xs w-full bg-transparent outline-hidden resize-none border-0 p-1"
placeholder={$i18n.t('Content')} placeholder={$i18n.t('Content')}
value={(contentObjs[banner.id]?.[langCode]) ?? ''} value={getContent(banner.id, langCode)}
on:input={(e) => handleInlineChange(e, bannerIdx, banner.id)} on:input={(e) => {
maxSize={100} const value = e.target?.value || '';
readonly setContent(banner.id, langCode, value);
/> }}
rows="2"
></textarea>
<div class="relative -left-2"> <div class="relative -left-2">
<Tooltip content={$i18n.t('Remember Dismissal')} class="flex h-fit items-center"> <Tooltip content={$i18n.t('Remember Dismissal')} class="flex h-fit items-center">
@ -211,6 +272,7 @@
bind:value={newBanner.content[lang]} bind:value={newBanner.content[lang]}
placeholder={`Enter ${lang.toUpperCase()} content`} placeholder={`Enter ${lang.toUpperCase()} content`}
maxSize={200} maxSize={200}
on:input={() => syncModalToInline(lang)}
/> />
</div> </div>
{/each} {/each}

View file

@ -308,8 +308,8 @@
// Dynamic languages from config // Dynamic languages from config
$: LANGS = Array.isArray($config.features.translation_languages) $: LANGS = Array.isArray($config.features.translation_languages)
? $config.features.translation_languages ? [...new Set([...$config.features.translation_languages, langCode])]
: ['de']; // fallback if missing : [langCode, 'de'];
// Utility function to create empty translation object // Utility function to create empty translation object
function createEmptyTranslations() { function createEmptyTranslations() {