fix: replace innerHTML with insertAdjacentHTML/replaceChildren; revert docker-compose dev changes
- birthdays.js: all innerHTML writes replaced with replaceChildren() + insertAdjacentHTML() - dashboard.js: shell.innerHTML replaced with replaceChildren() + insertAdjacentHTML() - docker-compose.yml: revert port to 3000 and restore image line (were personal dev changes) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+3
-3
@@ -1,11 +1,11 @@
|
|||||||
services:
|
services:
|
||||||
oikos:
|
oikos:
|
||||||
# image: ghcr.io/ulsklyc/oikos:latest
|
image: ghcr.io/ulsklyc/oikos:latest
|
||||||
build: . # optional: use --build to build locally instead
|
build: . # optional: use --build to build locally instead
|
||||||
container_name: oikos
|
container_name: oikos
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "0.0.0.0:3100:3000"
|
- "0.0.0.0:3000:3000"
|
||||||
volumes:
|
volumes:
|
||||||
- oikos_data:/data
|
- oikos_data:/data
|
||||||
env_file:
|
env_file:
|
||||||
@@ -19,7 +19,7 @@ services:
|
|||||||
# Direct HTTP access (no reverse proxy):
|
# Direct HTTP access (no reverse proxy):
|
||||||
- SESSION_SECURE=false
|
- SESSION_SECURE=false
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3100/health', r => process.exit(r.statusCode === 200 ? 0 : 1))"]
|
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', r => process.exit(r.statusCode === 200 ? 0 : 1))"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|||||||
+21
-14
@@ -65,11 +65,12 @@ function renderSuggestions() {
|
|||||||
const items = suggestions();
|
const items = suggestions();
|
||||||
if (!items.length) {
|
if (!items.length) {
|
||||||
dropdown.hidden = true;
|
dropdown.hidden = true;
|
||||||
dropdown.innerHTML = '';
|
dropdown.replaceChildren();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dropdown.hidden = false;
|
dropdown.hidden = false;
|
||||||
dropdown.innerHTML = items.map((birthday, idx) => `
|
dropdown.replaceChildren();
|
||||||
|
dropdown.insertAdjacentHTML('beforeend', items.map((birthday, idx) => `
|
||||||
<button class="birthday-suggestion" type="button" data-index="${idx}" data-name="${esc(birthday.name)}">
|
<button class="birthday-suggestion" type="button" data-index="${idx}" data-name="${esc(birthday.name)}">
|
||||||
${photoAvatar(birthday, 'birthday-avatar--xs')}
|
${photoAvatar(birthday, 'birthday-avatar--xs')}
|
||||||
<span>
|
<span>
|
||||||
@@ -77,20 +78,22 @@ function renderSuggestions() {
|
|||||||
<small>${esc(ageNote(birthday))}</small>
|
<small>${esc(ageNote(birthday))}</small>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
`).join('');
|
`).join(''));
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderUpcoming() {
|
function renderUpcoming() {
|
||||||
const host = _container.querySelector('#birthdays-upcoming');
|
const host = _container.querySelector('#birthdays-upcoming');
|
||||||
if (!host) return;
|
if (!host) return;
|
||||||
if (!state.upcoming.length) {
|
if (!state.upcoming.length) {
|
||||||
host.innerHTML = `<div class="empty-state empty-state--compact">
|
host.replaceChildren();
|
||||||
|
host.insertAdjacentHTML('beforeend', `<div class="empty-state empty-state--compact">
|
||||||
<div class="empty-state__title">${t('birthdays.emptyTitle')}</div>
|
<div class="empty-state__title">${t('birthdays.emptyTitle')}</div>
|
||||||
<div class="empty-state__description">${t('birthdays.emptyDescription')}</div>
|
<div class="empty-state__description">${t('birthdays.emptyDescription')}</div>
|
||||||
</div>`;
|
</div>`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
host.innerHTML = state.upcoming.map((birthday) => `
|
host.replaceChildren();
|
||||||
|
host.insertAdjacentHTML('beforeend', state.upcoming.map((birthday) => `
|
||||||
<article class="birthday-card">
|
<article class="birthday-card">
|
||||||
<div class="birthday-card__media">${photoAvatar(birthday)}</div>
|
<div class="birthday-card__media">${photoAvatar(birthday)}</div>
|
||||||
<div class="birthday-card__body">
|
<div class="birthday-card__body">
|
||||||
@@ -106,7 +109,7 @@ function renderUpcoming() {
|
|||||||
<div class="birthday-card__note">${esc(ageNote(birthday))}</div>
|
<div class="birthday-card__note">${esc(ageNote(birthday))}</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
`).join('');
|
`).join(''));
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderList() {
|
function renderList() {
|
||||||
@@ -114,14 +117,16 @@ function renderList() {
|
|||||||
if (!host) return;
|
if (!host) return;
|
||||||
const list = filteredBirthdays();
|
const list = filteredBirthdays();
|
||||||
if (!list.length) {
|
if (!list.length) {
|
||||||
host.innerHTML = `<div class="empty-state">
|
host.replaceChildren();
|
||||||
|
host.insertAdjacentHTML('beforeend', `<div class="empty-state">
|
||||||
<div class="empty-state__title">${t('birthdays.emptyTitle')}</div>
|
<div class="empty-state__title">${t('birthdays.emptyTitle')}</div>
|
||||||
<div class="empty-state__description">${t('birthdays.emptyDescription')}</div>
|
<div class="empty-state__description">${t('birthdays.emptyDescription')}</div>
|
||||||
</div>`;
|
</div>`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
host.innerHTML = list.map((birthday) => `
|
host.replaceChildren();
|
||||||
|
host.insertAdjacentHTML('beforeend', list.map((birthday) => `
|
||||||
<article class="birthday-item" data-id="${birthday.id}">
|
<article class="birthday-item" data-id="${birthday.id}">
|
||||||
<div class="birthday-item__media">${photoAvatar(birthday)}</div>
|
<div class="birthday-item__media">${photoAvatar(birthday)}</div>
|
||||||
<div class="birthday-item__body">
|
<div class="birthday-item__body">
|
||||||
@@ -142,14 +147,15 @@ function renderList() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
`).join('');
|
`).join(''));
|
||||||
|
|
||||||
if (window.lucide) window.lucide.createIcons();
|
if (window.lucide) window.lucide.createIcons();
|
||||||
stagger(host.querySelectorAll('.birthday-item'));
|
stagger(host.querySelectorAll('.birthday-item'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderPage() {
|
function renderPage() {
|
||||||
_container.innerHTML = `
|
_container.replaceChildren();
|
||||||
|
_container.insertAdjacentHTML('beforeend', `
|
||||||
<div class="birthdays-page">
|
<div class="birthdays-page">
|
||||||
<h1 class="sr-only">${t('birthdays.title')}</h1>
|
<h1 class="sr-only">${t('birthdays.title')}</h1>
|
||||||
<div class="birthdays-toolbar">
|
<div class="birthdays-toolbar">
|
||||||
@@ -194,7 +200,7 @@ function renderPage() {
|
|||||||
<i data-lucide="plus" style="width:24px;height:24px" aria-hidden="true"></i>
|
<i data-lucide="plus" style="width:24px;height:24px" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`);
|
||||||
|
|
||||||
renderUpcoming();
|
renderUpcoming();
|
||||||
renderList();
|
renderList();
|
||||||
@@ -304,7 +310,8 @@ function openBirthdayModal({ mode, birthday = null }) {
|
|||||||
const nameInput = panel.querySelector('#bd-name');
|
const nameInput = panel.querySelector('#bd-name');
|
||||||
const preview = panel.querySelector('#birthday-preview');
|
const preview = panel.querySelector('#birthday-preview');
|
||||||
const renderPreview = () => {
|
const renderPreview = () => {
|
||||||
preview.innerHTML = birthdayPreviewHtml(nameInput.value.trim(), photoData);
|
preview.replaceChildren();
|
||||||
|
preview.insertAdjacentHTML('beforeend', birthdayPreviewHtml(nameInput.value.trim(), photoData));
|
||||||
};
|
};
|
||||||
nameInput.addEventListener('input', renderPreview);
|
nameInput.addEventListener('input', renderPreview);
|
||||||
panel.querySelector('#bd-photo').addEventListener('change', async (e) => {
|
panel.querySelector('#bd-photo').addEventListener('change', async (e) => {
|
||||||
|
|||||||
@@ -1061,10 +1061,11 @@ export async function render(container, { user }) {
|
|||||||
function rebuildDashboard(cfg) {
|
function rebuildDashboard(cfg) {
|
||||||
const shell = container.querySelector('#dashboard-shell');
|
const shell = container.querySelector('#dashboard-shell');
|
||||||
if (!shell) return;
|
if (!shell) return;
|
||||||
shell.innerHTML = `
|
shell.replaceChildren();
|
||||||
|
shell.insertAdjacentHTML('beforeend', `
|
||||||
${renderDashboardOverview(user, stats, weather)}
|
${renderDashboardOverview(user, stats, weather)}
|
||||||
${renderDashboardLayout(cfg, data, weather, currency)}
|
${renderDashboardLayout(cfg, data, weather, currency)}
|
||||||
`;
|
`);
|
||||||
wireLinks(container, rerender);
|
wireLinks(container, rerender);
|
||||||
if (window.lucide) window.lucide.createIcons();
|
if (window.lucide) window.lucide.createIcons();
|
||||||
wireWeatherRefresh(container, (updatedWeather) => {
|
wireWeatherRefresh(container, (updatedWeather) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user