mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-05 23:38:38 +09:00
574 lines
24 KiB
Swift
574 lines
24 KiB
Swift
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
import UIKit
|
|
|
|
import Shared
|
|
import Storage
|
|
import XCGLogger
|
|
import Deferred
|
|
|
|
private typealias SectionNumber = Int
|
|
private typealias CategoryNumber = Int
|
|
private typealias CategorySpec = (section: SectionNumber?, rows: Int, offset: Int)
|
|
|
|
private struct HistoryPanelUX {
|
|
static let WelcomeScreenItemTextColor = UIColor.gray
|
|
static let WelcomeScreenItemWidth = 170
|
|
static let IconSize = 23
|
|
static let IconBorderColor = UIColor(white: 0, alpha: 0.1)
|
|
static let IconBorderWidth: CGFloat = 0.5
|
|
}
|
|
|
|
private func getDate(_ dayOffset: Int) -> Date {
|
|
let calendar = Calendar(identifier: Calendar.Identifier.gregorian)
|
|
let nowComponents = (calendar as NSCalendar).components([.year, .month, .day], from: Date())
|
|
let today = calendar.date(from: nowComponents)!
|
|
return (calendar as NSCalendar).date(byAdding: NSCalendar.Unit.day, value: dayOffset, to: today, options: [])!
|
|
}
|
|
|
|
class HistoryPanel: SiteTableViewController, HomePanel {
|
|
weak var homePanelDelegate: HomePanelDelegate?
|
|
private var currentSyncedDevicesCount: Int?
|
|
|
|
var events = [NotificationFirefoxAccountChanged, NotificationPrivateDataClearedHistory, NotificationDynamicFontChanged]
|
|
var refreshControl: UIRefreshControl?
|
|
|
|
fileprivate lazy var longPressRecognizer: UILongPressGestureRecognizer = {
|
|
return UILongPressGestureRecognizer(target: self, action: #selector(HistoryPanel.longPress(_:)))
|
|
}()
|
|
|
|
private lazy var emptyStateOverlayView: UIView = self.createEmptyStateOverlayView()
|
|
private let QueryLimit = 100
|
|
private let NumSections = 5
|
|
private let Today = getDate(0)
|
|
private let Yesterday = getDate(-1)
|
|
private let ThisWeek = getDate(-7)
|
|
private var categories: [CategorySpec] = [CategorySpec]() // Category number (index) -> (UI section, row count, cursor offset).
|
|
private var sectionLookup = [SectionNumber: CategoryNumber]() // Reverse lookup from UI section to data category.
|
|
|
|
var syncDetailText = ""
|
|
var hasRecentlyClosed: Bool {
|
|
return self.profile.recentlyClosedTabs.tabs.count > 0
|
|
}
|
|
|
|
// MARK: - Lifecycle
|
|
init() {
|
|
super.init(nibName: nil, bundle: nil)
|
|
events.forEach { NotificationCenter.default.addObserver(self, selector: #selector(HistoryPanel.notificationReceived(_:)), name: $0, object: nil) }
|
|
}
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
tableView.addGestureRecognizer(longPressRecognizer)
|
|
tableView.accessibilityIdentifier = "History List"
|
|
updateSyncedDevicesCount().uponQueue(DispatchQueue.main) { result in
|
|
self.updateNumberOfSyncedDevices(self.currentSyncedDevicesCount)
|
|
}
|
|
}
|
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
super.viewWillAppear(animated)
|
|
|
|
// Add a refresh control if the user is logged in and the control was not added before. If the user is not
|
|
// logged in, remove any existing control but only when it is not currently refreshing. Otherwise, wait for
|
|
// the refresh to finish before removing the control.
|
|
if profile.hasSyncableAccount() && refreshControl == nil {
|
|
addRefreshControl()
|
|
} else if refreshControl?.isRefreshing == false {
|
|
removeRefreshControl()
|
|
}
|
|
|
|
if profile.hasSyncableAccount() {
|
|
syncDetailText = " "
|
|
updateSyncedDevicesCount().uponQueue(DispatchQueue.main) { result in
|
|
self.updateNumberOfSyncedDevices(self.currentSyncedDevicesCount)
|
|
}
|
|
} else {
|
|
syncDetailText = ""
|
|
}
|
|
}
|
|
|
|
@objc fileprivate func longPress(_ longPressGestureRecognizer: UILongPressGestureRecognizer) {
|
|
guard longPressGestureRecognizer.state == UIGestureRecognizerState.began else { return }
|
|
let touchPoint = longPressGestureRecognizer.location(in: tableView)
|
|
guard let indexPath = tableView.indexPathForRow(at: touchPoint) else { return }
|
|
|
|
if indexPath.section != 0 {
|
|
presentContextMenu(for: indexPath)
|
|
}
|
|
}
|
|
|
|
// MARK: - History Data Store
|
|
func updateNumberOfSyncedDevices(_ count: Int?) {
|
|
if let count = count, count > 0 {
|
|
syncDetailText = String.localizedStringWithFormat(Strings.SyncedTabsTableViewCellDescription, count)
|
|
} else {
|
|
syncDetailText = ""
|
|
}
|
|
self.tableView.reloadRows(at: [IndexPath(row: 1, section: 0)], with: .automatic)
|
|
}
|
|
|
|
func updateSyncedDevicesCount() -> Success {
|
|
return chainDeferred(self.profile.getCachedClientsAndTabs()) { tabsAndClients in
|
|
self.currentSyncedDevicesCount = tabsAndClients.count
|
|
return succeed()
|
|
}
|
|
}
|
|
|
|
func notificationReceived(_ notification: Notification) {
|
|
reloadData()
|
|
|
|
switch notification.name {
|
|
case NotificationFirefoxAccountChanged, NotificationPrivateDataClearedHistory:
|
|
if self.profile.hasSyncableAccount() {
|
|
resyncHistory()
|
|
}
|
|
break
|
|
case NotificationDynamicFontChanged:
|
|
if emptyStateOverlayView.superview != nil {
|
|
emptyStateOverlayView.removeFromSuperview()
|
|
}
|
|
emptyStateOverlayView = createEmptyStateOverlayView()
|
|
resyncHistory()
|
|
break
|
|
default:
|
|
// no need to do anything at all
|
|
print("Error: Received unexpected notification \(notification.name)")
|
|
break
|
|
}
|
|
}
|
|
|
|
private func fetchData() -> Deferred<Maybe<Cursor<Site>>> {
|
|
return profile.history.getSitesByLastVisit(QueryLimit)
|
|
}
|
|
|
|
private func setData(_ data: Cursor<Site>) {
|
|
self.data = data
|
|
self.computeSectionOffsets()
|
|
}
|
|
|
|
func resyncHistory() {
|
|
profile.syncManager.syncHistory().uponQueue(DispatchQueue.main) { result in
|
|
if result.isSuccess {
|
|
self.reloadData()
|
|
} else {
|
|
self.endRefreshing()
|
|
}
|
|
|
|
self.updateSyncedDevicesCount().uponQueue(DispatchQueue.main) { result in
|
|
self.updateNumberOfSyncedDevices(self.currentSyncedDevicesCount)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Refreshing TableView
|
|
func addRefreshControl() {
|
|
let refresh = UIRefreshControl()
|
|
refresh.addTarget(self, action: #selector(HistoryPanel.refresh), for: UIControlEvents.valueChanged)
|
|
self.refreshControl = refresh
|
|
self.tableView.refreshControl = refresh
|
|
}
|
|
|
|
func removeRefreshControl() {
|
|
self.tableView.refreshControl = nil
|
|
self.refreshControl = nil
|
|
}
|
|
|
|
func endRefreshing() {
|
|
// Always end refreshing, even if we failed!
|
|
self.refreshControl?.endRefreshing()
|
|
|
|
// Remove the refresh control if the user has logged out in the meantime
|
|
if !self.profile.hasSyncableAccount() {
|
|
self.removeRefreshControl()
|
|
}
|
|
}
|
|
|
|
@objc func refresh() {
|
|
self.refreshControl?.beginRefreshing()
|
|
resyncHistory()
|
|
}
|
|
|
|
override func reloadData() {
|
|
self.fetchData().uponQueue(DispatchQueue.main) { result in
|
|
if let data = result.successValue {
|
|
self.setData(data)
|
|
self.tableView.reloadData()
|
|
self.updateEmptyPanelState()
|
|
}
|
|
self.endRefreshing()
|
|
}
|
|
}
|
|
|
|
// MARK: - Empty State
|
|
private func updateEmptyPanelState() {
|
|
if data.count == 0 {
|
|
if self.emptyStateOverlayView.superview == nil {
|
|
self.tableView.addSubview(self.emptyStateOverlayView)
|
|
self.emptyStateOverlayView.snp.makeConstraints { make -> Void in
|
|
make.left.right.bottom.equalTo(self.view)
|
|
make.top.equalTo(self.view).offset(100)
|
|
}
|
|
}
|
|
} else {
|
|
self.tableView.alwaysBounceVertical = true
|
|
self.emptyStateOverlayView.removeFromSuperview()
|
|
}
|
|
}
|
|
|
|
private func createEmptyStateOverlayView() -> UIView {
|
|
let overlayView = UIView()
|
|
overlayView.backgroundColor = UIColor.white
|
|
|
|
let welcomeLabel = UILabel()
|
|
overlayView.addSubview(welcomeLabel)
|
|
welcomeLabel.text = Strings.HistoryPanelEmptyStateTitle
|
|
welcomeLabel.textAlignment = NSTextAlignment.center
|
|
welcomeLabel.font = DynamicFontHelper.defaultHelper.DeviceFontLight
|
|
welcomeLabel.textColor = HistoryPanelUX.WelcomeScreenItemTextColor
|
|
welcomeLabel.numberOfLines = 0
|
|
welcomeLabel.adjustsFontSizeToFitWidth = true
|
|
|
|
welcomeLabel.snp.makeConstraints { make in
|
|
make.centerX.equalTo(overlayView)
|
|
// Sets proper top constraint for iPhone 6 in portait and for iPad.
|
|
make.centerY.equalTo(overlayView).offset(HomePanelUX.EmptyTabContentOffset).priority(100)
|
|
// Sets proper top constraint for iPhone 4, 5 in portrait.
|
|
make.top.greaterThanOrEqualTo(overlayView).offset(50)
|
|
make.width.equalTo(HistoryPanelUX.WelcomeScreenItemWidth)
|
|
}
|
|
return overlayView
|
|
}
|
|
|
|
// MARK: - TableView Row Helpers
|
|
func computeSectionOffsets() {
|
|
var counts = [Int](repeating: 0, count: NumSections)
|
|
|
|
// Loop over all the data. Record the start of each "section" of our list.
|
|
for i in 0..<data.count {
|
|
if let site = data[i] {
|
|
counts[categoryForDate(site.latestVisit!.date) + 1] += 1
|
|
}
|
|
}
|
|
|
|
var section = 0
|
|
var offset = 0
|
|
self.categories = [CategorySpec]()
|
|
for i in 0..<NumSections {
|
|
let count = counts[i]
|
|
if i == 0 {
|
|
sectionLookup[section] = i
|
|
section += 1
|
|
}
|
|
if count > 0 {
|
|
self.categories.append((section: section, rows: count, offset: offset))
|
|
sectionLookup[section] = i
|
|
offset += count
|
|
section += 1
|
|
} else {
|
|
self.categories.append((section: nil, rows: 0, offset: offset))
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func siteForIndexPath(_ indexPath: IndexPath) -> Site? {
|
|
let offset = self.categories[sectionLookup[indexPath.section]!].offset
|
|
return data[indexPath.row + offset]
|
|
}
|
|
|
|
private func categoryForDate(_ date: MicrosecondTimestamp) -> Int {
|
|
let date = Double(date)
|
|
if date > (1000000 * Today.timeIntervalSince1970) {
|
|
return 0
|
|
}
|
|
if date > (1000000 * Yesterday.timeIntervalSince1970) {
|
|
return 1
|
|
}
|
|
if date > (1000000 * ThisWeek.timeIntervalSince1970) {
|
|
return 2
|
|
}
|
|
return 3
|
|
}
|
|
|
|
private func isInCategory(_ date: MicrosecondTimestamp, category: Int) -> Bool {
|
|
return self.categoryForDate(date) == category
|
|
}
|
|
|
|
// UI sections disappear as categories empty. We need to translate back and forth.
|
|
private func uiSectionToCategory(_ section: SectionNumber) -> CategoryNumber {
|
|
for i in 0..<self.categories.count {
|
|
if let s = self.categories[i].section, s == section {
|
|
return i
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
private func categoryToUISection(_ category: CategoryNumber) -> SectionNumber? {
|
|
return self.categories[category].section
|
|
}
|
|
|
|
// MARK: - TableView Delegate / DataSource
|
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
let cell = super.tableView(tableView, cellForRowAt: indexPath)
|
|
cell.accessoryType = UITableViewCellAccessoryType.none
|
|
|
|
if indexPath.section == 0 {
|
|
cell.imageView!.layer.borderWidth = 0
|
|
return indexPath.row == 0 ? configureRecentlyClosed(cell, for: indexPath) : configureSyncedTabs(cell, for: indexPath)
|
|
} else {
|
|
return configureSite(cell, for: indexPath)
|
|
}
|
|
}
|
|
|
|
func configureRecentlyClosed(_ cell: UITableViewCell, for indexPath: IndexPath) -> UITableViewCell {
|
|
cell.accessoryType = UITableViewCellAccessoryType.disclosureIndicator
|
|
cell.textLabel!.text = Strings.RecentlyClosedTabsButtonTitle
|
|
cell.detailTextLabel!.text = ""
|
|
cell.imageView!.image = UIImage(named: "recently_closed")
|
|
cell.imageView?.backgroundColor = UIColor.white
|
|
if !hasRecentlyClosed {
|
|
cell.textLabel?.alpha = 0.5
|
|
cell.imageView!.alpha = 0.5
|
|
cell.selectionStyle = .none
|
|
}
|
|
cell.accessibilityIdentifier = "HistoryPanel.recentlyClosedCell"
|
|
return cell
|
|
}
|
|
|
|
func configureSyncedTabs(_ cell: UITableViewCell, for indexPath: IndexPath) -> UITableViewCell {
|
|
cell.accessoryType = UITableViewCellAccessoryType.disclosureIndicator
|
|
cell.textLabel!.text = Strings.SyncedTabsTableViewCellTitle
|
|
cell.detailTextLabel!.text = self.syncDetailText
|
|
cell.imageView!.image = UIImage(named: "synced_devices")
|
|
cell.imageView?.backgroundColor = UIColor.white
|
|
cell.accessibilityIdentifier = "HistoryPanel.syncedDevicesCell"
|
|
return cell
|
|
}
|
|
|
|
func configureSite(_ cell: UITableViewCell, for indexPath: IndexPath) -> UITableViewCell {
|
|
if let site = siteForIndexPath(indexPath), let cell = cell as? TwoLineTableViewCell {
|
|
cell.setLines(site.title, detailText: site.url)
|
|
|
|
cell.imageView!.layer.borderColor = HistoryPanelUX.IconBorderColor.cgColor
|
|
cell.imageView!.layer.borderWidth = HistoryPanelUX.IconBorderWidth
|
|
cell.imageView?.setIcon(site.icon, forURL: site.tileURL, completed: { (color, url) in
|
|
if site.tileURL == url {
|
|
cell.imageView?.image = cell.imageView?.image?.createScaled(CGSize(width: HistoryPanelUX.IconSize, height: HistoryPanelUX.IconSize))
|
|
cell.imageView?.backgroundColor = color
|
|
cell.imageView?.contentMode = .center
|
|
}
|
|
})
|
|
}
|
|
return cell
|
|
}
|
|
|
|
func numberOfSectionsInTableView(_ tableView: UITableView) -> Int {
|
|
var count = 1
|
|
for category in self.categories where category.rows > 0 {
|
|
count += 1
|
|
}
|
|
return count
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath) {
|
|
if indexPath.section == 0 {
|
|
self.tableView.deselectRow(at: indexPath, animated: true)
|
|
return indexPath.row == 0 ? self.showRecentlyClosed() : self.showSyncedTabs()
|
|
}
|
|
if let site = self.siteForIndexPath(indexPath), let url = URL(string: site.url) {
|
|
let visitType = VisitType.typed // Means History, too.
|
|
if let homePanelDelegate = homePanelDelegate {
|
|
homePanelDelegate.homePanel(self, didSelectURL: url, visitType: visitType)
|
|
}
|
|
return
|
|
}
|
|
print("Error: No site or no URL when selecting row.")
|
|
}
|
|
|
|
func pinTopSite(_ site: Site) {
|
|
_ = profile.history.addPinnedTopSite(site).value
|
|
}
|
|
|
|
func showSyncedTabs() {
|
|
let nextController = RemoteTabsPanel()
|
|
nextController.homePanelDelegate = self.homePanelDelegate
|
|
nextController.profile = self.profile
|
|
self.refreshControl?.endRefreshing()
|
|
self.navigationController?.pushViewController(nextController, animated: true)
|
|
}
|
|
|
|
func showRecentlyClosed() {
|
|
guard hasRecentlyClosed else {
|
|
return
|
|
}
|
|
let nextController = RecentlyClosedTabsPanel()
|
|
nextController.homePanelDelegate = self.homePanelDelegate
|
|
nextController.profile = self.profile
|
|
self.refreshControl?.endRefreshing()
|
|
self.navigationController?.pushViewController(nextController, animated: true)
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
|
var title = String()
|
|
switch sectionLookup[section]! {
|
|
case 0: return nil
|
|
case 1: title = NSLocalizedString("Today", comment: "History tableview section header")
|
|
case 2: title = NSLocalizedString("Yesterday", comment: "History tableview section header")
|
|
case 3: title = NSLocalizedString("Last week", comment: "History tableview section header")
|
|
case 4: title = NSLocalizedString("Last month", comment: "History tableview section header")
|
|
default:
|
|
assertionFailure("Invalid history section \(section)")
|
|
}
|
|
return title
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
|
if section == 0 {
|
|
return nil
|
|
}
|
|
return super.tableView(tableView, viewForHeaderInSection: section)
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
|
if section == 0 {
|
|
return 0
|
|
}
|
|
return super.tableView(tableView, heightForHeaderInSection: section)
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
if section == 0 {
|
|
return 2
|
|
}
|
|
return self.categories[uiSectionToCategory(section)].rows
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: IndexPath) {
|
|
// Intentionally blank. Required to use UITableViewRowActions
|
|
}
|
|
|
|
fileprivate func removeHistoryForURLAtIndexPath(indexPath: IndexPath) {
|
|
if let site = self.siteForIndexPath(indexPath) {
|
|
// Why the dispatches? Because we call success and failure on the DB
|
|
// queue, and so calling anything else that calls through to the DB will
|
|
// deadlock. This problem will go away when the history API switches to
|
|
// Deferred instead of using callbacks.
|
|
self.profile.history.removeHistoryForURL(site.url)
|
|
.upon { res in
|
|
self.fetchData().uponQueue(DispatchQueue.main) { result in
|
|
// If a section will be empty after removal, we must remove the section itself.
|
|
if let data = result.successValue {
|
|
|
|
let oldCategories = self.categories
|
|
self.data = data
|
|
self.computeSectionOffsets()
|
|
|
|
let sectionsToDelete = NSMutableIndexSet()
|
|
var rowsToDelete = [IndexPath]()
|
|
let sectionsToAdd = NSMutableIndexSet()
|
|
var rowsToAdd = [IndexPath]()
|
|
|
|
for (index, category) in self.categories.enumerated() {
|
|
let oldCategory = oldCategories[index]
|
|
|
|
// don't bother if we're not displaying this category
|
|
if oldCategory.section == nil && category.section == nil {
|
|
continue
|
|
}
|
|
|
|
// 1. add a new section if the section didn't previously exist
|
|
if oldCategory.section == nil && category.section != oldCategory.section {
|
|
sectionsToAdd.add(category.section!)
|
|
}
|
|
|
|
// 2. add a new row if there are more rows now than there were before
|
|
if oldCategory.rows < category.rows {
|
|
rowsToAdd.append(IndexPath(row: category.rows-1, section: category.section!))
|
|
}
|
|
|
|
// if we're dealing with the section where the row was deleted:
|
|
// 1. if the category no longer has a section, then we need to delete the entire section
|
|
// 2. delete a row if the number of rows has been reduced
|
|
// 3. delete the selected row and add a new one on the bottom of the section if the number of rows has stayed the same
|
|
if oldCategory.section == indexPath.section {
|
|
if category.section == nil {
|
|
sectionsToDelete.add(indexPath.section)
|
|
} else if oldCategory.section == category.section {
|
|
if oldCategory.rows > category.rows {
|
|
rowsToDelete.append(indexPath)
|
|
} else if category.rows == oldCategory.rows {
|
|
rowsToDelete.append(indexPath)
|
|
rowsToAdd.append(IndexPath(row: category.rows-1, section: indexPath.section))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
self.tableView.beginUpdates()
|
|
if sectionsToAdd.count > 0 {
|
|
self.tableView.insertSections(sectionsToAdd as IndexSet, with: UITableViewRowAnimation.left)
|
|
}
|
|
if sectionsToDelete.count > 0 {
|
|
self.tableView.deleteSections(sectionsToDelete as IndexSet, with: UITableViewRowAnimation.right)
|
|
}
|
|
if !rowsToDelete.isEmpty {
|
|
self.tableView.deleteRows(at: rowsToDelete, with: UITableViewRowAnimation.right)
|
|
}
|
|
|
|
if !rowsToAdd.isEmpty {
|
|
self.tableView.insertRows(at: rowsToAdd, with: UITableViewRowAnimation.right)
|
|
}
|
|
|
|
self.tableView.endUpdates()
|
|
self.updateEmptyPanelState()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, editActionsForRowAtIndexPath indexPath: IndexPath) -> [AnyObject]? {
|
|
if indexPath.section == 0 {
|
|
return []
|
|
}
|
|
let title = NSLocalizedString("Delete", tableName: "HistoryPanel", comment: "Action button for deleting history entries in the history panel.")
|
|
|
|
let delete = UITableViewRowAction(style: UITableViewRowActionStyle.default, title: title, handler: { (action, indexPath) in
|
|
self.removeHistoryForURLAtIndexPath(indexPath: indexPath)
|
|
})
|
|
return [delete]
|
|
}
|
|
}
|
|
|
|
extension HistoryPanel: HomePanelContextMenu {
|
|
func presentContextMenu(for site: Site, with indexPath: IndexPath, completionHandler: @escaping () -> PhotonActionSheet?) {
|
|
guard let contextMenu = completionHandler() else { return }
|
|
self.present(contextMenu, animated: true, completion: nil)
|
|
}
|
|
|
|
func getSiteDetails(for indexPath: IndexPath) -> Site? {
|
|
return siteForIndexPath(indexPath)
|
|
}
|
|
|
|
func getContextMenuActions(for site: Site, with indexPath: IndexPath) -> [PhotonActionSheetItem]? {
|
|
guard var actions = getDefaultContextMenuActions(for: site, homePanelDelegate: homePanelDelegate) else { return nil }
|
|
|
|
let removeAction = PhotonActionSheetItem(title: Strings.DeleteFromHistoryContextMenuTitle, iconString: "action_delete", handler: { action in
|
|
self.removeHistoryForURLAtIndexPath(indexPath: indexPath)
|
|
})
|
|
|
|
let pinTopSite = PhotonActionSheetItem(title: Strings.PinTopsiteActionTitle, iconString: "action_pin", handler: { action in
|
|
self.pinTopSite(site)
|
|
})
|
|
actions.append(pinTopSite)
|
|
actions.append(removeAction)
|
|
return actions
|
|
}
|
|
}
|