From 820412d8d46bcc3c9ee4b4dcdc53159289b402ae Mon Sep 17 00:00:00 2001 From: yonaries Date: Mon, 16 Mar 2026 01:20:43 +0300 Subject: [PATCH 01/17] style: update nav button hover opacities --- ora/Features/Browser/URLBar/URLBarButton.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ora/Features/Browser/URLBar/URLBarButton.swift b/ora/Features/Browser/URLBar/URLBarButton.swift index 515f1791..edf1db74 100644 --- a/ora/Features/Browser/URLBar/URLBarButton.swift +++ b/ora/Features/Browser/URLBar/URLBarButton.swift @@ -12,7 +12,7 @@ struct URLBarButton: View { if #available(macOS 26, *) { return 10 } else { - return 6 + return 10 } } @@ -21,8 +21,8 @@ struct URLBarButton: View { Image(systemName: systemName) .font(.system(size: 14, weight: .medium)) .foregroundColor(isEnabled ? - (isHovering ? foregroundColor.opacity(0.8) : foregroundColor.opacity(0.4)) : - foregroundColor.opacity(0.5) + (isHovering ? foregroundColor : foregroundColor.opacity(0.7)) : + foregroundColor.opacity(0.25) ) .frame(width: 30, height: 30) .background( From d00871aed283b90bdc95304b2c62452a96e618cf Mon Sep 17 00:00:00 2001 From: yonaries Date: Mon, 16 Mar 2026 01:21:08 +0300 Subject: [PATCH 02/17] feat: add shield lock and ban icons --- ora/Shared/Components/Icons/OraIcon.swift | 4 + .../Components/Icons/ShieldBanIcon.swift | 79 ++++++++++++ ora/Shared/Components/Icons/ShieldLock.swift | 121 ++++++++++++++++++ 3 files changed, 204 insertions(+) create mode 100644 ora/Shared/Components/Icons/ShieldBanIcon.swift create mode 100644 ora/Shared/Components/Icons/ShieldLock.swift diff --git a/ora/Shared/Components/Icons/OraIcon.swift b/ora/Shared/Components/Icons/OraIcon.swift index f5d87f6b..8dc9549a 100644 --- a/ora/Shared/Components/Icons/OraIcon.swift +++ b/ora/Shared/Components/Icons/OraIcon.swift @@ -46,6 +46,8 @@ enum OraIconType { case brush2 case downloadBox case downloadBox2 + case shieldLock + case shieldBan case custom(AnyOraShape) var shape: AnyOraShape { @@ -61,6 +63,8 @@ enum OraIconType { case .brush2: AnyOraShape(Brush2()) case .downloadBox: AnyOraShape(DownloadBox()) case .downloadBox2: AnyOraShape(DownloadBox2()) + case .shieldLock: AnyOraShape(ShieldLockIcon()) + case .shieldBan: AnyOraShape(ShieldBanIcon()) case let .custom(shape): shape } } diff --git a/ora/Shared/Components/Icons/ShieldBanIcon.swift b/ora/Shared/Components/Icons/ShieldBanIcon.swift new file mode 100644 index 00000000..d500145c --- /dev/null +++ b/ora/Shared/Components/Icons/ShieldBanIcon.swift @@ -0,0 +1,79 @@ +import SwiftUI + +struct ShieldBanIcon: Shape { + // swiftlint:disable:next function_body_length + func path(in rect: CGRect) -> Path { + var path = Path() + let width = rect.size.width + let height = rect.size.height + var strokePath2 = Path() + strokePath2.move(to: CGPoint(x: 0.8459 * width, y: 0.66408 * height)) + strokePath2.addCurve( + to: CGPoint(x: 0.89773 * width, y: 0.45396 * height), + control1: CGPoint(x: 0.87744 * width, y: 0.60464 * height), + control2: CGPoint(x: 0.89773 * width, y: 0.53493 * height) + ) + strokePath2.addLine(to: CGPoint(x: 0.89773 * width, y: 0.29537 * height)) + strokePath2.addCurve( + to: CGPoint(x: 0.87729 * width, y: 0.16654 * height), + control1: CGPoint(x: 0.89773 * width, y: 0.23148 * height), + control2: CGPoint(x: 0.89773 * width, y: 0.19953 * height) + ) + strokePath2.addCurve( + to: CGPoint(x: 0.79358 * width, y: 0.10206 * height), + control1: CGPoint(x: 0.85685 * width, y: 0.13354 * height), + control2: CGPoint(x: 0.83576 * width, y: 0.12305 * height) + ) + strokePath2.addCurve( + to: CGPoint(x: 0.48864 * width, y: 0.03409 * height), + control1: CGPoint(x: 0.70757 * width, y: 0.05926 * height), + control2: CGPoint(x: 0.60231 * width, y: 0.03409 * height) + ) + strokePath2.addCurve( + to: CGPoint(x: 0.25428 * width, y: 0.07246 * height), + control1: CGPoint(x: 0.40443 * width, y: 0.03409 * height), + control2: CGPoint(x: 0.32485 * width, y: 0.0479 * height) + ) + strokePath2.move(to: CGPoint(x: 0.13144 * width, y: 0.13144 * height)) + strokePath2.addCurve( + to: CGPoint(x: 0.09998 * width, y: 0.16654 * height), + control1: CGPoint(x: 0.11938 * width, y: 0.14044 * height), + control2: CGPoint(x: 0.10961 * width, y: 0.15099 * height) + ) + strokePath2.addCurve( + to: CGPoint(x: 0.07955 * width, y: 0.29537 * height), + control1: CGPoint(x: 0.07955 * width, y: 0.19953 * height), + control2: CGPoint(x: 0.07955 * width, y: 0.23148 * height) + ) + strokePath2.addLine(to: CGPoint(x: 0.07955 * width, y: 0.45396 * height)) + strokePath2.addCurve( + to: CGPoint(x: 0.40559 * width, y: 0.91745 * height), + control1: CGPoint(x: 0.07955 * width, y: 0.71229 * height), + control2: CGPoint(x: 0.28602 * width, y: 0.85592 * height) + ) + strokePath2.addCurve( + to: CGPoint(x: 0.48864 * width, y: 0.94318 * height), + control1: CGPoint(x: 0.43894 * width, y: 0.9346 * height), + control2: CGPoint(x: 0.45561 * width, y: 0.94318 * height) + ) + strokePath2.addCurve( + to: CGPoint(x: 0.57168 * width, y: 0.91745 * height), + control1: CGPoint(x: 0.52166 * width, y: 0.94318 * height), + control2: CGPoint(x: 0.53833 * width, y: 0.9346 * height) + ) + strokePath2.addCurve( + to: CGPoint(x: 0.77005 * width, y: 0.77005 * height), + control1: CGPoint(x: 0.62865 * width, y: 0.88813 * height), + control2: CGPoint(x: 0.70536 * width, y: 0.84018 * height) + ) + strokePath2.move(to: CGPoint(x: 0.03409 * width, y: 0.03409 * height)) + strokePath2.addLine(to: CGPoint(x: 0.94318 * width, y: 0.94318 * height)) + path.addPath(strokePath2.strokedPath(StrokeStyle( + lineWidth: 0.06818 * width, + lineCap: .round, + lineJoin: .round, + miterLimit: 4 + ))) + return path + } +} diff --git a/ora/Shared/Components/Icons/ShieldLock.swift b/ora/Shared/Components/Icons/ShieldLock.swift new file mode 100644 index 00000000..74a29da6 --- /dev/null +++ b/ora/Shared/Components/Icons/ShieldLock.swift @@ -0,0 +1,121 @@ +import SwiftUI + +struct ShieldLockIcon: Shape { + // swiftlint:disable:next function_body_length + func path(in rect: CGRect) -> Path { + var path = Path() + let width = rect.size.width + let height = rect.size.height + var strokePath2 = Path() + strokePath2.move(to: CGPoint(x: 0.4875 * width, y: 0.35227 * height)) + strokePath2.addLine(to: CGPoint(x: 0.4875 * width, y: 0.39773 * height)) + strokePath2.move(to: CGPoint(x: 0.4875 * width, y: 0.42045 * height)) + strokePath2.addCurve( + to: CGPoint(x: 0.45214 * width, y: 0.40714 * height), + control1: CGPoint(x: 0.47424 * width, y: 0.42045 * height), + control2: CGPoint(x: 0.46152 * width, y: 0.41567 * height) + ) + strokePath2.addCurve( + to: CGPoint(x: 0.4375 * width, y: 0.375 * height), + control1: CGPoint(x: 0.44277 * width, y: 0.39862 * height), + control2: CGPoint(x: 0.4375 * width, y: 0.38706 * height) + ) + strokePath2.addCurve( + to: CGPoint(x: 0.45214 * width, y: 0.34286 * height), + control1: CGPoint(x: 0.4375 * width, y: 0.36295 * height), + control2: CGPoint(x: 0.44277 * width, y: 0.35138 * height) + ) + strokePath2.addCurve( + to: CGPoint(x: 0.4875 * width, y: 0.32955 * height), + control1: CGPoint(x: 0.46152 * width, y: 0.33433 * height), + control2: CGPoint(x: 0.47424 * width, y: 0.32955 * height) + ) + strokePath2.addCurve( + to: CGPoint(x: 0.52286 * width, y: 0.34286 * height), + control1: CGPoint(x: 0.50076 * width, y: 0.32955 * height), + control2: CGPoint(x: 0.51348 * width, y: 0.33433 * height) + ) + strokePath2.addCurve( + to: CGPoint(x: 0.5375 * width, y: 0.375 * height), + control1: CGPoint(x: 0.53223 * width, y: 0.35138 * height), + control2: CGPoint(x: 0.5375 * width, y: 0.36295 * height) + ) + strokePath2.addCurve( + to: CGPoint(x: 0.52286 * width, y: 0.40714 * height), + control1: CGPoint(x: 0.5375 * width, y: 0.38706 * height), + control2: CGPoint(x: 0.53223 * width, y: 0.39862 * height) + ) + strokePath2.addCurve( + to: CGPoint(x: 0.4875 * width, y: 0.42045 * height), + control1: CGPoint(x: 0.51348 * width, y: 0.41567 * height), + control2: CGPoint(x: 0.50076 * width, y: 0.42045 * height) + ) + strokePath2.closeSubpath() + strokePath2.move(to: CGPoint(x: 0.4875 * width, y: 0.42045 * height)) + strokePath2.addLine(to: CGPoint(x: 0.45 * width, y: 0.57955 * height)) + strokePath2.addLine(to: CGPoint(x: 0.525 * width, y: 0.57955 * height)) + strokePath2.addLine(to: CGPoint(x: 0.4875 * width, y: 0.42045 * height)) + strokePath2.closeSubpath() + strokePath2.move(to: CGPoint(x: 0.82294 * width, y: 0.10206 * height)) + strokePath2.addCurve( + to: CGPoint(x: 0.4875 * width, y: 0.03409 * height), + control1: CGPoint(x: 0.72833 * width, y: 0.05926 * height), + control2: CGPoint(x: 0.61255 * width, y: 0.03409 * height) + ) + strokePath2.addCurve( + to: CGPoint(x: 0.15206 * width, y: 0.10206 * height), + control1: CGPoint(x: 0.36246 * width, y: 0.03409 * height), + control2: CGPoint(x: 0.24668 * width, y: 0.05926 * height) + ) + strokePath2.addCurve( + to: CGPoint(x: 0.05998 * width, y: 0.16654 * height), + control1: CGPoint(x: 0.10566 * width, y: 0.12305 * height), + control2: CGPoint(x: 0.08246 * width, y: 0.13354 * height) + ) + strokePath2.addCurve( + to: CGPoint(x: 0.0375 * width, y: 0.29537 * height), + control1: CGPoint(x: 0.0375 * width, y: 0.19953 * height), + control2: CGPoint(x: 0.0375 * width, y: 0.23148 * height) + ) + strokePath2.addLine(to: CGPoint(x: 0.0375 * width, y: 0.45396 * height)) + strokePath2.addCurve( + to: CGPoint(x: 0.39615 * width, y: 0.91745 * height), + control1: CGPoint(x: 0.0375 * width, y: 0.7123 * height), + control2: CGPoint(x: 0.26462 * width, y: 0.85593 * height) + ) + strokePath2.addCurve( + to: CGPoint(x: 0.4875 * width, y: 0.94318 * height), + control1: CGPoint(x: 0.43284 * width, y: 0.9346 * height), + control2: CGPoint(x: 0.45117 * width, y: 0.94318 * height) + ) + strokePath2.addCurve( + to: CGPoint(x: 0.57885 * width, y: 0.91745 * height), + control1: CGPoint(x: 0.52382 * width, y: 0.94318 * height), + control2: CGPoint(x: 0.54217 * width, y: 0.9346 * height) + ) + strokePath2.addCurve( + to: CGPoint(x: 0.9375 * width, y: 0.45396 * height), + control1: CGPoint(x: 0.71038 * width, y: 0.85593 * height), + control2: CGPoint(x: 0.9375 * width, y: 0.7123 * height) + ) + strokePath2.addLine(to: CGPoint(x: 0.9375 * width, y: 0.29537 * height)) + strokePath2.addCurve( + to: CGPoint(x: 0.91502 * width, y: 0.16654 * height), + control1: CGPoint(x: 0.9375 * width, y: 0.23148 * height), + control2: CGPoint(x: 0.9375 * width, y: 0.19953 * height) + ) + strokePath2.addCurve( + to: CGPoint(x: 0.82294 * width, y: 0.10206 * height), + control1: CGPoint(x: 0.89254 * width, y: 0.13354 * height), + control2: CGPoint(x: 0.86934 * width, y: 0.12305 * height) + ) + strokePath2.closeSubpath() + path.addPath(strokePath2.strokedPath(StrokeStyle( + lineWidth: 0.075 * width, + lineCap: .round, + lineJoin: .round, + miterLimit: 4 + ))) + return path + } +} From 9560819f94a63fc9f95ae0ee3fc4f3897c2cbf10 Mon Sep 17 00:00:00 2001 From: yonaries Date: Mon, 16 Mar 2026 01:21:12 +0300 Subject: [PATCH 03/17] feat: add URL display utility --- ora/Core/Utilities/URLDisplayUtils.swift | 34 ++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 ora/Core/Utilities/URLDisplayUtils.swift diff --git a/ora/Core/Utilities/URLDisplayUtils.swift b/ora/Core/Utilities/URLDisplayUtils.swift new file mode 100644 index 00000000..acc29ee6 --- /dev/null +++ b/ora/Core/Utilities/URLDisplayUtils.swift @@ -0,0 +1,34 @@ +import Foundation + +struct URLDisplayParts { + let host: String + let title: String? +} + +enum URLDisplayUtils { + static func displayString(url: URL, title: String, showFull: Bool) -> String { + let parts = displayParts(url: url, title: title, showFull: showFull) + if let title = parts.title { + return "\(parts.host) / \(title)" + } + return parts.host + } + + static func displayParts(url: URL, title: String, showFull: Bool) -> URLDisplayParts { + if showFull { + return URLDisplayParts(host: url.absoluteString, title: nil) + } + + var host = url.host ?? url.absoluteString + if host.hasPrefix("www.") { + host = String(host.dropFirst(4)) + } + + let trimmedTitle = title.trimmingCharacters(in: .whitespaces) + if trimmedTitle.isEmpty { + return URLDisplayParts(host: host, title: nil) + } + + return URLDisplayParts(host: host, title: trimmedTitle) + } +} From d7214b2f2498eb9fd5481d275d1671be93b10fcb Mon Sep 17 00:00:00 2001 From: yonaries Date: Mon, 16 Mar 2026 01:21:16 +0300 Subject: [PATCH 04/17] feat: add URL bar menu button --- .../Browser/URLBar/URLBarMenuButton.swift | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 ora/Features/Browser/URLBar/URLBarMenuButton.swift diff --git a/ora/Features/Browser/URLBar/URLBarMenuButton.swift b/ora/Features/Browser/URLBar/URLBarMenuButton.swift new file mode 100644 index 00000000..b26bdb6f --- /dev/null +++ b/ora/Features/Browser/URLBar/URLBarMenuButton.swift @@ -0,0 +1,92 @@ +import AppKit +import SwiftUI + +struct URLBarMenuButton: View { + let foregroundColor: Color + let onShare: (NSView, NSRect) -> Void + + @State private var isHovering = false + @State private var menuSourceView: NSView? + + private var cornerRadius: CGFloat { + if #available(macOS 26, *) { + return 10 + } else { + return 6 + } + } + + var body: some View { + Button { + showMenu() + } label: { + Image(systemName: "ellipsis") + .font(.system(size: 14, weight: .medium)) + .foregroundColor(isHovering ? foregroundColor : foregroundColor.opacity(0.7)) + .frame(width: 30, height: 30) + .background( + ConditionallyConcentricRectangle(cornerRadius: cornerRadius) + .fill(isHovering ? foregroundColor.opacity(0.1) : Color.clear) + ) + } + .buttonStyle(PlainButtonStyle()) + .onHover { hovering in + isHovering = hovering + } + .background( + MenuSourceView { nsView in + menuSourceView = nsView + } + ) + } + + private func showMenu() { + guard let sourceView = menuSourceView else { return } + + let menu = NSMenu() + + let shareItem = NSMenuItem( + title: "Share link", + action: #selector(MenuActions.shareAction(_:)), + keyEquivalent: "" + ) + let delegate = MenuActions { [sourceView] in + let rect = sourceView.bounds + onShare(sourceView, rect) + } + shareItem.target = delegate + shareItem.representedObject = delegate // prevent deallocation + menu.addItem(shareItem) + + let point = NSPoint(x: 0, y: sourceView.bounds.height + 4) + menu.popUp(positioning: nil, at: point, in: sourceView) + } +} + +private class MenuActions: NSObject { + let handler: () -> Void + + init(handler: @escaping () -> Void) { + self.handler = handler + } + + @objc func shareAction(_ sender: Any?) { + handler() + } +} + +private struct MenuSourceView: NSViewRepresentable { + let onViewCreated: (NSView) -> Void + + func makeNSView(context: Context) -> NSView { + let view = NSView() + view.wantsLayer = true + view.layer?.backgroundColor = NSColor.clear.cgColor + DispatchQueue.main.async { + onViewCreated(view) + } + return view + } + + func updateNSView(_ nsView: NSView, context: Context) {} +} From 265b9cef25b1f9734b63b0580916489a971ffd68 Mon Sep 17 00:00:00 2001 From: yonaries Date: Mon, 16 Mar 2026 01:21:32 +0300 Subject: [PATCH 05/17] refactor: use URL display utils and menu button in URL bar --- ora/Features/Browser/URLBar/URLBar.swift | 47 +++++++++++++----------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/ora/Features/Browser/URLBar/URLBar.swift b/ora/Features/Browser/URLBar/URLBar.swift index 70b334ee..09067079 100644 --- a/ora/Features/Browser/URLBar/URLBar.swift +++ b/ora/Features/Browser/URLBar/URLBar.swift @@ -46,11 +46,7 @@ struct URLBar: View { } private func getDisplayURL(_ tab: Tab) -> String { - if toolbarManager.showFullURL { - return tab.url.absoluteString - } else { - return tab.url.host ?? tab.url.absoluteString - } + URLDisplayUtils.displayString(url: tab.url, title: tab.title, showFull: toolbarManager.showFullURL) } private func shareCurrentPage(tab: Tab, sourceView: NSView, sourceRect: NSRect) { @@ -155,7 +151,7 @@ struct URLBar: View { tab.loadURL(editingURLString) isEditing = false } - .opacity(showCopiedAnimation ? 0 : 1) + .opacity(isEditing ? (showCopiedAnimation ? 0 : 1) : 0) .offset(y: showCopiedAnimation ? (startWheelAnimation ? -12 : 12) : 0) .animation(.easeOut(duration: 0.3), value: showCopiedAnimation) .animation(.easeOut(duration: 0.3), value: startWheelAnimation) @@ -181,15 +177,25 @@ struct URLBar: View { // Overlay the URL/host when not editing .overlay( Group { - if !isEditing, editingURLString.isEmpty { - HStack { - Text(getDisplayURL(tab)) + if !isEditing { + let parts = URLDisplayUtils.displayParts( + url: tab.url, + title: tab.title, + showFull: toolbarManager.showFullURL + ) + HStack(spacing: 0) { + Text(parts.host) .font(.system(size: 14)) .foregroundColor(getUrlFieldColor(tab)) - .lineLimit(1) - .truncationMode(.middle) + if let title = parts.title { + Text(" / \(title)") + .font(.system(size: 14)) + .foregroundColor(getUrlFieldColor(tab).opacity(0.6)) + } Spacer() } + .lineLimit(1) + .truncationMode(.middle) } } ) @@ -212,10 +218,10 @@ struct URLBar: View { .frame(height: 30) .padding(.horizontal, 8) .background( - RoundedRectangle(cornerRadius: 6, style: .continuous) + ConditionallyConcentricRectangle(cornerRadius: 10, style: .continuous) .fill(getUrlFieldColor(tab).opacity(0.08)) .overlay( - RoundedRectangle(cornerRadius: 6, style: .continuous) + ConditionallyConcentricRectangle(cornerRadius: 10, style: .continuous) .stroke( isEditing ? getUrlFieldColor(tab).opacity(0.1) : Color.clear, lineWidth: 1.2 @@ -232,8 +238,7 @@ struct URLBar: View { .allowsHitTesting(false) ) - ShareLinkButton( - isEnabled: true, + URLBarMenuButton( foregroundColor: buttonForegroundColor, onShare: { sourceView, sourceRect in if let activeTab = tabManager.activeTab { @@ -242,13 +247,6 @@ struct URLBar: View { } ) - URLBarButton( - systemName: "ellipsis", - isEnabled: true, - foregroundColor: buttonForegroundColor, - action: {} - ) - if sidebarManager.sidebarPosition == .secondary { URLBarButton( systemName: "sidebar.right", @@ -271,6 +269,11 @@ struct URLBar: View { editingURLString = getDisplayURL(tab) } } + .onChange(of: tab.title) { _, _ in + if !isEditing { + editingURLString = getDisplayURL(tab) + } + } .onChange(of: toolbarManager.showFullURL) { _, _ in if !isEditing, let tab = tabManager.activeTab { editingURLString = getDisplayURL(tab) From 1247e9aa1e3ac48183ca2e1864b40e088f456c74 Mon Sep 17 00:00:00 2001 From: yonaries Date: Mon, 16 Mar 2026 01:21:51 +0300 Subject: [PATCH 06/17] refactor: rewrite sidebar URL display --- .../Sidebar/Views/ContainerView.swift | 12 +- .../Sidebar/Views/SidebarURLDisplay.swift | 186 ++++++++---------- 2 files changed, 81 insertions(+), 117 deletions(-) diff --git a/ora/Features/Sidebar/Views/ContainerView.swift b/ora/Features/Sidebar/Views/ContainerView.swift index ed9c57af..55df9e83 100644 --- a/ora/Features/Sidebar/Views/ContainerView.swift +++ b/ora/Features/Sidebar/Views/ContainerView.swift @@ -13,19 +13,11 @@ struct ContainerView: View { @State var isDragging = false @State private var draggedItem: UUID? - @State private var editingURLString: String = "" var body: some View { VStack(alignment: .leading, spacing: 16) { - if toolbarManager.isToolbarHidden, let tab = tabManager.activeTab { - SidebarURLDisplay( - tab: tab, - editingURLString: $editingURLString - ) - .transition(.asymmetric( - insertion: .push(from: .top).combined(with: .opacity), - removal: .push(from: .bottom).combined(with: .opacity) - )) + if toolbarManager.isToolbarHidden { + SidebarURLDisplay() } if !privacyMode.isPrivate { FavTabsGrid( diff --git a/ora/Features/Sidebar/Views/SidebarURLDisplay.swift b/ora/Features/Sidebar/Views/SidebarURLDisplay.swift index 5cef62a4..59c0606f 100644 --- a/ora/Features/Sidebar/Views/SidebarURLDisplay.swift +++ b/ora/Features/Sidebar/Views/SidebarURLDisplay.swift @@ -1,151 +1,123 @@ import AppKit -import SwiftData import SwiftUI struct SidebarURLDisplay: View { @Environment(\.theme) private var theme - @EnvironmentObject var tabManager: TabManager @EnvironmentObject var appState: AppState + @EnvironmentObject var tabManager: TabManager @EnvironmentObject var toolbarManager: ToolbarManager @EnvironmentObject var toastManager: ToastManager - let tab: Tab - @Binding var editingURLString: String - @FocusState private var isEditing: Bool + @State private var isHoveringCopy = false + @State private var isHovering = false @State private var showCopiedAnimation = false @State private var startWheelAnimation = false - init(tab: Tab, editingURLString: Binding) { - self.tab = tab - self._editingURLString = editingURLString - } - private func triggerCopy(_ text: String) { ClipboardUtils.triggerCopy( text, showCopiedAnimation: $showCopiedAnimation, startWheelAnimation: $startWheelAnimation ) - toastManager.show("Link copied", icon: .ora(.copy)) } var body: some View { - HStack(alignment: .bottom, spacing: 8) { - ZStack { - if tab.isLoading { - ProgressView() - .tint(theme.foreground.opacity(0.7)) - .scaleEffect(0.5) - .frame(width: 12, height: 12) - } else { - Image(systemName: tab.url.scheme == "https" ? "shield.lefthalf.filled" : "globe") - .font(.system(size: 12)) - .foregroundColor(theme.mutedForeground) - } - } - .frame(width: 16) - - ZStack(alignment: .leading) { - TextField("", text: $editingURLString) - .font(.system(size: 14)) - .textFieldStyle(PlainTextFieldStyle()) - .foregroundColor(isEditing ? theme.foreground : theme.mutedForeground) - .focused($isEditing) - .onSubmit { - tab.loadURL(editingURLString) - isEditing = false - } - .onTapGesture { - editingURLString = tab.url.absoluteString - isEditing = true - } - .onKeyPress(.escape) { - isEditing = false - return .handled + HStack(alignment: .center, spacing: 8) { + if let tab = tabManager.activeTab { + HStack(spacing: 8) { + if tab.isLoading { + ProgressView() + .tint(theme.foreground.opacity(0.7)) + .scaleEffect(0.5) + .frame(width: 12, height: 12) + } else { + if tab.url.scheme != "https" { + Image(systemName: "shield.slash") + .font(.system(size: 12)) + .foregroundColor(theme.mutedForeground) + } } - .opacity(showCopiedAnimation ? 0 : 1) - .offset(y: showCopiedAnimation ? (startWheelAnimation ? -12 : 12) : 0) - .animation(.easeOut(duration: 0.3), value: showCopiedAnimation) - .animation(.easeOut(duration: 0.3), value: startWheelAnimation) - CopiedURLOverlay( - foregroundColor: theme.foreground, - showCopiedAnimation: $showCopiedAnimation, - startWheelAnimation: $startWheelAnimation - ) - } - .font(.system(size: 14)) - .foregroundColor(theme.mutedForeground) - .overlay( - Group { - if !isEditing, editingURLString.isEmpty, !showCopiedAnimation { - HStack { - Text(getDisplayURL()) + ZStack(alignment: .leading) { + let parts = displayParts(for: tab) + HStack(spacing: 0) { + Text(parts.host) .font(.system(size: 14)) .foregroundColor(theme.mutedForeground) - .lineLimit(1) - .truncationMode(.middle) - Spacer() + if let title = parts.title { + Text(" / \(title)") + .font(.system(size: 14)) + .foregroundColor(theme.mutedForeground.opacity(0.6)) + } } + .lineLimit(1) + .truncationMode(.tail) + .opacity(showCopiedAnimation ? 0 : 1) + .offset(y: showCopiedAnimation ? (startWheelAnimation ? -12 : 12) : 0) + .animation(.easeOut(duration: 0.3), value: showCopiedAnimation) + .animation(.easeOut(duration: 0.3), value: startWheelAnimation) + + CopiedURLOverlay( + foregroundColor: theme.foreground, + showCopiedAnimation: $showCopiedAnimation, + startWheelAnimation: $startWheelAnimation + ) } } - .allowsHitTesting(false) - ) - .overlay( - Button("") { + Spacer(minLength: 0) + + Button { triggerCopy(tab.url.absoluteString) + } label: { + Image(systemName: "link") + .font(.system(size: 14)) + .foregroundColor(isHoveringCopy ? theme.foreground.opacity(0.8) : theme.mutedForeground) } - .opacity(0) - ) + .buttonStyle(.plain) + .onHover { hovering in + isHoveringCopy = hovering + } + .animation(.easeOut(duration: 0.15), value: isHoveringCopy) + } else { + Image(systemName: "magnifyingglass") + .font(.system(size: 12)) + .foregroundColor(theme.mutedForeground) + + Text("Search or enter URL") + .font(.system(size: 14)) + .foregroundColor(theme.mutedForeground) + Spacer(minLength: 0) + } } .padding(.horizontal, 8) .padding(.vertical, 7) - .frame(maxWidth: .infinity) + .frame(maxWidth: .infinity, alignment: .topLeading) .contentShape(Rectangle()) .onTapGesture { - isEditing = true - editingURLString = tab.url.absoluteString + if let tab = tabManager.activeTab { + appState.launcherSearchText = tab.url.absoluteString + } + appState.showLauncher = true + } + .onHover { hovering in + isHovering = hovering } .background( - RoundedRectangle(cornerRadius: 8, style: .continuous) - .fill(theme.invertedSolidWindowBackgroundColor.opacity(0.07)) + ConditionallyConcentricRectangle(cornerRadius: 10, style: .continuous) + .fill(theme.invertedSolidWindowBackgroundColor.opacity(isHovering ? 0.11 : 0.07)) ) .overlay( - RoundedRectangle(cornerRadius: 8, style: .continuous) - .stroke(theme.invertedSolidWindowBackgroundColor.opacity(0.1), lineWidth: 1) + ConditionallyConcentricRectangle(cornerRadius: 10, style: .continuous) + .stroke(theme.invertedSolidWindowBackgroundColor.opacity(0.05), lineWidth: 1) ) - .onAppear { - editingURLString = "" - DispatchQueue.main.async { - isEditing = false - } - } - .onChange(of: tab.url) { _, _ in - if !isEditing { editingURLString = "" } - } - .onChange(of: toolbarManager.showFullURL) { _, _ in - if !isEditing { editingURLString = "" } - } - .onChange(of: isEditing) { _, newValue in - if newValue { - editingURLString = tab.url.absoluteString - DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { - NSApp.sendAction(#selector(NSText.selectAll(_:)), to: nil, from: nil) - } - } else { - editingURLString = "" - } - } - .onReceive(NotificationCenter.default.publisher(for: .copyAddressURL)) { _ in - triggerCopy(tab.url.absoluteString) - } + .animation(.easeOut(duration: 0.15), value: isHovering) } - private func getDisplayURL() -> String { - if toolbarManager.showFullURL { - return tab.url.absoluteString - } else { - return tab.url.host ?? tab.url.absoluteString - } + private func displayURL(for tab: Tab) -> String { + URLDisplayUtils.displayString(url: tab.url, title: tab.title, showFull: toolbarManager.showFullURL) + } + + private func displayParts(for tab: Tab) -> URLDisplayParts { + URLDisplayUtils.displayParts(url: tab.url, title: tab.title, showFull: toolbarManager.showFullURL) } } From 9f6af21beb611ea02f972ccdd45206fb3a9805dc Mon Sep 17 00:00:00 2001 From: yonaries Date: Mon, 16 Mar 2026 01:21:55 +0300 Subject: [PATCH 07/17] style: misc UI polish --- ora/Features/Browser/Components/CopiedURLOverlay.swift | 2 +- ora/Features/Launcher/LauncherView.swift | 4 ++++ ora/Features/Sidebar/Views/FloatingSidebar.swift | 1 + ora/Features/Sidebar/Views/SidebarView.swift | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ora/Features/Browser/Components/CopiedURLOverlay.swift b/ora/Features/Browser/Components/CopiedURLOverlay.swift index 38c0c51e..753ec59b 100644 --- a/ora/Features/Browser/Components/CopiedURLOverlay.swift +++ b/ora/Features/Browser/Components/CopiedURLOverlay.swift @@ -7,7 +7,7 @@ struct CopiedURLOverlay: View { var body: some View { HStack { - Image(systemName: "link") + // Image(systemName: "link") Text("Copied Current URL") } .font(.system(size: 14)) diff --git a/ora/Features/Launcher/LauncherView.swift b/ora/Features/Launcher/LauncherView.swift index 332d2966..c0a70903 100644 --- a/ora/Features/Launcher/LauncherView.swift +++ b/ora/Features/Launcher/LauncherView.swift @@ -100,6 +100,10 @@ struct LauncherView: View { .onAppear { isVisible = true isTextFieldFocused = true + if !appState.launcherSearchText.isEmpty { + input = appState.launcherSearchText + appState.launcherSearchText = "" + } viewModel.searchEngineService.setTheme(theme) viewModel.configure( tabManager: tabManager, diff --git a/ora/Features/Sidebar/Views/FloatingSidebar.swift b/ora/Features/Sidebar/Views/FloatingSidebar.swift index 4d3ecff4..52175fd6 100644 --- a/ora/Features/Sidebar/Views/FloatingSidebar.swift +++ b/ora/Features/Sidebar/Views/FloatingSidebar.swift @@ -22,6 +22,7 @@ struct FloatingSidebar: View { .overlay(clipShape .stroke(theme.invertedSolidWindowBackgroundColor.opacity(0.3), lineWidth: 1) ) + .padding(.top, 0) } .padding(6) } diff --git a/ora/Features/Sidebar/Views/SidebarView.swift b/ora/Features/Sidebar/Views/SidebarView.swift index ea358585..2f971894 100644 --- a/ora/Features/Sidebar/Views/SidebarView.swift +++ b/ora/Features/Sidebar/Views/SidebarView.swift @@ -195,7 +195,7 @@ struct SidebarView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .padding( EdgeInsets( - top: 0, + top: 4, leading: 0, bottom: 10, trailing: 0 From 709415c85b9cbf87a583f2280f49457bccfdc6b9 Mon Sep 17 00:00:00 2001 From: yonaries Date: Mon, 16 Mar 2026 02:54:58 +0300 Subject: [PATCH 08/17] refactor: add inline launcher support to LauncherViewModel --- ora/App/OraApp.swift | 1 + ora/Features/Launcher/LauncherView.swift | 5 +- .../Launcher/State/LauncherViewModel.swift | 63 +++++++++++++------ 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/ora/App/OraApp.swift b/ora/App/OraApp.swift index 2df7f68d..76dee4ef 100644 --- a/ora/App/OraApp.swift +++ b/ora/App/OraApp.swift @@ -62,6 +62,7 @@ class AppState: ObservableObject { @Published var showFinderIn: UUID? @Published var isFloatingTabSwitchVisible: Bool = false @Published var isFullscreen: Bool = false + @Published var isURLBarEditing: Bool = false } @main diff --git a/ora/Features/Launcher/LauncherView.swift b/ora/Features/Launcher/LauncherView.swift index c0a70903..376c51f7 100644 --- a/ora/Features/Launcher/LauncherView.swift +++ b/ora/Features/Launcher/LauncherView.swift @@ -111,7 +111,10 @@ struct LauncherView: View { downloadManager: downloadManager, appState: appState, privacyMode: privacyMode, - onSubmit: onSubmit + onSubmit: onSubmit, + onDismiss: { [weak appState] in + appState?.showLauncher = false + } ) mouseHasMoved = false mouseMonitor = NSEvent.addLocalMonitorForEvents(matching: .mouseMoved) { event in diff --git a/ora/Features/Launcher/State/LauncherViewModel.swift b/ora/Features/Launcher/State/LauncherViewModel.swift index 1e6c3dda..173ab476 100644 --- a/ora/Features/Launcher/State/LauncherViewModel.swift +++ b/ora/Features/Launcher/State/LauncherViewModel.swift @@ -19,6 +19,8 @@ class LauncherViewModel: ObservableObject { private(set) var appState: AppState? private(set) var privacyMode: PrivacyMode? private(set) var onSubmit: ((String?) -> Void)? + private(set) var onDismiss: (() -> Void)? + var navigateInCurrentTab: Bool = false func configure( tabManager: TabManager, @@ -26,7 +28,9 @@ class LauncherViewModel: ObservableObject { downloadManager: DownloadManager, appState: AppState, privacyMode: PrivacyMode, - onSubmit: @escaping (String?) -> Void + onSubmit: @escaping (String?) -> Void, + onDismiss: (() -> Void)? = nil, + navigateInCurrentTab: Bool = false ) { self.tabManager = tabManager self.historyManager = historyManager @@ -34,6 +38,8 @@ class LauncherViewModel: ObservableObject { self.appState = appState self.privacyMode = privacyMode self.onSubmit = onSubmit + self.onDismiss = onDismiss + self.navigateInCurrentTab = navigateInCurrentTab } // MARK: - Search Logic @@ -88,7 +94,7 @@ class LauncherViewModel: ObservableObject { func executeCommand() { if let suggestion = suggestions.first(where: { $0.id == focusedElement }) { suggestion.action() - appState?.showLauncher = false + onDismiss?() } } @@ -125,12 +131,21 @@ class LauncherViewModel: ObservableObject { let historyManager = self.historyManager, let privacyMode = self.privacyMode else { return } - tabManager.openFromEngine( - engineName: engineName, - query: query ?? self.currentText, - historyManager: historyManager, - isPrivate: privacyMode.isPrivate - ) + if self.navigateInCurrentTab, let tab = tabManager.activeTab { + if let url = self.searchEngineService.getSearchURLForEngine( + engineName: engineName, + query: query ?? self.currentText + ) { + tab.loadURL(url.absoluteString) + } + } else { + tabManager.openFromEngine( + engineName: engineName, + query: query ?? self.currentText, + historyManager: historyManager, + isPrivate: privacyMode.isPrivate + ) + } } ) } @@ -179,18 +194,23 @@ class LauncherViewModel: ObservableObject { nil } guard let url = finalURL else { return } + let navigateCurrent = self.navigateInCurrentTab suggestions.append( LauncherSuggestion( type: .suggestedLink, title: text, url: url, action: { - tabManager.openTab( - url: url, - historyManager: historyManager, - downloadManager: downloadManager, - isPrivate: privacyMode.isPrivate - ) + if navigateCurrent { + tabManager.activeTab?.loadURL(url.absoluteString) + } else { + tabManager.openTab( + url: url, + historyManager: historyManager, + downloadManager: downloadManager, + isPrivate: privacyMode.isPrivate + ) + } } ) ) @@ -244,6 +264,7 @@ class LauncherViewModel: ObservableObject { private func appendHistorySuggestions(_ histories: [History], itemsCount: inout Int) { guard let tabManager, let historyManager, let privacyMode else { return } + let navigateCurrent = self.navigateInCurrentTab for history in histories { if itemsCount >= 5 { break } suggestions.append( @@ -254,11 +275,15 @@ class LauncherViewModel: ObservableObject { faviconURL: history.faviconURL, faviconLocalFile: history.faviconLocalFile, action: { - tabManager.openTab( - url: history.url, - historyManager: historyManager, - isPrivate: privacyMode.isPrivate - ) + if navigateCurrent { + tabManager.activeTab?.loadURL(history.url.absoluteString) + } else { + tabManager.openTab( + url: history.url, + historyManager: historyManager, + isPrivate: privacyMode.isPrivate + ) + } } ) ) From 73cc6287c7a3c696b27edabe928c3f6a8d11507b Mon Sep 17 00:00:00 2001 From: yonaries Date: Mon, 16 Mar 2026 02:55:03 +0300 Subject: [PATCH 09/17] feat: add textColor support to LauncherTextField --- ora/Features/Launcher/Main/LauncherTextField.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ora/Features/Launcher/Main/LauncherTextField.swift b/ora/Features/Launcher/Main/LauncherTextField.swift index 2e806f9f..d2cf7781 100644 --- a/ora/Features/Launcher/Main/LauncherTextField.swift +++ b/ora/Features/Launcher/Main/LauncherTextField.swift @@ -9,6 +9,7 @@ struct LauncherTextField: NSViewRepresentable { let onMoveUp: () -> Void let onMoveDown: () -> Void var cursorColor: Color + var textColor: Color? var placeholder: String class CustomTextField: NSTextField { @@ -36,6 +37,9 @@ struct LauncherTextField: NSViewRepresentable { textField.focusRingType = .none textField.drawsBackground = false textField.placeholderString = placeholder + if let textColor { + textField.textColor = NSColor(textColor) + } return textField } @@ -43,6 +47,9 @@ struct LauncherTextField: NSViewRepresentable { nsView.stringValue = text nsView.cursorColor = NSColor(cursorColor) nsView.placeholderString = placeholder + if let textColor { + nsView.textColor = NSColor(textColor) + } if let textView = nsView.currentEditor() as? NSTextView { textView.insertionPointColor = nsView.cursorColor } From e93ae31b0ff57b8e8861ba48d1838f9d70b4bf5f Mon Sep 17 00:00:00 2001 From: yonaries Date: Mon, 16 Mar 2026 02:55:08 +0300 Subject: [PATCH 10/17] feat: inline launcher mode for URL bar editing --- ora/Features/Browser/URLBar/URLBar.swift | 597 +++++++++++------- .../Browser/Views/BrowserWebContentView.swift | 102 +-- 2 files changed, 436 insertions(+), 263 deletions(-) diff --git a/ora/Features/Browser/URLBar/URLBar.swift b/ora/Features/Browser/URLBar/URLBar.swift index 09067079..f17425f1 100644 --- a/ora/Features/Browser/URLBar/URLBar.swift +++ b/ora/Features/Browser/URLBar/URLBar.swift @@ -9,27 +9,45 @@ struct URLBar: View { @EnvironmentObject var sidebarManager: SidebarManager @EnvironmentObject var toolbarManager: ToolbarManager @EnvironmentObject var toastManager: ToastManager + @EnvironmentObject var historyManager: HistoryManager + @EnvironmentObject var downloadManager: DownloadManager + @EnvironmentObject var privacyMode: PrivacyMode + @Environment(\.theme) private var theme + @Environment(\.colorScheme) var colorScheme + + // Display mode state @State private var showCopiedAnimation = false @State private var startWheelAnimation = false @State private var editingURLString: String = "" - @FocusState private var isEditing: Bool - @Environment(\.colorScheme) var colorScheme + + // Inline launcher state + @StateObject private var launcherViewModel = LauncherViewModel() + @State private var launcherInput = "" + @FocusState private var isLauncherFocused: Bool + @State private var mouseHasMoved = false + @State private var mouseMonitor: Any? + + let onSidebarToggle: () -> Void + + private var isEditing: Bool { + appState.isURLBarEditing + } + + // MARK: - Helpers private func getForegroundColor(_ tab: Tab) -> Color { - // Convert backgroundColor to NSColor for luminance calculation let nsColor = NSColor(tab.backgroundColor) if let ciColor = CIColor(color: nsColor) { let luminance = 0.299 * ciColor.red + 0.587 * ciColor.green + 0.114 * ciColor.blue return luminance < 0.5 ? .white : .black } else { - // Fallback to black if CIColor conversion fails return .black } } private func getUrlFieldColor(_ tab: Tab) -> Color { - return tabManager.activeTab.map { getForegroundColor($0).opacity(isEditing ? 1.0 : 0.5) } ?? .gray + return tabManager.activeTab.map { getForegroundColor($0).opacity(0.5) } ?? .gray } private func triggerCopy(_ text: String) { @@ -53,257 +71,392 @@ struct URLBar: View { let url = tab.url let title = tab.title.isEmpty ? "Shared from Ora" : tab.title let items: [Any] = [title, url] - let picker = NSSharingServicePicker(items: items) picker.delegate = nil - DispatchQueue.main.async { picker.show(relativeTo: sourceRect, of: sourceView, preferredEdge: .minY) } } - let onSidebarToggle: () -> Void + // MARK: - Inline Launcher - var body: some View { - HStack { - if let tab = tabManager.activeTab { - HStack(spacing: 4) { - if toolbarManager.isToolbarHidden || sidebarManager.sidebarPosition == .secondary { - WindowControls(isFullscreen: appState.isFullscreen) - } + private func startEditing() { + guard !isEditing else { return } + if let tab = tabManager.activeTab { + launcherInput = tab.url.absoluteString + } + launcherViewModel.searchEngineService.setTheme(theme) + launcherViewModel.configure( + tabManager: tabManager, + historyManager: historyManager, + downloadManager: downloadManager, + appState: appState, + privacyMode: privacyMode, + onSubmit: onLauncherSubmit, + onDismiss: dismissEditing, + navigateInCurrentTab: true + ) - if sidebarManager.sidebarPosition == .primary { - URLBarButton( - systemName: "sidebar.left", - isEnabled: true, - foregroundColor: buttonForegroundColor, - action: onSidebarToggle - ) - .oraShortcutHelp("Toggle Sidebar", for: KeyboardShortcuts.App.toggleSidebar) - } + withAnimation(.easeOut(duration: 0.25)) { + appState.isURLBarEditing = true + } - // Back button - URLBarButton( - systemName: "chevron.left", - isEnabled: tabManager.activeTab?.canGoBack ?? false, - foregroundColor: buttonForegroundColor, - action: { - if let activeTab = tabManager.activeTab { - activeTab.goBack() - } - } - ) - .oraShortcut(KeyboardShortcuts.Navigation.back) - .oraShortcutHelp("Go Back", for: KeyboardShortcuts.Navigation.back) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { + isLauncherFocused = true + } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + NSApp.sendAction(#selector(NSText.selectAll(_:)), to: nil, from: nil) + } - // Forward button - URLBarButton( - systemName: "chevron.right", - isEnabled: tabManager.activeTab?.canGoForward ?? false, - foregroundColor: buttonForegroundColor, - action: { - if let activeTab = tabManager.activeTab { - activeTab.goForward() - } - } - ) - .oraShortcut(KeyboardShortcuts.Navigation.forward) - .oraShortcutHelp("Go Forward", for: KeyboardShortcuts.Navigation.forward) + mouseHasMoved = false + mouseMonitor = NSEvent.addLocalMonitorForEvents(matching: .mouseMoved) { event in + mouseHasMoved = true + if let monitor = mouseMonitor { + NSEvent.removeMonitor(monitor) + mouseMonitor = nil + } + return event + } + } + + private func dismissEditing() { + withAnimation(.easeOut(duration: 0.2)) { + appState.isURLBarEditing = false + } + isLauncherFocused = false + launcherInput = "" + launcherViewModel.suggestions = [] + + if let monitor = mouseMonitor { + NSEvent.removeMonitor(monitor) + mouseMonitor = nil + } + } + + private func onLauncherSubmit(_ newInput: String? = nil) { + let correctInput = newInput ?? launcherInput + + if let defaultEngine = launcherViewModel.searchEngineService.getDefaultSearchEngine( + for: tabManager.activeContainer?.id + ) { + let customEngine = launcherViewModel.searchEngineService.settings.customSearchEngines + .first { $0.searchURL == defaultEngine.searchURL } + let match = defaultEngine.toLauncherMatch( + originalAlias: correctInput, + customEngine: customEngine + ) + if let url = launcherViewModel.searchEngineService.createSearchURL(for: match, query: correctInput) { + tabManager.activeTab?.loadURL(url.absoluteString) + } + } + dismissEditing() + } + + // MARK: - Body + + var body: some View { + if let tab = tabManager.activeTab { + HStack(spacing: 4) { + if toolbarManager.isToolbarHidden || sidebarManager.sidebarPosition == .secondary { + WindowControls(isFullscreen: appState.isFullscreen) + } - // Reload button + if sidebarManager.sidebarPosition == .primary { URLBarButton( - systemName: "arrow.clockwise", - isEnabled: tabManager.activeTab != nil, + systemName: "sidebar.left", + isEnabled: true, foregroundColor: buttonForegroundColor, - action: { - if let activeTab = tabManager.activeTab { - activeTab.reload() - } - } + action: onSidebarToggle ) - .oraShortcut(KeyboardShortcuts.Navigation.reload) - .oraShortcutHelp("Reload This Page", for: KeyboardShortcuts.Navigation.reload) - - // URL field - HStack(spacing: 8) { - // Security indicator - if !isEditing { - ZStack { - if tab.isLoading { - ProgressView() - // .progressViewStyle(CircularProgressViewStyle(tint: getForegroundColor(tab))) - .tint(buttonForegroundColor) - .scaleEffect(0.5) - } else { - Image(systemName: tab.url.scheme == "https" ? "shield.lefthalf.filled" : "globe") - .font(.system(size: 12)) - .foregroundColor(buttonForegroundColor) - } - } - .frame(width: 16, height: 16) - } + .oraShortcutHelp("Toggle Sidebar", for: KeyboardShortcuts.App.toggleSidebar) + } - ZStack(alignment: .leading) { - TextField("", text: $editingURLString) - .textFieldStyle(PlainTextFieldStyle()) - .focused($isEditing) - .onSubmit { - tab.loadURL(editingURLString) - isEditing = false - } - .opacity(isEditing ? (showCopiedAnimation ? 0 : 1) : 0) - .offset(y: showCopiedAnimation ? (startWheelAnimation ? -12 : 12) : 0) - .animation(.easeOut(duration: 0.3), value: showCopiedAnimation) - .animation(.easeOut(duration: 0.3), value: startWheelAnimation) - - CopiedURLOverlay( - foregroundColor: getUrlFieldColor(tab), - showCopiedAnimation: $showCopiedAnimation, - startWheelAnimation: $startWheelAnimation - ) - } - .font(.system(size: 14)) - .foregroundColor(getUrlFieldColor(tab)) - .onTapGesture { - if let activeTab = tabManager.activeTab { - editingURLString = activeTab.url.absoluteString - } - isEditing = true - } - .onKeyPress(.escape) { - isEditing = false - return .handled - } - // Overlay the URL/host when not editing - .overlay( - Group { - if !isEditing { - let parts = URLDisplayUtils.displayParts( - url: tab.url, - title: tab.title, - showFull: toolbarManager.showFullURL - ) - HStack(spacing: 0) { - Text(parts.host) - .font(.system(size: 14)) - .foregroundColor(getUrlFieldColor(tab)) - if let title = parts.title { - Text(" / \(title)") - .font(.system(size: 14)) - .foregroundColor(getUrlFieldColor(tab).opacity(0.6)) - } - Spacer() - } - .lineLimit(1) - .truncationMode(.middle) - } - } - ) - .frame(maxWidth: .infinity, alignment: .leading) - - Button { - if let activeTab = tabManager.activeTab { - triggerCopy(activeTab.url.absoluteString) - } - } label: { - Image(systemName: "link") - .font(.system(size: 12, weight: .regular)) - .foregroundColor(getUrlFieldColor(tab)) - .frame(width: 16, height: 16) - } - .buttonStyle(.plain) - .oraShortcutHelp("Copy URL", for: KeyboardShortcuts.Address.copyURL) - .accessibilityLabel(Text("Copy URL")) + // Back button + URLBarButton( + systemName: "chevron.left", + isEnabled: tabManager.activeTab?.canGoBack ?? false, + foregroundColor: buttonForegroundColor, + action: { tabManager.activeTab?.goBack() } + ) + .oraShortcut(KeyboardShortcuts.Navigation.back) + .oraShortcutHelp("Go Back", for: KeyboardShortcuts.Navigation.back) + + // Forward button + URLBarButton( + systemName: "chevron.right", + isEnabled: tabManager.activeTab?.canGoForward ?? false, + foregroundColor: buttonForegroundColor, + action: { tabManager.activeTab?.goForward() } + ) + .oraShortcut(KeyboardShortcuts.Navigation.forward) + .oraShortcutHelp("Go Forward", for: KeyboardShortcuts.Navigation.forward) + + // Reload button + URLBarButton( + systemName: "arrow.clockwise", + isEnabled: tabManager.activeTab != nil, + foregroundColor: buttonForegroundColor, + action: { tabManager.activeTab?.reload() } + ) + .oraShortcut(KeyboardShortcuts.Navigation.reload) + .oraShortcutHelp("Reload This Page", for: KeyboardShortcuts.Navigation.reload) + + // URL field area - morphs between display and launcher input + Group { + if isEditing { + inlineLauncherInput(tab: tab) + .transition(.blurReplace) + } else { + urlDisplayField(tab: tab) + .transition(.blurReplace) } - .frame(height: 30) - .padding(.horizontal, 8) - .background( - ConditionallyConcentricRectangle(cornerRadius: 10, style: .continuous) - .fill(getUrlFieldColor(tab).opacity(0.08)) - .overlay( - ConditionallyConcentricRectangle(cornerRadius: 10, style: .continuous) - .stroke( - isEditing ? getUrlFieldColor(tab).opacity(0.1) : Color.clear, - lineWidth: 1.2 - ) - ) - ) - .overlay( - // Hidden button for keyboard shortcut - Button("") { - isEditing = true + } + .overlay(alignment: .top) { + if isEditing { + suggestionsOverlay() + .offset(y: 38) + .transition(.move(edge: .top).combined(with: .opacity)) + } + } + .zIndex(1) + + URLBarMenuButton( + foregroundColor: buttonForegroundColor, + onShare: { sourceView, sourceRect in + if let activeTab = tabManager.activeTab { + shareCurrentPage(tab: activeTab, sourceView: sourceView, sourceRect: sourceRect) } - .oraShortcut(KeyboardShortcuts.Address.focus) - .opacity(0) - .allowsHitTesting(false) - ) + } + ) - URLBarMenuButton( + if sidebarManager.sidebarPosition == .secondary { + URLBarButton( + systemName: "sidebar.right", + isEnabled: true, foregroundColor: buttonForegroundColor, - onShare: { sourceView, sourceRect in - if let activeTab = tabManager.activeTab { - shareCurrentPage(tab: activeTab, sourceView: sourceView, sourceRect: sourceRect) - } - } + action: onSidebarToggle ) - - if sidebarManager.sidebarPosition == .secondary { - URLBarButton( - systemName: "sidebar.right", - isEnabled: true, - foregroundColor: buttonForegroundColor, - action: onSidebarToggle - ) - .oraShortcutHelp("Toggle Sidebar", for: KeyboardShortcuts.App.toggleSidebar) - } + .oraShortcutHelp("Toggle Sidebar", for: KeyboardShortcuts.App.toggleSidebar) } - .padding(4) - .onAppear { + } + .padding(4) + .background( + Rectangle() + .fill(tab.backgroundColor) + ) + .animation(.easeOut(duration: 0.25), value: isEditing) + // Hidden button for keyboard shortcut + .overlay( + Button("") { startEditing() } + .oraShortcut(KeyboardShortcuts.Address.focus) + .opacity(0) + .allowsHitTesting(false) + ) + .onAppear { + editingURLString = getDisplayURL(tab) + } + .onChange(of: tab.url) { _, _ in + if !isEditing { editingURLString = getDisplayURL(tab) } + } + .onChange(of: tab.title) { _, _ in + if !isEditing { editingURLString = getDisplayURL(tab) } + } + .onChange(of: toolbarManager.showFullURL) { _, _ in + if !isEditing, let tab = tabManager.activeTab { editingURLString = getDisplayURL(tab) - DispatchQueue.main.async { - isEditing = false - } } - .onChange(of: tab.url) { _, _ in - if !isEditing { - editingURLString = getDisplayURL(tab) - } + } + .onChange(of: tabManager.activeTab?.id) { _, _ in + if isEditing { dismissEditing() } + if let tab = tabManager.activeTab { + editingURLString = getDisplayURL(tab) } - .onChange(of: tab.title) { _, _ in - if !isEditing { - editingURLString = getDisplayURL(tab) + } + .onChange(of: appState.isURLBarEditing) { _, newValue in + // Clean up internal state when editing ends (e.g., from external dismiss) + if !newValue { + isLauncherFocused = false + launcherInput = "" + launcherViewModel.suggestions = [] + if let monitor = mouseMonitor { + NSEvent.removeMonitor(monitor) + mouseMonitor = nil } } - .onChange(of: toolbarManager.showFullURL) { _, _ in - if !isEditing, let tab = tabManager.activeTab { - editingURLString = getDisplayURL(tab) - } + } + .onChange(of: appState.showLauncher) { _, newValue in + // Dismiss URL bar editing if the center launcher is opened + if newValue, isEditing { dismissEditing() } + } + .onReceive(NotificationCenter.default.publisher(for: .copyAddressURL)) { _ in + if let activeTab = tabManager.activeTab { + triggerCopy(activeTab.url.absoluteString) } - .onChange(of: tabManager.activeTab?.id) { _, _ in - if !isEditing, let tab = tabManager.activeTab { - editingURLString = getDisplayURL(tab) - } + } + } + } + + // MARK: - URL Display Field (non-editing) + + private func urlDisplayField(tab: Tab) -> some View { + HStack(spacing: 8) { + // Security indicator + ZStack { + if tab.isLoading { + ProgressView() + .tint(buttonForegroundColor) + .scaleEffect(0.5) + } else { + Image(systemName: tab.url.scheme == "https" ? "shield.lefthalf.filled" : "globe") + .font(.system(size: 12)) + .foregroundColor(buttonForegroundColor) } - .onChange(of: isEditing) { _, newValue in - if newValue, let tab = tabManager.activeTab { - editingURLString = tab.url.absoluteString - DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { - NSApp.sendAction(#selector(NSText.selectAll(_:)), to: nil, from: nil) - } - } else if let tab = tabManager.activeTab { - editingURLString = getDisplayURL(tab) + } + .frame(width: 16, height: 16) + + ZStack(alignment: .leading) { + // Hidden text field (maintains keyboard shortcut compatibility) + TextField("", text: $editingURLString) + .textFieldStyle(PlainTextFieldStyle()) + .opacity(0) + + CopiedURLOverlay( + foregroundColor: getUrlFieldColor(tab), + showCopiedAnimation: $showCopiedAnimation, + startWheelAnimation: $startWheelAnimation + ) + + // URL display + let parts = URLDisplayUtils.displayParts( + url: tab.url, + title: tab.title, + showFull: toolbarManager.showFullURL + ) + HStack(spacing: 0) { + Text(parts.host) + .font(.system(size: 14)) + .foregroundColor(getUrlFieldColor(tab)) + if let title = parts.title { + Text(" / \(title)") + .font(.system(size: 14)) + .foregroundColor(getUrlFieldColor(tab).opacity(0.6)) } + Spacer() } - .onReceive(NotificationCenter.default.publisher(for: .copyAddressURL)) { _ in - if let activeTab = tabManager.activeTab { - triggerCopy(activeTab.url.absoluteString) - } + .lineLimit(1) + .truncationMode(.middle) + .opacity(showCopiedAnimation ? 0 : 1) + .offset(y: showCopiedAnimation ? (startWheelAnimation ? -12 : 12) : 0) + .animation(.easeOut(duration: 0.3), value: showCopiedAnimation) + .animation(.easeOut(duration: 0.3), value: startWheelAnimation) + } + .font(.system(size: 14)) + .foregroundColor(getUrlFieldColor(tab)) + .frame(maxWidth: .infinity, alignment: .leading) + + Button { + if let activeTab = tabManager.activeTab { + triggerCopy(activeTab.url.absoluteString) } - .background( - Rectangle() - .fill(tab.backgroundColor) + } label: { + Image(systemName: "link") + .font(.system(size: 12, weight: .regular)) + .foregroundColor(getUrlFieldColor(tab)) + .frame(width: 16, height: 16) + } + .buttonStyle(.plain) + .oraShortcutHelp("Copy URL", for: KeyboardShortcuts.Address.copyURL) + .accessibilityLabel(Text("Copy URL")) + } + .frame(height: 30) + .padding(.horizontal, 8) + .background( + ConditionallyConcentricRectangle(cornerRadius: 10, style: .continuous) + .fill(getUrlFieldColor(tab).opacity(0.08)) + ) + .contentShape(Rectangle()) + .onTapGesture { startEditing() } + } + + // MARK: - Inline Launcher Input (editing) + + private func inlineLauncherInput(tab: Tab) -> some View { + HStack(spacing: 8) { + Image(systemName: isValidURL(launcherInput) ? "globe" : "magnifyingglass") + .font(.system(size: 12)) + .foregroundColor(buttonForegroundColor) + .frame(width: 16, height: 16) + + LauncherTextField( + text: $launcherInput, + font: NSFont.systemFont(ofSize: 14, weight: .regular), + onTab: {}, + onSubmit: { + launcherViewModel.executeCommand() + }, + onDelete: { false }, + onMoveUp: { + launcherViewModel.moveFocusedElement(.up) + }, + onMoveDown: { + launcherViewModel.moveFocusedElement(.down) + }, + cursorColor: getForegroundColor(tab).opacity(0.8), + textColor: getForegroundColor(tab).opacity(0.7), + placeholder: "Search the web or enter URL..." + ) + .textFieldStyle(PlainTextFieldStyle()) + .focused($isLauncherFocused) + .onChange(of: launcherInput) { _, newValue in + launcherViewModel.currentText = newValue + launcherViewModel.searchHandler(newValue) + } + .onKeyPress(.escape) { + dismissEditing() + return .handled + } + } + .frame(height: 30) + .padding(.horizontal, 8) + .frame(maxWidth: .infinity, alignment: .leading) + .background( + ConditionallyConcentricRectangle(cornerRadius: 10, style: .continuous) + .fill(getUrlFieldColor(tab).opacity(0.08)) + .overlay( + ConditionallyConcentricRectangle(cornerRadius: 10, style: .continuous) + .stroke( + getUrlFieldColor(tab).opacity(0.1), + lineWidth: 1.2 + ) ) + ) + } + + // MARK: - Suggestions Overlay + + @ViewBuilder + private func suggestionsOverlay() -> some View { + if !launcherViewModel.suggestions.isEmpty { + VStack(alignment: .leading, spacing: 8) { + ForEach(launcherViewModel.suggestions) { suggestion in + LauncherSuggestionItem( + suggestion: suggestion, + focusedElement: $launcherViewModel.focusedElement + ) + } } + .padding(8) + .frame(maxWidth: .infinity, alignment: .leading) + .background(theme.launcherMainBackground) + .background(BlurEffectView(material: .popover, blendingMode: .withinWindow)) + .clipShape(ConditionallyConcentricRectangle(cornerRadius: 14, style: .continuous)) + .overlay( + ConditionallyConcentricRectangle(cornerRadius: 14, style: .continuous) + .stroke(theme.foreground.opacity(0.05), lineWidth: 1) + .padding(0.25) + ) + .shadow(color: .black.opacity(0.1), radius: 20, x: 0, y: 10) + .environment(\.launcherMouseHasMoved, mouseHasMoved) } } } diff --git a/ora/Features/Browser/Views/BrowserWebContentView.swift b/ora/Features/Browser/Views/BrowserWebContentView.swift index 77e99e99..0187930e 100644 --- a/ora/Features/Browser/Views/BrowserWebContentView.swift +++ b/ora/Features/Browser/Views/BrowserWebContentView.swift @@ -17,6 +17,7 @@ struct BrowserWebContentView: View { ) } ) + .zIndex(1) .transition( .asymmetric( insertion: .push(from: .top), @@ -25,53 +26,66 @@ struct BrowserWebContentView: View { ) } - if tab.isWebViewReady { - if tab.hasNavigationError, let error = tab.navigationError { - StatusPageView( - error: error, - failedURL: tab.failedURL, - onRetry: { tab.retryNavigation() }, - onGoBack: tab.canGoBack - ? { - tab.goBack() - tab.clearNavigationError() - } : nil, - onContinueAnyway: { - tab.continueToInsecureSite() + ZStack { + webContent + .frame(maxWidth: .infinity, maxHeight: .infinity) + + if appState.isURLBarEditing { + Color.black.opacity(0.15) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .contentShape(Rectangle()) + .onTapGesture { + appState.isURLBarEditing = false } - ) - .id(tab.id) - } else if let page = tab.browserPage { - BrowserPageView(page: page).id(tab.id) - .overlay(alignment: .topLeading) { - if let triggerState = tab.passwordTriggerOverlayState { - PasswordAutofillTriggerView(overlay: triggerState, tab: tab) - } + .transition(.opacity) + } + } + } + .animation(.easeOut(duration: 0.2), value: appState.isURLBarEditing) + } + + @ViewBuilder + private var webContent: some View { + if tab.isWebViewReady { + if tab.hasNavigationError, let error = tab.navigationError { + StatusPageView( + error: error, + failedURL: tab.failedURL, + onRetry: { tab.retryNavigation() }, + onGoBack: tab.canGoBack + ? { + tab.goBack() + tab.clearNavigationError() + } : nil, + onContinueAnyway: { + tab.continueToInsecureSite() + } + ) + .id(tab.id) + } else if let page = tab.browserPage { + BrowserPageView(page: page).id(tab.id) + .overlay(alignment: .topLeading) { + if let triggerState = tab.passwordTriggerOverlayState { + PasswordAutofillTriggerView(overlay: triggerState, tab: tab) } - .overlay(alignment: .topLeading) { - if let passwordOverlayState = tab.passwordOverlayState { - PasswordAutofillOverlayView(overlay: passwordOverlayState, tab: tab) - } + } + .overlay(alignment: .topLeading) { + if let passwordOverlayState = tab.passwordOverlayState { + PasswordAutofillOverlayView(overlay: passwordOverlayState, tab: tab) } - .overlay(alignment: .topTrailing) { - if appState.showFinderIn == tab.id { - FindView(page: page) - .padding(.top, 16) - .padding(.trailing, 16) - } + } + .overlay(alignment: .topTrailing) { + if appState.showFinderIn == tab.id { + FindView(page: page) + .padding(.top, 16) + .padding(.trailing, 16) } - .overlay(alignment: .bottomLeading) { - if let hovered = tab.hoveredLinkURL, !hovered.isEmpty { - LinkPreview(text: hovered) - } + } + .overlay(alignment: .bottomLeading) { + if let hovered = tab.hoveredLinkURL, !hovered.isEmpty { + LinkPreview(text: hovered) } - } else { - ZStack { - Rectangle().fill(theme.background) - ProgressView().frame(width: 32, height: 32) } - .frame(maxWidth: .infinity, maxHeight: .infinity) - } } else { ZStack { Rectangle().fill(theme.background) @@ -79,6 +93,12 @@ struct BrowserWebContentView: View { } .frame(maxWidth: .infinity, maxHeight: .infinity) } + } else { + ZStack { + Rectangle().fill(theme.background) + ProgressView().frame(width: 32, height: 32) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) } } } From afd509b7709628c6c027a25c851ed41513fa59ff Mon Sep 17 00:00:00 2001 From: yonaries Date: Mon, 16 Mar 2026 02:55:14 +0300 Subject: [PATCH 11/17] fix: make suggestion rows responsive --- ora/Features/Launcher/Suggestions/LauncherSuggestionItem.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ora/Features/Launcher/Suggestions/LauncherSuggestionItem.swift b/ora/Features/Launcher/Suggestions/LauncherSuggestionItem.swift index d2edc5fb..e099d23e 100644 --- a/ora/Features/Launcher/Suggestions/LauncherSuggestionItem.swift +++ b/ora/Features/Launcher/Suggestions/LauncherSuggestionItem.swift @@ -118,7 +118,6 @@ struct LauncherSuggestionItem: View { Text(" — \(suggestion.url?.absoluteString ?? "")") .font(.system(size: 13)) .foregroundStyle(Color(.secondaryLabelColor)) - .frame(width: 250, alignment: .leading) .lineLimit(1) .truncationMode(.middle) } @@ -128,7 +127,7 @@ struct LauncherSuggestionItem: View { } .padding(.horizontal, 8) .padding(.vertical, 10) - .frame(width: 798, alignment: .leading) + .frame(maxWidth: .infinity, alignment: .leading) .background(backgroundColor) .clipShape(ConditionallyConcentricRectangle(cornerRadius: 12, style: .continuous)) .onTapGesture { From 6efb60363f067652390dbd50a8ea6d13357cf0cc Mon Sep 17 00:00:00 2001 From: yonaries Date: Mon, 16 Mar 2026 02:55:58 +0300 Subject: [PATCH 12/17] style: default showFullURL to false --- ora/App/OraCommands.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ora/App/OraCommands.swift b/ora/App/OraCommands.swift index 3f17e5d6..2ec663d5 100644 --- a/ora/App/OraCommands.swift +++ b/ora/App/OraCommands.swift @@ -5,7 +5,7 @@ struct OraCommands: Commands { @AppStorage("ui.sidebar.hidden") private var isSidebarHidden: Bool = false @AppStorage("ui.sidebar.position") private var sidebarPosition: SidebarPosition = .primary @AppStorage("ui.toolbar.hidden") private var isToolbarHidden: Bool = false - @AppStorage("ui.toolbar.showfullurl") private var showFullURL: Bool = true + @AppStorage("ui.toolbar.showfullurl") private var showFullURL: Bool = false @Environment(\.openWindow) private var openWindow var body: some Commands { From 6ce009346dc90282ee7124c36bd09f60f9dbdef2 Mon Sep 17 00:00:00 2001 From: yonaries Date: Mon, 16 Mar 2026 03:06:13 +0300 Subject: [PATCH 13/17] fix: resolve Cmd+L not populating URL in edit mode --- ora/Features/Browser/URLBar/URLBar.swift | 49 ++++++++++++------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/ora/Features/Browser/URLBar/URLBar.swift b/ora/Features/Browser/URLBar/URLBar.swift index f17425f1..dc925346 100644 --- a/ora/Features/Browser/URLBar/URLBar.swift +++ b/ora/Features/Browser/URLBar/URLBar.swift @@ -82,6 +82,12 @@ struct URLBar: View { private func startEditing() { guard !isEditing else { return } + withAnimation(.easeOut(duration: 0.25)) { + appState.isURLBarEditing = true + } + } + + private func setupInlineLauncher() { if let tab = tabManager.activeTab { launcherInput = tab.url.absoluteString } @@ -97,10 +103,6 @@ struct URLBar: View { navigateInCurrentTab: true ) - withAnimation(.easeOut(duration: 0.25)) { - appState.isURLBarEditing = true - } - DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { isLauncherFocused = true } @@ -109,30 +111,34 @@ struct URLBar: View { } mouseHasMoved = false - mouseMonitor = NSEvent.addLocalMonitorForEvents(matching: .mouseMoved) { event in - mouseHasMoved = true - if let monitor = mouseMonitor { - NSEvent.removeMonitor(monitor) - mouseMonitor = nil + if mouseMonitor == nil { + mouseMonitor = NSEvent.addLocalMonitorForEvents(matching: .mouseMoved) { event in + mouseHasMoved = true + if let monitor = mouseMonitor { + NSEvent.removeMonitor(monitor) + mouseMonitor = nil + } + return event } - return event } } - private func dismissEditing() { - withAnimation(.easeOut(duration: 0.2)) { - appState.isURLBarEditing = false - } + private func cleanupInlineLauncher() { isLauncherFocused = false launcherInput = "" launcherViewModel.suggestions = [] - if let monitor = mouseMonitor { NSEvent.removeMonitor(monitor) mouseMonitor = nil } } + private func dismissEditing() { + withAnimation(.easeOut(duration: 0.2)) { + appState.isURLBarEditing = false + } + } + private func onLauncherSubmit(_ newInput: String? = nil) { let correctInput = newInput ?? launcherInput @@ -273,15 +279,10 @@ struct URLBar: View { } } .onChange(of: appState.isURLBarEditing) { _, newValue in - // Clean up internal state when editing ends (e.g., from external dismiss) - if !newValue { - isLauncherFocused = false - launcherInput = "" - launcherViewModel.suggestions = [] - if let monitor = mouseMonitor { - NSEvent.removeMonitor(monitor) - mouseMonitor = nil - } + if newValue { + setupInlineLauncher() + } else { + cleanupInlineLauncher() } } .onChange(of: appState.showLauncher) { _, newValue in From efe209c4d5f4e27a7c29cd49e8f1c27a31ebc479 Mon Sep 17 00:00:00 2001 From: yonaries Date: Mon, 16 Mar 2026 03:23:03 +0300 Subject: [PATCH 14/17] feat: pre-fill URL bar input with the active tab's URL when editing starts. --- ora/Features/Browser/URLBar/URLBar.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ora/Features/Browser/URLBar/URLBar.swift b/ora/Features/Browser/URLBar/URLBar.swift index dc925346..42f24e8a 100644 --- a/ora/Features/Browser/URLBar/URLBar.swift +++ b/ora/Features/Browser/URLBar/URLBar.swift @@ -82,6 +82,10 @@ struct URLBar: View { private func startEditing() { guard !isEditing else { return } + // Pre-fill input before animation so the text field isn't empty on appear + if let tab = tabManager.activeTab { + launcherInput = tab.url.absoluteString + } withAnimation(.easeOut(duration: 0.25)) { appState.isURLBarEditing = true } From 9e3ef255ce1de0c64a760c3de9d37ef131d84645 Mon Sep 17 00:00:00 2001 From: yonaries Date: Mon, 16 Mar 2026 03:31:24 +0300 Subject: [PATCH 15/17] fix: suppress initial search trigger when the URL bar is programmatically populated and focused. --- ora/Features/Browser/URLBar/URLBar.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ora/Features/Browser/URLBar/URLBar.swift b/ora/Features/Browser/URLBar/URLBar.swift index 42f24e8a..388cc7c6 100644 --- a/ora/Features/Browser/URLBar/URLBar.swift +++ b/ora/Features/Browser/URLBar/URLBar.swift @@ -27,6 +27,7 @@ struct URLBar: View { @FocusState private var isLauncherFocused: Bool @State private var mouseHasMoved = false @State private var mouseMonitor: Any? + @State private var suppressInitialSearch = false let onSidebarToggle: () -> Void @@ -92,6 +93,7 @@ struct URLBar: View { } private func setupInlineLauncher() { + suppressInitialSearch = true if let tab = tabManager.activeTab { launcherInput = tab.url.absoluteString } @@ -110,8 +112,8 @@ struct URLBar: View { DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { isLauncherFocused = true } - DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { - NSApp.sendAction(#selector(NSText.selectAll(_:)), to: nil, from: nil) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { + suppressInitialSearch = false } mouseHasMoved = false @@ -414,6 +416,7 @@ struct URLBar: View { .focused($isLauncherFocused) .onChange(of: launcherInput) { _, newValue in launcherViewModel.currentText = newValue + guard !suppressInitialSearch else { return } launcherViewModel.searchHandler(newValue) } .onKeyPress(.escape) { From ad37e4d22dc31d2222ef2ef175ec2f888f8c2736 Mon Sep 17 00:00:00 2001 From: yonaries Date: Mon, 16 Mar 2026 03:36:49 +0300 Subject: [PATCH 16/17] refactor: clean up URLBar and launcher leftovers --- ora/Features/Browser/URLBar/URLBar.swift | 48 ++++--------------- .../Launcher/State/LauncherViewModel.swift | 3 +- .../Suggestions/LauncherSuggestionItem.swift | 1 + 3 files changed, 10 insertions(+), 42 deletions(-) diff --git a/ora/Features/Browser/URLBar/URLBar.swift b/ora/Features/Browser/URLBar/URLBar.swift index 388cc7c6..b5ac5987 100644 --- a/ora/Features/Browser/URLBar/URLBar.swift +++ b/ora/Features/Browser/URLBar/URLBar.swift @@ -14,12 +14,10 @@ struct URLBar: View { @EnvironmentObject var privacyMode: PrivacyMode @Environment(\.theme) private var theme - @Environment(\.colorScheme) var colorScheme // Display mode state @State private var showCopiedAnimation = false @State private var startWheelAnimation = false - @State private var editingURLString: String = "" // Inline launcher state @StateObject private var launcherViewModel = LauncherViewModel() @@ -47,10 +45,6 @@ struct URLBar: View { } } - private func getUrlFieldColor(_ tab: Tab) -> Color { - return tabManager.activeTab.map { getForegroundColor($0).opacity(0.5) } ?? .gray - } - private func triggerCopy(_ text: String) { ClipboardUtils.triggerCopy( text, @@ -64,10 +58,6 @@ struct URLBar: View { return tabManager.activeTab.map { getForegroundColor($0).opacity(0.5) } ?? .gray } - private func getDisplayURL(_ tab: Tab) -> String { - URLDisplayUtils.displayString(url: tab.url, title: tab.title, showFull: toolbarManager.showFullURL) - } - private func shareCurrentPage(tab: Tab, sourceView: NSView, sourceRect: NSRect) { let url = tab.url let title = tab.title.isEmpty ? "Shared from Ora" : tab.title @@ -264,25 +254,8 @@ struct URLBar: View { .opacity(0) .allowsHitTesting(false) ) - .onAppear { - editingURLString = getDisplayURL(tab) - } - .onChange(of: tab.url) { _, _ in - if !isEditing { editingURLString = getDisplayURL(tab) } - } - .onChange(of: tab.title) { _, _ in - if !isEditing { editingURLString = getDisplayURL(tab) } - } - .onChange(of: toolbarManager.showFullURL) { _, _ in - if !isEditing, let tab = tabManager.activeTab { - editingURLString = getDisplayURL(tab) - } - } .onChange(of: tabManager.activeTab?.id) { _, _ in if isEditing { dismissEditing() } - if let tab = tabManager.activeTab { - editingURLString = getDisplayURL(tab) - } } .onChange(of: appState.isURLBarEditing) { _, newValue in if newValue { @@ -322,13 +295,8 @@ struct URLBar: View { .frame(width: 16, height: 16) ZStack(alignment: .leading) { - // Hidden text field (maintains keyboard shortcut compatibility) - TextField("", text: $editingURLString) - .textFieldStyle(PlainTextFieldStyle()) - .opacity(0) - CopiedURLOverlay( - foregroundColor: getUrlFieldColor(tab), + foregroundColor: buttonForegroundColor, showCopiedAnimation: $showCopiedAnimation, startWheelAnimation: $startWheelAnimation ) @@ -342,11 +310,11 @@ struct URLBar: View { HStack(spacing: 0) { Text(parts.host) .font(.system(size: 14)) - .foregroundColor(getUrlFieldColor(tab)) + .foregroundColor(buttonForegroundColor) if let title = parts.title { Text(" / \(title)") .font(.system(size: 14)) - .foregroundColor(getUrlFieldColor(tab).opacity(0.6)) + .foregroundColor(buttonForegroundColor.opacity(0.6)) } Spacer() } @@ -358,7 +326,7 @@ struct URLBar: View { .animation(.easeOut(duration: 0.3), value: startWheelAnimation) } .font(.system(size: 14)) - .foregroundColor(getUrlFieldColor(tab)) + .foregroundColor(buttonForegroundColor) .frame(maxWidth: .infinity, alignment: .leading) Button { @@ -368,7 +336,7 @@ struct URLBar: View { } label: { Image(systemName: "link") .font(.system(size: 12, weight: .regular)) - .foregroundColor(getUrlFieldColor(tab)) + .foregroundColor(buttonForegroundColor) .frame(width: 16, height: 16) } .buttonStyle(.plain) @@ -379,7 +347,7 @@ struct URLBar: View { .padding(.horizontal, 8) .background( ConditionallyConcentricRectangle(cornerRadius: 10, style: .continuous) - .fill(getUrlFieldColor(tab).opacity(0.08)) + .fill(buttonForegroundColor.opacity(0.08)) ) .contentShape(Rectangle()) .onTapGesture { startEditing() } @@ -429,11 +397,11 @@ struct URLBar: View { .frame(maxWidth: .infinity, alignment: .leading) .background( ConditionallyConcentricRectangle(cornerRadius: 10, style: .continuous) - .fill(getUrlFieldColor(tab).opacity(0.08)) + .fill(buttonForegroundColor.opacity(0.08)) .overlay( ConditionallyConcentricRectangle(cornerRadius: 10, style: .continuous) .stroke( - getUrlFieldColor(tab).opacity(0.1), + buttonForegroundColor.opacity(0.1), lineWidth: 1.2 ) ) diff --git a/ora/Features/Launcher/State/LauncherViewModel.swift b/ora/Features/Launcher/State/LauncherViewModel.swift index 173ab476..450b5bfc 100644 --- a/ora/Features/Launcher/State/LauncherViewModel.swift +++ b/ora/Features/Launcher/State/LauncherViewModel.swift @@ -181,7 +181,7 @@ class LauncherViewModel: ObservableObject { } private func appendOpenURLSuggestionIfNeeded(_ text: String) { - guard let tabManager, let historyManager, let downloadManager, let privacyMode else { + guard let tabManager, let historyManager, let privacyMode else { return } let finalURL: URL? = if let candidateURL = URL(string: text), candidateURL.scheme != nil, @@ -207,7 +207,6 @@ class LauncherViewModel: ObservableObject { tabManager.openTab( url: url, historyManager: historyManager, - downloadManager: downloadManager, isPrivate: privacyMode.isPrivate ) } diff --git a/ora/Features/Launcher/Suggestions/LauncherSuggestionItem.swift b/ora/Features/Launcher/Suggestions/LauncherSuggestionItem.swift index e099d23e..2e1347f6 100644 --- a/ora/Features/Launcher/Suggestions/LauncherSuggestionItem.swift +++ b/ora/Features/Launcher/Suggestions/LauncherSuggestionItem.swift @@ -133,6 +133,7 @@ struct LauncherSuggestionItem: View { .onTapGesture { suggestion.action() appState.showLauncher = false + appState.isURLBarEditing = false } .onHover { hover in if hover, mouseHasMoved { From 1591b3e3e07f479ea4a58b43961bd8599c6eccd6 Mon Sep 17 00:00:00 2001 From: yonaries Date: Mon, 16 Mar 2026 10:30:28 +0300 Subject: [PATCH 17/17] fix: split copy feedback between inline animation and toast --- .../Browser/Components/CopiedURLOverlay.swift | 1 - ora/Features/Browser/URLBar/URLBar.swift | 6 ++++-- .../Sidebar/Views/SidebarURLDisplay.swift | 16 +++++++++++----- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/ora/Features/Browser/Components/CopiedURLOverlay.swift b/ora/Features/Browser/Components/CopiedURLOverlay.swift index 753ec59b..dcb65b5c 100644 --- a/ora/Features/Browser/Components/CopiedURLOverlay.swift +++ b/ora/Features/Browser/Components/CopiedURLOverlay.swift @@ -7,7 +7,6 @@ struct CopiedURLOverlay: View { var body: some View { HStack { - // Image(systemName: "link") Text("Copied Current URL") } .font(.system(size: 14)) diff --git a/ora/Features/Browser/URLBar/URLBar.swift b/ora/Features/Browser/URLBar/URLBar.swift index b5ac5987..a8151aa6 100644 --- a/ora/Features/Browser/URLBar/URLBar.swift +++ b/ora/Features/Browser/URLBar/URLBar.swift @@ -51,7 +51,6 @@ struct URLBar: View { showCopiedAnimation: $showCopiedAnimation, startWheelAnimation: $startWheelAnimation ) - toastManager.show("Link copied", icon: .ora(.copy)) } var buttonForegroundColor: Color { @@ -270,7 +269,10 @@ struct URLBar: View { } .onReceive(NotificationCenter.default.publisher(for: .copyAddressURL)) { _ in if let activeTab = tabManager.activeTab { - triggerCopy(activeTab.url.absoluteString) + ClipboardUtils.copyWithToast( + activeTab.url.absoluteString, + toastManager: toastManager + ) } } } diff --git a/ora/Features/Sidebar/Views/SidebarURLDisplay.swift b/ora/Features/Sidebar/Views/SidebarURLDisplay.swift index 59c0606f..52ced5a7 100644 --- a/ora/Features/Sidebar/Views/SidebarURLDisplay.swift +++ b/ora/Features/Sidebar/Views/SidebarURLDisplay.swift @@ -4,6 +4,7 @@ import SwiftUI struct SidebarURLDisplay: View { @Environment(\.theme) private var theme @EnvironmentObject var appState: AppState + @EnvironmentObject var sidebarManager: SidebarManager @EnvironmentObject var tabManager: TabManager @EnvironmentObject var toolbarManager: ToolbarManager @EnvironmentObject var toastManager: ToastManager @@ -58,7 +59,7 @@ struct SidebarURLDisplay: View { .animation(.easeOut(duration: 0.3), value: startWheelAnimation) CopiedURLOverlay( - foregroundColor: theme.foreground, + foregroundColor: theme.mutedForeground, showCopiedAnimation: $showCopiedAnimation, startWheelAnimation: $startWheelAnimation ) @@ -111,10 +112,15 @@ struct SidebarURLDisplay: View { .stroke(theme.invertedSolidWindowBackgroundColor.opacity(0.05), lineWidth: 1) ) .animation(.easeOut(duration: 0.15), value: isHovering) - } - - private func displayURL(for tab: Tab) -> String { - URLDisplayUtils.displayString(url: tab.url, title: tab.title, showFull: toolbarManager.showFullURL) + .onReceive(NotificationCenter.default.publisher(for: .copyAddressURL)) { _ in + guard toolbarManager.isToolbarHidden, sidebarManager.sidebarPosition == .primary else { return } + if let activeTab = tabManager.activeTab { + ClipboardUtils.copyWithToast( + activeTab.url.absoluteString, + toastManager: toastManager + ) + } + } } private func displayParts(for tab: Tab) -> URLDisplayParts {