diff --git a/Shared/Article Rendering/ArticleRenderer.swift b/Shared/Article Rendering/ArticleRenderer.swift index 4dfe61757..7643b1091 100644 --- a/Shared/Article Rendering/ArticleRenderer.swift +++ b/Shared/Article Rendering/ArticleRenderer.swift @@ -365,6 +365,9 @@ private extension ArticleRenderer { func renderHTML(withBody body: String) -> String { var s = "\n" + if let baseURL = baseURL { + s += ("") + } s += "\n" s += title.htmlBySurroundingWithTag("title") s += styleString().htmlBySurroundingWithTag("style") diff --git a/iOS/Detail/DetailViewController.swift b/iOS/Detail/DetailViewController.swift index 338f67998..0b9f81269 100644 --- a/iOS/Detail/DetailViewController.swift +++ b/iOS/Detail/DetailViewController.swift @@ -64,7 +64,7 @@ class DetailViewController: UIViewController { } let style = ArticleStylesManager.shared.currentStyle let html = ArticleRenderer.articleHTML(article: article, style: style) - webView.loadHTMLString(html, baseURL: article.baseURL) + webView.loadHTMLString(html, baseURL: nil) } @objc func statusesDidChange(_ note: Notification) { @@ -155,33 +155,5 @@ extension DetailViewController: WKNavigationDelegate { } } -} - -private extension Article { - - var baseURL: URL? { - var s = url - if s == nil { - s = feed?.homePageURL - } - if s == nil { - s = feed?.url - } - - guard let urlString = s else { - return nil - } - var urlComponents = URLComponents(string: urlString) - if urlComponents == nil { - return nil - } - - // Can’t use url-with-fragment as base URL. The webview won’t load. See scripting.com/rss.xml for example. - urlComponents!.fragment = nil - guard let url = urlComponents!.url, url.scheme == "http" || url.scheme == "https" else { - return nil - } - return url - } } diff --git a/iOS/Extensions/String-Extensions.swift b/iOS/Extensions/String-Extensions.swift index d546ce63f..c56dda628 100644 --- a/iOS/Extensions/String-Extensions.swift +++ b/iOS/Extensions/String-Extensions.swift @@ -1,5 +1,5 @@ // -// String+.swift +// String-Extensions.swift // NetNewsWire // // Created by Maurice Parker on 4/8/19. diff --git a/iOS/Extensions/UIImage-Extensions.swift b/iOS/Extensions/UIImage-Extensions.swift index 79aff8d1f..e9e071c40 100644 --- a/iOS/Extensions/UIImage-Extensions.swift +++ b/iOS/Extensions/UIImage-Extensions.swift @@ -1,4 +1,10 @@ -//Copyright © 2019 Vincode, Inc. All rights reserved. +// +// UIImage-Extensions.swift +// NetNewsWire +// +// Created by Maurice Parker on 4/18/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// import UIKit diff --git a/iOS/Extensions/UISplitViewController-Extensions.swift b/iOS/Extensions/UISplitViewController-Extensions.swift index 617b417eb..98fa33c5d 100644 --- a/iOS/Extensions/UISplitViewController-Extensions.swift +++ b/iOS/Extensions/UISplitViewController-Extensions.swift @@ -1,4 +1,10 @@ -//Copyright © 2019 Ranchero Software. All rights reserved. +// +// UISplitViewController-Extensions.swift +// NetNewsWire +// +// Created by Maurice Parker on 4/18/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// import UIKit diff --git a/iOS/Extensions/UIStoryboard-Extensions.swift b/iOS/Extensions/UIStoryboard-Extensions.swift index 66bfc485a..540b96e22 100644 --- a/iOS/Extensions/UIStoryboard-Extensions.swift +++ b/iOS/Extensions/UIStoryboard-Extensions.swift @@ -1,5 +1,5 @@ // -// UIStoryboard+.swift +// UIStoryboard-Extensions.swift // NetNewsWire // // Created by Maurice Parker on 4/8/19. diff --git a/iOS/Master/Cell/MasterTableViewCell.swift b/iOS/Master/Cell/MasterTableViewCell.swift index 4ab775021..8cea92c37 100644 --- a/iOS/Master/Cell/MasterTableViewCell.swift +++ b/iOS/Master/Cell/MasterTableViewCell.swift @@ -32,14 +32,6 @@ class MasterTableViewCell : UITableViewCell { } } - var indent = false { - didSet { - if indent != oldValue { - setNeedsLayout() - } - } - } - var disclosureExpanded = false { didSet { updateDisclosureImage() @@ -115,12 +107,12 @@ class MasterTableViewCell : UITableViewCell { override func willTransition(to state: UITableViewCell.StateMask) { super.willTransition(to: state) - showingEditControl = state == .showingEditControl + showingEditControl = state.contains(.showingEditControl) } override func layoutSubviews() { super.layoutSubviews() - let layout = MasterTableViewCellLayout(cellSize: bounds.size, shouldShowImage: shouldShowImage, label: titleView, unreadCountView: unreadCountView, showingEditingControl: showingEditControl, indent: indent, shouldShowDisclosure: true) + let layout = MasterTableViewCellLayout(cellSize: bounds.size, insets: safeAreaInsets, shouldShowImage: shouldShowImage, label: titleView, unreadCountView: unreadCountView, showingEditingControl: showingEditControl, indent: indentationLevel == 1, shouldShowDisclosure: !showsReorderControl) layoutWith(layout) } @@ -167,18 +159,10 @@ private extension MasterTableViewCell { } func layoutWith(_ layout: MasterTableViewCellLayout) { - faviconImageView.rs_setFrameIfNotEqual(layout.faviconRect) - titleView.rs_setFrameIfNotEqual(layout.titleRect) - unreadCountView.rs_setFrameIfNotEqual(layout.unreadCountRect) - disclosureButton?.rs_setFrameIfNotEqual(layout.disclosureButtonRect) + faviconImageView.setFrameIfNotEqual(layout.faviconRect) + titleView.setFrameIfNotEqual(layout.titleRect) + unreadCountView.setFrameIfNotEqual(layout.unreadCountRect) + disclosureButton?.setFrameIfNotEqual(layout.disclosureButtonRect) } } - -extension UIView { - func rs_setFrameIfNotEqual(_ rect: CGRect) { - if !self.frame.equalTo(rect) { - self.frame = rect - } - } -} diff --git a/iOS/Master/Cell/MasterTableViewCellLayout.swift b/iOS/Master/Cell/MasterTableViewCellLayout.swift index f22621742..cc692740c 100644 --- a/iOS/Master/Cell/MasterTableViewCellLayout.swift +++ b/iOS/Master/Cell/MasterTableViewCellLayout.swift @@ -11,6 +11,8 @@ import RSCore struct MasterTableViewCellLayout { + private static let indent = CGFloat(integerLiteral: 14) + private static let editingControlIndent = CGFloat(integerLiteral: 40) private static let imageSize = CGSize(width: 16, height: 16) private static let marginLeft = CGFloat(integerLiteral: 8) private static let imageMarginRight = CGFloat(integerLiteral: 8) @@ -23,16 +25,22 @@ struct MasterTableViewCellLayout { let unreadCountRect: CGRect let disclosureButtonRect: CGRect - init(cellSize: CGSize, shouldShowImage: Bool, label: UILabel, unreadCountView: MasterUnreadCountView, showingEditingControl: Bool, indent: Bool, shouldShowDisclosure: Bool) { - - let bounds = CGRect(x: 0.0, y: 0.0, width: floor(cellSize.width), height: floor(cellSize.height)) + init(cellSize: CGSize, insets: UIEdgeInsets, shouldShowImage: Bool, label: UILabel, unreadCountView: MasterUnreadCountView, showingEditingControl: Bool, indent: Bool, shouldShowDisclosure: Bool) { + var initialIndent = MasterTableViewCellLayout.marginLeft + insets.left + if indent { + initialIndent += MasterTableViewCellLayout.indent + } + if showingEditingControl { + initialIndent += MasterTableViewCellLayout.editingControlIndent + } + + let bounds = CGRect(x: initialIndent, y: 0.0, width: floor(cellSize.width - initialIndent - insets.right), height: floor(cellSize.height)) + // Favicon var rFavicon = CGRect.zero if shouldShowImage { - var indentX = showingEditingControl ? MasterTableViewCellLayout.marginLeft + 40 : MasterTableViewCellLayout.marginLeft - indentX = indent ? indentX + 20 : indentX - rFavicon = CGRect(x: indentX, y: 0.0, width: MasterTableViewCellLayout.imageSize.width, height: MasterTableViewCellLayout.imageSize.height) + rFavicon = CGRect(x: bounds.origin.x, y: 0.0, width: MasterTableViewCellLayout.imageSize.width, height: MasterTableViewCellLayout.imageSize.height) rFavicon = MasterTableViewCellLayout.centerVertically(rFavicon, bounds) } self.faviconRect = rFavicon @@ -44,7 +52,7 @@ struct MasterTableViewCellLayout { if shouldShowImage { rLabel.origin.x = rFavicon.maxX + MasterTableViewCellLayout.imageMarginRight } else { - rLabel.origin.x = indent ? MasterTableViewCellLayout.marginLeft + 10 : MasterTableViewCellLayout.marginLeft + rLabel.origin.x = bounds.minX } rLabel = MasterTableViewCellLayout.centerVertically(rLabel, bounds) diff --git a/iOS/Master/Cell/MasterTableViewSectionHeader.swift b/iOS/Master/Cell/MasterTableViewSectionHeader.swift index df91732be..ed3d6ae94 100644 --- a/iOS/Master/Cell/MasterTableViewSectionHeader.swift +++ b/iOS/Master/Cell/MasterTableViewSectionHeader.swift @@ -71,7 +71,7 @@ class MasterTableViewSectionHeader: UITableViewHeaderFooterView { override func layoutSubviews() { super.layoutSubviews() - let layout = MasterTableViewCellLayout(cellSize: bounds.size, shouldShowImage: false, label: titleView, unreadCountView: unreadCountView, showingEditingControl: false, indent: true, shouldShowDisclosure: false) + let layout = MasterTableViewCellLayout(cellSize: bounds.size, insets: safeAreaInsets, shouldShowImage: false, label: titleView, unreadCountView: unreadCountView, showingEditingControl: false, indent: true, shouldShowDisclosure: false) layoutWith(layout) } @@ -80,7 +80,9 @@ class MasterTableViewSectionHeader: UITableViewHeaderFooterView { private extension MasterTableViewSectionHeader { func commonInit() { - backgroundColor = AppAssets.tableSectionHeaderColor + let view = UIView() + view.backgroundColor = AppAssets.tableSectionHeaderColor + backgroundView = view addSubviewAtInit(unreadCountView) addSubviewAtInit(titleView) } @@ -91,8 +93,8 @@ private extension MasterTableViewSectionHeader { } func layoutWith(_ layout: MasterTableViewCellLayout) { - titleView.rs_setFrameIfNotEqual(layout.titleRect) - unreadCountView.rs_setFrameIfNotEqual(layout.unreadCountRect) + titleView.setFrameIfNotEqual(layout.titleRect) + unreadCountView.setFrameIfNotEqual(layout.unreadCountRect) } } diff --git a/iOS/Master/MasterViewController.swift b/iOS/Master/MasterViewController.swift index cb855e307..a77dc206f 100644 --- a/iOS/Master/MasterViewController.swift +++ b/iOS/Master/MasterViewController.swift @@ -268,6 +268,110 @@ class MasterViewController: UITableViewController, UndoableCommandRunner { self.navigationController?.pushViewController(timeline, animated: true) } + + override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { + guard let node = nodeFor(indexPath) else { + return false + } + return node.representedObject is Feed + } + + override func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath { + + // Adjust the index path so that it will never be in the smart feeds area + let destIndexPath: IndexPath = { + if proposedDestinationIndexPath.section == 0 { + return IndexPath(row: 0, section: 1) + } + return proposedDestinationIndexPath + }() + + guard let draggedNode = nodeFor(sourceIndexPath), let destNode = nodeFor(destIndexPath), let parentNode = destNode.parent else { + assertionFailure("This should never happen") + return sourceIndexPath + } + + // If this is a folder and isn't expanded or doesn't have any entries, let the users drop on it + if destNode.representedObject is Folder && (destNode.numberOfChildNodes == 0 || !expandedNodes.contains(destNode)) { + let movementAdjustment = sourceIndexPath > destIndexPath ? 1 : 0 + return IndexPath(row: destIndexPath.row + movementAdjustment, section: destIndexPath.section) + } + + // If we are dragging around in the same container, just return the original source + if parentNode.childNodes.contains(draggedNode) { + return sourceIndexPath + } + + // Suggest to the user the best place to drop the feed + // Revisit if the tree controller can ever be sorted in some other way. + let nodes = parentNode.childNodes + [draggedNode] + var sortedNodes = nodes.sortedAlphabeticallyWithFoldersAtEnd() + let index = sortedNodes.firstIndex(of: draggedNode)! + + if index == 0 { + + if parentNode.representedObject is Account { + return IndexPath(row: 0, section: destIndexPath.section) + } else { + return indexPathFor(parentNode)! + } + + } else { + + sortedNodes.remove(at: index) + + let movementAdjustment = sourceIndexPath < destIndexPath ? 1 : 0 + let adjustedIndex = index - movementAdjustment + if adjustedIndex >= sortedNodes.count { + let lastSortedIndexPath = indexPathFor(sortedNodes[sortedNodes.count - 1])! + return IndexPath(row: lastSortedIndexPath.row + 1, section: lastSortedIndexPath.section) + } else { + return indexPathFor(sortedNodes[adjustedIndex])! + } + + } + + } + + override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { + + guard let sourceNode = nodeFor(sourceIndexPath), let feed = sourceNode.representedObject as? Feed else { + return + } + + // Based on the drop we have to determine a node to start looking for a parent container. + let destNode: Node = { + if destinationIndexPath.row == 0 { + return treeController.rootNode.childAtIndex(destinationIndexPath.section)! + } else { + let movementAdjustment = sourceIndexPath > destinationIndexPath ? 1 : 0 + let adjustedDestIndexPath = IndexPath(row: destinationIndexPath.row - movementAdjustment, section: destinationIndexPath.section) + return nodeFor(adjustedDestIndexPath)! + } + }() + + // Now we start looking for the parent container + let destParentNode: Node? = { + if destNode.representedObject is Container { + return destNode + } else { + if destNode.parent?.representedObject is Container { + return destNode.parent! + } else { + return nil + } + } + }() + + // Move the Feed + let account = accountForNode(destNode) + let sourceContainer = sourceNode.parent?.representedObject as? Container + let destinationFolder = destParentNode?.representedObject as? Folder + sourceContainer?.deleteFeed(feed) + account?.addFeed(feed, to: destinationFolder) + account?.structureDidChange() + + } // MARK: Actions @@ -383,7 +487,11 @@ class MasterViewController: UITableViewController, UndoableCommandRunner { func configure(_ cell: MasterTableViewCell, _ node: Node) { cell.delegate = self - cell.indent = node.parent?.representedObject is Folder + if node.parent?.representedObject is Folder { + cell.indentationLevel = 1 + } else { + cell.indentationLevel = 0 + } cell.disclosureExpanded = expandedNodes.contains(node) cell.allowDisclosureSelection = node.canHaveChildNodes @@ -570,7 +678,20 @@ private extension MasterViewController { callback(cell as! MasterTableViewCell, node) } } - + + private func accountForNode(_ node: Node) -> Account? { + if let account = node.representedObject as? Account { + return account + } + if let folder = node.representedObject as? Folder { + return folder.account + } + if let feed = node.representedObject as? Feed { + return feed.account + } + return nil + } + func rebuildShadowTable() { for i in 0..