gemenon

[ACTIVE] The Safari of the Gemini ecosystem
git clone git://git.figbert.com/gemenon.git
Log | Files | Refs

commit 8a02bdb939406f9e6b1546f6e0da0c3d13f7d2eb
parent 25314131ae1515877569d17dd1c5ff6d3bac56e6
Author: FIGBERT <figbert@figbert.com>
Date:   Fri, 21 Oct 2022 23:40:32 -0700

Add bookmarks

Diffstat:
MGemenon.xcodeproj/project.pbxproj | 6++++++
AShared/BookmarksView.swift | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
MShared/BrowserFunctions.swift | 32+++++++++++++++++++++++++++++++-
MShared/ContentView.swift | 36+++++++++++++++++++++++++++++++++---
Mtodo | 2+-
5 files changed, 124 insertions(+), 5 deletions(-)

diff --git a/Gemenon.xcodeproj/project.pbxproj b/Gemenon.xcodeproj/project.pbxproj @@ -21,6 +21,8 @@ 3AB5B67E28BF23FF00F0C1A5 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB5B66C28BF23FF00F0C1A5 /* ContentView.swift */; }; 3AB5B67F28BF23FF00F0C1A5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3AB5B66D28BF23FF00F0C1A5 /* Assets.xcassets */; }; 3AB5B68028BF23FF00F0C1A5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3AB5B66D28BF23FF00F0C1A5 /* Assets.xcassets */; }; + 3ABE77C32903B77600A7EE90 /* BookmarksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABE77C22903B77600A7EE90 /* BookmarksView.swift */; }; + 3ABE77C42903B77600A7EE90 /* BookmarksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABE77C22903B77600A7EE90 /* BookmarksView.swift */; }; 3AD4A8B928C3EE120095B98E /* SwiftGemini in Frameworks */ = {isa = PBXBuildFile; productRef = 3AD4A8B828C3EE120095B98E /* SwiftGemini */; }; 3AD4A8BC28C3EE440095B98E /* SwiftGemtext in Frameworks */ = {isa = PBXBuildFile; productRef = 3AD4A8BB28C3EE440095B98E /* SwiftGemtext */; }; /* End PBXBuildFile section */ @@ -37,6 +39,7 @@ 3AB5B67228BF23FF00F0C1A5 /* Gemenon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Gemenon.app; sourceTree = BUILT_PRODUCTS_DIR; }; 3AB5B67828BF23FF00F0C1A5 /* Gemenon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Gemenon.app; sourceTree = BUILT_PRODUCTS_DIR; }; 3AB5B67A28BF23FF00F0C1A5 /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = "<group>"; }; + 3ABE77C22903B77600A7EE90 /* BookmarksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksView.swift; sourceTree = "<group>"; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -76,6 +79,7 @@ 3A00265228C854A100625B2C /* StartPage.swift */, 3A00264F28C8398A00625B2C /* CapsuleView.swift */, 3A00264928C8340500625B2C /* HistoryView.swift */, + 3ABE77C22903B77600A7EE90 /* BookmarksView.swift */, 3A00264C28C8350200625B2C /* BrowserFunctions.swift */, 3AB5B66D28BF23FF00F0C1A5 /* Assets.xcassets */, ); @@ -208,6 +212,7 @@ files = ( 3AB5B67D28BF23FF00F0C1A5 /* ContentView.swift in Sources */, 3A00265328C854A100625B2C /* StartPage.swift in Sources */, + 3ABE77C32903B77600A7EE90 /* BookmarksView.swift in Sources */, 3A00265028C8398A00625B2C /* CapsuleView.swift in Sources */, 3A00264A28C8340500625B2C /* HistoryView.swift in Sources */, 3A00264D28C8350200625B2C /* BrowserFunctions.swift in Sources */, @@ -221,6 +226,7 @@ files = ( 3AB5B67E28BF23FF00F0C1A5 /* ContentView.swift in Sources */, 3A00265428C854A100625B2C /* StartPage.swift in Sources */, + 3ABE77C42903B77600A7EE90 /* BookmarksView.swift in Sources */, 3A00265128C8398A00625B2C /* CapsuleView.swift in Sources */, 3A00264B28C8340500625B2C /* HistoryView.swift in Sources */, 3A00264E28C8350200625B2C /* BrowserFunctions.swift in Sources */, diff --git a/Shared/BookmarksView.swift b/Shared/BookmarksView.swift @@ -0,0 +1,53 @@ +// +// BookmarksView.swift +// Gemenon +// +// Created by figbert on 10/21/22. +// + +import SwiftUI + +struct BookmarksView: View { + @EnvironmentObject var data: BrowserData + @State private var editing: UUID? + @State private var url = "" + @State private var name = "" + + var body: some View { + List { + ForEach(data.bookmarks.sorted(by: { a, b in a.timestamp < b.timestamp })) { bookmark in + HStack { + if editing == bookmark.id { + TextField("Bookmark URL", text: $url) + .textFieldStyle(.roundedBorder) + .autocorrectionDisabled(true) + TextField("Bookmark Name", text: $name) + .textFieldStyle(.roundedBorder) + .autocorrectionDisabled(true) + Button(action: { + if let url = URL(string: url) { + data.dropURLFromBookmarks(bookmark.url) + data.addURLToBookmarks(url, label: name, timestamp: bookmark.timestamp) + } + self.editing = nil + }) { Text("Done") } + } else { + Link(destination: bookmark.url) { Text(bookmark.name) } + Spacer() + Button(action: { + self.url = bookmark.url.absoluteString + self.name = bookmark.name + self.editing = bookmark.id + }) { Text("Edit") } + } + } + } + } + } +} + +struct BookmarksView_Previews: PreviewProvider { + static var previews: some View { + BookmarksView() + } +} diff --git a/Shared/BrowserFunctions.swift b/Shared/BrowserFunctions.swift @@ -14,8 +14,10 @@ class BrowserData: ObservableObject { @Published var tab = Tab() @Published var loading = false + @Published var bookmarks: [Bookmark] = [] + @Published var currentView: GemenonView = .Capsule - @Published var views: [GemenonView] = [.Capsule, .History] + @Published var views: [GemenonView] = [.Capsule, .History, .Bookmarks] @Published var parser = SwiftGemtext.Gemtext() @Published var engine = SwiftGemini.GeminiRequestor() @@ -52,6 +54,25 @@ extension BrowserData { } extension BrowserData { + func hasURLInBookmarks(_ url: URL) -> Bool { + guard !self.bookmarks.isEmpty else { return false } + return bookmarks.contains(where: { bookmark in + bookmark.url == url + }) + } + func addURLToBookmarks(_ url: URL, label: String, timestamp: Date) { + guard !self.hasURLInBookmarks(url) else { return } + self.bookmarks.append(Bookmark(url: url, name: label, timestamp: timestamp)) + } + func dropURLFromBookmarks(_ url: URL) { + guard self.hasURLInBookmarks(url) else { return } + self.bookmarks.removeAll(where: { bookmark in + bookmark.url == url + }) + } +} + +extension BrowserData { func openURL(_ url: URL) async { self.loading = true let response = try! await self.engine.request(url) @@ -92,7 +113,16 @@ class Tab: Identifiable { } } +struct Bookmark: Identifiable { + let id = UUID() + + let url: URL + let name: String + let timestamp: Date +} + enum GemenonView { case Capsule case History + case Bookmarks } diff --git a/Shared/ContentView.swift b/Shared/ContentView.swift @@ -11,7 +11,6 @@ struct ContentView: View { @EnvironmentObject var data: BrowserData @State private var columnVisibility = NavigationSplitViewVisibility.detailOnly - @State private var hoveringURLBar = false var body: some View { NavigationSplitView(columnVisibility: $columnVisibility) { @@ -22,6 +21,8 @@ struct ContentView: View { CapsuleView() case .History: HistoryView() + case .Bookmarks: + BookmarksView() } } label: { switch view { @@ -33,6 +34,8 @@ struct ContentView: View { } case .History: Label("History", systemImage: "clock") + case .Bookmarks: + Label("Bookmarks", systemImage: "star") } } } @@ -42,6 +45,8 @@ struct ContentView: View { CapsuleView() case .History: HistoryView() + case .Bookmarks: + BookmarksView() } } .toolbar { @@ -50,9 +55,9 @@ struct ContentView: View { .frame(minWidth: 500) .textFieldStyle(.roundedBorder) .autocorrectionDisabled(true) - .onHover(perform: { hovering in hoveringURLBar = hovering }) .onSubmit { Task { await submitURLBar() } } .overlay(HStack { + bookmarksButton reloadButton loading }, alignment: .trailing) @@ -91,8 +96,33 @@ struct ContentView: View { }) } + @ViewBuilder private var bookmarksButton: some View { + if data.tab.response != nil { + if data.hasURLInBookmarks(data.tab.url!) { + Button(action: { data.dropURLFromBookmarks(data.tab.url!) }) { + Label("Drop from bookmarks", systemImage: "star.fill") + .labelStyle(.iconOnly) + } + .buttonStyle(.borderless) + .padding(.trailing, 2) + } else { + Button(action: { + data.addURLToBookmarks( + data.tab.url!, + label: data.tab.url?.host() ?? data.tab.url!.absoluteString, + timestamp: Date.now + ) + }) { + Label("Add to bookmarks", systemImage: "star") + .labelStyle(.iconOnly) + } + .buttonStyle(.borderless) + .padding(.trailing, 2) + } + } + } @ViewBuilder private var reloadButton: some View { - if data.tab.response != nil && hoveringURLBar { + if data.tab.response != nil { Button(action: { Task { await data.reload() } }) { Label("Reload page", systemImage: "arrow.clockwise") .labelStyle(.iconOnly) diff --git a/todo b/todo @@ -1,7 +1,7 @@ [ ] tabs [ ] TOFU [ ] ANSI in preformatted blocks -[ ] bookmarks/favorites +[X] bookmarks/favorites [X] reload page [X] share menu (link?) [ ] better home/startpage