feat: enhance note creation and navigation UX
- Added note creation event handlers to sync selection, scrolling and focus across components - Improved context menu styling with better opacity and z-index handling - Updated mobile layout to hide search input on larger screens - Fixed focus timing issues in move-note-to-folder component - Added URL state management for newly created notes - Removed unused archived note files - Added proper note ID tracking and scroll-into-view behavior - Enhanced error
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Component, EventEmitter, Output, computed, signal, effect, inject, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { Component, EventEmitter, Output, computed, signal, effect, inject, ChangeDetectionStrategy, ViewChild, ElementRef } from '@angular/core';
|
||||
import { input } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import type { Note } from '../../../types';
|
||||
@@ -9,6 +9,7 @@ import { NoteCreationService } from '../../services/note-creation.service';
|
||||
import { NoteContextMenuComponent } from '../../../components/note-context-menu/note-context-menu.component';
|
||||
import { WarningPanelComponent } from '../../components/warning-panel/warning-panel.component';
|
||||
import { NoteContextMenuService } from '../../services/note-context-menu.service';
|
||||
import { UrlStateService } from '../../services/url-state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-notes-list',
|
||||
@@ -117,7 +118,7 @@ import { NoteContextMenuService } from '../../services/note-context-menu.service
|
||||
</div>
|
||||
|
||||
<!-- List Container -->
|
||||
<div class="flex-1 min-h-0 overflow-y-auto list-scroll" appScrollableOverlay>
|
||||
<div class="flex-1 min-h-0 overflow-y-auto list-scroll" appScrollableOverlay #listContainer>
|
||||
<!-- Empty state when a folder is selected but no notes match -->
|
||||
<div *ngIf="(folderFilter() && filtered().length === 0)" class="h-full min-h-[240px] flex items-center justify-center text-center p-6">
|
||||
<div>
|
||||
@@ -133,10 +134,12 @@ import { NoteContextMenuService } from '../../services/note-context-menu.service
|
||||
<ul class="notes-list">
|
||||
<li *ngFor="let n of filtered()"
|
||||
class="note-row cursor-pointer"
|
||||
[class.active]="selectedId() === n.id"
|
||||
[class.active]="(selectedId() === n.id) || (pendingSelectId() === n.id)"
|
||||
[ngClass]="getListItemClasses()"
|
||||
[ngStyle]="getNoteGradientStyle(n)"
|
||||
[attr.data-note-path]="n.filePath"
|
||||
[attr.data-note-id]="n.id"
|
||||
tabindex="-1"
|
||||
(click)="openNote.emit(n.id)"
|
||||
(contextmenu)="openContextMenu($event, n)">
|
||||
<!-- Compact View -->
|
||||
@@ -373,11 +376,15 @@ export class NotesListComponent {
|
||||
@Output() queryChange = new EventEmitter<string>();
|
||||
@Output() clearQuickLinkFilter = new EventEmitter<void>();
|
||||
@Output() noteCreated = new EventEmitter<string>();
|
||||
@Output() noteCreatedAndSelected = new EventEmitter<{ id: string; filePath: string }>();
|
||||
|
||||
private store = inject(TagFilterStore);
|
||||
readonly state = inject(NotesListStateService);
|
||||
private noteCreationService = inject(NoteCreationService);
|
||||
readonly contextMenuService = inject(NoteContextMenuService);
|
||||
@ViewChild('listContainer') listContainer?: ElementRef<HTMLElement>;
|
||||
private urlState = inject(UrlStateService);
|
||||
private pendingSelectId = signal<string | null>(null);
|
||||
|
||||
// Delete warning modal state
|
||||
deleteWarningOpen = signal<boolean>(false);
|
||||
@@ -435,6 +442,45 @@ export class NotesListComponent {
|
||||
}, 10);
|
||||
});
|
||||
|
||||
private scrollToSelectedEffect = effect(() => {
|
||||
const id = this.selectedId() || this.pendingSelectId();
|
||||
if (!id) return;
|
||||
const host = this.listContainer?.nativeElement;
|
||||
if (!host) return;
|
||||
|
||||
const tryFocus = () => {
|
||||
const el = host.querySelector(`li[data-note-id="${id}"]`) as HTMLElement | null;
|
||||
if (el) {
|
||||
try { el.scrollIntoView({ block: 'nearest', inline: 'nearest' }); } catch {}
|
||||
try { el.focus({ preventScroll: true } as any); } catch {}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Attempt after microtask, then a few RAF retries to wait for DOM
|
||||
queueMicrotask(() => {
|
||||
if (tryFocus()) {
|
||||
// If parent hasn't yet reflected selection, keep local pending highlight
|
||||
return;
|
||||
}
|
||||
let attempts = 4;
|
||||
const raf = () => {
|
||||
if (tryFocus()) return;
|
||||
if (--attempts > 0) requestAnimationFrame(raf);
|
||||
};
|
||||
requestAnimationFrame(raf);
|
||||
});
|
||||
});
|
||||
|
||||
private clearPendingWhenSynced = effect(() => {
|
||||
const sel = this.selectedId();
|
||||
const pending = this.pendingSelectId();
|
||||
if (sel && pending && sel === pending) {
|
||||
this.pendingSelectId.set(null);
|
||||
}
|
||||
});
|
||||
|
||||
private syncTagFromStore = effect(() => {
|
||||
const inputTag = this.tagFilter();
|
||||
if (inputTag !== null && inputTag !== undefined) {
|
||||
@@ -603,8 +649,27 @@ export class NotesListComponent {
|
||||
|
||||
this.noteCreationService.createNote(fileName, folderPath)
|
||||
.then(response => {
|
||||
if (!response?.filePath || !response?.id) {
|
||||
console.error('Invalid response from note creation:', response);
|
||||
this.state.setRequestStats(false, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1) Set URL immediately with note + folder
|
||||
this.urlState.setNote(response.filePath, folderPath).catch(err => {
|
||||
console.error('Failed to update URL:', err);
|
||||
});
|
||||
|
||||
// 2) Local optimistic selection
|
||||
this.pendingSelectId.set(response.id);
|
||||
|
||||
// 3) Emit events for parent to handle scroll + focus
|
||||
this.noteCreated.emit(response.id);
|
||||
this.openNote.emit(response.id);
|
||||
this.noteCreatedAndSelected.emit({
|
||||
id: response.id,
|
||||
filePath: response.filePath
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to create note:', error);
|
||||
|
||||
+4
-1
@@ -132,7 +132,7 @@ export class MoveNoteToFolderComponent {
|
||||
|
||||
queueMicrotask(() => {
|
||||
this.computeMenuPosition();
|
||||
this.focusSearchField();
|
||||
setTimeout(() => this.focusSearchField(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -308,6 +308,9 @@ export class MoveNoteToFolderComponent {
|
||||
}
|
||||
|
||||
private focusSearchField(): void {
|
||||
if (!this.showMenu()) {
|
||||
return;
|
||||
}
|
||||
const el = this.searchField?.nativeElement;
|
||||
if (el) {
|
||||
el.focus();
|
||||
|
||||
Reference in New Issue
Block a user