codex: add custom search engines

This commit is contained in:
2025-09-28 21:10:31 -07:00
parent 797c4c7c52
commit 265d393cdc
5 changed files with 108 additions and 39 deletions

View File

@@ -113,11 +113,16 @@ class GeneralSettingsViewController: UIViewController
typealias Item = String
static let SearchProviderPopupItem = "searchProvider.popup"
static let SearchEngineNameFieldItem = "searchEngine.add.name"
static let SearchEngineURLFieldItem = "searchEngine.add.url"
static let SyncServerItem = "syncServer.field"
let dataSource: UICollectionViewDiffableDataSource<Section, Item>
let collectionView: UICollectionView
private var pendingEngineName: String = ""
private var pendingEngineURL: String = ""
static func createLayout(forIdiom idiom: UIUserInterfaceIdiom) -> UICollectionViewLayout {
#if targetEnvironment(macCatalyst)
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
@@ -180,20 +185,45 @@ class GeneralSettingsViewController: UIViewController
init() {
let actionHandler = { (action: UIAction) in
let providerString = action.title
let provider = Settings.SearchProviderSetting(rawValue: providerString)!
Settings.shared.searchProvider = provider
let engineName = action.title
Settings.shared.defaultSearchEngineName = engineName
}
let itemCellRegistry = UICollectionView.CellRegistration<UICollectionViewCell, Item> { cell, indexPath, identifier in
if identifier == Self.SearchProviderPopupItem {
let menu = UIMenu(children: Settings.SearchProviderSetting.allCases.map { provider in
let action = UIAction(title: provider.rawValue, handler: actionHandler)
action.state = Settings.shared.searchProvider == provider ? .on : .off
let names = Settings.shared.searchEngines.keys.sorted()
let menu = UIMenu(children: names.map { name in
let action = UIAction(title: name, handler: actionHandler)
action.state = (Settings.shared.defaultSearchEngineName == name) ? .on : .off
return action
})
cell.contentConfiguration = ButtonContentConfiguration(menu: menu)
} else if identifier == Self.SearchEngineNameFieldItem {
cell.contentConfiguration = TextFieldContentConfiguration(
text: self.pendingEngineName,
placeholderText: "Name (e.g., Startpage)",
textChanged: { [weak self] newString in
self?.pendingEngineName = newString
},
pressedReturn: { $0.resignFirstResponder() },
keyboardType: .default,
returnKeyType: .next
)
} else if identifier == Self.SearchEngineURLFieldItem {
cell.contentConfiguration = TextFieldContentConfiguration(
text: self.pendingEngineURL,
placeholderText: "URL template (use %q or %s)",
textChanged: { [weak self] newString in
self?.pendingEngineURL = newString
},
pressedReturn: { [weak self] textField in
guard let self = self else { return }
self.tryAddPendingSearchEngine()
textField.resignFirstResponder()
},
keyboardType: .URL,
returnKeyType: .done
)
} else if identifier == Self.SyncServerItem {
cell.contentConfiguration = TextFieldContentConfiguration(
text: Settings.shared.syncServer ?? "",
@@ -248,14 +278,7 @@ class GeneralSettingsViewController: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
var snapshot = dataSource.snapshot()
snapshot.appendSections(Section.allCases)
// iOS
// snapshot.appendItems(Settings.SearchProviderSetting.allCases.map { $0.rawValue }, toSection: .searchEngine)
snapshot.appendItems([ Self.SearchProviderPopupItem ], toSection: .searchEngine)
snapshot.appendItems([ Self.SyncServerItem ], toSection: .syncServer)
dataSource.apply(snapshot, animatingDifferences: false)
applySnapshot(animatingDifferences: false)
}
}
@@ -265,3 +288,33 @@ extension GeneralSettingsViewController : UICollectionViewDelegate {
false
}
}
private extension GeneralSettingsViewController {
func applySnapshot(animatingDifferences: Bool) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections(Section.allCases)
snapshot.appendItems([ Self.SearchProviderPopupItem,
Self.SearchEngineNameFieldItem,
Self.SearchEngineURLFieldItem ], toSection: .searchEngine)
snapshot.appendItems([ Self.SyncServerItem ], toSection: .syncServer)
dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
}
func tryAddPendingSearchEngine() {
let name = pendingEngineName.trimmingCharacters(in: .whitespacesAndNewlines)
let url = pendingEngineURL.trimmingCharacters(in: .whitespacesAndNewlines)
guard !name.isEmpty, !url.isEmpty else { return }
// Require placeholder
guard url.contains("%q") || url.contains("%s") else { return }
// Save
var engines = Settings.shared.searchEngines
engines[name] = url
Settings.shared.searchEngines = engines
// Reset inputs and refresh UI
pendingEngineName = ""
pendingEngineURL = ""
applySnapshot(animatingDifferences: true)
}
}