Merge dev into main (v0.27.1)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 28s

This commit is contained in:
sandrodach committed 2026-10-31 21:21:55 +01:00
commit e5dd797201
7 files changed
+71 -13

No files matched your search

+7 -1
View File
@@ -6,6 +6,11 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V
## [Unreleased]
## [0.27.1] - 2026-10-31
### Fixed
- Notizen am Handy: Die Leiste zum Ein- und Ausrücken über der Tastatur blieb stehen, wenn man die Tastatur schloss (z. B. mit der Zurück-Taste), und verdeckte die untere Navigation. Sie erscheint jetzt nur, solange die Tastatur offen ist; ein Tipp in den Punkt holt beides zurück.
## [0.27.0] - 2026-10-31
### Changed
@@ -539,7 +544,8 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V
- Geteilte Listen (Einkaufs-, Pack- und Checklisten) mit `#Kategorie`-Gruppierung, Mengenangaben und Echtzeit-Abhaken.
- Benutzer-, Gruppen- und Admin-Verwaltung (Ersteinrichtungs-Assistent, Einladungscodes, Profilbilder, persönliche API-Keys & Webhook-Ingest `/api/v1/webhooks/ingest`, JSON-Datenexport) sowie CSS-Theme-Engine mit 4 Themes (`Dark`, `Light`, `High Contrast`, `Soft`) und PWA-Unterstützung.
[Unreleased]: https://git.dsit.me/sandrodach/MindSift/compare/v0.27.0...dev
[Unreleased]: https://git.dsit.me/sandrodach/MindSift/compare/v0.27.1...dev
[0.27.1]: https://git.dsit.me/sandrodach/MindSift/compare/v0.27.0...v0.27.1
[0.27.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.26.1...v0.27.0
[0.26.1]: https://git.dsit.me/sandrodach/MindSift/compare/v0.26.0...v0.26.1
[0.26.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.25.1...v0.26.0
+1 -1
View File
@@ -6,7 +6,7 @@
[![Self-Hosted](https://img.shields.io/badge/100%25-Self--Hosted-2563eb?style=flat-square)](#)
[![Docker](https://img.shields.io/badge/Deployment-Single%20Container-2496ed?style=flat-square&logo=docker&logoColor=white)](#-schnellstart)
[![SQLite](https://img.shields.io/badge/Daten-SQLite-003b57?style=flat-square&logo=sqlite&logoColor=white)](#-deine-daten-gehören-dir)
[![Version](https://img.shields.io/badge/Version-v0.27.0-16a34a?style=flat-square)](CHANGELOG.md)
[![Version](https://img.shields.io/badge/Version-v0.27.1-16a34a?style=flat-square)](CHANGELOG.md)
**MindSift** ist eine selbst gehostete Schaltstelle für spontane Gedanken, Aufgaben mit Kanban-Board, hierarchische Outliner-Notizen, die mehrere Personen gleichzeitig bearbeiten können, und gemeinsame Listen. Alles läuft als ein einzelner Container mit eingebetteter SQLite-Datenbank, auf deinem eigenen Server.
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "mindsift-frontend",
"version": "0.27.0",
"version": "0.27.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mindsift-frontend",
"version": "0.27.0",
"version": "0.27.1",
"license": "Unlicense",
"dependencies": {
"@fontsource-variable/inter": "5.3.0",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "mindsift-frontend",
"private": true,
"version": "0.27.0",
"version": "0.27.1",
"license": "Unlicense",
"type": "module",
"scripts": {
@@ -4,7 +4,7 @@ import { ArrowDown, ArrowUp, IndentDecrease, IndentIncrease } from 'lucide-react
import { useKeyboardInset } from '../../lib/useKeyboardInset';
// Phones and tablets have no Tab key: while a bullet is being edited, a slim bar sits right above the
// on-screen keyboard with outdent / indent and move up / down (the bullet takes its children along).
// on-screen keyboard (only while it is open) with outdent / indent and move up / down (the bullet takes its children along).
export const isTouchOnly =
typeof window !== 'undefined' &&
!!window.matchMedia?.('(hover: none) and (pointer: coarse)').matches &&
@@ -19,7 +19,7 @@ interface MobileOutlineBarProps {
export const MobileOutlineBar: React.FC<MobileOutlineBarProps> = ({ onOutdent, onIndent, onMoveUp, onMoveDown }) => {
// iOS keeps fixed elements behind the keyboard: lift the bar by the keyboard's height
const { inset } = useKeyboardInset();
const { inset, keyboardOpen } = useKeyboardInset();
const button = (label: string, icon: React.ReactNode, action: () => void) => (
<button
@@ -36,6 +36,9 @@ export const MobileOutlineBar: React.FC<MobileOutlineBarProps> = ({ onOutdent, o
</button>
);
// Keyboard closed (e.g. back button) while the bullet keeps its focus: no bar over the bottom navigation
if (!keyboardOpen) return null;
return createPortal(
<div
className="fixed left-0 right-0 z-[70] flex items-center justify-center gap-2 px-3 py-1.5 bg-panel/95 backdrop-blur-md border-t border-border"
@@ -26,10 +26,30 @@ describe('useKeyboardInset', () => {
const vv = mockVisualViewport(844);
const { result } = renderHook(() => useKeyboardInset());
expect(result.current.inset).toBe(0);
expect(result.current.keyboardOpen).toBe(false);
act(() => vv.open(336));
expect(result.current.inset).toBe(336);
expect(result.current.visibleHeight).toBe(508);
expect(result.current.keyboardOpen).toBe(true);
act(() => vv.open(0));
expect(result.current.inset).toBe(0);
expect(result.current.keyboardOpen).toBe(false);
});
it('notices the keyboard also when the page shrinks instead (no inset)', () => {
const vv = mockVisualViewport(844);
const { result } = renderHook(() => useKeyboardInset());
act(() => {
Object.defineProperty(window, 'innerHeight', { value: 508, configurable: true });
vv.open(336);
});
expect(result.current.inset).toBe(0);
expect(result.current.keyboardOpen).toBe(true);
// Browser bars appearing are not a keyboard
act(() => {
Object.defineProperty(window, 'innerHeight', { value: 788, configurable: true });
vv.open(56);
});
expect(result.current.keyboardOpen).toBe(false);
});
});
+35 -6
View File
@@ -1,13 +1,42 @@
import { useEffect, useState } from 'react';
// Space the on-screen keyboard takes at the bottom (0 without keyboard) and the height that stays
// visible. Fixed bottom sheets use it to sit right above the keyboard instead of behind it (iOS keeps
// fixed elements on the layout viewport; on Android the inset is usually 0 because the page shrinks).
export function useKeyboardInset(): { inset: number; visibleHeight: number } {
// Tallest visible height seen at the current width, i.e. the height without keyboard. Tracked from app
// start, so it is known before the first field is focused; a new width (rotation) starts over.
let fullHeight = 0;
let fullWidth = 0;
function noteFullHeight() {
const vv = window.visualViewport;
if (!vv) return;
if (window.innerWidth !== fullWidth) {
fullWidth = window.innerWidth;
fullHeight = 0;
}
fullHeight = Math.max(fullHeight, Math.round(vv.height));
}
if (typeof window !== 'undefined' && window.visualViewport) {
noteFullHeight();
window.visualViewport.addEventListener('resize', noteFullHeight);
}
// More than browser bars appearing or disappearing (about 60 px)
const KEYBOARD_MIN_PX = 150;
// Space the on-screen keyboard takes at the bottom (0 without keyboard), the height that stays visible
// and whether a keyboard is open. Fixed bottom sheets use the inset to sit right above the keyboard
// instead of behind it (iOS keeps fixed elements on the layout viewport; on Android the inset is usually
// 0 because the page shrinks). keyboardOpen compares with the height without keyboard, so it works in
// both cases; without visualViewport it is unknown and reported as open.
export function useKeyboardInset(): { inset: number; visibleHeight: number; keyboardOpen: boolean } {
const read = () => {
const vv = typeof window !== 'undefined' ? window.visualViewport : null;
if (!vv) return { inset: 0, visibleHeight: typeof window !== 'undefined' ? window.innerHeight : 0 };
return { inset: Math.max(0, Math.round(window.innerHeight - vv.height - vv.offsetTop)), visibleHeight: Math.round(vv.height) };
if (!vv) return { inset: 0, visibleHeight: typeof window !== 'undefined' ? window.innerHeight : 0, keyboardOpen: true };
noteFullHeight();
const visibleHeight = Math.round(vv.height);
return {
inset: Math.max(0, Math.round(window.innerHeight - vv.height - vv.offsetTop)),
visibleHeight,
keyboardOpen: visibleHeight < fullHeight - KEYBOARD_MIN_PX,
};
};
const [state, setState] = useState(read);