// // ContentLabel.swift // Tusker // // Created by Shadowfacts on 10/1/18. // Copyright © 2018 Shadowfacts. All rights reserved. // import UIKit import SafariServices import TTTAttributedLabel import Pachyderm import SwiftSoup class ContentLabel: LinkLabel { var navigationDelegate: TuskerNavigationDelegate? // MARK: - HTML Parsing func setTextFromHtml(_ html: String) { let doc = try! SwiftSoup.parse(html) let body = doc.body()! let (attributedText, links) = attributedTextForHTMLNode(body) let mutAttrString = NSMutableAttributedString(attributedString: attributedText) // only trailing whitespace can be trimmed here // when posting an attachment without any text, pleromafe includes U+200B ZERO WIDTH SPACE at the beginning // this would get trimmed and cause range out of bounds crashes mutAttrString.trimTrailingCharactersInSet(.whitespacesAndNewlines) mutAttrString.addAttribute(.font, value: font, range: NSRange(location: 0, length: mutAttrString.length)) let linkAttributes: [NSAttributedString.Key: Any] = [ .foregroundColor: UIColor.blue, ] for (range, url) in links { mutAttrString.addAttributes(linkAttributes, range: range) self.links.append(Link(range: range, url: url)) } self.attributedText = mutAttrString } private func attributedTextForHTMLNode(_ node: Node) -> (NSAttributedString, [NSRange: URL]) { switch node { case let node as TextNode: return (NSAttributedString(string: node.text()), [:]) case let node as Element: var links = [NSRange: URL]() let attributed = NSMutableAttributedString() for child in node.getChildNodes() { let (text, childLinks) = attributedTextForHTMLNode(child) for (range, url) in childLinks { let newRange = NSRange(location: range.location + attributed.length, length: range.length) links[newRange] = url } attributed.append(text) } switch node.tagName() { case "br": attributed.append(NSAttributedString(string: "\n")) case "a": if let link = try? node.attr("href"), let url = URL(string: link) { let linkRange = NSRange(location: 0, length: attributed.length) links[linkRange] = url } case "p": attributed.append(NSAttributedString(string: "\n\n")) default: break } return (attributed, links) default: fatalError("Unexpected node type: \(type(of: node))") } } func getViewController(forLink url: URL, inRange range: NSRange) -> UIViewController { let text = (self.text! as NSString).substring(with: range) if let mention = getMention(for: url, text: text) { return ProfileTableViewController(accountID: mention.id) } else if let tag = getHashtag(for: url, text: text) { return TimelineTableViewController(for: .tag(hashtag: tag.name)) } else { return SFSafariViewController(url: url) } } func getViewController(forLinkAt point: CGPoint) -> UIViewController? { guard let link = getLink(atPoint: point) else { return nil } return getViewController(forLink: link.url, inRange: link.range) } // MARK: - Interaction override func linkTapped(_ link: LinkLabel.Link) { let text = (self.text! as NSString).substring(with: link.range) if let mention = getMention(for: link.url, text: text) { navigationDelegate?.selected(mention: mention) } else if let tag = getHashtag(for: link.url, text: text) { navigationDelegate?.selected(tag: tag) } else { navigationDelegate?.selected(url: link.url) } } override func linkLongPressed(_ link: LinkLabel.Link) { navigationDelegate?.showMoreOptions(forURL: link.url) } // MARK: - Navigation func getMention(for url: URL, text: String) -> Mention? { return nil } func getHashtag(for url: URL, text: String) -> Hashtag? { if text.starts(with: "#") { let tag = String(text.dropFirst()) return Hashtag(name: tag, url: url) } else { return nil } } }