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

This commit is contained in:
sandrodach committed 2026-11-02 21:17:58 +01:00
commit 9b02d7cfa1
5 files changed
+45 -8

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] ## [Unreleased]
## [0.63.1] - 2026-11-02
### Fixed
- Notizen am Desktop: Die Knopfleiste an einem Punkt (Fokus, Auslagern, Löschen …) erscheint nur noch, während sich die Maus über dem Punkt bewegt, und verschwindet etwa eine Sekunde später. Eine ruhende Maus, auch beim Tippen, lässt sie nicht mehr über dem Text stehen. Auf der Leiste selbst bleibt sie sichtbar.
## [0.63.0] - 2026-11-02 ## [0.63.0] - 2026-11-02
### Added ### Added
@@ -902,7 +907,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. - 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. - 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.63.0...dev [Unreleased]: https://git.dsit.me/sandrodach/MindSift/compare/v0.63.1...dev
[0.63.1]: https://git.dsit.me/sandrodach/MindSift/compare/v0.63.0...v0.63.1
[0.63.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.62.0...v0.63.0 [0.63.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.62.0...v0.63.0
[0.62.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.61.0...v0.62.0 [0.62.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.61.0...v0.62.0
[0.61.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.60.0...v0.61.0 [0.61.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.60.0...v0.61.0
+1 -1
View File
@@ -6,7 +6,7 @@
[![Self-Hosted](https://img.shields.io/badge/100%25-Self--Hosted-2563eb?style=flat-square)](#) [![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) [![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) [![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.63.0-16a34a?style=flat-square)](CHANGELOG.md) [![Version](https://img.shields.io/badge/Version-v0.63.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. **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", "name": "mindsift-frontend",
"version": "0.63.0", "version": "0.63.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mindsift-frontend", "name": "mindsift-frontend",
"version": "0.63.0", "version": "0.63.1",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"dependencies": { "dependencies": {
"@fontsource-variable/inter": "5.3.0", "@fontsource-variable/inter": "5.3.0",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "mindsift-frontend", "name": "mindsift-frontend",
"private": true, "private": true,
"version": "0.63.0", "version": "0.63.1",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -67,6 +67,9 @@ function slashRank(c: SlashCommand, q: string): number {
return 4; return 4;
} }
// How long the action bubble stays after the last mouse move over a bullet
const BUBBLE_IDLE_MS = 1000;
const SLASH_COMMANDS: SlashCommand[] = [ const SLASH_COMMANDS: SlashCommand[] = [
{ type: 'todo', label: '/todo', desc: 'Aufgabe mit Checkbox', icon: <ListTodo className="w-3.5 h-3.5" /> }, { type: 'todo', label: '/todo', desc: 'Aufgabe mit Checkbox', icon: <ListTodo className="w-3.5 h-3.5" /> },
{ type: 'numbered', label: '/number', desc: 'Nummerierte Liste (1., a., i.)', icon: <ListOrdered className="w-3.5 h-3.5" />, aliases: ['nummer', 'liste', 'ol'] }, { type: 'numbered', label: '/number', desc: 'Nummerierte Liste (1., a., i.)', icon: <ListOrdered className="w-3.5 h-3.5" />, aliases: ['nummer', 'liste', 'ol'] },
@@ -209,6 +212,28 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
const [showLinkModal, setShowLinkModal] = useState<boolean>(false); const [showLinkModal, setShowLinkModal] = useState<boolean>(false);
const [showTodoMenu, setShowTodoMenu] = useState<boolean>(false); const [showTodoMenu, setShowTodoMenu] = useState<boolean>(false);
// Action bubble with a real pointer: shown only while the mouse moves over the row (until a moment after
// the last move), while it rests on the bubble or a menu of it is open; a resting mouse must not leave it
// over the text. Touch devices keep showing it at the focused bullet.
const hasHover = useMemo(() => typeof window !== 'undefined' && !!window.matchMedia?.('(hover: hover)').matches, []);
const [pointerMoving, setPointerMoving] = useState(false);
const [pointerOnBubble, setPointerOnBubble] = useState(false);
const pointerIdleTimerRef = useRef<number | null>(null);
const handleRowMouseMove = () => {
if (!hasHover) return;
setPointerMoving(true);
if (pointerIdleTimerRef.current !== null) window.clearTimeout(pointerIdleTimerRef.current);
pointerIdleTimerRef.current = window.setTimeout(() => setPointerMoving(false), BUBBLE_IDLE_MS);
};
const handleRowMouseLeave = () => {
if (pointerIdleTimerRef.current !== null) window.clearTimeout(pointerIdleTimerRef.current);
pointerIdleTimerRef.current = null;
setPointerMoving(false);
};
useEffect(() => () => {
if (pointerIdleTimerRef.current !== null) window.clearTimeout(pointerIdleTimerRef.current);
}, []);
const textareaRef = useRef<HTMLTextAreaElement | null>(null); const textareaRef = useRef<HTMLTextAreaElement | null>(null);
const debounceTimerRef = useRef<number | null>(null); const debounceTimerRef = useRef<number | null>(null);
const latestBlockRef = useRef<DocumentBlock>(block); const latestBlockRef = useRef<DocumentBlock>(block);
@@ -1087,9 +1112,13 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
const isEmpty = (!localContent || localContent.trim() === '') && !hasChildren; const isEmpty = (!localContent || localContent.trim() === '') && !hasChildren;
const showBubble = hasHover ? pointerMoving || pointerOnBubble || showTodoMenu : activeFocus;
return ( return (
<div <div
data-outline-block={block.id} data-outline-block={block.id}
onMouseMove={handleRowMouseMove}
onMouseLeave={handleRowMouseLeave}
className={`group relative flex items-start gap-1 py-1 px-0.5 sm:px-1.5 rounded-card transition-all duration-300 ${ className={`group relative flex items-start gap-1 py-1 px-0.5 sm:px-1.5 rounded-card transition-all duration-300 ${
inRangeSelection ? 'bg-accent/15' : '' inRangeSelection ? 'bg-accent/15' : ''
} ${ } ${
@@ -1595,11 +1624,13 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
{/* Floating Actions Overlay Bubble (Zoom, Auslagern, Löschen, TODO-Menü, Text-Umwandlung) */} {/* Floating Actions Overlay Bubble (Zoom, Auslagern, Löschen, TODO-Menü, Text-Umwandlung) */}
<div <div
onMouseEnter={() => setPointerOnBubble(true)}
onMouseLeave={() => setPointerOnBubble(false)}
className={`absolute right-1 sm:right-2 top-0.5 sm:top-1 z-20 bg-panel/90 backdrop-blur-sm border border-border shadow-xs rounded-pill px-1 sm:px-1.5 py-0.5 flex items-center gap-0.5 sm:gap-1 transition-opacity ${ className={`absolute right-1 sm:right-2 top-0.5 sm:top-1 z-20 bg-panel/90 backdrop-blur-sm border border-border shadow-xs rounded-pill px-1 sm:px-1.5 py-0.5 flex items-center gap-0.5 sm:gap-1 transition-opacity ${
activeFocus showBubble
? 'opacity-100 pointer-events-auto' ? 'opacity-100 pointer-events-auto'
: // Hover only with a real pointer: a tap on a phone would make the invisible buttons clickable : // Hidden buttons must not take clicks; keyboard focus inside still shows it
'opacity-0 [@media(hover:hover)]:group-hover:opacity-100 focus-within:opacity-100 pointer-events-none [@media(hover:hover)]:group-hover:pointer-events-auto focus-within:pointer-events-auto' 'opacity-0 pointer-events-none focus-within:opacity-100 focus-within:pointer-events-auto'
}`} }`}
> >
{/* Convert back to bullet for headings, callouts, dividers, code and numbered items */} {/* Convert back to bullet for headings, callouts, dividers, code and numbered items */}