Skip to content

Commit

Permalink
Merge pull request #9 from Infomaniak/fix-open-link
Browse files Browse the repository at this point in the history
fix(macOS): Links are opened in the editor itself
  • Loading branch information
PhilippeWeidmann authored Oct 10, 2024
2 parents 24a406a + bfdac2b commit 8ce8a21
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 2 deletions.
32 changes: 32 additions & 0 deletions Examples/Example macOS/Example macOS/UI/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ final class ViewController: NSViewController {
super.viewDidLoad()

editor = RichHTMLEditorView()
editor.delegate = self
if let cssURL = Bundle.main.url(forResource: "style", withExtension: "css"),
let styleCSS = try? String(contentsOf: cssURL) {
editor.injectAdditionalCSS(styleCSS)
Expand Down Expand Up @@ -50,8 +51,39 @@ final class ViewController: NSViewController {
editor.italic()
case .underline:
editor.underline()
case .addLink:
presentLinkAlert()
default:
print("Action not handled.")
}
}

private func presentLinkAlert() {
let alert = NSAlert()
alert.alertStyle = .informational
alert.messageText = "Insert a new link"
let validateButton = alert.addButton(withTitle: "Validate")
alert.addButton(withTitle: "Cancel")

let textField = NSTextField(frame: NSRect(x: 0, y: 0, width: 200, height: 24))
alert.accessoryView = textField

let response = alert.runModal()

guard response.rawValue == validateButton.tag,
!textField.stringValue.isEmpty,
let url = URL(string: textField.stringValue)
else { return }

editor.addLink(url: url, text: url.absoluteString)
}
}

// MARK: - RichHTMLEditorViewDelegate

