aboutsummaryrefslogtreecommitdiff
path: root/Hotline/Shared
diff options
context:
space:
mode:
authorDustin Mierau <dustin@mierau.me>2025-10-20 22:15:48 -0700
committerDustin Mierau <dustin@mierau.me>2025-10-20 22:15:48 -0700
commitede41868962ffed386b0da694d14cdfe6cfdb34f (patch)
treef0b91edd1d9c1bbc23de525224b49d6f8e48e815 /Hotline/Shared
parent77b3ac3c051fc4e8fa126cd21e261be28f4aad1a (diff)
Cleanup and a bunch of work to fix issues syncing, loading, and general bookmark states in TrackerView. Added search field (substring conjunctive filter on server/bookmark names and descriptions).
Diffstat (limited to 'Hotline/Shared')
-rw-r--r--Hotline/Shared/AsyncLinkPreview.swift57
-rw-r--r--Hotline/Shared/DataAdditions.swift23
-rw-r--r--Hotline/Shared/FileIconView.swift74
-rw-r--r--Hotline/Shared/FileImageView.swift98
-rw-r--r--Hotline/Shared/HotlinePanel.swift64
-rw-r--r--Hotline/Shared/NetSocket.swift269
-rw-r--r--Hotline/Shared/TextView.swift119
-rw-r--r--Hotline/Shared/URLAdditions.swift26
-rw-r--r--Hotline/Shared/VisualEffectView.swift22
9 files changed, 0 insertions, 752 deletions
diff --git a/Hotline/Shared/AsyncLinkPreview.swift b/Hotline/Shared/AsyncLinkPreview.swift
deleted file mode 100644
index 89b186f..0000000
--- a/Hotline/Shared/AsyncLinkPreview.swift
+++ /dev/null
@@ -1,57 +0,0 @@
-import SwiftUI
-import LinkPresentation
-
-fileprivate class CustomLinkView: LPLinkView {
- override var intrinsicContentSize: CGSize { CGSize(width: 0, height: super.intrinsicContentSize.height) }
-}
-
-struct AsyncLinkPreview: View {
- @State private var metadata: LPLinkMetadata?
- @State private var isLoading = true
- let url: URL?
-
- func fetchMetadata() async {
- guard let url else {
- self.isLoading = false
- return
- }
- do {
- let provider = LPMetadataProvider()
- let metadata = try await provider.startFetchingMetadata(for: url)
- self.metadata = metadata
- self.isLoading = false
- } catch {
- self.isLoading = false
- }
- }
-
- var body: some View {
- if isLoading {
- ProgressView()
- .controlSize(.small)
- .task {
- await self.fetchMetadata()
- }
- } else if let metadata = metadata {
- LinkView(metadata: metadata)
- .frame(width: 200)
- } else {
- Text(LocalizedStringKey(url!.absoluteString))
- .multilineTextAlignment(.leading)
- .tint(Color("Link Color"))
- }
- }
-}
-
-struct LinkView: NSViewRepresentable {
- var metadata: LPLinkMetadata
-
- func makeNSView(context: Context) -> LPLinkView {
- let linkView = CustomLinkView(metadata: metadata)
- return linkView
- }
-
- func updateNSView(_ nsView: LPLinkView, context: Context) {
- // Nothing required
- }
-}
diff --git a/Hotline/Shared/DataAdditions.swift b/Hotline/Shared/DataAdditions.swift
deleted file mode 100644
index 0e9dd96..0000000
--- a/Hotline/Shared/DataAdditions.swift
+++ /dev/null
@@ -1,23 +0,0 @@
-import Foundation
-
-extension Data {
- func saveAsFileToDownloads(filename: String, bounceDock: Bool = true) -> Bool {
- let folderURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0]
- let filePath = folderURL.generateUniqueFilePath(filename: filename)
- if FileManager.default.createFile(atPath: filePath, contents: nil) {
- if let h = FileHandle(forWritingAtPath: filePath) {
- try? h.write(contentsOf: self)
- try? h.close()
- if bounceDock {
- #if os(macOS)
- var downloadURL = URL(filePath: filePath)
- downloadURL.resolveSymlinksInPath()
- DistributedNotificationCenter.default().post(name: .init("com.apple.DownloadFileFinished"), object: downloadURL.path)
- #endif
- }
- return true
- }
- }
- return false
- }
-}
diff --git a/Hotline/Shared/FileIconView.swift b/Hotline/Shared/FileIconView.swift
deleted file mode 100644
index d0949fa..0000000
--- a/Hotline/Shared/FileIconView.swift
+++ /dev/null
@@ -1,74 +0,0 @@
-import SwiftUI
-import UniformTypeIdentifiers
-
-struct FolderIconView: View {
- private func folderIcon() -> Image {
-#if os(iOS)
- return Image(systemName: "folder.fill")
-#elseif os(macOS)
- return Image(nsImage: NSWorkspace.shared.icon(for: UTType.folder))
-#endif
- }
-
- var body: some View {
- folderIcon()
- .resizable()
- .scaledToFit()
- }
-}
-
-struct FileIconView: View {
- let filename: String
- let fileType: String?
-
- #if os(iOS)
- private func fileIcon() -> Image {
- let fileExtension = (self.filename as NSString).pathExtension
- if let fileType = UTType(filenameExtension: fileExtension) {
- if fileType.isSubtype(of: .movie) {
- return Image(systemName: "play.rectangle")
- }
- else if fileType.isSubtype(of: .image) {
- return Image(systemName: "photo")
- }
- else if fileType.isSubtype(of: .archive) {
- return Image(systemName: "doc.zipper")
- }
- else if fileType.isSubtype(of: .text) {
- return Image(systemName: "doc.text")
- }
- else {
- return Image(systemName: "doc")
- }
- }
-
- return Image(systemName: "doc")
- }
- #elseif os(macOS)
- private func fileIcon() -> Image {
- let fileExtension = (self.filename as NSString).pathExtension
-
- if !fileExtension.isEmpty,
- let uttype = UTType(filenameExtension: fileExtension) {
- return Image(nsImage: NSWorkspace.shared.icon(for: uttype))
- }
- else if let fileType = self.fileType,
- let fileTypeExtension = FileManager.HFSTypeToExtension[fileType.lowercased()],
- let uttype = UTType(filenameExtension: fileTypeExtension) {
- return Image(nsImage: NSWorkspace.shared.icon(for: uttype))
- }
- else {
- return Image(nsImage: NSWorkspace.shared.icon(for: UTType.data))
- }
-
-// Image(nsImage: NSWorkspace.shared.icon(for: UTType(filenameExtension: (filename as NSString).pathExtension) ?? UTType.content))
- }
- #endif
-
-
- var body: some View {
- fileIcon()
- .resizable()
- .scaledToFit()
- }
-}
diff --git a/Hotline/Shared/FileImageView.swift b/Hotline/Shared/FileImageView.swift
deleted file mode 100644
index 0cc50f1..0000000
--- a/Hotline/Shared/FileImageView.swift
+++ /dev/null
@@ -1,98 +0,0 @@
-import SwiftUI
-
-struct FileImageView: NSViewRepresentable {
- var image: NSImage?
-
- let minimumSize: CGSize = CGSize(width: 350, height: 350)
- let presentationPaddingRatio: Double = 0.5
-
- func makeNSView(context: Context) -> NSImageView {
- let imageView = NSImageView()
- imageView.imageScaling = .scaleProportionallyUpOrDown
- imageView.animates = true
- imageView.isEditable = false
- imageView.allowsCutCopyPaste = true
- imageView.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
- imageView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
-
- if let img = self.image {
- imageView.image = img
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
- self.resizeWindowForImageView(imageView)
- }
- }
-
- return imageView
- }
-
- func updateNSView(_ nsView: NSImageView, context: Context) {
- nsView.image = self.image
- }
-
- // MARK: -
-
- func resizeWindowForImageView(_ imageView: NSImageView) {
- guard let window = imageView.window, let img = imageView.image else {
- return
- }
-
- var windowRect = window.contentLayoutRect
- let windowChromeSize = CGSize(width: window.frame.width - windowRect.width, height: window.frame.height - windowRect.height)
-
- var windowMinSize: CGSize = windowRect.size
- let centerPoint = CGPoint(x: window.frame.midX, y: window.frame.midY)
-
- windowRect.size = img.size
-
- if let screen = window.screen {
- var paddedScreenSize = screen.frame.size
- paddedScreenSize.width *= self.presentationPaddingRatio
- paddedScreenSize.height *= self.presentationPaddingRatio
-
- windowMinSize = aspectFit(source: windowRect.size, bounds: self.minimumSize)
- windowRect.size = aspectFit(source: windowRect.size, bounds: paddedScreenSize, minimum: windowMinSize)
- }
-
- windowRect.size.width += windowChromeSize.width
- windowRect.size.height += windowChromeSize.height
-
- windowRect.origin.x = centerPoint.x - windowRect.width / 2.0
- windowRect.origin.y = centerPoint.y - windowRect.height / 2.0
-
- window.setFrame(windowRect, display: true, animate: true)
-
-// Do these APIs even work??
-// window.aspectRatio = windowRect.size
-// window.contentAspectRatio = windowRect.size
- }
-
- func aspectFit(source sourceSize: CGSize, bounds boundingSize: CGSize, minimum minSize: CGSize? = nil) -> CGSize {
- let sourceAspectRatio = sourceSize.width / sourceSize.height
-
- var fitSize: CGSize = sourceSize
-
- if fitSize.width > boundingSize.width {
- fitSize.width = boundingSize.width
- fitSize.height = fitSize.width / sourceAspectRatio
- }
-
- if fitSize.height > boundingSize.height {
- fitSize.height = boundingSize.height
- fitSize.width = fitSize.height * sourceAspectRatio
- }
-
- if let m = minSize {
- if fitSize.width < m.width {
- fitSize.width = m.width
- fitSize.height = fitSize.width / sourceAspectRatio
- }
-
- if fitSize.height < m.height {
- fitSize.height = m.height
- fitSize.width = fitSize.height * sourceAspectRatio
- }
- }
-
- return fitSize
- }
-}
diff --git a/Hotline/Shared/HotlinePanel.swift b/Hotline/Shared/HotlinePanel.swift
deleted file mode 100644
index 191e89a..0000000
--- a/Hotline/Shared/HotlinePanel.swift
+++ /dev/null
@@ -1,64 +0,0 @@
-import Cocoa
-import SwiftUI
-
-fileprivate let HOTLINE_PANEL_SIZE: CGSize = CGSizeMake(468, 114 - 10)
-
-class HotlinePanel: NSPanel {
- init(_ view: HotlinePanelView) {
- super.init(contentRect: NSRect(x: 0, y: 0, width: HOTLINE_PANEL_SIZE.width, height: HOTLINE_PANEL_SIZE.height), styleMask: [.nonactivatingPanel, .titled, .closable, .utilityWindow, .fullSizeContentView], backing: .buffered, defer: false)
-
- // Make sure that the panel is in front of almost all other windows
- self.isFloatingPanel = false
- self.level = .floating
- self.hidesOnDeactivate = true
- self.animationBehavior = .utilityWindow
-
- // Allow the panel to appear in a fullscreen space
-// self.collectionBehavior.insert(.fullScreenAuxiliary)
- self.collectionBehavior.insert(.canJoinAllSpaces)
- self.collectionBehavior.insert(.ignoresCycle)
-
-// self.appearance = NSAppearance(named: .vibrantDark)
-
- // Don't delete panel state when it's closed.
- self.isReleasedWhenClosed = false
-
- self.standardWindowButton(.closeButton)?.isHidden = true
- self.standardWindowButton(.zoomButton)?.isHidden = true
- self.standardWindowButton(.miniaturizeButton)?.isHidden = true
-
- // Make it transparent, the view inside will have to set the background.
- // This is necessary because otherwise, we will have some space for the titlebar on top of the height of the view itself which we don't want.
- self.isOpaque = false
- self.backgroundColor = .clear
-
- // Since we don't show a statusbar, this allows us to drag the window by its background instead of the titlebar.
- self.isMovableByWindowBackground = true
- self.titlebarAppearsTransparent = true
-
- let hostingView = NSHostingView(rootView: view.edgesIgnoringSafeArea(.top))
- hostingView.sizingOptions = [.preferredContentSize]
-
- let visualEffectView = NSVisualEffectView(frame: NSRect(x: 0, y: 0, width: HOTLINE_PANEL_SIZE.width, height: HOTLINE_PANEL_SIZE.height))
- visualEffectView.material = .sidebar
- visualEffectView.blendingMode = .behindWindow
- visualEffectView.state = NSVisualEffectView.State.active
- visualEffectView.autoresizingMask = [.width, .height]
- visualEffectView.autoresizesSubviews = true
- visualEffectView.addSubview(hostingView)
-
- self.contentView = visualEffectView
-
- hostingView.frame = visualEffectView.bounds
-
- self.cascadeTopLeft(from: NSMakePoint(16, 16))
- }
-
- override var canBecomeKey: Bool {
- return false
- }
-
- override var canBecomeMain: Bool {
- return false
- }
-}
diff --git a/Hotline/Shared/NetSocket.swift b/Hotline/Shared/NetSocket.swift
deleted file mode 100644
index a7d44ce..0000000
--- a/Hotline/Shared/NetSocket.swift
+++ /dev/null
@@ -1,269 +0,0 @@
-
-// NetSocket.swift
-// A simple delegate based buffered read/write TCP socket.
-// Created by Dustin Mierau
-
-import Foundation
-
-protocol NetSocketDelegate: AnyObject {
- func netsocketConnected(socket: NetSocket)
- func netsocketDisconnected(socket: NetSocket, error: Error?)
- func netsocketReceived(socket: NetSocket, bytes: [UInt8])
- func netsocketSent(socket: NetSocket, count: Int)
-}
-
-extension NetSocketDelegate {
- func netsocketConnected(socket: NetSocket) {}
- func netsocketDisconnected(socket: NetSocket, error: Error?) {}
- func netsocketReceived(socket: NetSocket, bytes: [UInt8]) {}
- func netsocketSent(socket: NetSocket, count: Int) {}
-}
-
-enum NetSocketStatus {
- case disconnected
- case connecting
- case connected
-}
-
-final class NetSocket: NSObject, StreamDelegate {
- weak var delegate: NetSocketDelegate? = nil
-
- private var output: OutputStream? = nil
- private var input: InputStream? = nil
-
- private var outputBuffer: [UInt8] = []
- private var inputBuffer: [UInt8] = []
-
- private var readBuffer: [UInt8] = Array(repeating: 0, count: 4 * 1024)
-
- public func peek() -> [UInt8] { self.inputBuffer }
- public var available: Int { self.inputBuffer.count }
-
- private var status: NetSocketStatus = .disconnected
-
- @MainActor public func has(_ length: Int) -> Bool {
- return (self.available >= length)
- }
-
- override init() {}
-
- @MainActor public func connect(host: String, port: Int) {
- self.close()
-
- var outputStream: OutputStream? = nil
- var inputStream: InputStream? = nil
-
- self.status = .connecting
-
- Stream.getStreamsToHost(withName: host, port: port, inputStream: &inputStream, outputStream: &outputStream)
-
- self.input = inputStream
- self.output = outputStream
-
- inputStream?.delegate = self
- outputStream?.delegate = self
-
- inputStream?.schedule(in: .current, forMode: .default)
- outputStream?.schedule(in: .current, forMode: .default)
-
- inputStream?.open()
- outputStream?.open()
- }
-
- @MainActor public func close(_ err: Error? = nil) {
- print("NetSocket: Closed")
-
- let disconnected = (self.status != .disconnected)
-
- self.status = .disconnected
-
- self.input?.delegate = nil
- self.output?.delegate = nil
- self.input?.close()
- self.output?.close()
- self.input?.remove(from: .current, forMode: .default)
- self.output?.remove(from: .current, forMode: .default)
- self.input = nil
- self.output = nil
- self.inputBuffer = []
- self.outputBuffer = []
-
- if disconnected {
- self.delegate?.netsocketDisconnected(socket: self, error: err)
- }
- }
-
- @MainActor public func write(_ data: Data) {
- guard let output = self.output else {
- return
- }
-
- self.outputBuffer.append(contentsOf: data)
-
- if output.hasSpaceAvailable {
- self.writeBufferToStream()
- }
- }
-
- @MainActor public func write(_ data: [UInt8]) {
- guard let output = self.output else {
- return
- }
-
- self.outputBuffer.append(contentsOf: data)
-
- if output.hasSpaceAvailable {
- self.writeBufferToStream()
- }
- }
-
- @MainActor public func read(count: Int) -> [UInt8] {
- guard self.inputBuffer.count > 0, count > 0 else {
- return []
- }
-
- let amountToRead = min(count, self.inputBuffer.count)
- let dataRead: [UInt8] = Array(self.inputBuffer[0..<amountToRead])
- self.inputBuffer.removeFirst(amountToRead)
-
- return dataRead
- }
-
- @MainActor public func read(count: Int) -> Data {
- guard self.inputBuffer.count > 0, count > 0 else {
- return Data()
- }
-
- let amountToRead = min(count, self.inputBuffer.count)
-
- let dataRead: Data = Data(self.inputBuffer[0..<amountToRead])
- self.inputBuffer.removeFirst(amountToRead)
-
- return dataRead
- }
-
- @MainActor public func readAll() -> [UInt8] {
- guard self.inputBuffer.count > 0 else {
- return []
- }
-
- let dataRead: [UInt8] = Array(self.inputBuffer)
- self.inputBuffer = []
-
- return dataRead
- }
-
- @MainActor public func readAll() -> Data {
- guard self.inputBuffer.count > 0 else {
- return Data()
- }
-
- let dataRead: Data = Data(self.inputBuffer)
- self.inputBuffer = []
-
- return dataRead
- }
-
- @MainActor private func writeBufferToStream() {
- guard let output = self.output, self.outputBuffer.count > 0 else {
- return
- }
-
- let bytesWritten = output.write(self.outputBuffer, maxLength: self.outputBuffer.count)
- print("NetSocket => \(bytesWritten) bytes")
- if bytesWritten > 0 {
- self.outputBuffer.removeFirst(bytesWritten)
- self.delegate?.netsocketSent(socket: self, count: bytesWritten)
- }
- else if bytesWritten == -1 {
- self.close(output.streamError)
- }
- }
-
- @MainActor private func readStreamToBuffer() {
- guard let input = self.input else {
- return
- }
-
- let bytesRead = input.read(&self.readBuffer, maxLength: 4 * 1024)
- print("NetSocket <= \(bytesRead) bytes")
- if bytesRead > 0 {
- self.inputBuffer.append(contentsOf: self.readBuffer[0..<bytesRead])
- self.delegate?.netsocketReceived(socket: self, bytes: self.inputBuffer)
- }
- else if bytesRead == -1 {
- self.close(input.streamError)
- }
- }
-
- // MARK: -
-
- @MainActor func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
- guard let input = self.input, let output = self.output else {
- return
- }
-
- switch eventCode {
- case .openCompleted:
- if aStream == input {
- self.setupStreamOptions()
- }
- if input.streamStatus == .open && output.streamStatus == .open {
- if self.status == .connecting {
- print("NetSocket: Connected")
- self.status = .connected
- self.delegate?.netsocketConnected(socket: self)
- }
- }
- break
- case .hasBytesAvailable:
- print("NetSocket: Data available")
- self.readStreamToBuffer()
- case .hasSpaceAvailable:
- print("NetSocket: Space available")
- self.writeBufferToStream()
- case .endEncountered:
- print("NetSocket: End encounterd")
- self.close()
- case .errorOccurred:
- print("NetSocket: Error encounterd", input.streamError.debugDescription, output.streamError.debugDescription)
- let err = input.streamError ?? output.streamError
- self.close(err)
- default:
- break
- }
- }
-
- // MARK: -
-
- private func setupStreamOptions() {
- if let input = self.input {
- let socketData: Data = CFReadStreamCopyProperty(input as CFReadStream, CFStreamPropertyKey.socketNativeHandle) as! Data;
- var socketHandle: CFSocketNativeHandle = 0;
- (socketData as NSData).getBytes(&socketHandle, length: MemoryLayout.size(ofValue: socketHandle));
-
- var value: Int = 0;
- let size = UInt32(MemoryLayout.size(ofValue: value));
-
- value = 1;
- if setsockopt(socketHandle, IPPROTO_TCP, TCP_NODELAY, &value, size) != 0 {
- print("NetSocket: failed to set TCP_NODELAY");
- }
- // Enable keepalive
- value = 1;
- if setsockopt(socketHandle, SOL_SOCKET, SO_KEEPALIVE, &value, size) != 0 {
- print("NetSocket: failed to set SO_KEEPALIVE");
- }
-// // Number of keepalives before close (including first keepalive packet)
-// value = 5
-// if setsockopt(socketHandle, IPPROTO_TCP, TCP_KEEPCNT, &value, size) != 0 {
-// print("NetSocket: failed to set TCP_KEEPCNT");
-// }
-// // Idle time used when SO_KEEPALIVE is enabled. Sets how long connection must be idle before keepalive is sent.
-// value = 60
-// if setsockopt(socketHandle, IPPROTO_TCP, TCP_KEEPALIVE, &value, size) != 0 {
-// print("NetSocket: failed to set TCP_KEEPALIVE")
-// }
- }
- }
-}
diff --git a/Hotline/Shared/TextView.swift b/Hotline/Shared/TextView.swift
deleted file mode 100644
index 47746e2..0000000
--- a/Hotline/Shared/TextView.swift
+++ /dev/null
@@ -1,119 +0,0 @@
-import SwiftUI
-
-struct BetterTextEditor: NSViewRepresentable {
-
- @Environment(\.lineSpacing) private var lineSpacing
-
- @Binding private var text: String
-
- private var customizations: [(NSTextView) -> Void] = []
-
- init(text: Binding<String>) {
- self._text = text
- }
-
- func makeCoordinator() -> Coordinator {
- Coordinator(self)
- }
-
- func makeNSView(context: Context) -> NSScrollView {
- let scrollview = NSTextView.scrollablePlainDocumentContentTextView()
- let textview = scrollview.documentView as! NSTextView
-
- textview.string = self.text
- textview.delegate = context.coordinator
-
-// let p = NSMutableParagraphStyle()
-// p.lineSpacing = self.lineSpacing
-//// textview.defaultParagraphStyle = p
-// textview.typingAttributes = [
-// .paragraphStyle: p
-// ]
-
- textview.isEditable = true
- textview.isRichText = false
- textview.allowsUndo = true
- textview.isFieldEditor = false
- textview.usesAdaptiveColorMappingForDarkAppearance = true
- textview.drawsBackground = false // true
- textview.usesRuler = false
- textview.usesFindBar = false
- textview.isIncrementalSearchingEnabled = false
- textview.isAutomaticQuoteSubstitutionEnabled = false
- textview.isAutomaticDashSubstitutionEnabled = false
- textview.isAutomaticSpellingCorrectionEnabled = true
- textview.isAutomaticDataDetectionEnabled = false
- textview.isAutomaticLinkDetectionEnabled = false
- textview.usesInspectorBar = false
- textview.usesFontPanel = false
- textview.importsGraphics = false
- textview.allowsImageEditing = false
- textview.displaysLinkToolTips = true
- textview.backgroundColor = NSColor.textBackgroundColor
- textview.textContainerInset = NSSize(width: 16, height: 16)
- textview.isContinuousSpellCheckingEnabled = true
- textview.setSelectedRange(NSMakeRange(0, 0))
- self.customizations.forEach { $0(textview) }
-
- scrollview.scrollerStyle = .overlay
-
- return scrollview
- }
-
- func updateNSView(_ nsView: NSScrollView, context: Context) {
- let textview = nsView.documentView as! NSTextView
-
- if textview.string != text {
- textview.string = text
- }
-
- self.customizations.forEach { $0(textview) }
- }
-
- func betterEditorFont(_ font: NSFont) -> Self {
- self.customized { $0.font = font }
- }
-
- func betterEditorParagraphStyle(_ paragraphStyle: NSParagraphStyle) -> Self {
- self.customized { $0.defaultParagraphStyle = paragraphStyle }
- }
-
- func betterEditorAutomaticDashSubstitution(_ enabled: Bool) -> Self {
- self.customized { $0.isAutomaticDashSubstitutionEnabled = enabled }
- }
-
- func betterEditorAutomaticQuoteSubstitution(_ enabled: Bool) -> Self {
- self.customized { $0.isAutomaticQuoteSubstitutionEnabled = enabled }
- }
-
- func betterEditorAutomaticSpellingCorrection(_ enabled: Bool) -> Self {
- self.customized { $0.isAutomaticSpellingCorrectionEnabled = enabled }
- }
-
- func betterEditorTextInset(_ size: NSSize) -> Self {
- self.customized { $0.textContainerInset = size }
- }
-
- class Coordinator: NSObject, NSTextViewDelegate {
- var parent: BetterTextEditor
-
- init(_ parent: BetterTextEditor) {
- self.parent = parent
- }
-
- func textDidChange(_ notification: Notification) {
- guard let textview = notification.object as? NSTextView else {
- return
- }
- self.parent.text = textview.string
- }
- }
-}
-
-private extension BetterTextEditor {
- private func customized(_ customization: @escaping (NSTextView) -> Void) -> Self {
- var copy = self
- copy.customizations.append(customization)
- return copy
- }
-}
diff --git a/Hotline/Shared/URLAdditions.swift b/Hotline/Shared/URLAdditions.swift
deleted file mode 100644
index 1f25541..0000000
--- a/Hotline/Shared/URLAdditions.swift
+++ /dev/null
@@ -1,26 +0,0 @@
-import Foundation
-
-extension URL {
- func generateUniqueFilePath(filename base: String) -> String {
- let fileManager = FileManager.default
- var finalName = base
- var counter = 2
-
- // Helper function to generate a new filename with a counter
- func makeFileName() -> String {
- let baseName = (base as NSString).deletingPathExtension
- let extensionName = (base as NSString).pathExtension
- return extensionName.isEmpty ? "\(baseName) \(counter)" : "\(baseName) \(counter).\(extensionName)"
- }
-
- // Check if file exists and append counter until a unique name is found
- var filePath = self.appending(component: finalName).path(percentEncoded: false)
- while fileManager.fileExists(atPath: filePath) {
- finalName = makeFileName()
- filePath = self.appending(component: finalName).path(percentEncoded: false)
- counter += 1
- }
-
- return filePath
- }
-}
diff --git a/Hotline/Shared/VisualEffectView.swift b/Hotline/Shared/VisualEffectView.swift
deleted file mode 100644
index e595c55..0000000
--- a/Hotline/Shared/VisualEffectView.swift
+++ /dev/null
@@ -1,22 +0,0 @@
-import SwiftUI
-
-struct VisualEffectView: NSViewRepresentable
-{
- let material: NSVisualEffectView.Material
- let blendingMode: NSVisualEffectView.BlendingMode
-
- func makeNSView(context: Context) -> NSVisualEffectView
- {
- let visualEffectView = NSVisualEffectView()
- visualEffectView.material = material
- visualEffectView.blendingMode = blendingMode
- visualEffectView.state = NSVisualEffectView.State.active
- return visualEffectView
- }
-
- func updateNSView(_ visualEffectView: NSVisualEffectView, context: Context)
- {
- visualEffectView.material = material
- visualEffectView.blendingMode = blendingMode
- }
-}