Merge branch 'master' of https://github.com/brentsimmons/NetNewsWire
This commit is contained in:
commit
d96f692b4c
@ -365,6 +365,9 @@ private extension ArticleRenderer {
|
|||||||
func renderHTML(withBody body: String) -> String {
|
func renderHTML(withBody body: String) -> String {
|
||||||
|
|
||||||
var s = "<!DOCTYPE html><html><head>\n"
|
var s = "<!DOCTYPE html><html><head>\n"
|
||||||
|
if let baseURL = baseURL {
|
||||||
|
s += ("<base href=\"" + baseURL + "\"\n>")
|
||||||
|
}
|
||||||
s += "<meta name=\"viewport\" content=\"width=device-width\">\n"
|
s += "<meta name=\"viewport\" content=\"width=device-width\">\n"
|
||||||
s += title.htmlBySurroundingWithTag("title")
|
s += title.htmlBySurroundingWithTag("title")
|
||||||
s += styleString().htmlBySurroundingWithTag("style")
|
s += styleString().htmlBySurroundingWithTag("style")
|
||||||
|
@ -64,7 +64,7 @@ class DetailViewController: UIViewController {
|
|||||||
}
|
}
|
||||||
let style = ArticleStylesManager.shared.currentStyle
|
let style = ArticleStylesManager.shared.currentStyle
|
||||||
let html = ArticleRenderer.articleHTML(article: article, style: style)
|
let html = ArticleRenderer.articleHTML(article: article, style: style)
|
||||||
webView.loadHTMLString(html, baseURL: article.baseURL)
|
webView.loadHTMLString(html, baseURL: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func statusesDidChange(_ note: Notification) {
|
@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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// String+.swift
|
// String-Extensions.swift
|
||||||
// NetNewsWire
|
// NetNewsWire
|
||||||
//
|
//
|
||||||
// Created by Maurice Parker on 4/8/19.
|
// Created by Maurice Parker on 4/8/19.
|
||||||
|
@ -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
|
import UIKit
|
||||||
|
|
||||||
|
@ -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
|
import UIKit
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// UIStoryboard+.swift
|
// UIStoryboard-Extensions.swift
|
||||||
// NetNewsWire
|
// NetNewsWire
|
||||||
//
|
//
|
||||||
// Created by Maurice Parker on 4/8/19.
|
// Created by Maurice Parker on 4/8/19.
|
||||||
|
@ -32,14 +32,6 @@ class MasterTableViewCell : UITableViewCell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var indent = false {
|
|
||||||
didSet {
|
|
||||||
if indent != oldValue {
|
|
||||||
setNeedsLayout()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var disclosureExpanded = false {
|
var disclosureExpanded = false {
|
||||||
didSet {
|
didSet {
|
||||||
updateDisclosureImage()
|
updateDisclosureImage()
|
||||||
@ -115,12 +107,12 @@ class MasterTableViewCell : UITableViewCell {
|
|||||||
|
|
||||||
override func willTransition(to state: UITableViewCell.StateMask) {
|
override func willTransition(to state: UITableViewCell.StateMask) {
|
||||||
super.willTransition(to: state)
|
super.willTransition(to: state)
|
||||||
showingEditControl = state == .showingEditControl
|
showingEditControl = state.contains(.showingEditControl)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func layoutSubviews() {
|
override func layoutSubviews() {
|
||||||
super.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)
|
layoutWith(layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,18 +159,10 @@ private extension MasterTableViewCell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func layoutWith(_ layout: MasterTableViewCellLayout) {
|
func layoutWith(_ layout: MasterTableViewCellLayout) {
|
||||||
faviconImageView.rs_setFrameIfNotEqual(layout.faviconRect)
|
faviconImageView.setFrameIfNotEqual(layout.faviconRect)
|
||||||
titleView.rs_setFrameIfNotEqual(layout.titleRect)
|
titleView.setFrameIfNotEqual(layout.titleRect)
|
||||||
unreadCountView.rs_setFrameIfNotEqual(layout.unreadCountRect)
|
unreadCountView.setFrameIfNotEqual(layout.unreadCountRect)
|
||||||
disclosureButton?.rs_setFrameIfNotEqual(layout.disclosureButtonRect)
|
disclosureButton?.setFrameIfNotEqual(layout.disclosureButtonRect)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UIView {
|
|
||||||
func rs_setFrameIfNotEqual(_ rect: CGRect) {
|
|
||||||
if !self.frame.equalTo(rect) {
|
|
||||||
self.frame = rect
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -11,6 +11,8 @@ import RSCore
|
|||||||
|
|
||||||
struct MasterTableViewCellLayout {
|
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 imageSize = CGSize(width: 16, height: 16)
|
||||||
private static let marginLeft = CGFloat(integerLiteral: 8)
|
private static let marginLeft = CGFloat(integerLiteral: 8)
|
||||||
private static let imageMarginRight = CGFloat(integerLiteral: 8)
|
private static let imageMarginRight = CGFloat(integerLiteral: 8)
|
||||||
@ -23,16 +25,22 @@ struct MasterTableViewCellLayout {
|
|||||||
let unreadCountRect: CGRect
|
let unreadCountRect: CGRect
|
||||||
let disclosureButtonRect: CGRect
|
let disclosureButtonRect: CGRect
|
||||||
|
|
||||||
init(cellSize: CGSize, shouldShowImage: Bool, label: UILabel, unreadCountView: MasterUnreadCountView, showingEditingControl: Bool, indent: Bool, shouldShowDisclosure: Bool) {
|
init(cellSize: CGSize, insets: UIEdgeInsets, 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))
|
|
||||||
|
|
||||||
|
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
|
// Favicon
|
||||||
var rFavicon = CGRect.zero
|
var rFavicon = CGRect.zero
|
||||||
if shouldShowImage {
|
if shouldShowImage {
|
||||||
var indentX = showingEditingControl ? MasterTableViewCellLayout.marginLeft + 40 : MasterTableViewCellLayout.marginLeft
|
rFavicon = CGRect(x: bounds.origin.x, y: 0.0, width: MasterTableViewCellLayout.imageSize.width, height: MasterTableViewCellLayout.imageSize.height)
|
||||||
indentX = indent ? indentX + 20 : indentX
|
|
||||||
rFavicon = CGRect(x: indentX, y: 0.0, width: MasterTableViewCellLayout.imageSize.width, height: MasterTableViewCellLayout.imageSize.height)
|
|
||||||
rFavicon = MasterTableViewCellLayout.centerVertically(rFavicon, bounds)
|
rFavicon = MasterTableViewCellLayout.centerVertically(rFavicon, bounds)
|
||||||
}
|
}
|
||||||
self.faviconRect = rFavicon
|
self.faviconRect = rFavicon
|
||||||
@ -44,7 +52,7 @@ struct MasterTableViewCellLayout {
|
|||||||
if shouldShowImage {
|
if shouldShowImage {
|
||||||
rLabel.origin.x = rFavicon.maxX + MasterTableViewCellLayout.imageMarginRight
|
rLabel.origin.x = rFavicon.maxX + MasterTableViewCellLayout.imageMarginRight
|
||||||
} else {
|
} else {
|
||||||
rLabel.origin.x = indent ? MasterTableViewCellLayout.marginLeft + 10 : MasterTableViewCellLayout.marginLeft
|
rLabel.origin.x = bounds.minX
|
||||||
}
|
}
|
||||||
|
|
||||||
rLabel = MasterTableViewCellLayout.centerVertically(rLabel, bounds)
|
rLabel = MasterTableViewCellLayout.centerVertically(rLabel, bounds)
|
||||||
|
@ -71,7 +71,7 @@ class MasterTableViewSectionHeader: UITableViewHeaderFooterView {
|
|||||||
|
|
||||||
override func layoutSubviews() {
|
override func layoutSubviews() {
|
||||||
super.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)
|
layoutWith(layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +80,9 @@ class MasterTableViewSectionHeader: UITableViewHeaderFooterView {
|
|||||||
private extension MasterTableViewSectionHeader {
|
private extension MasterTableViewSectionHeader {
|
||||||
|
|
||||||
func commonInit() {
|
func commonInit() {
|
||||||
backgroundColor = AppAssets.tableSectionHeaderColor
|
let view = UIView()
|
||||||
|
view.backgroundColor = AppAssets.tableSectionHeaderColor
|
||||||
|
backgroundView = view
|
||||||
addSubviewAtInit(unreadCountView)
|
addSubviewAtInit(unreadCountView)
|
||||||
addSubviewAtInit(titleView)
|
addSubviewAtInit(titleView)
|
||||||
}
|
}
|
||||||
@ -91,8 +93,8 @@ private extension MasterTableViewSectionHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func layoutWith(_ layout: MasterTableViewCellLayout) {
|
func layoutWith(_ layout: MasterTableViewCellLayout) {
|
||||||
titleView.rs_setFrameIfNotEqual(layout.titleRect)
|
titleView.setFrameIfNotEqual(layout.titleRect)
|
||||||
unreadCountView.rs_setFrameIfNotEqual(layout.unreadCountRect)
|
unreadCountView.setFrameIfNotEqual(layout.unreadCountRect)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -268,6 +268,110 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
|
|||||||
self.navigationController?.pushViewController(timeline, animated: true)
|
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
|
// MARK: Actions
|
||||||
|
|
||||||
@ -383,7 +487,11 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
|
|||||||
func configure(_ cell: MasterTableViewCell, _ node: Node) {
|
func configure(_ cell: MasterTableViewCell, _ node: Node) {
|
||||||
|
|
||||||
cell.delegate = self
|
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.disclosureExpanded = expandedNodes.contains(node)
|
||||||
cell.allowDisclosureSelection = node.canHaveChildNodes
|
cell.allowDisclosureSelection = node.canHaveChildNodes
|
||||||
|
|
||||||
@ -570,7 +678,20 @@ private extension MasterViewController {
|
|||||||
callback(cell as! MasterTableViewCell, node)
|
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() {
|
func rebuildShadowTable() {
|
||||||
|
|
||||||
for i in 0..<treeController.rootNode.numberOfChildNodes {
|
for i in 0..<treeController.rootNode.numberOfChildNodes {
|
||||||
|
@ -59,11 +59,11 @@ class MasterTimelineTableViewCell: UITableViewCell {
|
|||||||
setFrame(for: summaryView, rect: layoutRects.summaryRect)
|
setFrame(for: summaryView, rect: layoutRects.summaryRect)
|
||||||
setFrame(for: textView, rect: layoutRects.textRect)
|
setFrame(for: textView, rect: layoutRects.textRect)
|
||||||
|
|
||||||
dateView.rs_setFrameIfNotEqual(layoutRects.dateRect)
|
dateView.setFrameIfNotEqual(layoutRects.dateRect)
|
||||||
unreadIndicatorView.rs_setFrameIfNotEqual(layoutRects.unreadIndicatorRect)
|
unreadIndicatorView.setFrameIfNotEqual(layoutRects.unreadIndicatorRect)
|
||||||
feedNameView.rs_setFrameIfNotEqual(layoutRects.feedNameRect)
|
feedNameView.setFrameIfNotEqual(layoutRects.feedNameRect)
|
||||||
avatarImageView.rs_setFrameIfNotEqual(layoutRects.avatarImageRect)
|
avatarImageView.setFrameIfNotEqual(layoutRects.avatarImageRect)
|
||||||
starView.rs_setFrameIfNotEqual(layoutRects.starRect)
|
starView.setFrameIfNotEqual(layoutRects.starRect)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ private extension MasterTimelineTableViewCell {
|
|||||||
hideView(label)
|
hideView(label)
|
||||||
} else {
|
} else {
|
||||||
showView(label)
|
showView(label)
|
||||||
label.rs_setFrameIfNotEqual(rect)
|
label.setFrameIfNotEqual(rect)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 7c3e722bfac7939cf11e2fe6f579faaa4203c248
|
Subproject commit ac59e34818d4a0c2d3e510f0dad4adf33cf43ce7
|
Loading…
x
Reference in New Issue
Block a user