Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Comment Detail: Render content in web view #17129

Merged
merged 29 commits into from
Sep 9, 2021
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ec1825c
Update webview constraints
dvdchr Sep 6, 2021
f00ef80
Set dynamic font size on header labels
dvdchr Sep 6, 2021
bad5534
Configure webView and load contents with richEmbedTemplate
dvdchr Sep 7, 2021
66104b5
Calculate height and call completion block for caller to refresh height
dvdchr Sep 7, 2021
d1ec51c
Fix avatar URL using wrong property
dvdchr Sep 7, 2021
1f31e07
minor: callback docs
dvdchr Sep 7, 2021
512d266
Cache the HTML template string
dvdchr Sep 7, 2021
2612090
Add html template for rich comment content
dvdchr Sep 7, 2021
83be37e
Remove empty HTML tags from comment content
dvdchr Sep 7, 2021
c123b05
Recalculate cell height on orientation change.
dvdchr Sep 7, 2021
6fcbc09
Fix white flash for web view in dark mode
dvdchr Sep 7, 2021
b5f0f22
Allow overflowing table to scroll horizontally
dvdchr Sep 7, 2021
08a4db6
Remove print statement
dvdchr Sep 7, 2021
7416de7
Improve styling for image and code snippets
dvdchr Sep 7, 2021
a14b931
Override hardcoded block styles
dvdchr Sep 7, 2021
96362a4
Extract styles to a standalone CSS file.
dvdchr Sep 7, 2021
e6bf25a
Fix resources location
dvdchr Sep 7, 2021
5b31501
Patch blocks gallery grid appearance
dvdchr Sep 7, 2021
b08fb47
Reject all web view navigation requests for now.
dvdchr Sep 7, 2021
920d094
Fix navigation policy to allow loading local files
dvdchr Sep 7, 2021
cb74ee7
Implement a smarter cache for the HTML string processing.
dvdchr Sep 8, 2021
550a4d2
Use viewWillTransition to react to orientation changes.
dvdchr Sep 8, 2021
ee2d6b1
Disable shrinkToFit behavior on iPad
dvdchr Sep 8, 2021
1337847
Improve styling for several components.
dvdchr Sep 8, 2021
a0dab47
Set the webview back to opaque after content has finished loading.
dvdchr Sep 8, 2021
29e1ba8
Increase background color visibility on mentions in dark mode
dvdchr Sep 8, 2021
2c0ae96
Improve regex pattern to remove empty elements
dvdchr Sep 8, 2021
c37a44e
Improve component margins
dvdchr Sep 8, 2021
a7c7fe8
Mark onContentLoaded property as private.
dvdchr Sep 8, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ class CommentContentTableViewCell: UITableViewCell, NibReusable {

var accessoryButtonAction: (() -> Void)? = nil

/// Called when the cell has finished loading and calculating the height of the HTML content. Passes the new content height as parameter.
var onContentLoaded: ((CGFloat) -> Void)? = nil

var replyButtonAction: (() -> Void)? = nil

var likeButtonAction: (() -> Void)? = nil
Expand All @@ -32,36 +35,123 @@ class CommentContentTableViewCell: UITableViewCell, NibReusable {
@IBOutlet private weak var accessoryButton: UIButton!

@IBOutlet private weak var webView: WKWebView!
@IBOutlet private weak var webViewHeightConstraint: NSLayoutConstraint!

@IBOutlet private weak var reactionBarView: UIView!
@IBOutlet private weak var replyButton: UIButton!
@IBOutlet private weak var likeButton: UIButton!

/// Cache the HTML template format. We only need read the template once.
private static let htmlTemplateFormat: String? = {
guard let templatePath = Bundle.main.path(forResource: "richCommentTemplate", ofType: "html"),
let templateString = try? String(contentsOfFile: templatePath) else {
return nil
}

return templateString
}()

/// Used for the web view's `baseURL`, to reference any local files (i.e. CSS) linked from the HTML.
private static let resourceURL: URL? = {
Bundle.main.resourceURL
}()

/// Caches the HTML content, to be reused when the orientation changed.
private var htmlContentCache: String? = nil

// MARK: Lifecycle

override func awakeFromNib() {
super.awakeFromNib()
configureViews()
}

override func prepareForReuse() {
onContentLoaded = nil
htmlContentCache = nil
}

// MARK: Public Methods

func configure(with comment: Comment) {
/// Configures the cell with a `Comment` object.
///
/// - Parameters:
/// - comment: The `Comment` object to display.
/// - onContentLoaded: Callback to be called once the content has been loaded. Provides the new content height as parameter.
func configure(with comment: Comment, onContentLoaded: ((CGFloat) -> Void)?) {
nameLabel?.setText(comment.authorForDisplay())
dateLabel?.setText(comment.dateForDisplay()?.toMediumString() ?? String())

if let authorURL = comment.authorURL() {
configureImage(with: authorURL)
if let avatarURL = URL(string: comment.authorAvatarURL) {
configureImage(with: avatarURL)
} else {
configureImageWithGravatarEmail(comment.gravatarEmailForDisplay())
}

updateLikeButton(liked: comment.isLiked, numberOfLikes: comment.numberOfLikes())

// TODO: Configure comment content
// configure comment content
guard let templateFormat = Self.htmlTemplateFormat else {
return
}

self.onContentLoaded = onContentLoaded

// remove empty HTML elements from the `content`, as the content often contains empty paragraph elements which adds unnecessary padding/margin.
// `rawContent` does not have this problem, but it's not used because `rawContent` gets rid of links (<a> tags) for mentions.
let sanitizedContent = comment.content
.trimmingCharacters(in: .whitespacesAndNewlines)
.replacingOccurrences(of: String.emptyElementRegexPattern, with: String(), options: [.regularExpression])
let htmlString = String(format: templateFormat, sanitizedContent)

self.htmlContentCache = htmlString
webView.loadHTMLString(htmlString, baseURL: Self.resourceURL)

// TODO: Configure component visibility
}

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
// when screen orientation changed, reload the HTML content to ensure that the content height accounts for the new width.
dvdchr marked this conversation as resolved.
Show resolved Hide resolved
guard let cachedContent = htmlContentCache,
traitCollection.verticalSizeClass != previousTraitCollection?.verticalSizeClass
|| traitCollection.horizontalSizeClass != previousTraitCollection?.horizontalSizeClass else {
return
}

webViewHeightConstraint.constant = 1
dvdchr marked this conversation as resolved.
Show resolved Hide resolved
webView.loadHTMLString(cachedContent, baseURL: nil)
dvdchr marked this conversation as resolved.
Show resolved Hide resolved
}
}

// MARK: - WKNavigationDelegate

extension CommentContentTableViewCell: WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// Wait until the HTML document finished loading.
// This also waits for all of resources within the HTML (images, video thumbnail images) to be fully loaded.
webView.evaluateJavaScript("document.readyState") { complete, _ in
guard complete != nil else {
return
}

// To capture the content height, the methods to use is either `document.body.scrollHeight` or `document.documentElement.scrollHeight`.
// `document.body` does not capture margins on <body> tag, so we'll use `document.documentElement` instead.
webView.evaluateJavaScript("document.documentElement.scrollHeight") { height, _ in
guard let height = height as? CGFloat else {
return
}
// update the web view height obtained from the evaluated Javascript.
self.webViewHeightConstraint.constant = height
self.onContentLoaded?(height)
}
}
}

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
// TODO: Offload the decision making to the delegate.
// For now, all navigation requests will be rejected.
decisionHandler(.cancel)
}
}

