Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions Nook/Components/Browser/Window/WindowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ struct WindowView: View {
// Gradient background for the current space (bottom-most layer)
SpaceGradientBackgroundView()
.environmentObject(windowState)

// Attach background context menu to the window background layer
Color.white.opacity(isDark ? 0.3 : 0.4)
.ignoresSafeArea(.all)
Expand All @@ -49,10 +49,8 @@ struct WindowView: View {
.environmentObject(browserManager)
.environmentObject(windowState)
.background(Color.clear)

mainLayout
}

// TopBar Command Palette overlay
TopBarCommandPalette()
.environmentObject(browserManager)
Expand Down Expand Up @@ -94,7 +92,7 @@ struct WindowView: View {
}
}


// Toast overlays (matches WebsitePopup style/presentation)
VStack {
HStack {
Expand Down Expand Up @@ -219,8 +217,8 @@ struct WindowView: View {
sidebarColumn
}
}
.padding(.trailing, windowState.isFullScreen ? 0 : (windowState.isSidebarVisible && browserManager.settingsManager.sidebarPosition == .right ? 0 : aiVisible ? 0 : 8))
.padding(.leading, windowState.isFullScreen ? 0 : (windowState.isSidebarVisible && browserManager.settingsManager.sidebarPosition == .left ? 0 : aiVisible ? 0 : 8))
.padding(.trailing, (windowState.isFullScreen || browserManager.settingsManager.borderless) ? 0 : (windowState.isSidebarVisible && browserManager.settingsManager.sidebarPosition == .right ? 0 : aiVisible ? 0 : 8))
.padding(.leading, (windowState.isFullScreen || browserManager.settingsManager.borderless) ? 0 : (windowState.isSidebarVisible && browserManager.settingsManager.sidebarPosition == .left ? 0 : aiVisible ? 0 : 8))
}

