|
|
@@ -210,6 +210,11 @@ class ViewController: NSViewController {
|
|
|
private weak var homePlaceholderLabel: NSTextField?
|
|
|
private weak var homeSearchField: NSTextField?
|
|
|
private weak var homeSearchPill: NSView?
|
|
|
+ private weak var meetingSearchOverlayView: NSView?
|
|
|
+ private weak var meetingSearchPanelView: NSView?
|
|
|
+ private weak var meetingSearchField: NSTextField?
|
|
|
+ private weak var meetingSearchResultsStack: NSStackView?
|
|
|
+ private weak var meetingSearchEmptyLabel: NSTextField?
|
|
|
private weak var homeSettingsView: NSView?
|
|
|
private weak var schedulerRootView: NSView?
|
|
|
private weak var schedulerMonthLabel: NSTextField?
|
|
|
@@ -278,6 +283,8 @@ class ViewController: NSViewController {
|
|
|
private var searchTextObserver: NSObjectProtocol?
|
|
|
private var searchShortcutMonitor: Any?
|
|
|
private var searchOutsideClickMonitor: Any?
|
|
|
+ private var meetingSearchTextObserver: NSObjectProtocol?
|
|
|
+ private var globalSearchResults: [ScheduledMeeting] = []
|
|
|
private var clockTimer: Timer?
|
|
|
private var meetingsRefreshTimer: Timer?
|
|
|
private var isSigningIn = false
|
|
|
@@ -503,9 +510,15 @@ class ViewController: NSViewController {
|
|
|
clearMeetingsScrollObserver()
|
|
|
clearMeetingsPageScrollObserver()
|
|
|
removeSearchFieldObserver()
|
|
|
+ removeMeetingSearchObserver()
|
|
|
removeSearchShortcutMonitor()
|
|
|
homeSearchField = nil
|
|
|
homeSearchPill = nil
|
|
|
+ meetingSearchOverlayView = nil
|
|
|
+ meetingSearchPanelView = nil
|
|
|
+ meetingSearchField = nil
|
|
|
+ meetingSearchResultsStack = nil
|
|
|
+ meetingSearchEmptyLabel = nil
|
|
|
meetingsPageRootView = nil
|
|
|
meetingsPageCategoryFilter = nil
|
|
|
meetingsPageFromDatePicker = nil
|
|
|
@@ -541,9 +554,15 @@ class ViewController: NSViewController {
|
|
|
clearMeetingsScrollObserver()
|
|
|
clearMeetingsPageScrollObserver()
|
|
|
removeSearchFieldObserver()
|
|
|
+ removeMeetingSearchObserver()
|
|
|
removeSearchShortcutMonitor()
|
|
|
homeSearchField = nil
|
|
|
homeSearchPill = nil
|
|
|
+ meetingSearchOverlayView = nil
|
|
|
+ meetingSearchPanelView = nil
|
|
|
+ meetingSearchField = nil
|
|
|
+ meetingSearchResultsStack = nil
|
|
|
+ meetingSearchEmptyLabel = nil
|
|
|
meetingsPageRootView = nil
|
|
|
homeView?.removeFromSuperview()
|
|
|
selectedMeetingsDayStart = Calendar.current.startOfDay(for: Date())
|
|
|
@@ -1469,31 +1488,41 @@ class ViewController: NSViewController {
|
|
|
searchTextObserver = nil
|
|
|
}
|
|
|
|
|
|
+ private func removeMeetingSearchObserver() {
|
|
|
+ if let meetingSearchTextObserver {
|
|
|
+ NotificationCenter.default.removeObserver(meetingSearchTextObserver)
|
|
|
+ }
|
|
|
+ meetingSearchTextObserver = nil
|
|
|
+ }
|
|
|
+
|
|
|
private func installSearchShortcutMonitor() {
|
|
|
removeSearchShortcutMonitor()
|
|
|
searchShortcutMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
|
|
|
guard let self else { return event }
|
|
|
+ if event.keyCode == 53, self.meetingSearchOverlayView?.isHidden == false {
|
|
|
+ DispatchQueue.main.async {
|
|
|
+ self.hideMeetingSearchPopup()
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+ }
|
|
|
guard event.modifierFlags.contains(.command),
|
|
|
event.charactersIgnoringModifiers?.lowercased() == "e" else { return event }
|
|
|
- guard self.homeSearchField != nil else { return event }
|
|
|
+ guard self.homeSearchPill != nil else { return event }
|
|
|
DispatchQueue.main.async {
|
|
|
- self.focusHomeSearchField()
|
|
|
+ self.showMeetingSearchPopup(seedQuery: self.meetingSearchField?.stringValue ?? "")
|
|
|
}
|
|
|
return nil
|
|
|
}
|
|
|
searchOutsideClickMonitor = NSEvent.addLocalMonitorForEvents(matching: .leftMouseDown) { [weak self] event in
|
|
|
guard let self else { return event }
|
|
|
guard let window = self.view.window, event.window === window else { return event }
|
|
|
- guard let field = self.homeSearchField else { return event }
|
|
|
- guard self.isSearchFieldActive(field, in: window) else { return event }
|
|
|
- let location = event.locationInWindow
|
|
|
- let pill = self.homeSearchPill ?? field
|
|
|
- let rectInWindow = pill.convert(pill.bounds, to: nil)
|
|
|
- if rectInWindow.contains(location) { return event }
|
|
|
- DispatchQueue.main.async {
|
|
|
- window.makeFirstResponder(nil)
|
|
|
- (field as? SearchPillTextField)?.forceClearFocusState()
|
|
|
- self.applySearchPillFocusBorder(focused: false)
|
|
|
+ guard self.meetingSearchOverlayView?.isHidden == false else { return event }
|
|
|
+ guard let panel = self.meetingSearchPanelView else { return event }
|
|
|
+ let rectInWindow = panel.convert(panel.bounds, to: nil)
|
|
|
+ if rectInWindow.contains(event.locationInWindow) == false {
|
|
|
+ DispatchQueue.main.async {
|
|
|
+ self.hideMeetingSearchPopup()
|
|
|
+ }
|
|
|
}
|
|
|
return event
|
|
|
}
|
|
|
@@ -1529,6 +1558,152 @@ class ViewController: NSViewController {
|
|
|
view.window?.makeFirstResponder(field)
|
|
|
}
|
|
|
|
|
|
+ @MainActor
|
|
|
+ private func showMeetingSearchPopup(seedQuery: String) {
|
|
|
+ guard let overlay = meetingSearchOverlayView else { return }
|
|
|
+ overlay.isHidden = false
|
|
|
+ overlay.alphaValue = 0
|
|
|
+ NSAnimationContext.runAnimationGroup { context in
|
|
|
+ context.duration = 0.16
|
|
|
+ overlay.animator().alphaValue = 1
|
|
|
+ }
|
|
|
+ if let field = meetingSearchField {
|
|
|
+ field.stringValue = seedQuery
|
|
|
+ view.window?.makeFirstResponder(field)
|
|
|
+ }
|
|
|
+ applyGlobalMeetingSearch(query: seedQuery)
|
|
|
+ }
|
|
|
+
|
|
|
+ @MainActor
|
|
|
+ private func hideMeetingSearchPopup() {
|
|
|
+ guard let overlay = meetingSearchOverlayView else { return }
|
|
|
+ overlay.isHidden = true
|
|
|
+ overlay.alphaValue = 1
|
|
|
+ view.window?.makeFirstResponder(nil)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func applyGlobalMeetingSearch(query: String) {
|
|
|
+ let trimmed = query.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
|
|
+ let now = Date()
|
|
|
+ let dateFormatter = DateFormatter()
|
|
|
+ dateFormatter.dateStyle = .medium
|
|
|
+ dateFormatter.timeStyle = .short
|
|
|
+
|
|
|
+ let filtered: [ScheduledMeeting]
|
|
|
+ if trimmed.isEmpty {
|
|
|
+ filtered = allScheduledMeetings
|
|
|
+ } else {
|
|
|
+ filtered = allScheduledMeetings.filter { meeting in
|
|
|
+ let dateText = dateFormatter.string(from: meeting.start).lowercased()
|
|
|
+ return meeting.title.lowercased().contains(trimmed)
|
|
|
+ || meeting.host.lowercased().contains(trimmed)
|
|
|
+ || meeting.source.lowercased().contains(trimmed)
|
|
|
+ || (meeting.meetingID?.lowercased().contains(trimmed) ?? false)
|
|
|
+ || dateText.contains(trimmed)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ globalSearchResults = filtered.sorted { lhs, rhs in
|
|
|
+ let lhsIsUpcoming = lhs.start >= now
|
|
|
+ let rhsIsUpcoming = rhs.start >= now
|
|
|
+ if lhsIsUpcoming != rhsIsUpcoming {
|
|
|
+ return lhsIsUpcoming && !rhsIsUpcoming
|
|
|
+ }
|
|
|
+ if lhsIsUpcoming {
|
|
|
+ return lhs.start < rhs.start
|
|
|
+ }
|
|
|
+ return lhs.start > rhs.start
|
|
|
+ }
|
|
|
+ renderMeetingSearchResults()
|
|
|
+ }
|
|
|
+
|
|
|
+ private func renderMeetingSearchResults() {
|
|
|
+ guard let stack = meetingSearchResultsStack else { return }
|
|
|
+ stack.arrangedSubviews.forEach { row in
|
|
|
+ stack.removeArrangedSubview(row)
|
|
|
+ row.removeFromSuperview()
|
|
|
+ }
|
|
|
+ meetingSearchEmptyLabel?.isHidden = globalSearchResults.isEmpty == false
|
|
|
+ for (index, meeting) in globalSearchResults.enumerated() {
|
|
|
+ let row = makeMeetingSearchResultRow(meeting)
|
|
|
+ row.tag = index
|
|
|
+ stack.addArrangedSubview(row)
|
|
|
+ row.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func makeMeetingSearchResultRow(_ meeting: ScheduledMeeting) -> NSButton {
|
|
|
+ let row = NSButton(title: "", target: self, action: #selector(meetingSearchResultTapped(_:)))
|
|
|
+ row.isBordered = false
|
|
|
+ row.bezelStyle = .regularSquare
|
|
|
+ row.setButtonType(.momentaryChange)
|
|
|
+ row.wantsLayer = true
|
|
|
+ row.layer?.backgroundColor = meetingCardBackground.cgColor
|
|
|
+ row.layer?.cornerRadius = 10
|
|
|
+ row.layer?.borderWidth = 1
|
|
|
+ row.layer?.borderColor = palette.inputBorder.cgColor
|
|
|
+ row.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ row.heightAnchor.constraint(equalToConstant: 84).isActive = true
|
|
|
+
|
|
|
+ let title = makeLabel(meeting.title, size: 14, color: primaryText, weight: .semibold, centered: false)
|
|
|
+ title.lineBreakMode = .byTruncatingTail
|
|
|
+ let subtitle = makeLabel(compactMeetingSummary(meeting), size: 12, color: secondaryText, weight: .regular, centered: false)
|
|
|
+ subtitle.lineBreakMode = .byTruncatingTail
|
|
|
+ let meta = makeLabel("Host: \(meeting.host) • \(meeting.source)", size: 11, color: mutedText, weight: .regular, centered: false)
|
|
|
+ meta.lineBreakMode = .byTruncatingTail
|
|
|
+ [title, subtitle, meta].forEach {
|
|
|
+ $0.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ row.addSubview($0)
|
|
|
+ }
|
|
|
+
|
|
|
+ NSLayoutConstraint.activate([
|
|
|
+ title.topAnchor.constraint(equalTo: row.topAnchor, constant: 10),
|
|
|
+ title.leadingAnchor.constraint(equalTo: row.leadingAnchor, constant: 12),
|
|
|
+ title.trailingAnchor.constraint(equalTo: row.trailingAnchor, constant: -12),
|
|
|
+ subtitle.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 4),
|
|
|
+ subtitle.leadingAnchor.constraint(equalTo: title.leadingAnchor),
|
|
|
+ subtitle.trailingAnchor.constraint(equalTo: title.trailingAnchor),
|
|
|
+ meta.topAnchor.constraint(equalTo: subtitle.bottomAnchor, constant: 4),
|
|
|
+ meta.leadingAnchor.constraint(equalTo: title.leadingAnchor),
|
|
|
+ meta.trailingAnchor.constraint(equalTo: title.trailingAnchor)
|
|
|
+ ])
|
|
|
+ return row
|
|
|
+ }
|
|
|
+
|
|
|
+ private func compactMeetingSummary(_ meeting: ScheduledMeeting) -> String {
|
|
|
+ let dateFormatter = DateFormatter()
|
|
|
+ dateFormatter.dateFormat = "EEE, MMM d"
|
|
|
+ let timeFormatter = DateFormatter()
|
|
|
+ timeFormatter.dateFormat = "h:mm a"
|
|
|
+ let start = timeFormatter.string(from: meeting.start)
|
|
|
+ let end = meeting.end.map { timeFormatter.string(from: $0) }
|
|
|
+ let range = end.map { "\(start) - \($0)" } ?? start
|
|
|
+ if let id = meeting.meetingID, id.isEmpty == false {
|
|
|
+ return "\(dateFormatter.string(from: meeting.start)) • \(range) • ID \(id)"
|
|
|
+ }
|
|
|
+ return "\(dateFormatter.string(from: meeting.start)) • \(range)"
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc private func topSearchPillTapped() {
|
|
|
+ showMeetingSearchPopup(seedQuery: meetingSearchField?.stringValue ?? "")
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc private func meetingSearchResultTapped(_ sender: NSButton) {
|
|
|
+ guard let meeting = globalSearchResults[safe: sender.tag] else { return }
|
|
|
+ if let url = meeting.webURL {
|
|
|
+ NSWorkspace.shared.open(url)
|
|
|
+ } else {
|
|
|
+ let pasteboard = NSPasteboard.general
|
|
|
+ pasteboard.clearContents()
|
|
|
+ if let id = meeting.meetingID, id.isEmpty == false {
|
|
|
+ pasteboard.setString(id, forType: .string)
|
|
|
+ } else {
|
|
|
+ pasteboard.setString(meeting.title, forType: .string)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ hideMeetingSearchPopup()
|
|
|
+ }
|
|
|
+
|
|
|
private func observeMeetingsScrollEdges(in scrollView: NSScrollView) {
|
|
|
clearMeetingsScrollObserver()
|
|
|
meetingsScrollView = scrollView
|
|
|
@@ -3521,6 +3696,8 @@ class ViewController: NSViewController {
|
|
|
searchField.drawsBackground = false
|
|
|
searchField.backgroundColor = .clear
|
|
|
searchField.focusRingType = .none
|
|
|
+ searchField.isEditable = false
|
|
|
+ searchField.isSelectable = false
|
|
|
searchField.font = .systemFont(ofSize: 13, weight: .regular)
|
|
|
searchField.textColor = primaryText
|
|
|
searchField.alignment = .left
|
|
|
@@ -3540,6 +3717,11 @@ class ViewController: NSViewController {
|
|
|
updateSearchHintVisibility()
|
|
|
}
|
|
|
updateSearchHintVisibility()
|
|
|
+ let searchTap = NSClickGestureRecognizer(target: self, action: #selector(topSearchPillTapped))
|
|
|
+ searchPill.addGestureRecognizer(searchTap)
|
|
|
+ searchField.addGestureRecognizer(NSClickGestureRecognizer(target: self, action: #selector(topSearchPillTapped)))
|
|
|
+ searchIcon.addGestureRecognizer(NSClickGestureRecognizer(target: self, action: #selector(topSearchPillTapped)))
|
|
|
+ searchHintLabel.addGestureRecognizer(NSClickGestureRecognizer(target: self, action: #selector(topSearchPillTapped)))
|
|
|
|
|
|
let searchRow = NSStackView()
|
|
|
searchRow.orientation = .horizontal
|
|
|
@@ -3646,6 +3828,80 @@ class ViewController: NSViewController {
|
|
|
let meetingsPageView = makeMeetingsPageView()
|
|
|
meetingsPageView.isHidden = selectedHomeSidebarItem != "Meetings"
|
|
|
|
|
|
+ let searchOverlay = NSView()
|
|
|
+ searchOverlay.wantsLayer = true
|
|
|
+ searchOverlay.layer?.backgroundColor = NSColor.black.withAlphaComponent(0.45).cgColor
|
|
|
+ searchOverlay.isHidden = true
|
|
|
+ searchOverlay.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+
|
|
|
+ let searchPanel = NSView()
|
|
|
+ searchPanel.wantsLayer = true
|
|
|
+ searchPanel.layer?.backgroundColor = secondaryCardBackground.cgColor
|
|
|
+ searchPanel.layer?.cornerRadius = 14
|
|
|
+ searchPanel.layer?.borderWidth = 1
|
|
|
+ searchPanel.layer?.borderColor = palette.inputBorder.cgColor
|
|
|
+ searchPanel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+
|
|
|
+ let popupTitle = makeLabel("Search Meetings", size: 18, color: primaryText, weight: .semibold, centered: false)
|
|
|
+ popupTitle.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ let popupSubtitle = makeLabel("Find any scheduled meeting instantly", size: 12, color: secondaryText, weight: .regular, centered: false)
|
|
|
+ popupSubtitle.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+
|
|
|
+ let popupSearchChrome = NSView()
|
|
|
+ popupSearchChrome.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ popupSearchChrome.wantsLayer = true
|
|
|
+ popupSearchChrome.layer?.backgroundColor = searchPillBackground.cgColor
|
|
|
+ popupSearchChrome.layer?.cornerRadius = 10
|
|
|
+ popupSearchChrome.layer?.borderWidth = 1
|
|
|
+ popupSearchChrome.layer?.borderColor = palette.inputBorder.cgColor
|
|
|
+
|
|
|
+ let popupSearchIcon = NSImageView()
|
|
|
+ popupSearchIcon.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ popupSearchIcon.image = NSImage(systemSymbolName: "magnifyingglass", accessibilityDescription: "Search meetings")
|
|
|
+ popupSearchIcon.contentTintColor = secondaryText
|
|
|
+ popupSearchIcon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 14, weight: .regular)
|
|
|
+
|
|
|
+ let popupSearchField = NSTextField()
|
|
|
+ popupSearchField.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ popupSearchField.isBordered = false
|
|
|
+ popupSearchField.drawsBackground = false
|
|
|
+ popupSearchField.backgroundColor = .clear
|
|
|
+ popupSearchField.focusRingType = .none
|
|
|
+ popupSearchField.font = .systemFont(ofSize: 15, weight: .regular)
|
|
|
+ popupSearchField.textColor = primaryText
|
|
|
+ popupSearchField.placeholderString = "Search all meetings by title, host, source, ID..."
|
|
|
+
|
|
|
+ let popupResultsScroll = NSScrollView()
|
|
|
+ popupResultsScroll.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ popupResultsScroll.drawsBackground = false
|
|
|
+ popupResultsScroll.hasVerticalScroller = true
|
|
|
+ popupResultsScroll.hasHorizontalScroller = false
|
|
|
+ popupResultsScroll.autohidesScrollers = true
|
|
|
+
|
|
|
+ let popupResultsDocument = FlippedView()
|
|
|
+ popupResultsDocument.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ let popupResultsStack = NSStackView()
|
|
|
+ popupResultsStack.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ popupResultsStack.orientation = .vertical
|
|
|
+ popupResultsStack.spacing = 10
|
|
|
+ popupResultsStack.alignment = .leading
|
|
|
+
|
|
|
+ let popupEmpty = makeLabel("No meetings match your search.", size: 13, color: secondaryText, weight: .regular, centered: true)
|
|
|
+ popupEmpty.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ popupEmpty.isHidden = true
|
|
|
+
|
|
|
+ root.addSubview(searchOverlay)
|
|
|
+ searchOverlay.addSubview(searchPanel)
|
|
|
+ searchPanel.addSubview(popupTitle)
|
|
|
+ searchPanel.addSubview(popupSubtitle)
|
|
|
+ searchPanel.addSubview(popupSearchChrome)
|
|
|
+ searchPanel.addSubview(popupResultsScroll)
|
|
|
+ searchPanel.addSubview(popupEmpty)
|
|
|
+ popupSearchChrome.addSubview(popupSearchIcon)
|
|
|
+ popupSearchChrome.addSubview(popupSearchField)
|
|
|
+ popupResultsScroll.documentView = popupResultsDocument
|
|
|
+ popupResultsDocument.addSubview(popupResultsStack)
|
|
|
+
|
|
|
let schedulerRoot = NSView()
|
|
|
schedulerRoot.isHidden = selectedHomeSidebarItem != "Scheduler"
|
|
|
|
|
|
@@ -3947,7 +4203,51 @@ class ViewController: NSViewController {
|
|
|
schedulerMeetingsStack.bottomAnchor.constraint(lessThanOrEqualTo: schedulerMeetingsDocument.bottomAnchor),
|
|
|
|
|
|
schedulerEmptyLabel.centerXAnchor.constraint(equalTo: schedulerMeetingsPanel.centerXAnchor),
|
|
|
- schedulerEmptyLabel.centerYAnchor.constraint(equalTo: schedulerMeetingsPanel.centerYAnchor)
|
|
|
+ schedulerEmptyLabel.centerYAnchor.constraint(equalTo: schedulerMeetingsPanel.centerYAnchor),
|
|
|
+
|
|
|
+ searchOverlay.leadingAnchor.constraint(equalTo: root.leadingAnchor),
|
|
|
+ searchOverlay.trailingAnchor.constraint(equalTo: root.trailingAnchor),
|
|
|
+ searchOverlay.topAnchor.constraint(equalTo: root.topAnchor),
|
|
|
+ searchOverlay.bottomAnchor.constraint(equalTo: root.bottomAnchor),
|
|
|
+
|
|
|
+ searchPanel.centerXAnchor.constraint(equalTo: searchOverlay.centerXAnchor),
|
|
|
+ searchPanel.centerYAnchor.constraint(equalTo: searchOverlay.centerYAnchor),
|
|
|
+ searchPanel.widthAnchor.constraint(equalToConstant: 640),
|
|
|
+ searchPanel.heightAnchor.constraint(equalToConstant: 460),
|
|
|
+
|
|
|
+ popupTitle.topAnchor.constraint(equalTo: searchPanel.topAnchor, constant: 18),
|
|
|
+ popupTitle.leadingAnchor.constraint(equalTo: searchPanel.leadingAnchor, constant: 18),
|
|
|
+ popupTitle.trailingAnchor.constraint(equalTo: searchPanel.trailingAnchor, constant: -18),
|
|
|
+ popupSubtitle.topAnchor.constraint(equalTo: popupTitle.bottomAnchor, constant: 4),
|
|
|
+ popupSubtitle.leadingAnchor.constraint(equalTo: popupTitle.leadingAnchor),
|
|
|
+ popupSubtitle.trailingAnchor.constraint(equalTo: popupTitle.trailingAnchor),
|
|
|
+
|
|
|
+ popupSearchChrome.topAnchor.constraint(equalTo: popupSubtitle.bottomAnchor, constant: 12),
|
|
|
+ popupSearchChrome.leadingAnchor.constraint(equalTo: searchPanel.leadingAnchor, constant: 18),
|
|
|
+ popupSearchChrome.trailingAnchor.constraint(equalTo: searchPanel.trailingAnchor, constant: -18),
|
|
|
+ popupSearchChrome.heightAnchor.constraint(equalToConstant: 40),
|
|
|
+
|
|
|
+ popupSearchIcon.leadingAnchor.constraint(equalTo: popupSearchChrome.leadingAnchor, constant: 12),
|
|
|
+ popupSearchIcon.centerYAnchor.constraint(equalTo: popupSearchChrome.centerYAnchor),
|
|
|
+ popupSearchIcon.widthAnchor.constraint(equalToConstant: 16),
|
|
|
+ popupSearchIcon.heightAnchor.constraint(equalToConstant: 16),
|
|
|
+ popupSearchField.leadingAnchor.constraint(equalTo: popupSearchIcon.trailingAnchor, constant: 8),
|
|
|
+ popupSearchField.trailingAnchor.constraint(equalTo: popupSearchChrome.trailingAnchor, constant: -12),
|
|
|
+ popupSearchField.centerYAnchor.constraint(equalTo: popupSearchChrome.centerYAnchor),
|
|
|
+
|
|
|
+ popupResultsScroll.topAnchor.constraint(equalTo: popupSearchChrome.bottomAnchor, constant: 12),
|
|
|
+ popupResultsScroll.leadingAnchor.constraint(equalTo: searchPanel.leadingAnchor, constant: 18),
|
|
|
+ popupResultsScroll.trailingAnchor.constraint(equalTo: searchPanel.trailingAnchor, constant: -18),
|
|
|
+ popupResultsScroll.bottomAnchor.constraint(equalTo: searchPanel.bottomAnchor, constant: -18),
|
|
|
+
|
|
|
+ popupResultsStack.topAnchor.constraint(equalTo: popupResultsDocument.topAnchor),
|
|
|
+ popupResultsStack.leadingAnchor.constraint(equalTo: popupResultsDocument.leadingAnchor),
|
|
|
+ popupResultsStack.trailingAnchor.constraint(equalTo: popupResultsDocument.trailingAnchor),
|
|
|
+ popupResultsStack.bottomAnchor.constraint(lessThanOrEqualTo: popupResultsDocument.bottomAnchor),
|
|
|
+ popupResultsStack.widthAnchor.constraint(equalTo: popupResultsScroll.contentView.widthAnchor),
|
|
|
+
|
|
|
+ popupEmpty.centerXAnchor.constraint(equalTo: popupResultsScroll.centerXAnchor),
|
|
|
+ popupEmpty.centerYAnchor.constraint(equalTo: popupResultsScroll.centerYAnchor)
|
|
|
])
|
|
|
|
|
|
timeLabel = timeTitle
|
|
|
@@ -3985,16 +4285,21 @@ class ViewController: NSViewController {
|
|
|
|
|
|
homeSearchField = searchField
|
|
|
homeSearchPill = searchPill
|
|
|
- searchTextObserver = NotificationCenter.default.addObserver(
|
|
|
+ meetingSearchOverlayView = searchOverlay
|
|
|
+ meetingSearchPanelView = searchPanel
|
|
|
+ meetingSearchField = popupSearchField
|
|
|
+ meetingSearchResultsStack = popupResultsStack
|
|
|
+ meetingSearchEmptyLabel = popupEmpty
|
|
|
+ removeMeetingSearchObserver()
|
|
|
+ meetingSearchTextObserver = NotificationCenter.default.addObserver(
|
|
|
forName: NSControl.textDidChangeNotification,
|
|
|
- object: searchField,
|
|
|
+ object: popupSearchField,
|
|
|
queue: .main
|
|
|
) { [weak self] _ in
|
|
|
- Task { @MainActor [weak self] in
|
|
|
- self?.applyFilteredMeetings()
|
|
|
- }
|
|
|
- updateSearchHintVisibility()
|
|
|
+ guard let self else { return }
|
|
|
+ self.applyGlobalMeetingSearch(query: popupSearchField.stringValue)
|
|
|
}
|
|
|
+ applyGlobalMeetingSearch(query: "")
|
|
|
|
|
|
return root
|
|
|
}
|
|
|
@@ -4192,6 +4497,7 @@ class ViewController: NSViewController {
|
|
|
|
|
|
deinit {
|
|
|
removeSearchFieldObserver()
|
|
|
+ removeMeetingSearchObserver()
|
|
|
clearMeetingsPageScrollObserver()
|
|
|
removeSearchShortcutMonitor()
|
|
|
}
|