// MARK: - Helpers
Expand Down Expand Up @@ -92,6 +182,13 @@ private extension CommentContentTableViewCell {
accessoryButton?.setImage(accessoryButtonImage, for: .normal)
accessoryButton?.addTarget(self, action: #selector(accessoryButtonTapped), for: .touchUpInside)

webView.navigationDelegate = self
webView.scrollView.bounces = false
webView.scrollView.contentInset = .zero
webView.scrollView.showsVerticalScrollIndicator = false
webView.backgroundColor = .clear
webView.isOpaque = false // gets rid of the white flash upon content load in dark mode.

replyButton?.tintColor = Style.buttonTintColor
replyButton?.titleLabel?.font = Style.reactionButtonFont
replyButton?.setTitle(.reply, for: .normal)
Expand Down Expand Up @@ -168,4 +265,7 @@ private extension String {
+ "%1$d is a placeholder for the number of Likes.")
static let pluralLikesFormat = NSLocalizedString("%1$d Likes", comment: "Plural button title to Like a comment. "
+ "%1$d is a placeholder for the number of Likes.")

// pattern that detects empty HTML elements (including HTML comments within).
static let emptyElementRegexPattern = "<[a-z]+>(<!-- [a-z\\/:]+ -->)+<\\/[a-z]+>"
dvdchr marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="247" id="KGk-i7-Jjw" customClass="CommentContentTableViewCell" customModule="WordPress" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="247"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="112" id="KGk-i7-Jjw" customClass="CommentContentTableViewCell" customModule="WordPress" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="112"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="247"/>
<rect key="frame" x="0.0" y="0.0" width="320" height="112"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="f2E-yC-BJS" userLabel="Header View">
<rect key="frame" x="16" y="0.0" width="288" height="73"/>
<view contentMode="scaleToFill" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" translatesAutoresizingMaskIntoConstraints="NO" id="f2E-yC-BJS" userLabel="Header View">
<rect key="frame" x="16" y="0.0" width="288" height="71"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="gravatar" translatesAutoresizingMaskIntoConstraints="NO" id="9QY-3I-cxv" userLabel="Avatar Image View" customClass="CircularImageView" customModule="WordPress" customModuleProvider="target">
<rect key="frame" x="0.0" y="20" width="38" height="38"/>
Expand All @@ -32,12 +32,12 @@
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="CzL-pe-Tnr">
<rect key="frame" x="48" y="20" width="208" height="31"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="252" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HpE-B7-6wr" userLabel="Name Label">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HpE-B7-6wr" userLabel="Name Label">
<rect key="frame" x="0.0" y="0.0" width="208" height="14.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ghT-Xy-q8c" userLabel="Date Label">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ghT-Xy-q8c" userLabel="Date Label">
<rect key="frame" x="0.0" y="16.5" width="208" height="14.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
<color key="textColor" systemColor="secondaryLabelColor"/>
Expand Down Expand Up @@ -76,20 +76,20 @@
</constraints>
</view>
<wkWebView contentMode="scaleToFill" verticalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="Je0-5Q-ty6">
<rect key="frame" x="16" y="73" width="288" height="133"/>
<rect key="frame" x="16" y="71" width="288" height="1"/>
<constraints>
<constraint firstAttribute="height" priority="250" constant="50" id="dGD-8Q-LSr"/>
<constraint firstAttribute="height" priority="999" constant="1" id="dGD-8Q-LSr"/>
</constraints>
<wkWebViewConfiguration key="configuration">
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
<wkPreferences key="preferences"/>
</wkWebViewConfiguration>
</wkWebView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ta5-Cz-flw" userLabel="Reaction Bar View">
<rect key="frame" x="16" y="206" width="288" height="41"/>
<rect key="frame" x="16" y="72" width="288" height="40"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="VoI-YI-Qgc" userLabel="Reply Button">
<rect key="frame" x="0.0" y="0.0" width="62.5" height="41"/>
<rect key="frame" x="0.0" y="0.0" width="62.5" height="40"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
<color key="tintColor" systemColor="secondaryLabelColor"/>
<inset key="contentEdgeInsets" minX="0.0" minY="15" maxX="15" maxY="10"/>
Expand All @@ -103,7 +103,7 @@
</state>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="X2J-8b-R5F" userLabel="Like Button">
<rect key="frame" x="62.5" y="0.0" width="78.5" height="41"/>
<rect key="frame" x="62.5" y="0.0" width="78.5" height="40"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
<color key="tintColor" systemColor="secondaryLabelColor"/>
<inset key="contentEdgeInsets" minX="5" minY="15" maxX="35" maxY="10"/>
Expand Down Expand Up @@ -154,8 +154,9 @@
<outlet property="reactionBarView" destination="ta5-Cz-flw" id="puY-Sa-fKk"/>
<outlet property="replyButton" destination="VoI-YI-Qgc" id="Z9J-Tp-bur"/>
<outlet property="webView" destination="Je0-5Q-ty6" id="YaD-wp-E6W"/>
<outlet property="webViewHeightConstraint" destination="dGD-8Q-LSr" id="rBk-4R-GCz"/>
</connections>
<point key="canvasLocation" x="131.8840579710145" y="279.57589285714283"/>
<point key="canvasLocation" x="131.8840579710145" y="234.375"/>
</tableViewCell>
</objects>
<resources>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ class CommentDetailViewController: UITableViewController {
guard let cell = tableView.dequeueReusableCell(withIdentifier: CommentContentTableViewCell.defaultReuseID) as? CommentContentTableViewCell else {
return .init()
}
cell.configure(with: comment)
cell.configure(with: comment) { _ in
self.tableView.performBatchUpdates({})
}
return cell

case .replyIndicator:
Expand Down
Loading