Merge pull request #2781 from omnivore-app/fix/mac-extension-update
Update safari extension, clean up the macos app
@ -1400,7 +1400,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MARKETING_VERSION = 1.30.0;
|
||||
MARKETING_VERSION = 1.31.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app;
|
||||
@ -1435,7 +1435,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MARKETING_VERSION = 1.30.0;
|
||||
MARKETING_VERSION = 1.31.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@ -1490,7 +1490,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.30.0;
|
||||
MARKETING_VERSION = 1.31.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app;
|
||||
PRODUCT_NAME = Omnivore;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -1831,7 +1831,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.30.0;
|
||||
MARKETING_VERSION = 1.31.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app;
|
||||
PRODUCT_NAME = Omnivore;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
||||
@ -47,7 +47,7 @@ let package = Package(
|
||||
.target(
|
||||
name: "Utils",
|
||||
dependencies: [
|
||||
.product(name: "PostHog", package: "posthog-ios")
|
||||
.product(name: "PostHog", package: "posthog-ios", condition: .when(platforms: [.iOS]))
|
||||
],
|
||||
resources: [.process("Resources")]
|
||||
),
|
||||
@ -58,7 +58,7 @@ let package = Package(
|
||||
var appPackageDependencies: [Target.Dependency] {
|
||||
var deps: [Target.Dependency] = ["Views", "Services", "Models", "Utils"]
|
||||
// Comment out following line for macOS build
|
||||
deps.append(.product(name: "PSPDFKit", package: "PSPDFKit-SP"))
|
||||
deps.append(.product(name: "PSPDFKit", package: "PSPDFKit-SP", condition: .when(platforms: [.iOS])))
|
||||
return deps
|
||||
}
|
||||
|
||||
|
||||
@ -12,18 +12,15 @@ struct MacFeedCardNavigationLink: View {
|
||||
@ObservedObject var viewModel: HomeFeedViewModel
|
||||
|
||||
var body: some View {
|
||||
NavigationLink(
|
||||
destination: LinkItemDetailView(
|
||||
ZStack {
|
||||
LibraryItemCard(item: item, viewer: dataService.currentViewer)
|
||||
NavigationLink(destination: LinkItemDetailView(
|
||||
linkedItemObjectID: item.objectID,
|
||||
isPDF: item.isPDF
|
||||
),
|
||||
tag: item.objectID,
|
||||
selection: $viewModel.selectedLinkItem
|
||||
) {
|
||||
LibraryItemCard(item: item, viewer: dataService.currentViewer)
|
||||
), label: {
|
||||
EmptyView()
|
||||
}).opacity(0)
|
||||
}
|
||||
.opacity(0)
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.onAppear {
|
||||
Task { await viewModel.itemAppeared(item: item, dataService: dataService) }
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ import Introspect
|
||||
import Models
|
||||
import Services
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
import Views
|
||||
|
||||
@MainActor final class FilterSelectorViewModel: NSObject, ObservableObject {
|
||||
@ -44,9 +43,11 @@ struct FilterSelectorView: View {
|
||||
.listStyle(.plain)
|
||||
#endif
|
||||
}
|
||||
.navigationBarTitle("Library")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(trailing: doneButton)
|
||||
#if os(iOS)
|
||||
.navigationBarTitle("Library")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(trailing: doneButton)
|
||||
#endif
|
||||
}
|
||||
|
||||
private var innerBody: some View {
|
||||
|
||||
@ -15,6 +15,8 @@ import Views
|
||||
@State private var confirmationShown = false
|
||||
@State private var presentProfileSheet = false
|
||||
|
||||
@Namespace var mainNamespace
|
||||
|
||||
@ObservedObject var viewModel: HomeFeedViewModel
|
||||
|
||||
func loadItems(isRefresh: Bool) {
|
||||
@ -26,6 +28,95 @@ import Views
|
||||
}
|
||||
}
|
||||
|
||||
func menuItems(_ item: LinkedItem) -> some View {
|
||||
Group {
|
||||
Button(
|
||||
action: { viewModel.itemUnderTitleEdit = item },
|
||||
label: { Label("Edit Info", systemImage: "info.circle") }
|
||||
)
|
||||
Button(
|
||||
action: { viewModel.itemUnderLabelEdit = item },
|
||||
label: { Label(item.labels?.count == 0 ? "Add Labels" : "Edit Labels", systemImage: "tag") }
|
||||
)
|
||||
Button(action: {
|
||||
withAnimation(.linear(duration: 0.4)) {
|
||||
viewModel.setLinkArchived(
|
||||
dataService: dataService,
|
||||
objectID: item.objectID,
|
||||
archived: !item.isArchived
|
||||
)
|
||||
}
|
||||
}, label: {
|
||||
Label(
|
||||
item.isArchived ? "Unarchive" : "Archive",
|
||||
systemImage: item.isArchived ? "tray.and.arrow.down.fill" : "archivebox"
|
||||
)
|
||||
})
|
||||
Button(
|
||||
action: {
|
||||
itemToRemove = item
|
||||
confirmationShown = true
|
||||
},
|
||||
label: { Label("Remove", systemImage: "trash") }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var refreshButton: some View {
|
||||
Button(
|
||||
action: {
|
||||
loadItems(isRefresh: true)
|
||||
},
|
||||
label: { Label("Refresh Feed", systemImage: "arrow.clockwise") }
|
||||
)
|
||||
.disabled(viewModel.isLoading)
|
||||
.opacity(viewModel.isLoading ? 0 : 1)
|
||||
.overlay {
|
||||
if viewModel.isLoading {
|
||||
ProgressView()
|
||||
.controlSize(.small)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var itemsList: some View {
|
||||
VStack {
|
||||
MacSearchBar(searchTerm: $viewModel.searchTerm)
|
||||
.padding(.leading, 10)
|
||||
.padding(.trailing, 10)
|
||||
.prefersDefaultFocus(false, in: mainNamespace)
|
||||
|
||||
List {
|
||||
ForEach(viewModel.items) { item in
|
||||
MacFeedCardNavigationLink(
|
||||
item: item,
|
||||
viewModel: viewModel
|
||||
)
|
||||
.listRowInsets(EdgeInsets())
|
||||
.contextMenu {
|
||||
menuItems(item)
|
||||
}
|
||||
Divider().padding(5)
|
||||
}
|
||||
.prefersDefaultFocus(true, in: mainNamespace)
|
||||
|
||||
if viewModel.isLoading {
|
||||
LoadingSection()
|
||||
}
|
||||
}
|
||||
.padding(0)
|
||||
.listStyle(InsetListStyle())
|
||||
.navigationTitle("Library")
|
||||
.onChange(of: viewModel.searchTerm) { _ in
|
||||
loadItems(isRefresh: true)
|
||||
}
|
||||
.toolbar {
|
||||
Spacer()
|
||||
refreshButton
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
if let linkRequest = viewModel.linkRequest {
|
||||
@ -37,97 +128,7 @@ import Views
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
List {
|
||||
Spacer(minLength: 10)
|
||||
|
||||
ForEach(viewModel.items) { item in
|
||||
MacFeedCardNavigationLink(
|
||||
item: item,
|
||||
viewModel: viewModel
|
||||
)
|
||||
.listRowInsets(EdgeInsets())
|
||||
.contextMenu {
|
||||
Button(
|
||||
action: { viewModel.itemUnderTitleEdit = item },
|
||||
label: { Label("Edit Info", systemImage: "info.circle") }
|
||||
)
|
||||
Button(
|
||||
action: { viewModel.itemUnderLabelEdit = item },
|
||||
label: { Label(item.labels?.count == 0 ? "Add Labels" : "Edit Labels", systemImage: "tag") }
|
||||
)
|
||||
Button(action: {
|
||||
withAnimation(.linear(duration: 0.4)) {
|
||||
viewModel.setLinkArchived(
|
||||
dataService: dataService,
|
||||
objectID: item.objectID,
|
||||
archived: !item.isArchived
|
||||
)
|
||||
}
|
||||
}, label: {
|
||||
Label(
|
||||
item.isArchived ? "Unarchive" : "Archive",
|
||||
systemImage: item.isArchived ? "tray.and.arrow.down.fill" : "archivebox"
|
||||
)
|
||||
})
|
||||
Button(
|
||||
action: {
|
||||
itemToRemove = item
|
||||
confirmationShown = true
|
||||
},
|
||||
label: { Label("Remove", systemImage: "trash") }
|
||||
)
|
||||
if FeatureFlag.enableSnooze {
|
||||
Button {
|
||||
viewModel.itemToSnoozeID = item.id
|
||||
viewModel.snoozePresented = true
|
||||
} label: {
|
||||
Label { Text(LocalText.genericSnooze) } icon: { Image.moon }
|
||||
}
|
||||
}
|
||||
}
|
||||
Divider().padding(5)
|
||||
}
|
||||
|
||||
if viewModel.isLoading {
|
||||
LoadingSection()
|
||||
}
|
||||
}
|
||||
.listStyle(InsetListStyle())
|
||||
.navigationTitle("Library")
|
||||
.searchable(
|
||||
text: $viewModel.searchTerm,
|
||||
placement: .toolbar
|
||||
) {
|
||||
if viewModel.searchTerm.isEmpty {
|
||||
Text(LocalText.inboxGeneric).searchCompletion("in:inbox ")
|
||||
Text(LocalText.allGeneric).searchCompletion("in:all ")
|
||||
Text(LocalText.archivedGeneric).searchCompletion("in:archive ")
|
||||
Text(LocalText.filesGeneric).searchCompletion("type:file ")
|
||||
}
|
||||
}
|
||||
.onChange(of: viewModel.searchTerm) { _ in
|
||||
loadItems(isRefresh: true)
|
||||
}
|
||||
.onSubmit(of: .search) {
|
||||
loadItems(isRefresh: true)
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem {
|
||||
Button(
|
||||
action: {
|
||||
loadItems(isRefresh: true)
|
||||
},
|
||||
label: { Label("Refresh Feed", systemImage: "arrow.clockwise") }
|
||||
)
|
||||
.disabled(viewModel.isLoading)
|
||||
.opacity(viewModel.isLoading ? 0 : 1)
|
||||
.overlay {
|
||||
if viewModel.isLoading {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
itemsList
|
||||
}
|
||||
.alert("Are you sure?", isPresented: $confirmationShown) {
|
||||
Button("Remove Link", role: .destructive) {
|
||||
@ -160,6 +161,7 @@ import Views
|
||||
loadItems(isRefresh: true)
|
||||
}
|
||||
}
|
||||
.focusScope(mainNamespace)
|
||||
.handlesExternalEvents(preferring: Set(["shareExtensionRequestID"]), allowing: Set(["*"]))
|
||||
.onOpenURL { url in
|
||||
viewModel.linkRequest = nil
|
||||
|
||||
@ -1,81 +1,93 @@
|
||||
#if os(iOS)
|
||||
import Introspect
|
||||
import Models
|
||||
import Services
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
import Views
|
||||
|
||||
@MainActor final class LibraryAddLinkViewModel: NSObject, ObservableObject {
|
||||
@Published var isLoading = false
|
||||
@Published var errorMessage: String = ""
|
||||
@Published var showErrorMessage: Bool = false
|
||||
import Introspect
|
||||
import Models
|
||||
import Services
|
||||
import SwiftUI
|
||||
import Views
|
||||
|
||||
func addLink(dataService: DataService, newLinkURL: String, dismiss: DismissAction) {
|
||||
isLoading = true
|
||||
Task {
|
||||
if URL(string: newLinkURL) == nil {
|
||||
error("Invalid link")
|
||||
@MainActor final class LibraryAddLinkViewModel: NSObject, ObservableObject {
|
||||
@Published var isLoading = false
|
||||
@Published var errorMessage: String = ""
|
||||
@Published var showErrorMessage: Bool = false
|
||||
|
||||
func addLink(dataService: DataService, newLinkURL: String, dismiss: DismissAction) {
|
||||
isLoading = true
|
||||
Task {
|
||||
if URL(string: newLinkURL) == nil {
|
||||
error("Invalid link")
|
||||
} else {
|
||||
let result = try? await dataService.saveURL(id: UUID().uuidString, url: newLinkURL)
|
||||
if result == nil {
|
||||
error("Error adding link")
|
||||
} else {
|
||||
let result = try? await dataService.saveURL(id: UUID().uuidString, url: newLinkURL)
|
||||
if result == nil {
|
||||
error("Error adding link")
|
||||
} else {
|
||||
dismiss()
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
func error(_ msg: String) {
|
||||
errorMessage = msg
|
||||
showErrorMessage = true
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
struct LibraryAddLinkView: View {
|
||||
@StateObject var viewModel = LibraryAddLinkViewModel()
|
||||
func error(_ msg: String) {
|
||||
errorMessage = msg
|
||||
showErrorMessage = true
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
@State var newLinkURL: String = ""
|
||||
@EnvironmentObject var dataService: DataService
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
struct LibraryAddLinkView: View {
|
||||
@StateObject var viewModel = LibraryAddLinkViewModel()
|
||||
|
||||
enum FocusField: Hashable {
|
||||
case addLinkEditor
|
||||
}
|
||||
@State var newLinkURL: String = ""
|
||||
@EnvironmentObject var dataService: DataService
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@FocusState private var focusedField: FocusField?
|
||||
enum FocusField: Hashable {
|
||||
case addLinkEditor
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
innerBody
|
||||
.navigationTitle("Add Link")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.onAppear {
|
||||
focusedField = .addLinkEditor
|
||||
}
|
||||
}
|
||||
@FocusState private var focusedField: FocusField?
|
||||
|
||||
var innerBody: some View {
|
||||
Form {
|
||||
TextField("Add Link", text: $newLinkURL)
|
||||
.keyboardType(.URL)
|
||||
.autocorrectionDisabled(true)
|
||||
.textFieldStyle(StandardTextFieldStyle())
|
||||
.focused($focusedField, equals: .addLinkEditor)
|
||||
|
||||
Button(action: {
|
||||
if let url = UIPasteboard.general.url {
|
||||
newLinkURL = url.absoluteString
|
||||
} else {
|
||||
viewModel.error("No URL on pasteboard")
|
||||
}
|
||||
}, label: {
|
||||
Text("Get from pasteboard")
|
||||
})
|
||||
}
|
||||
var body: some View {
|
||||
innerBody
|
||||
.navigationTitle("Add Link")
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
#endif
|
||||
.onAppear {
|
||||
focusedField = .addLinkEditor
|
||||
}
|
||||
}
|
||||
|
||||
var pasteboardString: String? {
|
||||
#if os(iOS)
|
||||
UIPasteboard.general.url?.absoluteString
|
||||
#else
|
||||
NSPasteboard.general.string(forType: NSPasteboard.PasteboardType.URL)
|
||||
#endif
|
||||
}
|
||||
|
||||
var innerBody: some View {
|
||||
Form {
|
||||
TextField("Add Link", text: $newLinkURL)
|
||||
#if os(iOS)
|
||||
.keyboardType(.URL)
|
||||
#endif
|
||||
.autocorrectionDisabled(true)
|
||||
.textFieldStyle(StandardTextFieldStyle())
|
||||
.focused($focusedField, equals: .addLinkEditor)
|
||||
|
||||
Button(action: {
|
||||
if let url = pasteboardString {
|
||||
newLinkURL = url
|
||||
} else {
|
||||
viewModel.error("No URL on pasteboard")
|
||||
}
|
||||
}, label: {
|
||||
Text("Get from pasteboard")
|
||||
})
|
||||
}
|
||||
.navigationTitle("Add Link")
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
@ -85,28 +97,28 @@
|
||||
viewModel.isLoading ? AnyView(ProgressView()) : AnyView(addButton)
|
||||
}
|
||||
}
|
||||
.alert(viewModel.errorMessage,
|
||||
isPresented: $viewModel.showErrorMessage) {
|
||||
Button(LocalText.genericOk, role: .cancel) { viewModel.showErrorMessage = false }
|
||||
}
|
||||
}
|
||||
|
||||
var addButton: some View {
|
||||
Button(
|
||||
action: {
|
||||
viewModel.addLink(dataService: dataService, newLinkURL: newLinkURL, dismiss: dismiss)
|
||||
},
|
||||
label: { Text("Add").bold() }
|
||||
)
|
||||
.disabled(viewModel.isLoading)
|
||||
}
|
||||
|
||||
var dismissButton: some View {
|
||||
Button(
|
||||
action: { dismiss() },
|
||||
label: { Text(LocalText.genericClose) }
|
||||
)
|
||||
.disabled(viewModel.isLoading)
|
||||
#endif
|
||||
.alert(viewModel.errorMessage,
|
||||
isPresented: $viewModel.showErrorMessage) {
|
||||
Button(LocalText.genericOk, role: .cancel) { viewModel.showErrorMessage = false }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
var addButton: some View {
|
||||
Button(
|
||||
action: {
|
||||
viewModel.addLink(dataService: dataService, newLinkURL: newLinkURL, dismiss: dismiss)
|
||||
},
|
||||
label: { Text("Add").bold() }
|
||||
)
|
||||
.disabled(viewModel.isLoading)
|
||||
}
|
||||
|
||||
var dismissButton: some View {
|
||||
Button(
|
||||
action: { dismiss() },
|
||||
label: { Text(LocalText.genericClose) }
|
||||
)
|
||||
.disabled(viewModel.isLoading)
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,14 +43,9 @@ struct LibraryTabView: View {
|
||||
)
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
NavigationView {
|
||||
HomeView(viewModel: libraryViewModel)
|
||||
.navigationBarHidden(false)
|
||||
}
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
EmptyView()
|
||||
NavigationView {
|
||||
HomeView(viewModel: libraryViewModel)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,10 +89,12 @@ struct LinkItemDetailView: View {
|
||||
pdfContainerView
|
||||
} else if let item = viewModel.item {
|
||||
WebReaderContainerView(item: item, pop: { dismiss() })
|
||||
#if os(iOS)
|
||||
.navigationBarHidden(true)
|
||||
.lazyPop(pop: {
|
||||
dismiss()
|
||||
}, isEnabled: $isEnabled)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
.task {
|
||||
|
||||
@ -9,6 +9,8 @@ import Views
|
||||
PrimaryContentCategory.profile
|
||||
]
|
||||
|
||||
@State var searchTerm: String = ""
|
||||
|
||||
public var body: some View {
|
||||
innerBody
|
||||
}
|
||||
@ -18,9 +20,10 @@ import Views
|
||||
if UIDevice.isIPad {
|
||||
return AnyView(splitView)
|
||||
} else {
|
||||
return AnyView(LibraryTabView())
|
||||
// .navigationViewStyle(.stack)
|
||||
// .navigationBarTitleDisplayMode(.inline)
|
||||
return AnyView(
|
||||
LibraryTabView()
|
||||
.navigationViewStyle(.stack)
|
||||
)
|
||||
}
|
||||
#else
|
||||
return AnyView(splitView)
|
||||
@ -33,7 +36,6 @@ import Views
|
||||
PrimaryContentCategory.feed.destinationView
|
||||
Text(LocalText.navigationSelectLink)
|
||||
}
|
||||
.accentColor(.appGrayTextContrast)
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -90,6 +92,7 @@ import Views
|
||||
.sheet(isPresented: $addLinkPresented) {
|
||||
NavigationView {
|
||||
LibraryAddLinkView()
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,60 +6,64 @@
|
||||
// Copyright © 2016 Warif Akhand Rishi. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
#if os(iOS)
|
||||
|
||||
class SlideAnimatedTransitioning: NSObject {}
|
||||
import UIKit
|
||||
|
||||
extension SlideAnimatedTransitioning: UIViewControllerAnimatedTransitioning {
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
let containerView = transitionContext.containerView
|
||||
guard
|
||||
let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
|
||||
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
|
||||
else {
|
||||
return
|
||||
class SlideAnimatedTransitioning: NSObject {}
|
||||
|
||||
extension SlideAnimatedTransitioning: UIViewControllerAnimatedTransitioning {
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
let containerView = transitionContext.containerView
|
||||
guard
|
||||
let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
|
||||
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
let width = containerView.frame.width
|
||||
|
||||
var offsetLeft = fromVC.view?.frame
|
||||
offsetLeft?.origin.x = width
|
||||
|
||||
var offscreenRight = fromVC.view?.frame
|
||||
offscreenRight?.origin.x = -width / 3.33
|
||||
|
||||
toVC.view?.frame = offscreenRight!
|
||||
|
||||
fromVC.view?.layer.shadowRadius = 5.0
|
||||
fromVC.view?.layer.shadowOpacity = 1.0
|
||||
toVC.view?.layer.opacity = 0.9
|
||||
|
||||
transitionContext.containerView.addSubview(toVC.view)
|
||||
transitionContext.containerView.addSubview(fromVC.view)
|
||||
|
||||
UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: .curveLinear, animations: {
|
||||
toVC.view?.frame = (fromVC.view?.frame)!
|
||||
fromVC.view?.frame = offsetLeft!
|
||||
|
||||
toVC.view?.layer.opacity = 1.0
|
||||
fromVC.view?.layer.shadowOpacity = 0.1
|
||||
|
||||
}, completion: { _ in
|
||||
if !transitionContext.transitionWasCancelled {
|
||||
toVC.view?.layer.opacity = 1.0
|
||||
toVC.view?.layer.shadowOpacity = 0
|
||||
fromVC.view?.layer.opacity = 1.0
|
||||
fromVC.view?.layer.shadowOpacity = 0
|
||||
|
||||
fromVC.view.removeFromSuperview()
|
||||
}
|
||||
// when cancelling or completing the animation, ios simulator seems to sometimes flash black backgrounds during the animation. on devices, this doesn't seem to happen though.
|
||||
// containerView.backgroundColor = [UIColor whiteColor];
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
})
|
||||
}
|
||||
|
||||
let width = containerView.frame.width
|
||||
|
||||
var offsetLeft = fromVC.view?.frame
|
||||
offsetLeft?.origin.x = width
|
||||
|
||||
var offscreenRight = fromVC.view?.frame
|
||||
offscreenRight?.origin.x = -width / 3.33
|
||||
|
||||
toVC.view?.frame = offscreenRight!
|
||||
|
||||
fromVC.view?.layer.shadowRadius = 5.0
|
||||
fromVC.view?.layer.shadowOpacity = 1.0
|
||||
toVC.view?.layer.opacity = 0.9
|
||||
|
||||
transitionContext.containerView.addSubview(toVC.view)
|
||||
transitionContext.containerView.addSubview(fromVC.view)
|
||||
|
||||
UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: .curveLinear, animations: {
|
||||
toVC.view?.frame = (fromVC.view?.frame)!
|
||||
fromVC.view?.frame = offsetLeft!
|
||||
|
||||
toVC.view?.layer.opacity = 1.0
|
||||
fromVC.view?.layer.shadowOpacity = 0.1
|
||||
|
||||
}, completion: { _ in
|
||||
if !transitionContext.transitionWasCancelled {
|
||||
toVC.view?.layer.opacity = 1.0
|
||||
toVC.view?.layer.shadowOpacity = 0
|
||||
fromVC.view?.layer.opacity = 1.0
|
||||
fromVC.view?.layer.shadowOpacity = 0
|
||||
|
||||
fromVC.view.removeFromSuperview()
|
||||
}
|
||||
// when cancelling or completing the animation, ios simulator seems to sometimes flash black backgrounds during the animation. on devices, this doesn't seem to happen though.
|
||||
// containerView.backgroundColor = [UIColor whiteColor];
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
})
|
||||
func transitionDuration(using _: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
0.3
|
||||
}
|
||||
}
|
||||
|
||||
func transitionDuration(using _: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
0.3
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -10,144 +10,146 @@
|
||||
// Copyright © 2019 Joseph Hinkle. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
private func < <T: Comparable>(lhs: T?, rhs: T?) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (lll?, rrr?):
|
||||
return lll < rrr
|
||||
case (nil, _?):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
#if os(iOS)
|
||||
|
||||
private func > <T: Comparable>(lhs: T?, rhs: T?) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (lll?, rrr?):
|
||||
return lll > rrr
|
||||
default:
|
||||
return rhs < lhs
|
||||
}
|
||||
}
|
||||
|
||||
class SwipeRightToPopViewController<Content>: UIHostingController<Content>, UINavigationControllerDelegate where Content: View {
|
||||
fileprivate var pop: (() -> Void)?
|
||||
fileprivate var lazyPopContent: LazyPop<Content>?
|
||||
private var percentDrivenInteractiveTransition: UIPercentDrivenInteractiveTransition?
|
||||
private var panGestureRecognizer: UIPanGestureRecognizer!
|
||||
private var parentNavigationControllerToUse: UINavigationController?
|
||||
private var gestureAdded = false
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
// You need to add gesture events after every subview layout to protect against weird edge cases
|
||||
// One notable edgecase is if you are in a splitview in landscape. In this case, there will be
|
||||
// no nav controller with 2 vcs, so our addGesture will fail. After rotating back to portrait,
|
||||
// the splitview will combine into one view with the details pushed on top. So only then would
|
||||
// would the addGesture find a parent nav controller with 2 view controllers. I don't know if
|
||||
// there are other edge cases, but running addGesture on every viewDidLayoutSubviews seems safe.
|
||||
addGesture()
|
||||
import SwiftUI
|
||||
private func < <T: Comparable>(lhs: T?, rhs: T?) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (lll?, rrr?):
|
||||
return lll < rrr
|
||||
case (nil, _?):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public func addGesture() {
|
||||
if !gestureAdded {
|
||||
// attempt to find a parent navigationController
|
||||
var currentVc: UIViewController = self
|
||||
while true {
|
||||
if currentVc.navigationController != nil,
|
||||
currentVc.navigationController?.viewControllers.count > 1
|
||||
{
|
||||
parentNavigationControllerToUse = currentVc.navigationController
|
||||
break
|
||||
private func > <T: Comparable>(lhs: T?, rhs: T?) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (lll?, rrr?):
|
||||
return lll > rrr
|
||||
default:
|
||||
return rhs < lhs
|
||||
}
|
||||
}
|
||||
|
||||
class SwipeRightToPopViewController<Content>: UIHostingController<Content>, UINavigationControllerDelegate where Content: View {
|
||||
fileprivate var pop: (() -> Void)?
|
||||
fileprivate var lazyPopContent: LazyPop<Content>?
|
||||
private var percentDrivenInteractiveTransition: UIPercentDrivenInteractiveTransition?
|
||||
private var panGestureRecognizer: UIPanGestureRecognizer!
|
||||
private var parentNavigationControllerToUse: UINavigationController?
|
||||
private var gestureAdded = false
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
// You need to add gesture events after every subview layout to protect against weird edge cases
|
||||
// One notable edgecase is if you are in a splitview in landscape. In this case, there will be
|
||||
// no nav controller with 2 vcs, so our addGesture will fail. After rotating back to portrait,
|
||||
// the splitview will combine into one view with the details pushed on top. So only then would
|
||||
// would the addGesture find a parent nav controller with 2 view controllers. I don't know if
|
||||
// there are other edge cases, but running addGesture on every viewDidLayoutSubviews seems safe.
|
||||
addGesture()
|
||||
}
|
||||
|
||||
public func addGesture() {
|
||||
if !gestureAdded {
|
||||
// attempt to find a parent navigationController
|
||||
var currentVc: UIViewController = self
|
||||
while true {
|
||||
if currentVc.navigationController != nil,
|
||||
currentVc.navigationController?.viewControllers.count > 1
|
||||
{
|
||||
parentNavigationControllerToUse = currentVc.navigationController
|
||||
break
|
||||
}
|
||||
guard let parent = currentVc.parent else {
|
||||
return
|
||||
}
|
||||
currentVc = parent
|
||||
}
|
||||
guard let parent = currentVc.parent else {
|
||||
guard parentNavigationControllerToUse?.viewControllers.count > 1 else {
|
||||
return
|
||||
}
|
||||
currentVc = parent
|
||||
}
|
||||
guard parentNavigationControllerToUse?.viewControllers.count > 1 else {
|
||||
return
|
||||
}
|
||||
|
||||
panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(SwipeRightToPopViewController.handlePanGesture(_:)))
|
||||
view.addGestureRecognizer(panGestureRecognizer)
|
||||
gestureAdded = true
|
||||
panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(SwipeRightToPopViewController.handlePanGesture(_:)))
|
||||
view.addGestureRecognizer(panGestureRecognizer)
|
||||
gestureAdded = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func handlePanGesture(_ panGesture: UIPanGestureRecognizer) {
|
||||
// if the parentNavigationControllerToUse has a width value, use that because it's more accurate. Otherwise use this view's width as a backup
|
||||
let total = parentNavigationControllerToUse?.view.frame.width ?? view.frame.width
|
||||
let percent = max(panGesture.translation(in: view).x, 0) / total
|
||||
@objc func handlePanGesture(_ panGesture: UIPanGestureRecognizer) {
|
||||
// if the parentNavigationControllerToUse has a width value, use that because it's more accurate. Otherwise use this view's width as a backup
|
||||
let total = parentNavigationControllerToUse?.view.frame.width ?? view.frame.width
|
||||
let percent = max(panGesture.translation(in: view).x, 0) / total
|
||||
|
||||
switch panGesture.state {
|
||||
case .began:
|
||||
if lazyPopContent?.isEnabled == true {
|
||||
parentNavigationControllerToUse?.delegate = self
|
||||
if let pop = self.pop {
|
||||
pop()
|
||||
switch panGesture.state {
|
||||
case .began:
|
||||
if lazyPopContent?.isEnabled == true {
|
||||
parentNavigationControllerToUse?.delegate = self
|
||||
if let pop = self.pop {
|
||||
pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case .changed:
|
||||
if let percentDrivenInteractiveTransition = percentDrivenInteractiveTransition {
|
||||
percentDrivenInteractiveTransition.update(percent)
|
||||
}
|
||||
case .changed:
|
||||
if let percentDrivenInteractiveTransition = percentDrivenInteractiveTransition {
|
||||
percentDrivenInteractiveTransition.update(percent)
|
||||
}
|
||||
|
||||
case .ended:
|
||||
let velocity = panGesture.velocity(in: view).x
|
||||
case .ended:
|
||||
let velocity = panGesture.velocity(in: view).x
|
||||
|
||||
// Continue if drag more than 50% of screen width or velocity is higher than 100
|
||||
if percent > 0.5 || velocity > 100 {
|
||||
percentDrivenInteractiveTransition?.finish()
|
||||
} else {
|
||||
// Continue if drag more than 50% of screen width or velocity is higher than 100
|
||||
if percent > 0.5 || velocity > 100 {
|
||||
percentDrivenInteractiveTransition?.finish()
|
||||
} else {
|
||||
percentDrivenInteractiveTransition?.cancel()
|
||||
}
|
||||
|
||||
case .cancelled, .failed:
|
||||
percentDrivenInteractiveTransition?.cancel()
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning() {
|
||||
super.didReceiveMemoryWarning()
|
||||
}
|
||||
|
||||
func navigationController(_: UINavigationController,
|
||||
animationControllerFor _: UINavigationController.Operation,
|
||||
from _: UIViewController,
|
||||
to _: UIViewController) -> UIViewControllerAnimatedTransitioning?
|
||||
{
|
||||
if #available(iOS 17.0, *) {
|
||||
return nil
|
||||
} else if UIDevice.isIPad {
|
||||
return nil
|
||||
} else {
|
||||
return SlideAnimatedTransitioning()
|
||||
}
|
||||
}
|
||||
|
||||
func navigationController(_: UINavigationController,
|
||||
interactionControllerFor _: UIViewControllerAnimatedTransitioning)
|
||||
-> UIViewControllerInteractiveTransitioning?
|
||||
{
|
||||
parentNavigationControllerToUse?.delegate = nil
|
||||
// navigationController.delegate = nil
|
||||
|
||||
if panGestureRecognizer.state == .began {
|
||||
percentDrivenInteractiveTransition = UIPercentDrivenInteractiveTransition()
|
||||
percentDrivenInteractiveTransition?.completionCurve = .easeOut
|
||||
} else {
|
||||
percentDrivenInteractiveTransition = nil
|
||||
}
|
||||
|
||||
case .cancelled, .failed:
|
||||
percentDrivenInteractiveTransition?.cancel()
|
||||
|
||||
default:
|
||||
break
|
||||
return percentDrivenInteractiveTransition
|
||||
}
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning() {
|
||||
super.didReceiveMemoryWarning()
|
||||
}
|
||||
|
||||
func navigationController(_: UINavigationController,
|
||||
animationControllerFor _: UINavigationController.Operation,
|
||||
from _: UIViewController,
|
||||
to _: UIViewController) -> UIViewControllerAnimatedTransitioning?
|
||||
{
|
||||
if #available(iOS 17.0, *) {
|
||||
return nil
|
||||
} else if UIDevice.isIPad {
|
||||
return nil
|
||||
} else {
|
||||
return SlideAnimatedTransitioning()
|
||||
}
|
||||
}
|
||||
|
||||
func navigationController(_: UINavigationController,
|
||||
interactionControllerFor _: UIViewControllerAnimatedTransitioning)
|
||||
-> UIViewControllerInteractiveTransitioning?
|
||||
{
|
||||
parentNavigationControllerToUse?.delegate = nil
|
||||
// navigationController.delegate = nil
|
||||
|
||||
if panGestureRecognizer.state == .began {
|
||||
percentDrivenInteractiveTransition = UIPercentDrivenInteractiveTransition()
|
||||
percentDrivenInteractiveTransition?.completionCurve = .easeOut
|
||||
} else {
|
||||
percentDrivenInteractiveTransition = nil
|
||||
}
|
||||
|
||||
return percentDrivenInteractiveTransition
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Lazy Pop SwiftUI Component
|
||||
//
|
||||
@ -155,33 +157,35 @@ class SwipeRightToPopViewController<Content>: UIHostingController<Content>, UINa
|
||||
// Copyright © 2019 Joseph Hinkle. All rights reserved.
|
||||
//
|
||||
|
||||
private struct LazyPop<Content: View>: UIViewControllerRepresentable {
|
||||
let rootView: Content
|
||||
let pop: () -> Void
|
||||
@Binding var isEnabled: Bool
|
||||
private struct LazyPop<Content: View>: UIViewControllerRepresentable {
|
||||
let rootView: Content
|
||||
let pop: () -> Void
|
||||
@Binding var isEnabled: Bool
|
||||
|
||||
init(_ rootView: Content, pop: @escaping () -> Void, isEnabled: (Binding<Bool>)? = nil) {
|
||||
self.rootView = rootView
|
||||
self.pop = pop
|
||||
self._isEnabled = isEnabled ?? Binding<Bool>(get: { true }, set: { _ in })
|
||||
}
|
||||
init(_ rootView: Content, pop: @escaping () -> Void, isEnabled: (Binding<Bool>)? = nil) {
|
||||
self.rootView = rootView
|
||||
self.pop = pop
|
||||
self._isEnabled = isEnabled ?? Binding<Bool>(get: { true }, set: { _ in })
|
||||
}
|
||||
|
||||
func makeUIViewController(context _: Context) -> UIViewController {
|
||||
let vc = SwipeRightToPopViewController(rootView: rootView)
|
||||
vc.pop = pop
|
||||
vc.lazyPopContent = self
|
||||
return vc
|
||||
}
|
||||
func makeUIViewController(context _: Context) -> UIViewController {
|
||||
let vc = SwipeRightToPopViewController(rootView: rootView)
|
||||
vc.pop = pop
|
||||
vc.lazyPopContent = self
|
||||
return vc
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewController, context _: Context) {
|
||||
if let host = uiViewController as? UIHostingController<Content> {
|
||||
host.rootView = rootView
|
||||
func updateUIViewController(_ uiViewController: UIViewController, context _: Context) {
|
||||
if let host = uiViewController as? UIHostingController<Content> {
|
||||
host.rootView = rootView
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension View {
|
||||
func lazyPop(pop: @escaping () -> Void, isEnabled: (Binding<Bool>)? = nil) -> some View {
|
||||
LazyPop(self, pop: pop, isEnabled: isEnabled)
|
||||
public extension View {
|
||||
func lazyPop(pop: @escaping () -> Void, isEnabled: (Binding<Bool>)? = nil) -> some View {
|
||||
LazyPop(self, pop: pop, isEnabled: isEnabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -427,7 +427,9 @@ struct WebReaderContainerView: View {
|
||||
showHighlightAnnotationModal: $showHighlightAnnotationModal
|
||||
)
|
||||
.background(ThemeManager.currentBgColor)
|
||||
.statusBar(hidden: prefersHideStatusBarInReader)
|
||||
#if os(iOS)
|
||||
.statusBar(hidden: prefersHideStatusBarInReader)
|
||||
#endif
|
||||
.onAppear {
|
||||
if item.isUnread {
|
||||
dataService.updateLinkReadingProgress(itemID: item.unwrappedID, readingProgress: 0.1, anchorIndex: 0)
|
||||
|
||||
@ -7,7 +7,6 @@ import Views
|
||||
struct WelcomeView: View {
|
||||
@EnvironmentObject var dataService: DataService
|
||||
@EnvironmentObject var authenticator: Authenticator
|
||||
@Environment(\.horizontalSizeClass) var horizontalSizeClass
|
||||
@Environment(\.openURL) var openURL
|
||||
|
||||
@StateObject private var viewModel = RegistrationViewModel()
|
||||
|
||||
@ -1,24 +1,29 @@
|
||||
import Foundation
|
||||
import PostHog
|
||||
|
||||
#if os(iOS)
|
||||
import PostHog
|
||||
#endif
|
||||
|
||||
public enum EventTracker {
|
||||
public static var posthog: PHGPostHog? = {
|
||||
guard let writeKey = AppKeys.sharedInstance?.posthogClientKey else {
|
||||
return nil
|
||||
}
|
||||
#if os(iOS)
|
||||
public static var posthog: PHGPostHog? = {
|
||||
guard let writeKey = AppKeys.sharedInstance?.posthogClientKey else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let posthogInstanceAddress = AppKeys.sharedInstance?.posthogInstanceAddress else {
|
||||
return nil
|
||||
}
|
||||
guard let posthogInstanceAddress = AppKeys.sharedInstance?.posthogInstanceAddress else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let configuration = PHGPostHogConfiguration(apiKey: writeKey, host: posthogInstanceAddress)
|
||||
let configuration = PHGPostHogConfiguration(apiKey: writeKey, host: posthogInstanceAddress)
|
||||
|
||||
configuration.recordScreenViews = false
|
||||
configuration.captureApplicationLifecycleEvents = true
|
||||
configuration.recordScreenViews = false
|
||||
configuration.captureApplicationLifecycleEvents = true
|
||||
|
||||
PHGPostHog.setup(with: configuration)
|
||||
return PHGPostHog.shared()
|
||||
}()
|
||||
PHGPostHog.setup(with: configuration)
|
||||
return PHGPostHog.shared()
|
||||
}()
|
||||
#endif
|
||||
|
||||
public static func trackForDebugging(_ message: String) {
|
||||
#if DEBUG
|
||||
@ -27,14 +32,20 @@ public enum EventTracker {
|
||||
}
|
||||
|
||||
public static func track(_ event: TrackableEvent) {
|
||||
posthog?.capture(event.name, properties: event.properties)
|
||||
#if os(iOS)
|
||||
posthog?.capture(event.name, properties: event.properties)
|
||||
#endif
|
||||
}
|
||||
|
||||
public static func registerUser(userID: String) {
|
||||
posthog?.identify(userID)
|
||||
#if os(iOS)
|
||||
posthog?.identify(userID)
|
||||
#endif
|
||||
}
|
||||
|
||||
public static func reset() {
|
||||
posthog?.reset()
|
||||
#if os(iOS)
|
||||
posthog?.reset()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
57
apple/OmnivoreKit/Sources/Views/MacSearchBar.swift
Normal file
@ -0,0 +1,57 @@
|
||||
import SwiftUI
|
||||
|
||||
#if os(macOS)
|
||||
public struct MacSearchBar: NSViewRepresentable {
|
||||
@Binding var searchTerm: String
|
||||
|
||||
public init(
|
||||
searchTerm: Binding<String>
|
||||
) {
|
||||
self._searchTerm = searchTerm
|
||||
}
|
||||
|
||||
public func makeNSView(context _: Context) -> NSSearchField {
|
||||
let searchField = NSSearchField(frame: .zero)
|
||||
searchField.translatesAutoresizingMaskIntoConstraints = false
|
||||
searchField.heightAnchor.constraint(greaterThanOrEqualToConstant: 28).isActive = true
|
||||
searchField.resignFirstResponder()
|
||||
|
||||
return searchField
|
||||
}
|
||||
|
||||
func changeSearchFieldItem(searchField: NSSearchField, sender: AnyObject) -> NSSearchField {
|
||||
// Based on the Menu item selection in the search field the placeholder string is set
|
||||
(searchField.cell as? NSSearchFieldCell)?.placeholderString = sender.title
|
||||
return searchField
|
||||
}
|
||||
|
||||
public func updateNSView(_ searchField: NSSearchField, context: Context) {
|
||||
searchField.font = searchField.font?.withSize(14)
|
||||
searchField.stringValue = searchTerm
|
||||
searchField.delegate = context.coordinator
|
||||
searchField.resignFirstResponder()
|
||||
}
|
||||
|
||||
public func makeCoordinator() -> Coordinator {
|
||||
let coordinator = Coordinator(searchTerm: $searchTerm)
|
||||
return coordinator
|
||||
}
|
||||
|
||||
public class Coordinator: NSObject, NSSearchFieldDelegate {
|
||||
var searchTerm: Binding<String>
|
||||
|
||||
init(searchTerm: Binding<String>) {
|
||||
self.searchTerm = searchTerm
|
||||
super.init()
|
||||
}
|
||||
|
||||
public func controlTextDidChange(_ notification: Notification) {
|
||||
guard let searchField = notification.object as? NSSearchField else {
|
||||
// log.error("Unexpected control in update notification", source: .ui)
|
||||
return
|
||||
}
|
||||
searchTerm.wrappedValue = searchField.stringValue
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -8,10 +8,10 @@ import SwiftUI
|
||||
|
||||
// https://stackoverflow.com/questions/63526478/swiftui-userinterfacesizeclass-for-universal-macos-ios-views
|
||||
#if os(macOS)
|
||||
public enum UserInterfaceSizeClass {
|
||||
case compact
|
||||
case regular
|
||||
}
|
||||
// public enum UserInterfaceSizeClass {
|
||||
// case compact
|
||||
// case regular
|
||||
// }
|
||||
|
||||
public struct HorizontalSizeClassEnvironmentKey: EnvironmentKey {
|
||||
public static let defaultValue: UserInterfaceSizeClass = .regular
|
||||
|
||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 348 B After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 572 B After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 716 B After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 10 KiB |
@ -2,7 +2,7 @@
|
||||
"manifest_version": 2,
|
||||
"name": "Omnivore",
|
||||
"short_name": "Omnivore",
|
||||
"version": "2.2.0",
|
||||
"version": "2.4.2",
|
||||
"description": "Save PDFs and Articles to your Omnivore library",
|
||||
"author": "Omnivore Media, Inc",
|
||||
"default_locale": "en",
|
||||
@ -75,12 +75,12 @@
|
||||
],
|
||||
"browser_action": {
|
||||
"default_icon": {
|
||||
"16": "/images/toolbar/icon-16.png",
|
||||
"19": "/images/toolbar/icon-19.png",
|
||||
"24": "/images/toolbar/icon-24.png",
|
||||
"32": "/images/toolbar/icon-32.png",
|
||||
"38": "/images/toolbar/icon-38.png",
|
||||
"48": "/images/toolbar/icon-48.png"
|
||||
"16": "/images/extension/icon-16.png",
|
||||
"19": "/images/extension/icon-19.png",
|
||||
"24": "/images/extension/icon-24.png",
|
||||
"32": "/images/extension/icon-32.png",
|
||||
"38": "/images/extension/icon-38.png",
|
||||
"48": "/images/extension/icon-48.png"
|
||||
},
|
||||
"default_title": "Omnivore Save Article"
|
||||
},
|
||||
|
||||
@ -1 +1 @@
|
||||
"use strict";!function(){const e={};function t(t){const n=t.tagName.toLowerCase();if("iframe"===n){const n=e[t.src];if(!n)return;const o=document.createElement("div");o.className="omnivore-instagram-embed",o.innerHTML=n;const r=t.parentNode;if(!r)return;return void r.replaceChild(o,t)}if("img"===n||"image"===n){if(-1===window.getComputedStyle(t).getPropertyValue("filter").indexOf("blur("))return;return void t.remove()}const o=window.getComputedStyle(t),r=o.getPropertyValue("background-image");if(r&&"none"!==r)return;const i=o.getPropertyValue("filter");if(i&&-1!==i.indexOf("blur("))return void t.remove();if(t.src)return;if(t.innerHTML.length>24)return;const c=/url\("(.+?)"\)/gi,a=c.exec(r);c.lastIndex=0;const l=a&&a[1];if(!l)return;const s=document.createElement("img");s.src=l;const d=t.parentNode;d&&d.replaceChild(s,t)}function n(){const e=document.createElement("div");e.style.position="absolute",e.style.left="-2000px",e.style.zIndex="-2000",e.innerHTML=document.body.innerHTML,document.documentElement.appendChild(e),Array.from(e.getElementsByTagName("*")).forEach(t);try{if("undefined"!=typeof create_time&&create_time){const t=new Date(1e3*create_time),n=document.createElement("div");n.className="omnivore-published-date",n.innerHTML=t.toLocaleString(),e.appendChild(n)}}catch(e){console.log("Error while trying to add published date to WeChat post",e)}const n=`<html><head>${document.head.innerHTML}</head><body>${e.innerHTML}</body></html>`;return e.remove(),n}function o(){const e=document.querySelectorAll(".webext-omnivore-backdrop");for(let t=0;t<e.length;t++)e[t].style.setProperty("opacity","0","important");setTimeout((()=>{for(let t=0;t<e.length;t++)e[t].remove()}),500)}browserApi.runtime.onMessage.addListener((({action:t,payload:n},o,r)=>{if(t!==ACTIONS.AddIframeContent)return;const{url:i,content:c}=n;e[i]=c,r({})})),window.prepareContent=async function(){const e=await async function(){const e=".pdf"===window.location.pathname.slice(-4).toLowerCase(),t=-1!==["application/acrobat","application/pdf","application/x-pdf","applications/vnd.pdf","text/pdf","text/x-pdf"].indexOf(document.contentType);if(!e&&!t)return Promise.resolve(null);const n=document.querySelector("embed");return n&&"application/pdf"!==n.type?Promise.resolve(null):ENV_DOES_NOT_SUPPORT_BLOB_URL_ACCESS&&n.src?Promise.resolve({type:"url",uploadContentObjUrl:n.src}):new Promise(((e,t)=>{const n=new XMLHttpRequest;n.open("GET","",!0),n.responseType="blob",n.onload=function(n){200===this.status?e({type:"pdf",uploadContentObjUrl:URL.createObjectURL(this.response)}):t(n)},n.send()}))}();if(e)return e;const t=window.location.href;try{if(handleBackendUrl(t))return{type:"url"}}catch{console.log("error checking url")}return await async function(e){const t=document.scrollingElement||document.body,n=t.scrollTop,r=t.scrollHeight;o();const i=function(){const e=document.createElement("div");return e.className="webext-omnivore-backdrop",e.style.cssText="all: initial !important;\n position: fixed !important;\n top: 0 !important;\n right: 0 !important;\n bottom: 0 !important;\n left: 0 !important;\n z-index: 99999 !important;\n background: #fff !important;\n opacity: 0.8 !important;\n transition: opacity 0.3s !important;\n -webkit-backdrop-filter: blur(4px) !important;\n backdrop-filter: blur(4px) !important;\n ",e}();for(document.body.appendChild(i);t.scrollTop<=r-500&&window.location.href===e;){const e=t.scrollTop;if(t.scrollTop+=500,await new Promise((e=>{setTimeout(e,10)})),t.scrollTop===e)break}t.scrollTop=n,await new Promise((e=>{setTimeout(e,10)}))}(t),o(),{type:"html",content:n()}}}();
|
||||
"use strict";!function(){const e={};function t(t){const n=t.tagName.toLowerCase();if("iframe"===n){const n=e[t.src];if(!n)return;const o=document.createElement("div");o.className="omnivore-instagram-embed",o.innerHTML=n;const r=t.parentNode;if(!r)return;return void r.replaceChild(o,t)}if("img"===n||"image"===n){if(-1===window.getComputedStyle(t).getPropertyValue("filter").indexOf("blur("))return;return void t.remove()}const o=window.getComputedStyle(t),r=o.getPropertyValue("background-image");if(r&&"none"!==r)return;const i=o.getPropertyValue("filter");if(i&&-1!==i.indexOf("blur("))return void t.remove();if(t.src)return;if(t.innerHTML.length>24)return;const c=/url\("(.+?)"\)/gi,a=c.exec(r);c.lastIndex=0;const l=a&&a[1];if(!l)return;const s=document.createElement("img");s.src=l;const p=t.parentNode;p&&p.replaceChild(s,t)}function n(){const e=document.createElement("div");e.style.position="absolute",e.style.left="-2000px",e.style.zIndex="-2000",e.innerHTML=document.body.innerHTML,document.documentElement.appendChild(e),Array.from(e.getElementsByTagName("*")).forEach(t);const n=`<html><head>${document.head.innerHTML}</head><body>${e.innerHTML}</body></html>`;return e.remove(),n}function o(){const e=document.querySelectorAll(".webext-omnivore-backdrop");for(let t=0;t<e.length;t++)e[t].style.setProperty("opacity","0","important");setTimeout((()=>{for(let t=0;t<e.length;t++)e[t].remove()}),500)}browserApi.runtime.onMessage.addListener((({action:t,payload:n},o,r)=>{if(t!==ACTIONS.AddIframeContent)return;const{url:i,content:c}=n;e[i]=c,r({})})),window.prepareContent=async function(){const e=await async function(){const e=".pdf"===window.location.pathname.slice(-4).toLowerCase(),t=-1!==["application/acrobat","application/pdf","application/x-pdf","applications/vnd.pdf","text/pdf","text/x-pdf"].indexOf(document.contentType);if(!e&&!t)return Promise.resolve(null);const n=document.querySelector("embed");return n&&"application/pdf"!==n.type?Promise.resolve(null):ENV_DOES_NOT_SUPPORT_BLOB_URL_ACCESS&&n.src?Promise.resolve({type:"url",uploadContentObjUrl:n.src}):new Promise(((e,t)=>{const n=new XMLHttpRequest;n.open("GET","",!0),n.responseType="blob",n.onload=function(n){200===this.status?e({type:"pdf",uploadContentObjUrl:URL.createObjectURL(this.response)}):t(n)},n.send()}))}();if(e)return e;const t=window.location.href;try{if(handleBackendUrl(t))return{type:"url"}}catch{console.log("error checking url")}return await async function(e){const t=document.scrollingElement||document.body,n=t.scrollTop,r=t.scrollHeight;o();const i=function(){const e=document.createElement("div");return e.className="webext-omnivore-backdrop",e.style.cssText="all: initial !important;\n position: fixed !important;\n top: 0 !important;\n right: 0 !important;\n bottom: 0 !important;\n left: 0 !important;\n z-index: 99999 !important;\n background: #fff !important;\n opacity: 0.8 !important;\n transition: opacity 0.3s !important;\n -webkit-backdrop-filter: blur(4px) !important;\n backdrop-filter: blur(4px) !important;\n ",e}();for(document.body.appendChild(i);t.scrollTop<=r-500&&window.location.href===e;){const e=t.scrollTop;if(t.scrollTop+=500,await new Promise((e=>{setTimeout(e,10)})),t.scrollTop===e)break}t.scrollTop=n,await new Promise((e=>{setTimeout(e,10)}))}(t),o(),{type:"html",content:n()}}}();
|
||||