44import SwiftUI
55import WebKit
66
7- // Fetches Accent Color values to use within CSS code
7+ // Fetches Accent Color values to use within CSS code, future usage I swear.
88extension NSColor {
99 var hexString : String {
1010 guard let rgbColor = usingColorSpace ( NSColorSpace . sRGB) else { return " #000000 " }
@@ -22,149 +22,174 @@ extension NSColor {
2222// Sets up BrowserView and does basic customizations
2323struct BrowserView : View {
2424 @State private var webView = WKWebView ( )
25- @State private var urlString = " "
25+ @State private var URLString = " "
2626 @State private var pageTitle = " Weave "
2727 @State private var faviconImage : NSImage ?
2828 @State private var userAgent = " Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3.1 Safari/605.1.15 "
2929
3030 var body : some View {
31- VStack ( spacing: 0 ) {
32- Divider ( )
33-
34- WebView ( webView: webView, pageTitle: $pageTitle, urlString: $urlString, faviconImage: $faviconImage, userAgent: $userAgent)
35- . onAppear {
36- // CSS Style Injection
37- let sysAccent = NSColor . systemAccentColor. hexString
38- let userScript = """
39- var style = document.createElement('style');
40- style.innerHTML = `
41- body, p, h1, h2, h3, h4, h5, h6, ul, ol, li, a {
42- font-family: -apple-system !important;
43- }
44- `;
45- document.head.appendChild(style);
46- """
31+ WebView ( webView: webView, pageTitle: $pageTitle, URLString: $URLString, faviconImage: $faviconImage, userAgent: $userAgent)
32+ . onAppear {
33+ // CSS Style Injection
34+ var sysAccent = NSColor . systemAccentColor. hexString
35+ let styleSheet = """
36+ var style = document.createElement('style');
37+ style.innerHTML = `
38+ * {
39+ font-family: -apple-system !important;
40+ }
41+ `;
42+ document.head.appendChild(style);
43+ """
4744
48- // Simplistic Adblocking Injection
49- let adBlockerScript = """
50- var adBlockStyle = document.createElement('style');
51- adBlockStyle.innerHTML = `
52- /* Hide common ad classes */
53- .ad-banner, .ad-wrapper, .ad-container, .ad, .ads, .adsense, .adslot, .ad-badge {
54- display: none !important;
55- }
45+ // Extremely basic CSS Adblock Injection
46+ let adBlockerScript = """
47+ var adBlockStyle = document.createElement('style');
48+ adBlockStyle.innerHTML = `
49+ /* Hide common ad classes */
50+ .ad-banner, .ad-wrapper, .ad-container, .ad, .ads, .adsense, .adslot, .ad-badge {
51+ display: none !important;
52+ }
5653
57- /* Hide ads from specific URLs */
58- [href*= " doubleclick.net " ], [href*= " googleadservices.com " ], [href*= " advertising.com " ], [src*= " adserver.com " ] {
59- display: none !important;
60- }
61- `;
62- document.head.appendChild(adBlockStyle);
63- """
64- let adBlockScript = WKUserScript ( source: adBlockerScript, injectionTime: . atDocumentEnd, forMainFrameOnly: true )
65- webView. configuration. userContentController. addUserScript ( adBlockScript )
54+ /* Hide ads from specific URLs */
55+ [href*= " doubleclick.net " ], [href*= " googleadservices.com " ], [href*= " advertising.com " ], [src*= " adserver.com " ] {
56+ display: none !important;
57+ }
58+ `;
59+ document.head.appendChild(adBlockStyle);
60+ """
61+ let adBlockInject = WKUserScript ( source: adBlockerScript, injectionTime: . atDocumentEnd, forMainFrameOnly: true )
62+ webView. configuration. userContentController. addUserScript ( adBlockInject )
6663
67- let userScriptInjection = WKUserScript ( source: userScript, injectionTime: . atDocumentEnd, forMainFrameOnly: true )
68- webView. configuration. userContentController. addUserScript ( userScriptInjection)
64+ let CSSInject = WKUserScript ( source: styleSheet, injectionTime: . atDocumentEnd, forMainFrameOnly: true )
65+ webView. configuration. userContentController. addUserScript ( CSSInject)
66+
67+ // Loads URL when app opens
68+ loadURL ( )
69+ }
70+
71+ // Sets minimum sizing for window and defines toolbar title's content
72+ . frame ( minWidth: 1000 , minHeight: 600 )
73+ . navigationTitle ( pageTitle)
74+
75+ // Toolbar with keyboard shortcuts and tooltips
76+ . toolbar {
77+ // Back button
78+ ToolbarItem ( placement: . navigation) {
79+ Button ( action: goBack) {
80+ Image ( systemName: " chevron.left " )
81+ }
82+ . help ( " Go back " )
83+ . keyboardShortcut ( KeyEquivalent . leftArrow, modifiers: [ . command] )
84+ . disabled ( !webView. canGoBack)
6985 }
70- }
71- // Toolbar and navigation stuff
72- . frame ( minWidth: 1000 , minHeight: 600 )
73- . navigationTitle ( " " )
74- . toolbar {
75- ToolbarItem ( placement: . navigation) {
76- Button ( action: goBack) {
77- Image ( systemName: " chevron.left " )
86+
87+ // Forward button
88+ ToolbarItem ( placement: . navigation) {
89+ Button ( action: goForward) {
90+ Image ( systemName: " chevron.right " )
91+ }
92+ . help ( " Go forward " )
93+ . keyboardShortcut ( KeyEquivalent . rightArrow, modifiers: [ . command] )
94+ . disabled ( !webView. canGoForward)
7895 }
79- . help ( " Go back " )
80- }
81-
82- ToolbarItem ( placement: . navigation) {
83- Button ( action: goForward) {
84- Image ( systemName: " chevron.right " )
96+
97+ // Address Bar
98+ ToolbarItem ( placement: . status) {
99+ TextField ( " Search or enter URL " , text: $URLString, onCommit: loadURL)
100+ . textFieldStyle ( RoundedBorderTextFieldStyle ( ) )
101+ . frame ( width: 500 , height: nil ) // Set a fixed width
102+ . multilineTextAlignment ( . center)
103+ . lineLimit ( 1 ) // Limit to 1 line
104+ . truncationMode ( . tail) // Truncates at the end
105+ . onAppear {
106+ self . URLString = webView. url? . absoluteString ?? " "
107+ }
108+ . help ( " Enter a URL or search term " )
85109 }
86- . help ( " Go forward " )
87- }
88-
89- ToolbarItem ( placement: . primaryAction) {
90- TextField ( " Search or enter URL " , text: $urlString, onCommit: loadUrl)
91- . textFieldStyle ( RoundedBorderTextFieldStyle ( ) )
92- . frame ( width: 400 , height: nil ) // Set a fixed width
93- . multilineTextAlignment ( . center)
94- . lineLimit ( 1 ) // Limit to 1 line
95- . truncationMode ( . tail) // Truncates at the end
96- . onAppear {
97- self . urlString = webView. url? . absoluteString ?? " "
110+
111+ // Refresh button
112+ ToolbarItem ( placement: . navigation) {
113+ Button ( action: refresh) {
114+ Image ( systemName: " arrow.clockwise " )
98115 }
99- . help ( " Enter a URL or search term " )
100- }
101-
102- ToolbarItem ( placement: . primaryAction) {
103- Button ( action: loadUrl) {
104- Image ( systemName: " arrow.up.circle.fill " )
116+ . help ( " Refresh this page " )
117+ . keyboardShortcut ( " r " , modifiers: [ . command] )
105118 }
106- . help ( " Begin loading page " )
107- }
108-
109- ToolbarItem ( placement: . status) {
110- if let faviconImage = faviconImage {
111- Image ( nsImage: faviconImage)
112- . resizable ( )
113- . frame ( width: 18 , height: 18 ) // Size set to 18x18 for favicon
114- } else {
115- Image ( systemName: " link " )
116- . resizable ( )
117- . frame ( width: 18 , height: 18 ) // Placeholder icon
119+
120+ // Favicon display
121+ ToolbarItem ( placement: . navigation) {
122+ if let faviconImage = faviconImage {
123+ Image ( nsImage: faviconImage)
124+ . resizable ( )
125+ . frame ( width: 18 , height: 18 ) // Size set to 18x18 for favicon
126+ } else {
127+ Image ( systemName: " globe.americas.fill " )
128+ . resizable ( )
129+ . frame ( width: 18 , height: 18 ) // Placeholder icon
130+ . font ( Font . title. weight ( . bold) )
131+ }
118132 }
119- }
120-
121- ToolbarItem ( placement: . status) {
122- Text ( pageTitle)
123- . font ( . headline)
124- . lineLimit ( 1 )
125- }
126-
127- ToolbarItem ( placement: . status) {
128- Spacer ( )
129- }
130-
131- ToolbarItem ( placement: . navigation) {
132- Button ( action: refresh) {
133- Image ( systemName: " arrow.clockwise " )
133+
134+ // Spacer between address bar and next button chunk
135+ ToolbarItem ( placement: . primaryAction) {
136+ Spacer ( )
137+ }
138+
139+ // Share button
140+ ToolbarItem ( placement: . primaryAction) {
141+ Button ( action: {
142+ shareURL ( URLString)
143+ } ) {
144+ Image ( systemName: " square.and.arrow.up " )
145+ }
146+ . help ( " Share this page " )
134147 }
135- . help ( " Reload this page " )
136- }
137- }
138- . onAppear {
139- if let userAgent = webView. value ( forKey: " userAgent " ) as? String {
140- self . userAgent = userAgent. replacingOccurrences ( of: " Mobile " , with: " Safari " )
141148 }
142- loadUrl ( )
143- }
144149 }
145150
146- // Basic functions
151+ // Sets up functions for webpage commands
147152 private func goBack( ) {
148153 if webView. canGoBack {
149154 webView. goBack ( )
155+ // Adds extremely slight delay before refreshing page when navigating
156+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.1 ) {
157+ refresh ( )
158+ }
150159 }
151160 }
152161
153162 private func goForward( ) {
154163 if webView. canGoForward {
155164 webView. goForward ( )
165+ // Adds extremely slight delay before refreshing page when navigating
166+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.1 ) {
167+ refresh ( )
168+ }
156169 }
157170 }
158171
159172 private func refresh( ) {
160173 webView. reload ( )
161174 }
162175
163- private func loadUrl( ) {
164- let trimmedUrlString = urlString. trimmingCharacters ( in: . whitespacesAndNewlines)
165- if trimmedUrlString. contains ( " . " ) {
176+ private func shareURL( _ URLString: String ) {
177+ guard let url = URL ( string: URLString) else {
178+ print ( " Invalid URL " )
179+ return
180+ }
181+
182+ let items : [ Any ] = [ url]
183+ let sharingServicePicker = NSSharingServicePicker ( items: items)
184+
185+ sharingServicePicker. show ( relativeTo: webView. bounds, of: webView, preferredEdge: . minY)
186+ }
187+
188+ private func loadURL( ) {
189+ let trimmedURLString = URLString . trimmingCharacters ( in: . whitespacesAndNewlines)
190+ if trimmedURLString. contains ( " . " ) {
166191 // Contains a dot, likely a URL
167- if let url = URL ( string: addHttpIfNeeded ( trimmedUrlString ) ) {
192+ if let url = URL ( string: addHttpIfNeeded ( trimmedURLString ) ) {
168193 let request = URLRequest ( url: url)
169194 webView. load ( request)
170195 }
@@ -175,26 +200,28 @@ struct BrowserView: View {
175200 }
176201
177202 private func search( ) {
178- guard let searchQuery = urlString . addingPercentEncoding ( withAllowedCharacters: . urlQueryAllowed) else { return }
203+ guard let searchQuery = URLString . addingPercentEncoding ( withAllowedCharacters: . urlQueryAllowed) else { return }
179204 if let searchURL = URL ( string: " https://google.com/search?q= \( searchQuery) " ) {
180205 let request = URLRequest ( url: searchURL)
181206 webView. load ( request)
182207 }
183208 }
184209
185- private func addHttpIfNeeded( _ urlString : String ) -> String {
186- if urlString . hasPrefix ( " http:// " ) || urlString . hasPrefix ( " https:// " ) {
187- return urlString
210+ private func addHttpIfNeeded( _ URLString : String ) -> String {
211+ if URLString . hasPrefix ( " http:// " ) || URLString . hasPrefix ( " https:// " ) {
212+ return URLString
188213 } else {
189- return " https:// \( urlString ) "
214+ return " https:// \( URLString ) "
190215 }
191216 }
192217}
193218
219+ // I genuinely do not know
220+
194221struct WebView : NSViewRepresentable {
195222 let webView : WKWebView
196223 @Binding var pageTitle : String // Binding for the pageTitle
197- @Binding var urlString : String // Binding for the urlString
224+ @Binding var URLString : String // Binding for the URLString
198225 @Binding var faviconImage : NSImage ? // Binding for the favicon image
199226 @Binding var userAgent : String // Binding for the user agent
200227
@@ -211,32 +238,34 @@ struct WebView: NSViewRepresentable {
211238 }
212239
213240 func makeCoordinator( ) -> Coordinator {
214- Coordinator ( webView: webView, pageTitle: $pageTitle, urlString : $urlString , faviconImage: $faviconImage)
241+ Coordinator ( webView: webView, pageTitle: $pageTitle, URLString : $URLString , faviconImage: $faviconImage)
215242 }
216243
217244 class Coordinator : NSObject , WKNavigationDelegate {
218245 let webView : WKWebView
219246 @Binding var pageTitle : String // Binding for the pageTitle
220- @Binding var urlString : String // Binding for the urlString
247+ @Binding var URLString : String // Binding for the URLString
221248 @Binding var faviconImage : NSImage ? // Binding for the favicon image
222249
223- init ( webView: WKWebView , pageTitle: Binding < String > , urlString : Binding < String > , faviconImage: Binding < NSImage ? > ) {
250+ init ( webView: WKWebView , pageTitle: Binding < String > , URLString : Binding < String > , faviconImage: Binding < NSImage ? > ) {
224251 self . webView = webView
225252 self . _pageTitle = pageTitle
226- self . _urlString = urlString
253+ self . _URLString = URLString
227254 self . _faviconImage = faviconImage
228255 }
256+
257+ // Sets up functions to gather webpage information to display in toolbar
229258
230259 func webView( _ webView: WKWebView , didFinish navigation: WKNavigation ! ) {
231260 webView. evaluateJavaScript ( " document.title " ) { ( result, error) in
232261 if let title = result as? String {
233262 self . pageTitle = title
234- self . urlString = webView. url? . absoluteString ?? " "
263+ self . URLString = webView. url? . absoluteString ?? " "
235264 self . loadFavicon ( )
236265 }
237266 }
238267 }
239-
268+
240269 private func loadFavicon( ) {
241270 let script = """
242271 var favicon = document.querySelector('link[rel= " shortcut icon " ]') || document.querySelector('link[rel= " icon " ]');
0 commit comments