feat: update notice after a server update, keep the open tab on reload, chunked Whisper in Quick Dump, compact emoji picker
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m31s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m31s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
20b907078f
commit
55112b940c
17 files changed
+250
-61
No files matched your search
@@ -80,9 +80,10 @@ export const HoldToRecordFab: React.FC<HoldToRecordFabProps> = ({ onTap, onTrans
|
||||
}
|
||||
};
|
||||
|
||||
// Own Whisper server (text after release) or the browser's recognition (text while speaking)
|
||||
// Own Whisper server (text in chunks while speaking) or the browser's recognition (text while speaking)
|
||||
const voice = useVoiceInput({
|
||||
continuous: true,
|
||||
chunked: true,
|
||||
onText: (text, final) => {
|
||||
transcriptRef.current = text;
|
||||
if (!final) {
|
||||
|
||||
@@ -70,6 +70,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
||||
|
||||
// Voice input: own Whisper server or the browser's recognition (lib/voice.ts)
|
||||
const voice = useVoiceInput({
|
||||
chunked: true,
|
||||
onText: (text, final) => {
|
||||
const base = baseVoiceTextRef.current;
|
||||
if (text) setContent(base ? `${base} ${text}` : text);
|
||||
@@ -206,7 +207,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
||||
setIsMultiline(false);
|
||||
setPriority('normal');
|
||||
setDueDate('');
|
||||
setTarget(fixedTarget || 'inbox');
|
||||
// Target and list stay for the next dump; only priority and due date belong to one entry
|
||||
setShowOptions(defaultShowOptions || hideTargetPicker);
|
||||
setIsSubmitting(true);
|
||||
triggerHaptic(15);
|
||||
@@ -544,7 +545,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
||||
}}
|
||||
className={`min-h-[30px] px-2.5 border rounded-btn text-xs flex items-center gap-1.5 transition-colors cursor-pointer ${
|
||||
isSelected
|
||||
? 'bg-accent text-on-accent border-accent font-medium'
|
||||
? 'bg-accent/10 text-main border-accent font-medium'
|
||||
: 'bg-canvas border-border text-muted hover:text-main hover:bg-panel-hover'
|
||||
}`}
|
||||
>
|
||||
@@ -578,7 +579,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
||||
}}
|
||||
className={`min-h-[30px] px-2.5 border rounded-btn text-xs flex items-center gap-1.5 transition-colors cursor-pointer ${
|
||||
isSelected
|
||||
? 'bg-accent text-on-accent border-accent font-medium'
|
||||
? 'bg-accent/10 text-main border-accent font-medium'
|
||||
: 'bg-canvas border-border text-muted hover:text-main hover:bg-panel-hover'
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -29,6 +29,8 @@ interface EmojiPickerModalProps {
|
||||
// Inside a dialog (which clips and transforms its content): the picker is put on the page instead and
|
||||
// opens below this button on desktop
|
||||
anchor?: DOMRect | null;
|
||||
// Offer "Icon entfernen" (notes; a create dialog only has its default icon to go back to)
|
||||
allowRemove?: boolean;
|
||||
}
|
||||
|
||||
export const EmojiPickerModal: React.FC<EmojiPickerModalProps> = ({
|
||||
@@ -36,11 +38,12 @@ export const EmojiPickerModal: React.FC<EmojiPickerModalProps> = ({
|
||||
onSelectEmoji,
|
||||
onClose,
|
||||
anchor,
|
||||
allowRemove = true,
|
||||
}) => {
|
||||
const { inset, visibleHeight } = useKeyboardInset();
|
||||
const isPhone = typeof window !== 'undefined' && window.innerWidth < 640;
|
||||
// On phones the picker shrinks with the keyboard so search field and results stay visible
|
||||
const pickerHeight = isPhone ? Math.max(200, Math.min(350, visibleHeight - 110)) : 350;
|
||||
const pickerHeight = isPhone ? Math.max(200, Math.min(350, visibleHeight - 110)) : 300;
|
||||
const [isDark, setIsDark] = useState<boolean>(() =>
|
||||
typeof document !== 'undefined'
|
||||
? document.documentElement.classList.contains('dark')
|
||||
@@ -98,8 +101,8 @@ export const EmojiPickerModal: React.FC<EmojiPickerModalProps> = ({
|
||||
|
||||
const anchorStyle = anchor
|
||||
? ({
|
||||
'--pick-left': `${Math.max(12, Math.min(anchor.left, window.innerWidth - 372))}px`,
|
||||
'--pick-top': `${Math.min(anchor.bottom + 4, window.innerHeight - 532)}px`,
|
||||
'--pick-left': `${Math.max(12, Math.min(anchor.left, window.innerWidth - 312))}px`,
|
||||
'--pick-top': `${Math.min(anchor.bottom + 4, window.innerHeight - 360)}px`,
|
||||
} as React.CSSProperties)
|
||||
: undefined;
|
||||
|
||||
@@ -117,7 +120,7 @@ export const EmojiPickerModal: React.FC<EmojiPickerModalProps> = ({
|
||||
ref={containerRef}
|
||||
className={`
|
||||
fixed inset-x-0 bottom-[var(--kb-inset)] rounded-t-2xl max-h-[var(--vv-max)] bg-panel border-t border-border shadow-2xl flex flex-col p-3 space-y-3
|
||||
sm:inset-auto sm:w-[360px] sm:max-h-[520px] sm:rounded-card sm:border sm:border-border sm:shadow-xl ${
|
||||
sm:inset-auto sm:w-[300px] sm:max-h-[360px] sm:p-1 sm:space-y-0 sm:bg-panel-elevated sm:rounded-card sm:border sm:border-border sm:shadow-xl ${
|
||||
anchor
|
||||
? 'z-[61] sm:fixed sm:left-[var(--pick-left)] sm:top-[max(12px,var(--pick-top))]'
|
||||
: 'z-50 sm:absolute sm:left-0 sm:top-full sm:mt-1 sm:z-40'
|
||||
@@ -126,17 +129,10 @@ export const EmojiPickerModal: React.FC<EmojiPickerModalProps> = ({
|
||||
style={
|
||||
{
|
||||
...anchorStyle,
|
||||
// Inject MindSift CSS variables into emoji-picker-react for seamless theming
|
||||
'--epr-bg-color': 'var(--bg-panel)',
|
||||
'--epr-category-label-bg-color': 'var(--bg-panel)',
|
||||
'--epr-picker-border-color': 'var(--border-color)',
|
||||
'--epr-text-color': 'var(--text-main)',
|
||||
'--epr-search-input-bg-color': 'var(--bg-app)',
|
||||
'--epr-search-input-bg-color-active': 'var(--bg-panel-elevated)',
|
||||
'--epr-hover-bg-color': 'var(--bg-panel-elevated)',
|
||||
'--epr-focus-bg-color': 'var(--bg-panel-elevated)',
|
||||
'--epr-highlight-color': 'var(--accent-primary)',
|
||||
'--epr-category-icon-active-color': 'var(--accent-primary)',
|
||||
// Theme colours for the picker (index.css); desktop: its own raised surface, so it stands apart
|
||||
// from the dialog it opens from
|
||||
'--pick-surface': isPhone ? 'var(--bg-panel)' : 'var(--bg-panel-elevated)',
|
||||
'--pick-hover': isPhone ? 'var(--bg-panel-elevated)' : 'var(--bg-panel-hover)',
|
||||
'--kb-inset': `${inset}px`,
|
||||
'--vv-max': `${Math.round(visibleHeight * 0.9)}px`,
|
||||
} as React.CSSProperties
|
||||
@@ -145,8 +141,8 @@ export const EmojiPickerModal: React.FC<EmojiPickerModalProps> = ({
|
||||
{/* Mobile handle indicator */}
|
||||
<div className="w-10 h-1 bg-border rounded-full mx-auto sm:hidden shrink-0" />
|
||||
|
||||
{/* Top Header: Title, Actions & Close */}
|
||||
<div className="flex items-center justify-between gap-2 shrink-0 border-b border-border pb-2">
|
||||
{/* Top Header: Title, Actions & Close (phone only; on desktop a click outside closes it) */}
|
||||
<div className="flex sm:hidden items-center justify-between gap-2 shrink-0 border-b border-border pb-2">
|
||||
<div className="flex items-center gap-1.5 text-xs font-semibold text-main">
|
||||
<span>Icon auswählen</span>
|
||||
{currentIcon && (
|
||||
@@ -155,7 +151,7 @@ export const EmojiPickerModal: React.FC<EmojiPickerModalProps> = ({
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
{currentIcon && (
|
||||
{currentIcon && allowRemove && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onSelectEmoji('')}
|
||||
@@ -208,6 +204,18 @@ export const EmojiPickerModal: React.FC<EmojiPickerModalProps> = ({
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
{/* Desktop: removing the icon as a quiet last row, like an entry of a context menu */}
|
||||
{currentIcon && allowRemove && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onSelectEmoji('')}
|
||||
className="hidden sm:flex items-center gap-1.5 w-full h-7 px-2 mt-1 rounded-btn text-xs text-muted hover:text-status-err hover:bg-panel-hover transition-colors cursor-pointer"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
Icon entfernen
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1891,20 +1891,6 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
if (e.target === e.currentTarget && markedDocIds.size > 0) setMarkedDocIds(new Set());
|
||||
}}
|
||||
>
|
||||
{markedDocIds.size > 0 && (
|
||||
<div className="sticky top-0 z-10 flex items-center gap-2 px-2 py-1.5 rounded-btn bg-accent/10 border border-accent/30 text-[11px] text-main">
|
||||
<span className="flex-1">
|
||||
{markedDocIds.size} markiert · in einen Ordner oder auf den Papierkorb ziehen
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMarkedDocIds(new Set())}
|
||||
className="text-accent hover:underline shrink-0"
|
||||
>
|
||||
Aufheben
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{/* Journal on top; open, it becomes a panel with the calendar (click the heading to close).
|
||||
Panel and calendar animate open (grid rows 0fr → 1fr) instead of jumping. Only from lg on:
|
||||
below, the journal is a view of its own and the button stays a plain entry like the folders
|
||||
|
||||
Reference in new issue
Block a user