private var sidebarColumn: some View {
Expand All @@ -230,7 +228,7 @@ struct WindowView: View {
if windowState.isSidebarVisible {
// Position to span 14pts into sidebar and 2pts into web content (moved 6pts left)
SidebarResizeView()

.frame(maxHeight: .infinity)
.environmentObject(browserManager)
.environmentObject(windowState)
Expand Down
16 changes: 16 additions & 0 deletions Nook/Components/Settings/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,22 @@ struct GeneralSettingsView: View {
.foregroundStyle(.secondary)
}
}

Divider().opacity(0.4)

Toggle(
isOn: $browserManager.settingsManager
.borderless
) {
VStack(alignment: .leading, spacing: 2) {
Text("Borderless")
Text(
"Remove the window border for a cleaner look"
)
.font(.caption)
.foregroundStyle(.secondary)
}
}
}

SettingsSectionCard(
Expand Down
3 changes: 3 additions & 0 deletions Nook/Components/WebsiteView/EmptyWebsiteView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ struct EmptyWebsiteView: View {
if windowState.isFullScreen {
return 0
}
if browserManager.settingsManager.borderless {
return 0
}
if #available(macOS 26.0, *) {
return 12
} else {
Expand Down
5 changes: 4 additions & 1 deletion Nook/Components/WebsiteView/WebsiteView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import AppKit
struct LinkStatusBar: View {
let hoveredLink: String?
let isCommandPressed: Bool

var body: some View {
if let link = hoveredLink, !link.isEmpty {
Text(isCommandPressed ? "Open \(link) in a new tab and focus it" : link)
Expand Down Expand Up @@ -73,6 +73,9 @@ struct WebsiteView: View {
if windowState.isFullScreen {
return 0
}
if browserManager.settingsManager.borderless {
return 0
}
if #available(macOS 26.0, *) {
return 12
} else {
Expand Down
39 changes: 24 additions & 15 deletions Nook/Managers/SettingsManager/SettingsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class SettingsManager {
private let webSearchEngineKey = "settings.webSearchEngine"
private let webSearchMaxResultsKey = "settings.webSearchMaxResults"
private let webSearchContextSizeKey = "settings.webSearchContextSize"
private let borderlessKey = "settings.borderless"
var currentSettingsTab: SettingsTabs = .general

// Stored properties
Expand Down Expand Up @@ -62,7 +63,7 @@ class SettingsManager {
userDefaults.set(searchEngine.rawValue, forKey: searchEngineKey)
}
}

var tabUnloadTimeout: TimeInterval {
didSet {
userDefaults.set(tabUnloadTimeout, forKey: tabUnloadTimeoutKey)
Expand All @@ -77,19 +78,19 @@ class SettingsManager {
NotificationCenter.default.post(name: .blockCrossSiteTrackingChanged, object: nil, userInfo: ["enabled": blockCrossSiteTracking])
}
}

var askBeforeQuit: Bool {
didSet {
userDefaults.set(askBeforeQuit, forKey: askBeforeQuitKey)
}
}

var sidebarPosition: SidebarPosition {
didSet {
userDefaults.set(sidebarPosition.rawValue, forKey: sidebarPositionKey)
}
}

var topBarAddressView: Bool {
didSet {
userDefaults.set(topBarAddressView, forKey: topBarAddressViewKey)
Expand Down Expand Up @@ -126,6 +127,12 @@ class SettingsManager {
}
}

var borderless: Bool {
didSet {
userDefaults.set(borderless, forKey: borderlessKey)
}
}

var aiProvider: AIProvider {
didSet {
userDefaults.set(aiProvider.rawValue, forKey: aiProviderKey)
Expand Down Expand Up @@ -205,7 +212,8 @@ class SettingsManager {
webSearchEnabledKey: false,
webSearchEngineKey: "auto",
webSearchMaxResultsKey: 5,
webSearchContextSizeKey: "medium"
webSearchContextSizeKey: "medium",
borderlessKey: false
])

// Initialize properties from UserDefaults
Expand All @@ -221,7 +229,7 @@ class SettingsManager {
// Fallback to google if the stored value is somehow invalid
self.searchEngine = .google
}

// Initialize tab unload timeout
self.tabUnloadTimeout = userDefaults.double(forKey: tabUnloadTimeoutKey)
self.blockCrossSiteTracking = userDefaults.bool(forKey: blockXSTKey)
Expand All @@ -242,6 +250,7 @@ class SettingsManager {
self.webSearchEngine = userDefaults.string(forKey: webSearchEngineKey) ?? "auto"
self.webSearchMaxResults = userDefaults.integer(forKey: webSearchMaxResultsKey)
self.webSearchContextSize = userDefaults.string(forKey: webSearchContextSizeKey) ?? "medium"
self.borderless = userDefaults.bool(forKey: borderlessKey)
}
}

Expand All @@ -251,17 +260,17 @@ public enum AIProvider: String, CaseIterable, Identifiable {
case gemini = "gemini"
case openRouter = "openrouter"
case ollama = "ollama"

public var id: String { rawValue }

var displayName: String {
switch self {
case .gemini: return "Google Gemini"
case .openRouter: return "OpenRouter"
case .ollama: return "Ollama (Local)"
}
}

var isRecommended: Bool {
return false
}
Expand All @@ -272,23 +281,23 @@ public enum AIProvider: String, CaseIterable, Identifiable {
public enum GeminiModel: String, CaseIterable, Identifiable {
case flash = "gemini-flash-latest"
case pro = "gemini-2.5-pro"

public var id: String { rawValue }

var displayName: String {
switch self {
case .flash: return "Gemini Flash"
case .pro: return "Gemini 2.5 Pro"
}
}

var description: String {
switch self {
case .flash: return "Fast responses, great for quick questions"
case .pro: return "Most capable model, best for complex analysis"
}
}

var icon: String {
switch self {
case .flash: return "bolt.fill"
Expand All @@ -311,9 +320,9 @@ public enum OpenRouterModel: String, CaseIterable, Identifiable {
case gpt5mini = "openai/gpt-5-mini"
case gpt5 = "openai/gpt-5"


public var id: String { rawValue }

var displayName: String {
switch self {
case .deepseekChatV31: return "DeepSeek Chat V3.1 (Free)"
Expand Down