diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/TheRichTextEditor.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/TheRichTextEditor.xcscheme
new file mode 100644
index 0000000..3a6c63a
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/TheRichTextEditor.xcscheme
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Package.swift b/Package.swift
index 5b0d0e5..8d1feda 100644
--- a/Package.swift
+++ b/Package.swift
@@ -5,6 +5,9 @@ import PackageDescription
let package = Package(
name: "TheRichTextEditor",
+ platforms: [
+ .iOS(.v11)
+ ],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
@@ -20,7 +23,8 @@ let package = Package(
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "TheRichTextEditor",
- dependencies: []),
+ dependencies: [],
+ resources: [.process("main.js"), .process("main.html")]),
.testTarget(
name: "TheRichTextEditorTests",
dependencies: ["TheRichTextEditor"]),
diff --git a/README.md b/README.md
index 9737bae..6aedefd 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
# TheRichTextEditor
A description of this package.
+
+
diff --git a/Sources/TheRichTextEditor/Assets.xcassets/Contents.json b/Sources/TheRichTextEditor/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Sources/TheRichTextEditor/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Sources/TheRichTextEditor/Assets.xcassets/alignCenter.imageset/Contents.json b/Sources/TheRichTextEditor/Assets.xcassets/alignCenter.imageset/Contents.json
new file mode 100644
index 0000000..74944a6
--- /dev/null
+++ b/Sources/TheRichTextEditor/Assets.xcassets/alignCenter.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "align-center.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Sources/TheRichTextEditor/Assets.xcassets/alignCenter.imageset/align-center.png b/Sources/TheRichTextEditor/Assets.xcassets/alignCenter.imageset/align-center.png
new file mode 100644
index 0000000..682c46c
Binary files /dev/null and b/Sources/TheRichTextEditor/Assets.xcassets/alignCenter.imageset/align-center.png differ
diff --git a/Sources/TheRichTextEditor/Assets.xcassets/alignLeft.imageset/Contents.json b/Sources/TheRichTextEditor/Assets.xcassets/alignLeft.imageset/Contents.json
new file mode 100644
index 0000000..d071c67
--- /dev/null
+++ b/Sources/TheRichTextEditor/Assets.xcassets/alignLeft.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "align-left.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Sources/TheRichTextEditor/Assets.xcassets/alignLeft.imageset/align-left.png b/Sources/TheRichTextEditor/Assets.xcassets/alignLeft.imageset/align-left.png
new file mode 100644
index 0000000..8b418ac
Binary files /dev/null and b/Sources/TheRichTextEditor/Assets.xcassets/alignLeft.imageset/align-left.png differ
diff --git a/Sources/TheRichTextEditor/Assets.xcassets/alignRight.imageset/Contents.json b/Sources/TheRichTextEditor/Assets.xcassets/alignRight.imageset/Contents.json
new file mode 100644
index 0000000..0dd6a7a
--- /dev/null
+++ b/Sources/TheRichTextEditor/Assets.xcassets/alignRight.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "align-right.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Sources/TheRichTextEditor/Assets.xcassets/alignRight.imageset/align-right.png b/Sources/TheRichTextEditor/Assets.xcassets/alignRight.imageset/align-right.png
new file mode 100644
index 0000000..764230c
Binary files /dev/null and b/Sources/TheRichTextEditor/Assets.xcassets/alignRight.imageset/align-right.png differ
diff --git a/Sources/TheRichTextEditor/Assets.xcassets/bold.imageset/Contents.json b/Sources/TheRichTextEditor/Assets.xcassets/bold.imageset/Contents.json
new file mode 100644
index 0000000..0acc732
--- /dev/null
+++ b/Sources/TheRichTextEditor/Assets.xcassets/bold.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "bold.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Sources/TheRichTextEditor/Assets.xcassets/bold.imageset/bold.png b/Sources/TheRichTextEditor/Assets.xcassets/bold.imageset/bold.png
new file mode 100644
index 0000000..5c9617f
Binary files /dev/null and b/Sources/TheRichTextEditor/Assets.xcassets/bold.imageset/bold.png differ
diff --git a/Sources/TheRichTextEditor/Assets.xcassets/clear.imageset/Contents.json b/Sources/TheRichTextEditor/Assets.xcassets/clear.imageset/Contents.json
new file mode 100644
index 0000000..1b5e288
--- /dev/null
+++ b/Sources/TheRichTextEditor/Assets.xcassets/clear.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "paragraph.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Sources/TheRichTextEditor/Assets.xcassets/clear.imageset/paragraph.png b/Sources/TheRichTextEditor/Assets.xcassets/clear.imageset/paragraph.png
new file mode 100644
index 0000000..07a351d
Binary files /dev/null and b/Sources/TheRichTextEditor/Assets.xcassets/clear.imageset/paragraph.png differ
diff --git a/Sources/TheRichTextEditor/Assets.xcassets/indent.imageset/Contents.json b/Sources/TheRichTextEditor/Assets.xcassets/indent.imageset/Contents.json
new file mode 100644
index 0000000..6e0c680
--- /dev/null
+++ b/Sources/TheRichTextEditor/Assets.xcassets/indent.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "indent.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Sources/TheRichTextEditor/Assets.xcassets/indent.imageset/indent.png b/Sources/TheRichTextEditor/Assets.xcassets/indent.imageset/indent.png
new file mode 100644
index 0000000..bb9e210
Binary files /dev/null and b/Sources/TheRichTextEditor/Assets.xcassets/indent.imageset/indent.png differ
diff --git a/Sources/TheRichTextEditor/Assets.xcassets/italic.imageset/Contents.json b/Sources/TheRichTextEditor/Assets.xcassets/italic.imageset/Contents.json
new file mode 100644
index 0000000..bd31029
--- /dev/null
+++ b/Sources/TheRichTextEditor/Assets.xcassets/italic.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "italic.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Sources/TheRichTextEditor/Assets.xcassets/italic.imageset/italic.png b/Sources/TheRichTextEditor/Assets.xcassets/italic.imageset/italic.png
new file mode 100644
index 0000000..670b10d
Binary files /dev/null and b/Sources/TheRichTextEditor/Assets.xcassets/italic.imageset/italic.png differ
diff --git a/Sources/TheRichTextEditor/Assets.xcassets/outdent.imageset/Contents.json b/Sources/TheRichTextEditor/Assets.xcassets/outdent.imageset/Contents.json
new file mode 100644
index 0000000..6c09046
--- /dev/null
+++ b/Sources/TheRichTextEditor/Assets.xcassets/outdent.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "right-indent.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Sources/TheRichTextEditor/Assets.xcassets/outdent.imageset/right-indent.png b/Sources/TheRichTextEditor/Assets.xcassets/outdent.imageset/right-indent.png
new file mode 100644
index 0000000..16b3028
Binary files /dev/null and b/Sources/TheRichTextEditor/Assets.xcassets/outdent.imageset/right-indent.png differ
diff --git a/Sources/TheRichTextEditor/Assets.xcassets/redo.imageset/Contents.json b/Sources/TheRichTextEditor/Assets.xcassets/redo.imageset/Contents.json
new file mode 100644
index 0000000..0cd92b1
--- /dev/null
+++ b/Sources/TheRichTextEditor/Assets.xcassets/redo.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "redo.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Sources/TheRichTextEditor/Assets.xcassets/redo.imageset/redo.png b/Sources/TheRichTextEditor/Assets.xcassets/redo.imageset/redo.png
new file mode 100644
index 0000000..3e982da
Binary files /dev/null and b/Sources/TheRichTextEditor/Assets.xcassets/redo.imageset/redo.png differ
diff --git a/Sources/TheRichTextEditor/Assets.xcassets/undo.imageset/Contents.json b/Sources/TheRichTextEditor/Assets.xcassets/undo.imageset/Contents.json
new file mode 100644
index 0000000..ad7cac4
--- /dev/null
+++ b/Sources/TheRichTextEditor/Assets.xcassets/undo.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "undo.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Sources/TheRichTextEditor/Assets.xcassets/undo.imageset/undo.png b/Sources/TheRichTextEditor/Assets.xcassets/undo.imageset/undo.png
new file mode 100644
index 0000000..462a986
Binary files /dev/null and b/Sources/TheRichTextEditor/Assets.xcassets/undo.imageset/undo.png differ
diff --git a/Sources/TheRichTextEditor/TheRichTextEditor.swift b/Sources/TheRichTextEditor/TheRichTextEditor.swift
index 0ebcc00..b7017b7 100644
--- a/Sources/TheRichTextEditor/TheRichTextEditor.swift
+++ b/Sources/TheRichTextEditor/TheRichTextEditor.swift
@@ -1,3 +1,351 @@
-struct TheRichTextEditor {
- var text = "Hello, World!"
+//
+// TheRichTextEditor.swift
+// iOS-Email-Client
+//
+// Created by Pedro Iniguez on 12/17/20.
+// Copyright © 2020 Criptext Inc. All rights reserved.
+//
+
+import Foundation
+import UIKit
+import WebKit
+
+public protocol TheRichTextEditorDelegate: class {
+ func textDidChange(content: String)
+ func heightDidChange()
+ func editorDidLoad()
+ func scrollOffset(verticalOffset: CGFloat)
+}
+
+fileprivate class WeakScriptMessageHandler: NSObject, WKScriptMessageHandler {
+ weak var delegate: WKScriptMessageHandler?
+
+ init(delegate: WKScriptMessageHandler) {
+ self.delegate = delegate
+ }
+
+ public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
+ self.delegate?.userContentController(userContentController, didReceive: message)
+ }
+}
+
+public class TheRichTextEditor: UIView, WKScriptMessageHandler, WKNavigationDelegate, UIScrollViewDelegate {
+ private static let textDidChange = "textDidChange"
+ private static let heightDidChange = "heightDidChange"
+ private static let previewDidChange = "previewDidChange"
+ private static let documentHasLoaded = "documentHasLoaded"
+
+ private static let defaultHeight: CGFloat = 60
+
+ public weak var delegate: TheRichTextEditorDelegate?
+ public var height: CGFloat = TheRichTextEditor.defaultHeight
+
+ public var placeholder: String? {
+ didSet {
+ webView.evaluateJavaScript("richeditor.setPlaceholderText('\(placeholder ?? "")')")
+ }
+ }
+
+ private var textToLoad: String?
+
+ public var html: String = "" {
+ didSet {
+ if webView.isLoading {
+ textToLoad = html
+ } else {
+ webView.evaluateJavaScript("richeditor.insertText(\"\(html.htmlEscapeQuotes)\");")
+ body = html
+ }
+ }
+ }
+
+ public var preview: String = ""
+ public var body: String = ""
+
+ var webView: WKWebView!
+
+ public override init(frame: CGRect = .zero) {
+ super.init(frame: frame)
+ setup()
+ }
+
+ public required init?(coder aDecoder: NSCoder) {
+ super.init(coder: aDecoder)
+ setup()
+ }
+
+ var enableAccessoryView: Bool {
+ set {
+ (webView as? CustomWebview)?.enableAccessoryView = newValue
+ }
+
+ get {
+ return (webView as? CustomWebview)?.enableAccessoryView ?? false
+ }
+ }
+
+ func setup() {
+ guard let scriptPath = Bundle.main.path(forResource: "main", ofType: "js"),
+ let scriptContent = try? String(contentsOfFile: scriptPath, encoding: String.Encoding.utf8),
+ let htmlPath = Bundle.main.path(forResource: "main", ofType: "html"),
+ let html = try? String(contentsOfFile: htmlPath, encoding: String.Encoding.utf8)
+ else {
+ fatalError("Unable to find javscript/html for text editor")
+ }
+
+ let configuration = WKWebViewConfiguration()
+ configuration.userContentController.addUserScript(
+ WKUserScript(source: scriptContent,
+ injectionTime: .atDocumentEnd,
+ forMainFrameOnly: true
+ )
+ )
+
+ webView = CustomWebview(frame: .zero, configuration: configuration)
+ (webView as? CustomWebview)?.toolbarDelegate = self
+
+ [TheRichTextEditor.textDidChange, TheRichTextEditor.heightDidChange, TheRichTextEditor.previewDidChange, TheRichTextEditor.documentHasLoaded].forEach {
+ configuration.userContentController.add(WeakScriptMessageHandler(delegate: self), name: $0)
+ }
+
+ webView.keyboardDisplayRequiresUserAction = false
+ webView.navigationDelegate = self
+ webView.isOpaque = false
+ webView.backgroundColor = .clear
+ webView.scrollView.maximumZoomScale = 1
+ webView.scrollView.minimumZoomScale = 1
+ webView.scrollView.showsHorizontalScrollIndicator = false
+ webView.scrollView.showsVerticalScrollIndicator = false
+ webView.scrollView.bounces = false
+ webView.scrollView.isScrollEnabled = false
+ webView.scrollView.delegate = self
+
+ addSubview(webView)
+ webView.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ webView.leadingAnchor.constraint(equalTo: leadingAnchor),
+ webView.topAnchor.constraint(equalTo: topAnchor, constant: 10),
+ webView.trailingAnchor.constraint(equalTo: trailingAnchor),
+ webView.bottomAnchor.constraint(equalTo: bottomAnchor)
+ ])
+
+ webView.loadHTMLString(html, baseURL: Bundle.main.bundleURL)
+ }
+
+ public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
+ switch message.name {
+ case TheRichTextEditor.textDidChange:
+ guard let body = message.body as? String else { return }
+ self.body = body
+ delegate?.textDidChange(content: body)
+ case TheRichTextEditor.heightDidChange:
+ guard let height = message.body as? CGFloat else { return }
+ if (height + 20 != self.height) {
+ print(self.height)
+ self.height = height + 20
+ delegate?.heightDidChange()
+ }
+ case TheRichTextEditor.previewDidChange:
+ guard let preview = message.body as? String else { return }
+ self.preview = preview
+ case TheRichTextEditor.documentHasLoaded:
+ delegate?.editorDidLoad()
+ default:
+ break
+ }
+ }
+
+ public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
+ if let textToLoad = textToLoad {
+ self.textToLoad = nil
+ html = textToLoad
+ }
+ }
+
+ public func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
+ scrollView.pinchGestureRecognizer?.isEnabled = false
+ }
+
+ public func scrollViewDidScroll(_ scrollView: UIScrollView) {
+ if (scrollView.contentOffset.y != 0) {
+ delegate?.scrollOffset(verticalOffset: scrollView.contentOffset.y)
+ }
+ scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: false)
+ }
+
+ public func viewForZooming(in: UIScrollView) -> UIView? {
+ return nil
+ }
+
+ public override func endEditing(_ force: Bool) -> Bool {
+ webView.endEditing(force)
+ return super.endEditing(force)
+ }
+
+ public func setEditorFontColor(_ color: UIColor) {
+ webView.evaluateJavaScript("richeditor.setBaseTextColor('#\(color.toHexString())');", completionHandler: nil)
+ }
+
+ public func setEditorBackgroundColor(_ color: UIColor) {
+ webView.evaluateJavaScript("richeditor.setBackgroundColor('#\(color.toHexString())');", completionHandler: nil)
+ }
+
+ public func focus() {
+ webView.evaluateJavaScript("richeditor.focus();", completionHandler: nil)
+ }
+
+ public func focus(at: CGPoint) {
+ webView.evaluateJavaScript("richeditor.focusAtPoint(\(at.x), \(at.y));", completionHandler: nil)
+ }
+
+ public func runCommand(_ command: String) {
+ webView.evaluateJavaScript("document.execCommand('\(command)', false, null);", completionHandler: nil)
+ }
+}
+
+extension TheRichTextEditor: WebviewToolbarDelegate {
+ func onUndoPress() {
+ self.runCommand("undo")
+ }
+
+ func onRedoPress() {
+ self.runCommand("redo")
+ }
+
+ func onTextAlignCenter() {
+ self.runCommand("justifyCenter")
+ }
+
+ func onIndentPress() {
+ self.runCommand("indent")
+ }
+
+ func onOutdentPress() {
+ self.runCommand("outdent")
+ }
+
+ func onClearPress() {
+ self.runCommand("removeFormat")
+ }
+
+ func onItalicPress() {
+ self.runCommand("italic")
+ }
+
+ func onTextAlignLeft() {
+ self.runCommand("justifyLeft")
+ }
+
+ func onTextAlignRight() {
+ self.runCommand("justifyRight")
+ }
+
+ func onBoldPress() {
+ self.runCommand("bold")
+ }
+}
+
+fileprivate extension String {
+
+ var htmlToPlainText: String {
+ return [
+ ("(<[^>]*>)|(&\\w+;)", " "),
+ ("[ ]+", " ")
+ ].reduce(self) {
+ try! $0.replacing(pattern: $1.0, with: $1.1)
+ }.resolvedHTMLEntities
+ }
+
+ var resolvedHTMLEntities: String {
+ return self
+ .replacingOccurrences(of: "'", with: "'")
+ .replacingOccurrences(of: "'", with: "'")
+ .replacingOccurrences(of: "&", with: "&")
+ .replacingOccurrences(of: " ", with: " ")
+ }
+
+ func replacing(pattern: String, with template: String) throws -> String {
+ let regex = try NSRegularExpression(pattern: pattern, options: .caseInsensitive)
+ return regex.stringByReplacingMatches(in: self, options: [], range: NSRange(0.. Void
+typealias NewClosureType = @convention(c) (Any, Selector, UnsafeRawPointer, Bool, Bool, Bool, Any?) -> Void
+
+extension WKWebView{
+ var keyboardDisplayRequiresUserAction: Bool? {
+ get {
+ return self.keyboardDisplayRequiresUserAction
+ }
+ set {
+ self.setKeyboardRequiresUserInteraction(newValue ?? true)
+ }
+ }
+
+ func setKeyboardRequiresUserInteraction( _ value: Bool) {
+ guard let WKContentView: AnyClass = NSClassFromString("WKContentView") else {
+ print("keyboardDisplayRequiresUserAction extension: Cannot find the WKContentView class")
+ return
+ }
+ // For iOS 10, *
+ let sel_10: Selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:")
+ // For iOS 11.3, *
+ let sel_11_3: Selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:")
+ // For iOS 12.2, *
+ let sel_12_2: Selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:changingActivityState:userObject:")
+ // For iOS 13.0, *
+ let sel_13_0: Selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:")
+
+ if let method = class_getInstanceMethod(WKContentView, sel_10) {
+ let originalImp: IMP = method_getImplementation(method)
+ let original: OldClosureType = unsafeBitCast(originalImp, to: OldClosureType.self)
+ let block : @convention(block) (Any, UnsafeRawPointer, Bool, Bool, Any?) -> Void = { (me, arg0, arg1, arg2, arg3) in
+ original(me, sel_10, arg0, !value, arg2, arg3)
+ }
+ let imp: IMP = imp_implementationWithBlock(block)
+ method_setImplementation(method, imp)
+ }
+
+ if let method = class_getInstanceMethod(WKContentView, sel_11_3) {
+ let originalImp: IMP = method_getImplementation(method)
+ let original: NewClosureType = unsafeBitCast(originalImp, to: NewClosureType.self)
+ let block : @convention(block) (Any, UnsafeRawPointer, Bool, Bool, Bool, Any?) -> Void = { (me, arg0, arg1, arg2, arg3, arg4) in
+ original(me, sel_11_3, arg0, !value, arg2, arg3, arg4)
+ }
+ let imp: IMP = imp_implementationWithBlock(block)
+ method_setImplementation(method, imp)
+ }
+
+ if let method = class_getInstanceMethod(WKContentView, sel_12_2) {
+ let originalImp: IMP = method_getImplementation(method)
+ let original: NewClosureType = unsafeBitCast(originalImp, to: NewClosureType.self)
+ let block : @convention(block) (Any, UnsafeRawPointer, Bool, Bool, Bool, Any?) -> Void = { (me, arg0, arg1, arg2, arg3, arg4) in
+ original(me, sel_12_2, arg0, !value, arg2, arg3, arg4)
+ }
+ let imp: IMP = imp_implementationWithBlock(block)
+ method_setImplementation(method, imp)
+ }
+
+ if let method = class_getInstanceMethod(WKContentView, sel_13_0) {
+ let originalImp: IMP = method_getImplementation(method)
+ let original: NewClosureType = unsafeBitCast(originalImp, to: NewClosureType.self)
+ let block : @convention(block) (Any, UnsafeRawPointer, Bool, Bool, Bool, Any?) -> Void = { (me, arg0, arg1, arg2, arg3, arg4) in
+ original(me, sel_13_0, arg0, !value, arg2, arg3, arg4)
+ }
+ let imp: IMP = imp_implementationWithBlock(block)
+ method_setImplementation(method, imp)
+ }
+ }
}
diff --git a/Sources/TheRichTextEditor/Utils/Extensions.swift b/Sources/TheRichTextEditor/Utils/Extensions.swift
new file mode 100644
index 0000000..6072b78
--- /dev/null
+++ b/Sources/TheRichTextEditor/Utils/Extensions.swift
@@ -0,0 +1,21 @@
+//
+// File.swift
+//
+//
+// Created by Pedro Iniguez on 12/29/20.
+//
+
+import Foundation
+import UIKit
+
+extension UIColor {
+ func toHexString() -> String {
+ var r:CGFloat = 0
+ var g:CGFloat = 0
+ var b:CGFloat = 0
+ var a:CGFloat = 0
+ getRed(&r, green: &g, blue: &b, alpha: &a)
+ let rgb:Int = (Int)(r*255)<<16 | (Int)(g*255)<<8 | (Int)(b*255)<<0
+ return String(format:"%06x", rgb)
+ }
+}
diff --git a/Sources/TheRichTextEditor/Views/AccessoryUICollectionViewCell.swift b/Sources/TheRichTextEditor/Views/AccessoryUICollectionViewCell.swift
new file mode 100644
index 0000000..8552274
--- /dev/null
+++ b/Sources/TheRichTextEditor/Views/AccessoryUICollectionViewCell.swift
@@ -0,0 +1,20 @@
+//
+// AccessoryUICollectionViewCell.swift
+// iOS-Email-Client
+//
+// Created by Pedro Iniguez on 12/28/20.
+// Copyright © 2020 Criptext Inc. All rights reserved.
+//
+
+import Foundation
+import UIKit
+
+class AccessoryUICollectionViewCell: UICollectionViewCell {
+ @IBOutlet weak var iconImageView: UIImageView!
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+
+ backgroundColor = .clear
+ }
+}
diff --git a/Sources/TheRichTextEditor/Views/AccessoryUICollectionViewCell.xib b/Sources/TheRichTextEditor/Views/AccessoryUICollectionViewCell.xib
new file mode 100644
index 0000000..6890ec9
--- /dev/null
+++ b/Sources/TheRichTextEditor/Views/AccessoryUICollectionViewCell.xib
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Sources/TheRichTextEditor/Views/CustomWebview.swift b/Sources/TheRichTextEditor/Views/CustomWebview.swift
new file mode 100644
index 0000000..4c1101c
--- /dev/null
+++ b/Sources/TheRichTextEditor/Views/CustomWebview.swift
@@ -0,0 +1,169 @@
+//
+// CustomWebview.swift
+// iOS-Email-Client
+//
+// Created by Pedro Iniguez on 12/28/20.
+// Copyright © 2020 Criptext Inc. All rights reserved.
+//
+
+import Foundation
+import UIKit
+import WebKit
+
+protocol WebviewToolbarDelegate: class {
+ func onBoldPress()
+ func onItalicPress()
+ func onTextAlignLeft()
+ func onTextAlignRight()
+ func onTextAlignCenter()
+ func onIndentPress()
+ func onOutdentPress()
+ func onClearPress()
+ func onUndoPress()
+ func onRedoPress()
+}
+
+class CustomWebview: WKWebView {
+
+ enum Modifier {
+ case bold
+ case italic
+ case textAlignLeft
+ case textAlignCenter
+ case textAlignRight
+ case indent
+ case outdent
+ case clear
+ case undo
+ case redo
+
+ var desc: String {
+ switch(self) {
+ case .bold:
+ return "Bold"
+ case .italic:
+ return "Italic"
+ case .textAlignLeft:
+ return "Left"
+ case .textAlignCenter:
+ return "Center"
+ case .textAlignRight:
+ return "Right"
+ case .indent:
+ return "Indent"
+ case .outdent:
+ return "Outdent"
+ case .clear:
+ return "Clear"
+ case .undo:
+ return "Undo"
+ case .redo:
+ return "Redo"
+ }
+ }
+
+ var image: UIImage {
+ switch(self) {
+ case .bold:
+ return UIImage(named: "bold")!
+ case .italic:
+ return UIImage(named: "italic")!
+ case .textAlignLeft:
+ return UIImage(named: "alignLeft")!
+ case .textAlignCenter:
+ return UIImage(named: "alignCenter")!
+ case .textAlignRight:
+ return UIImage(named: "alignRight")!
+ case .indent:
+ return UIImage(named: "indent")!
+ case .outdent:
+ return UIImage(named: "outdent")!
+ case .clear:
+ return UIImage(named: "clear")!
+ case .undo:
+ return UIImage(named: "undo")!
+ case .redo:
+ return UIImage(named: "redo")!
+ }
+ }
+ }
+ weak var toolbarDelegate: WebviewToolbarDelegate? = nil
+ var enableAccessoryView = true
+ var accessoryView: UIView? = nil
+ var modifiers: [Modifier] = [.bold, .italic, .textAlignRight, .textAlignCenter, .textAlignLeft, .indent, .outdent, .clear, .undo, .redo]
+
+ override var inputAccessoryView: UIView? {
+ get {
+ if enableAccessoryView,
+ accessoryView == nil {
+ let layout = UICollectionViewFlowLayout()
+ layout.scrollDirection = .horizontal
+
+ let collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: 100, height: 50), collectionViewLayout: layout)
+ //collectionView.backgroundColor = theme.background
+ collectionView.dataSource = self
+ collectionView.delegate = self
+ collectionView.isScrollEnabled = true
+ collectionView.bounces = false
+ collectionView.showsHorizontalScrollIndicator = false
+
+ let accessoryNib = UINib(nibName: "AccessoryUICollectionViewCell", bundle: nil)
+ collectionView.register(accessoryNib, forCellWithReuseIdentifier: "accessoryCell")
+
+ accessoryView = collectionView
+ }
+ return accessoryView
+ }
+ set {
+ accessoryView = newValue
+ }
+ }
+}
+
+extension CustomWebview: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
+ func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+ return modifiers.count
+ }
+
+ func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+ let modifier = modifiers[indexPath.item]
+
+ let collectionCell = collectionView.dequeueReusableCell(withReuseIdentifier: "accessoryCell", for: indexPath) as! AccessoryUICollectionViewCell
+ collectionCell.iconImageView.image = modifier.image
+ return collectionCell
+ }
+
+ func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
+ return CGSize(width: 40, height: 50)
+ }
+
+ func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
+ return UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
+ }
+
+ func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+ let modifier = modifiers[indexPath.item]
+ switch modifier {
+ case .bold:
+ self.toolbarDelegate?.onBoldPress()
+ case .italic:
+ self.toolbarDelegate?.onItalicPress()
+ case .textAlignLeft:
+ self.toolbarDelegate?.onTextAlignLeft()
+ case .textAlignRight:
+ self.toolbarDelegate?.onTextAlignRight()
+ case .textAlignCenter:
+ self.toolbarDelegate?.onTextAlignCenter()
+ case .indent:
+ self.toolbarDelegate?.onIndentPress()
+ case .outdent:
+ self.toolbarDelegate?.onOutdentPress()
+ case .clear:
+ self.toolbarDelegate?.onClearPress()
+ case .undo:
+ self.toolbarDelegate?.onUndoPress()
+ case .redo:
+ self.toolbarDelegate?.onRedoPress()
+ }
+ }
+}
diff --git a/Sources/TheRichTextEditor/Web Resources/main.html b/Sources/TheRichTextEditor/Web Resources/main.html
new file mode 100644
index 0000000..48e4c99
--- /dev/null
+++ b/Sources/TheRichTextEditor/Web Resources/main.html
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Sources/TheRichTextEditor/Web Resources/main.js b/Sources/TheRichTextEditor/Web Resources/main.js
new file mode 100644
index 0000000..81c9393
--- /dev/null
+++ b/Sources/TheRichTextEditor/Web Resources/main.js
@@ -0,0 +1,81 @@
+var richeditor = {};
+var editor = document.getElementById("editor");
+
+window.onload = function() {
+ window.webkit.messageHandlers.documentHasLoaded.postMessage("ready");
+};
+
+richeditor.updatePlaceholder = function() {
+ if (editor.innerHTML.indexOf('img') !== -1 || (editor.textContent.length > 0 && editor.innerHTML.length > 0)) {
+ editor.classList.remove("placeholder");
+ } else {
+ editor.classList.add("placeholder");
+ }
+}
+
+richeditor.insertText = function(text) {
+ editor.innerHTML = text;
+ richeditor.updatePlaceholder();
+ window.webkit.messageHandlers.heightDidChange.postMessage(document.body.offsetHeight);
+}
+
+richeditor.setBaseTextColor = function(color) {
+ editor.style.color = color;
+}
+
+richeditor.setBaseTextColor = function(color) {
+ editor.style.color = color;
+}
+
+richeditor.setBackgroundColor = function(color) {
+ editor.style.backgroundColor = color;
+}
+
+richeditor.focus = function() {
+ var range = document.createRange();
+ range.selectNodeContents(editor);
+ range.collapse(false);
+ var selection = window.getSelection();
+ selection.removeAllRanges();
+ selection.addRange(range);
+ editor.focus();
+}
+
+richeditor.focusAtPoint = function(x, y) {
+ var range = document.caretRangeFromPoint(x, y) || document.createRange();
+ var selection = window.getSelection();
+ selection.removeAllRanges();
+ selection.addRange(range);
+ editor.focus();
+};
+
+richeditor.setPlaceholderText = function(text) {
+ editor.setAttribute("placeholder", text);
+};
+
+editor.addEventListener("input", function() {
+ window.webkit.messageHandlers.textDidChange.postMessage(editor.innerHTML);
+ window.webkit.messageHandlers.previewDidChange.postMessage(editor.innerText);
+ richeditor.updatePlaceholder();
+}, false)
+
+document.addEventListener("selectionchange", function() {
+ window.webkit.messageHandlers.heightDidChange.postMessage(editor.clientHeight);
+}, false);
+
+document.getElementById("not-editor").addEventListener("click", () => {
+ if (editor == document.activeElement) {
+ editor.blur();
+ } else {
+ editor.focus();
+ document.execCommand('selectAll', false, null);
+ document.getSelection().collapseToEnd();
+ }
+})
+
+document.addEventListener('paste', e => {
+ var items = (event.clipboardData || event.originalEvent.clipboardData).items;
+ if (items[0] && items[0].kind === 'file') {
+ e.preventDefault();
+ }
+});
diff --git a/Tests/TheRichTextEditorTests/TheRichTextEditorTests.swift b/Tests/TheRichTextEditorTests/TheRichTextEditorTests.swift
index 7071781..cba4bb8 100644
--- a/Tests/TheRichTextEditorTests/TheRichTextEditorTests.swift
+++ b/Tests/TheRichTextEditorTests/TheRichTextEditorTests.swift
@@ -6,7 +6,7 @@ final class TheRichTextEditorTests: XCTestCase {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
- XCTAssertEqual(TheRichTextEditor().text, "Hello, World!")
+ //XCTAssertEqual(TheRichTextEditor(), "Hello, World!")
}
static var allTests = [