chore: release v0.37.1
This commit is contained in:
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.37.1] - 2026-04-30
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Bottom navigation: Tasks replaces Search as a primary tab bar item
|
||||||
|
- More menu: layout changed from two columns to a three-column grid (two rows of three)
|
||||||
|
- Search: embedded as a narrow bar at the top of the More sheet instead of a standalone bottom-nav button
|
||||||
|
|
||||||
## [0.37.0] - 2026-04-30
|
## [0.37.0] - 2026-04-30
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "oikos",
|
"name": "oikos",
|
||||||
"version": "0.36.1",
|
"version": "0.37.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "oikos",
|
"name": "oikos",
|
||||||
"version": "0.36.1",
|
"version": "0.37.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bcrypt": "^6.0.0",
|
"bcrypt": "^6.0.0",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "oikos",
|
"name": "oikos",
|
||||||
"version": "0.37.0",
|
"version": "0.37.1",
|
||||||
"description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.",
|
"description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.",
|
||||||
"main": "server/index.js",
|
"main": "server/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
+44
-31
@@ -132,7 +132,7 @@ let _pendingLoginRedirect = false;
|
|||||||
const ROUTE_ORDER = ['/', '/calendar', '/tasks', '/meals', '/recipes', '/shopping',
|
const ROUTE_ORDER = ['/', '/calendar', '/tasks', '/meals', '/recipes', '/shopping',
|
||||||
'/birthdays', '/notes', '/contacts', '/budget', '/documents', '/settings'];
|
'/birthdays', '/notes', '/contacts', '/budget', '/documents', '/settings'];
|
||||||
|
|
||||||
const PRIMARY_NAV = 2;
|
const PRIMARY_NAV = 3;
|
||||||
|
|
||||||
const DEFAULT_APP_NAME = 'Oikos';
|
const DEFAULT_APP_NAME = 'Oikos';
|
||||||
const APP_NAME_STORAGE_KEY = 'oikos-app-name';
|
const APP_NAME_STORAGE_KEY = 'oikos-app-name';
|
||||||
@@ -504,23 +504,6 @@ function renderAppShell(container) {
|
|||||||
kitchenBtn.addEventListener('click', () => navigate(getLastKitchenRoute()));
|
kitchenBtn.addEventListener('click', () => navigate(getLastKitchenRoute()));
|
||||||
bottomItems.appendChild(kitchenBtn);
|
bottomItems.appendChild(kitchenBtn);
|
||||||
|
|
||||||
const searchNavBtn = document.createElement('button');
|
|
||||||
searchNavBtn.className = 'nav-item nav-item--search';
|
|
||||||
searchNavBtn.id = 'search-btn';
|
|
||||||
searchNavBtn.type = 'button';
|
|
||||||
searchNavBtn.setAttribute('aria-label', t('nav.search'));
|
|
||||||
searchNavBtn.setAttribute('title', t('nav.search'));
|
|
||||||
const searchNavIcon = document.createElement('i');
|
|
||||||
searchNavIcon.dataset.lucide = 'search';
|
|
||||||
searchNavIcon.className = 'nav-item__icon';
|
|
||||||
searchNavIcon.setAttribute('aria-hidden', 'true');
|
|
||||||
const searchNavLabel = document.createElement('span');
|
|
||||||
searchNavLabel.className = 'nav-item__label';
|
|
||||||
searchNavLabel.textContent = t('nav.search');
|
|
||||||
searchNavBtn.appendChild(searchNavIcon);
|
|
||||||
searchNavBtn.appendChild(searchNavLabel);
|
|
||||||
bottomItems.appendChild(searchNavBtn);
|
|
||||||
|
|
||||||
const moreBtn = document.createElement('button');
|
const moreBtn = document.createElement('button');
|
||||||
moreBtn.className = 'nav-item nav-item--more';
|
moreBtn.className = 'nav-item nav-item--more';
|
||||||
moreBtn.id = 'more-btn';
|
moreBtn.id = 'more-btn';
|
||||||
@@ -553,6 +536,24 @@ function renderAppShell(container) {
|
|||||||
dragHandle.className = 'more-sheet__handle';
|
dragHandle.className = 'more-sheet__handle';
|
||||||
dragHandle.setAttribute('aria-hidden', 'true');
|
dragHandle.setAttribute('aria-hidden', 'true');
|
||||||
moreSheet.insertAdjacentElement('afterbegin', dragHandle);
|
moreSheet.insertAdjacentElement('afterbegin', dragHandle);
|
||||||
|
|
||||||
|
const moreSearchBar = document.createElement('div');
|
||||||
|
moreSearchBar.className = 'more-sheet__search';
|
||||||
|
moreSearchBar.id = 'more-sheet-search';
|
||||||
|
moreSearchBar.setAttribute('role', 'button');
|
||||||
|
moreSearchBar.setAttribute('tabindex', '0');
|
||||||
|
moreSearchBar.setAttribute('aria-label', t('search.placeholder'));
|
||||||
|
const moreSearchIcon = document.createElement('i');
|
||||||
|
moreSearchIcon.dataset.lucide = 'search';
|
||||||
|
moreSearchIcon.className = 'more-sheet__search-icon';
|
||||||
|
moreSearchIcon.setAttribute('aria-hidden', 'true');
|
||||||
|
const moreSearchPlaceholder = document.createElement('span');
|
||||||
|
moreSearchPlaceholder.className = 'more-sheet__search-placeholder';
|
||||||
|
moreSearchPlaceholder.textContent = t('search.placeholder');
|
||||||
|
moreSearchBar.appendChild(moreSearchIcon);
|
||||||
|
moreSearchBar.appendChild(moreSearchPlaceholder);
|
||||||
|
moreSheet.appendChild(moreSearchBar);
|
||||||
|
|
||||||
navItems().filter((i) => !i.kitchenGroup).slice(PRIMARY_NAV).forEach((item) => moreSheet.appendChild(moreItemEl(item)));
|
navItems().filter((i) => !i.kitchenGroup).slice(PRIMARY_NAV).forEach((item) => moreSheet.appendChild(moreItemEl(item)));
|
||||||
|
|
||||||
const searchOverlay = document.createElement('div');
|
const searchOverlay = document.createElement('div');
|
||||||
@@ -606,15 +607,15 @@ function renderAppShell(container) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
initMoreSheet(container);
|
const openSearch = initSearch(container);
|
||||||
|
initMoreSheet(container, openSearch);
|
||||||
initNavHideOnScroll(container);
|
initNavHideOnScroll(container);
|
||||||
initSearch(container);
|
|
||||||
initOfflineBanner();
|
initOfflineBanner();
|
||||||
initKeyboardShortcuts();
|
initKeyboardShortcuts();
|
||||||
}
|
}
|
||||||
|
|
||||||
const SHORTCUTS = [
|
const SHORTCUTS = [
|
||||||
{ key: '/', description: () => t('shortcuts.search'), action: () => document.getElementById('search-btn')?.click() },
|
{ key: '/', description: () => t('shortcuts.search'), action: () => document.getElementById('more-sheet-search')?.click() },
|
||||||
{ key: 'n', description: () => t('shortcuts.new'), action: () => document.querySelector('.page-fab')?.click() },
|
{ key: 'n', description: () => t('shortcuts.new'), action: () => document.querySelector('.page-fab')?.click() },
|
||||||
{ key: '?', description: () => t('shortcuts.help'), action: () => showShortcutsModal() },
|
{ key: '?', description: () => t('shortcuts.help'), action: () => showShortcutsModal() },
|
||||||
{ key: 'g d', description: () => t('shortcuts.goDash'), action: () => navigate('/') },
|
{ key: 'g d', description: () => t('shortcuts.goDash'), action: () => navigate('/') },
|
||||||
@@ -774,7 +775,7 @@ function initNavHideOnScroll(container) {
|
|||||||
/**
|
/**
|
||||||
* Öffnet/schließt das More-Sheet und die Backdrop.
|
* Öffnet/schließt das More-Sheet und die Backdrop.
|
||||||
*/
|
*/
|
||||||
function initMoreSheet(container) {
|
function initMoreSheet(container, openSearch) {
|
||||||
const moreBtn = container.querySelector('#more-btn');
|
const moreBtn = container.querySelector('#more-btn');
|
||||||
const backdrop = container.querySelector('#more-backdrop');
|
const backdrop = container.querySelector('#more-backdrop');
|
||||||
const sheet = container.querySelector('#more-sheet');
|
const sheet = container.querySelector('#more-sheet');
|
||||||
@@ -812,6 +813,15 @@ function initMoreSheet(container) {
|
|||||||
el.addEventListener('click', () => closeSheet());
|
el.addEventListener('click', () => closeSheet());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const moreSearchBar = sheet.querySelector('#more-sheet-search');
|
||||||
|
if (moreSearchBar && openSearch) {
|
||||||
|
const triggerSearch = () => { closeSheet(); openSearch(); };
|
||||||
|
moreSearchBar.addEventListener('click', triggerSearch);
|
||||||
|
moreSearchBar.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); triggerSearch(); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
window._closeMoreSheet = closeSheet;
|
window._closeMoreSheet = closeSheet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -819,12 +829,11 @@ function initMoreSheet(container) {
|
|||||||
* Initialisiert die Suchfunktion (Overlay + API-Calls).
|
* Initialisiert die Suchfunktion (Overlay + API-Calls).
|
||||||
*/
|
*/
|
||||||
function initSearch(container) {
|
function initSearch(container) {
|
||||||
const searchBtn = container.querySelector('#search-btn');
|
|
||||||
const searchClose = container.querySelector('#search-close');
|
const searchClose = container.querySelector('#search-close');
|
||||||
const overlay = container.querySelector('#search-overlay');
|
const overlay = container.querySelector('#search-overlay');
|
||||||
const input = container.querySelector('#search-input');
|
const input = container.querySelector('#search-input');
|
||||||
const results = container.querySelector('#search-results');
|
const results = container.querySelector('#search-results');
|
||||||
if (!searchBtn || !overlay || !input || !results) return;
|
if (!overlay || !input || !results) return null;
|
||||||
|
|
||||||
// Leichtgewichtiger Focus Trap für das Search Overlay.
|
// Leichtgewichtiger Focus Trap für das Search Overlay.
|
||||||
// Eigenständig (kein modal.js), da modul-globale Variablen in modal.js
|
// Eigenständig (kein modal.js), da modul-globale Variablen in modal.js
|
||||||
@@ -832,7 +841,6 @@ function initSearch(container) {
|
|||||||
let _searchTrapHandler = null;
|
let _searchTrapHandler = null;
|
||||||
|
|
||||||
function openSearch() {
|
function openSearch() {
|
||||||
window._openSearch = openSearch;
|
|
||||||
if (window._closeMoreSheet) window._closeMoreSheet();
|
if (window._closeMoreSheet) window._closeMoreSheet();
|
||||||
overlay.setAttribute('aria-hidden', 'false');
|
overlay.setAttribute('aria-hidden', 'false');
|
||||||
overlay.classList.add('search-overlay--visible');
|
overlay.classList.add('search-overlay--visible');
|
||||||
@@ -866,8 +874,7 @@ function initSearch(container) {
|
|||||||
results.replaceChildren();
|
results.replaceChildren();
|
||||||
}
|
}
|
||||||
|
|
||||||
searchBtn.addEventListener('click', openSearch);
|
if (searchClose) searchClose.addEventListener('click', closeSearch);
|
||||||
searchClose.addEventListener('click', closeSearch);
|
|
||||||
|
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
if (e.key === 'Escape' && overlay.classList.contains('search-overlay--visible')) {
|
if (e.key === 'Escape' && overlay.classList.contains('search-overlay--visible')) {
|
||||||
@@ -892,6 +899,8 @@ function initSearch(container) {
|
|||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return openSearch;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1253,17 +1262,21 @@ window.addEventListener('locale-changed', () => {
|
|||||||
}
|
}
|
||||||
if (bottomItems) {
|
if (bottomItems) {
|
||||||
const kitchenBtnEl = bottomItems.querySelector('#kitchen-btn');
|
const kitchenBtnEl = bottomItems.querySelector('#kitchen-btn');
|
||||||
const searchBtnEl = bottomItems.querySelector('#search-btn');
|
|
||||||
const moreBtn = bottomItems.querySelector('#more-btn');
|
const moreBtn = bottomItems.querySelector('#more-btn');
|
||||||
if (kitchenBtnEl) kitchenBtnEl.querySelector('.nav-item__label').textContent = t('nav.kitchen');
|
if (kitchenBtnEl) kitchenBtnEl.querySelector('.nav-item__label').textContent = t('nav.kitchen');
|
||||||
if (searchBtnEl) searchBtnEl.querySelector('.nav-item__label').textContent = t('nav.search');
|
|
||||||
const newItems = navItems().slice(0, PRIMARY_NAV).map(navItemEl);
|
const newItems = navItems().slice(0, PRIMARY_NAV).map(navItemEl);
|
||||||
bottomItems.replaceChildren(...newItems, kitchenBtnEl, searchBtnEl, moreBtn);
|
bottomItems.replaceChildren(...newItems, kitchenBtnEl, moreBtn);
|
||||||
}
|
}
|
||||||
if (moreSheet) {
|
if (moreSheet) {
|
||||||
const handle = moreSheet.querySelector('.more-sheet__handle');
|
const handle = moreSheet.querySelector('.more-sheet__handle');
|
||||||
|
const searchBar = moreSheet.querySelector('#more-sheet-search');
|
||||||
|
if (searchBar) {
|
||||||
|
const placeholder = searchBar.querySelector('.more-sheet__search-placeholder');
|
||||||
|
if (placeholder) placeholder.textContent = t('search.placeholder');
|
||||||
|
searchBar.setAttribute('aria-label', t('search.placeholder'));
|
||||||
|
}
|
||||||
const newMoreItems = navItems().filter((i) => !i.kitchenGroup).slice(PRIMARY_NAV).map(moreItemEl);
|
const newMoreItems = navItems().filter((i) => !i.kitchenGroup).slice(PRIMARY_NAV).map(moreItemEl);
|
||||||
moreSheet.replaceChildren(handle, ...newMoreItems);
|
moreSheet.replaceChildren(handle, ...(searchBar ? [searchBar] : []), ...newMoreItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelectorAll('[data-route]').forEach((el) => {
|
document.querySelectorAll('[data-route]').forEach((el) => {
|
||||||
|
|||||||
@@ -218,7 +218,7 @@
|
|||||||
padding: var(--space-4) var(--space-4) calc(var(--space-4) + var(--safe-area-inset-bottom));
|
padding: var(--space-4) var(--space-4) calc(var(--space-4) + var(--safe-area-inset-bottom));
|
||||||
z-index: calc(var(--z-nav) + 2);
|
z-index: calc(var(--z-nav) + 2);
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: var(--space-3);
|
gap: var(--space-3);
|
||||||
transform: translateY(100%);
|
transform: translateY(100%);
|
||||||
transition: transform 0.25s var(--ease-out);
|
transition: transform 0.25s var(--ease-out);
|
||||||
@@ -238,6 +238,40 @@
|
|||||||
margin: 0 auto var(--space-2);
|
margin: 0 auto var(--space-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── More-Sheet Suchleiste ── */
|
||||||
|
.more-sheet__search {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
padding: var(--space-2) var(--space-3);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
background-color: var(--color-surface-elevated);
|
||||||
|
border: 1.5px solid var(--color-border);
|
||||||
|
color: var(--color-text-tertiary);
|
||||||
|
cursor: text;
|
||||||
|
margin-bottom: var(--space-1);
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
transition: border-color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-sheet__search:active,
|
||||||
|
.more-sheet__search:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-sheet__search-icon {
|
||||||
|
width: var(--space-4);
|
||||||
|
height: var(--space-4);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-sheet__search-placeholder {
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
color: var(--color-text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
/* ── More-Item ── */
|
/* ── More-Item ── */
|
||||||
.more-item {
|
.more-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user