extension ViewController: RichHTMLEditorViewDelegate {
func richHTMLEditorView(_ richHTMLEditorView: RichHTMLEditorView, shouldHandleLink link: URL) -> Bool {
NSWorkspace.shared.open(link)
return true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ extension NSToolbarItem.Identifier {
static let bold = NSToolbarItem.Identifier(rawValue: "Bold")
static let italic = NSToolbarItem.Identifier(rawValue: "Italic")
static let underline = NSToolbarItem.Identifier(rawValue: "Underline")
static let addLink = NSToolbarItem.Identifier(rawValue: "AddLink")
}

extension Notification.Name {
Expand All @@ -40,7 +41,8 @@ extension WindowController: NSToolbarDelegate {
return [
NSToolbarItem.Identifier.bold,
NSToolbarItem.Identifier.italic,
NSToolbarItem.Identifier.underline
NSToolbarItem.Identifier.underline,
NSToolbarItem.Identifier.addLink
]
}

Expand All @@ -49,7 +51,8 @@ extension WindowController: NSToolbarDelegate {
NSToolbarItem.Identifier.flexibleSpace,
NSToolbarItem.Identifier.bold,
NSToolbarItem.Identifier.italic,
NSToolbarItem.Identifier.underline
NSToolbarItem.Identifier.underline,
NSToolbarItem.Identifier.addLink
]
}

Expand All @@ -65,6 +68,8 @@ extension WindowController: NSToolbarDelegate {
return createToolbarItem(itemIdentifier: itemIdentifier, image: "italic", label: "Italic")
case .underline:
return createToolbarItem(itemIdentifier: itemIdentifier, image: "underline", label: "Underline")
case .addLink:
return createToolbarItem(itemIdentifier: itemIdentifier, image: "link", label: "Link")
default:
return nil
}
Expand Down
22 changes: 22 additions & 0 deletions Sources/InfomaniakRichHTMLEditor/RichHTMLEditorView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ public extension RichHTMLEditorView {
#if canImport(UIKit)
webView.scrollView.delegate = self
#endif
webView.navigationDelegate = self
addSubview(webView)

NSLayoutConstraint.activate([
Expand Down Expand Up @@ -276,6 +277,27 @@ public extension RichHTMLEditorView {
#endif
}

// MARK: - WKNavigationDelegate

extension RichHTMLEditorView: WKNavigationDelegate {
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void) {
switch navigationAction.navigationType {
case .linkActivated:
if let url = navigationAction.request.url, delegate?.richHTMLEditorView(self, shouldHandleLink: url) == true {
decisionHandler(.cancel)
} else {
decisionHandler(.allow)
}
case .backForward, .formSubmitted, .reload, .formResubmitted:
decisionHandler(.cancel)
case .other:
decisionHandler(.allow)
@unknown default:
decisionHandler(.allow)
}
}
}

// MARK: - UIScrollViewDelegate

#if canImport(UIKit)
Expand Down
19 changes: 19 additions & 0 deletions Sources/InfomaniakRichHTMLEditor/RichHTMLEditorViewDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,22 @@ public protocol RichHTMLEditorViewDelegate: AnyObject {
javascriptFunctionDidFail javascriptError: any Error,
whileExecuting function: String
)

/// Asks the delegate if the editor should handle opening the link itself.
///
/// The user can open the link thanks to the contextual menu. If the editor handles this task itself,
/// the link will open in the default browser on iOS but in the editor on macOS.
/// You may need to customize this behaviour. If the method returns `true`, the editor won't
/// open the link and you will be responsible for doing so.
///
/// Implementation of this method is optional. Default return value is `false`.
///
/// - Parameters:
/// - richHTMLEditorView: The editor which is loaded.
/// - shouldHandleLink: The URL the user clicked on.
///
/// - Returns: `false` if the editor should handle the link opening itself.
func richHTMLEditorView(_ richHTMLEditorView: RichHTMLEditorView, shouldHandleLink link: URL) -> Bool
}

// Default implementation for optional functions
Expand All @@ -97,4 +113,7 @@ public extension RichHTMLEditorViewDelegate {
javascriptFunctionDidFail javascriptError: any Error,
whileExecuting function: String
) {}
func richHTMLEditorView(_ richHTMLEditorView: RichHTMLEditorView, shouldHandleLink link: URL) -> Bool {
return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,17 @@ public extension View {
func introspectEditor(perform action: @escaping (RichHTMLEditorView) -> Void) -> some View {
environment(\.introspectEditor, action)
}

/// Tells the editor whether to handle the opening a link or perform an action to open it.
///
/// The default behavior is to let the editor handle opening links.
///
/// - Parameter action: A closure to run when the editor tries to open a link. The closure
/// should return `false` if the editor should handle the opening, `true` if you intend to manage
/// this task yourself.
///
/// - Returns: A view with the customizations applied to editor.
func handleLinkOpening(perform action: @escaping (URL) -> Bool) -> some View {
environment(\.handleLinkOpening, action)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ public struct IntrospectEditorKey: EnvironmentKey {
public static let defaultValue: ((RichHTMLEditorView) -> Void)? = nil
}

public struct HandleLinkOpeningKey: EnvironmentKey {
public static let defaultValue: ((URL) -> Bool)? = nil
}

// MARK: - Environment Values

public extension EnvironmentValues {
Expand Down Expand Up @@ -88,4 +92,9 @@ public extension EnvironmentValues {
get { self[IntrospectEditorKey.self] }
set { self[IntrospectEditorKey.self] = newValue }
}

var handleLinkOpening: ((URL) -> Bool)? {
get { self[HandleLinkOpeningKey.self] }
set { self[HandleLinkOpeningKey.self] = newValue }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public struct RichHTMLEditor: PlateformViewRepresentable {
@Environment(\.onCaretPositionChange) var onCaretPositionChange
@Environment(\.onJavaScriptFunctionFail) var onJavaScriptFunctionFail
@Environment(\.introspectEditor) var introspectEditor
@Environment(\.handleLinkOpening) var handleLinkOpening

@Binding public var html: String
@ObservedObject public var textAttributes: TextAttributes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,8 @@ public final class RichHTMLEditorCoordinator: RichHTMLEditorViewDelegate {
) {
parent.onJavaScriptFunctionFail?(javascriptError, function)
}

public func richHTMLEditorView(_ richHTMLEditorView: RichHTMLEditorView, shouldHandleLink link: URL) -> Bool {
return parent.handleLinkOpening?(link) ?? false
}
}

0 comments on commit 8ce8a21

Please sign in to comment.