import SwiftUI struct SidebarView: View { @ObservedObject var state: AppState @State private var lastClickedID: UUID? private let dateFormatter: DateFormatter = { let f = DateFormatter() f.dateStyle = .short f.timeStyle = .short return f }() var body: some View { VStack(spacing: 0) { HStack { Text("Notes") .font(.headline) .foregroundColor(Color(ns: Theme.current.text)) Spacer() Button(action: { state.newNote() }) { Image(systemName: "plus") .foregroundColor(Color(ns: Theme.current.text)) } .buttonStyle(.plain) .help("New Note") } .padding(.horizontal, 12) .padding(.vertical, 8) Divider() if state.noteList.isEmpty { VStack { Spacer() Text("No notes yet") .foregroundColor(Color(ns: Theme.current.overlay1)) .font(Font(Theme.sidebarFont as CTFont)) Spacer() } } else { ScrollView { LazyVStack(spacing: 0) { ForEach(Array(state.noteList.enumerated()), id: \.element.id) { index, note in NoteRow( note: note, isSelected: state.selectedNoteIDs.contains(note.id), isActive: note.id == state.currentNoteID, dateFormatter: dateFormatter ) .contentShape(Rectangle()) .onTapGesture(count: 2) { state.openNote(note.id) } .onTapGesture(count: 1) { handleClick(note: note, index: index) } .contextMenu { if state.selectedNoteIDs.count > 1 && state.selectedNoteIDs.contains(note.id) { Button("Delete \(state.selectedNoteIDs.count) Notes") { state.deleteNotes(state.selectedNoteIDs) lastClickedID = nil } } else { Button("Delete") { state.deleteNote(note.id) } } } } } } .onDeleteCommand { guard !state.selectedNoteIDs.isEmpty else { return } state.deleteNotes(state.selectedNoteIDs) lastClickedID = nil } } } .background(Color(ns: Theme.current.base)) .onAppear { state.refreshNoteList() } } private func handleClick(note: NoteInfo, index: Int) { let flags = NSEvent.modifierFlags if flags.contains(.command) { state.selectNote(note.id, extend: true) lastClickedID = note.id } else if flags.contains(.shift), let lastID = lastClickedID { if let lastIndex = state.noteList.firstIndex(where: { $0.id == lastID }) { let range = min(lastIndex, index)...max(lastIndex, index) var ids = Set() for i in range { ids.insert(state.noteList[i].id) } state.selectedNoteIDs = ids } } else { state.selectNote(note.id) lastClickedID = note.id } } } struct NoteRow: View { let note: NoteInfo let isSelected: Bool var isActive: Bool = false let dateFormatter: DateFormatter var body: some View { VStack(alignment: .leading, spacing: 2) { Text(note.title) .font(Font(isActive ? NSFontManager.shared.convert(Theme.sidebarFont, toHaveTrait: .boldFontMask) as CTFont : Theme.sidebarFont as CTFont)) .foregroundColor(Color(ns: Theme.current.text)) .lineLimit(1) Text(dateFormatter.string(from: note.lastModified)) .font(Font(Theme.sidebarDateFont as CTFont)) .foregroundColor(Color(ns: Theme.current.subtext0)) } .frame(maxWidth: .infinity, alignment: .leading) .padding(.vertical, 4) .padding(.horizontal, 12) .background( isActive ? Color(ns: Theme.current.surface1) : isSelected ? Color(ns: Theme.current.surface0) : Color.clear ) } }