From 23a03e4aa2b4be15ba427ff0cd9fd674a2fb372a Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 28 Jun 2022 15:17:50 -0700 Subject: [PATCH 01/10] rename WebView to OmnivoreWebView to avaoid clash with AppKit class --- .../App/Views/WebReader/WebReader.swift | 14 ++++++------- .../{WebView.swift => OmnivoreWebView.swift} | 4 ++-- .../Sources/Views/Article/WebAppView.swift | 20 +++++++++---------- .../Sources/Views/Web/BasicWebAppView.swift | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) rename apple/OmnivoreKit/Sources/Views/Article/{WebView.swift => OmnivoreWebView.swift} (98%) diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift index 2c8757026..bafbaf6d1 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift @@ -77,32 +77,32 @@ import WebKit func updateUIView(_ webView: WKWebView, context: Context) { if annotationSaveTransactionID != context.coordinator.lastSavedAnnotationID { context.coordinator.lastSavedAnnotationID = annotationSaveTransactionID - (webView as? WebView)?.dispatchEvent(.saveAnnotation(annotation: annotation)) + (webView as? OmnivoreWebView)?.dispatchEvent(.saveAnnotation(annotation: annotation)) } if updateFontFamilyActionID != context.coordinator.previousUpdateFontFamilyActionID { context.coordinator.previousUpdateFontFamilyActionID = updateFontFamilyActionID - (webView as? WebView)?.updateFontFamily() + (webView as? OmnivoreWebView)?.updateFontFamily() } if updateFontActionID != context.coordinator.previousUpdateFontActionID { context.coordinator.previousUpdateFontActionID = updateFontActionID - (webView as? WebView)?.updateFontSize() + (webView as? OmnivoreWebView)?.updateFontSize() } if updateTextContrastActionID != context.coordinator.previousUpdateTextContrastActionID { context.coordinator.previousUpdateTextContrastActionID = updateTextContrastActionID - (webView as? WebView)?.updateTextContrast() + (webView as? OmnivoreWebView)?.updateTextContrast() } if updateMaxWidthActionID != context.coordinator.previousUpdateMaxWidthActionID { context.coordinator.previousUpdateMaxWidthActionID = updateMaxWidthActionID - (webView as? WebView)?.updateMaxWidthPercentage() + (webView as? OmnivoreWebView)?.updateMaxWidthPercentage() } if updateLineHeightActionID != context.coordinator.previousUpdateLineHeightActionID { context.coordinator.previousUpdateLineHeightActionID = updateLineHeightActionID - (webView as? WebView)?.updateLineHeight() + (webView as? OmnivoreWebView)?.updateLineHeight() } if showNavBarActionID != context.coordinator.previousShowNavBarActionID { @@ -112,7 +112,7 @@ import WebKit if shareActionID != context.coordinator.previousShareActionID { context.coordinator.previousShareActionID = shareActionID - (webView as? WebView)?.shareOriginalItem() + (webView as? OmnivoreWebView)?.shareOriginalItem() } // If the webview had been terminated `needsReload` will have been set to true diff --git a/apple/OmnivoreKit/Sources/Views/Article/WebView.swift b/apple/OmnivoreKit/Sources/Views/Article/OmnivoreWebView.swift similarity index 98% rename from apple/OmnivoreKit/Sources/Views/Article/WebView.swift rename to apple/OmnivoreKit/Sources/Views/Article/OmnivoreWebView.swift index 3828ea872..bc27bcbb4 100644 --- a/apple/OmnivoreKit/Sources/Views/Article/WebView.swift +++ b/apple/OmnivoreKit/Sources/Views/Article/OmnivoreWebView.swift @@ -8,7 +8,7 @@ public enum WebViewAction: String, CaseIterable { case readingProgressUpdate } -public final class WebView: WKWebView { +public final class OmnivoreWebView: WKWebView { #if os(iOS) private var panGestureRecognizer: UIPanGestureRecognizer? private var tapGestureRecognizer: UITapGestureRecognizer? @@ -95,7 +95,7 @@ public final class WebView: WKWebView { } #if os(iOS) - extension WebView: UIGestureRecognizerDelegate, WKScriptMessageHandler { + extension OmnivoreWebView: UIGestureRecognizerDelegate, WKScriptMessageHandler { func initNativeIOSMenus() { isUserInteractionEnabled = true diff --git a/apple/OmnivoreKit/Sources/Views/Article/WebAppView.swift b/apple/OmnivoreKit/Sources/Views/Article/WebAppView.swift index 6aa5e1803..bb390ac1b 100644 --- a/apple/OmnivoreKit/Sources/Views/Article/WebAppView.swift +++ b/apple/OmnivoreKit/Sources/Views/Article/WebAppView.swift @@ -20,12 +20,12 @@ enum WebViewConfigurationManager { public enum WebViewManager { public static let sharedView = create() - public static func shared() -> WebView { + public static func shared() -> OmnivoreWebView { sharedView } - public static func create() -> WebView { - WebView(frame: CGRect.zero, configuration: WebViewConfigurationManager.create()) + public static func create() -> OmnivoreWebView { + OmnivoreWebView(frame: CGRect.zero, configuration: WebViewConfigurationManager.create()) } } @@ -95,17 +95,17 @@ public enum WebViewManager { if annotationSaveTransactionID != context.coordinator.lastSavedAnnotationID { context.coordinator.lastSavedAnnotationID = annotationSaveTransactionID - (webView as? WebView)?.dispatchEvent(.saveAnnotation(annotation: annotation)) + (webView as? OmnivoreWebView)?.dispatchEvent(.saveAnnotation(annotation: annotation)) } if sendIncreaseFontSignal { sendIncreaseFontSignal = false - (webView as? WebView)?.updateFontSize() + (webView as? OmnivoreWebView)?.updateFontSize() } if sendDecreaseFontSignal { sendDecreaseFontSignal = false - (webView as? WebView)?.updateFontSize() + (webView as? OmnivoreWebView)?.updateFontSize() } } } @@ -135,7 +135,7 @@ public enum WebViewManager { func makeNSView(context: Context) -> WKWebView { let contentController = WKUserContentController() - let webView = WebView(frame: CGRect.zero) + let webView = OmnivoreWebView(frame: CGRect.zero) webView.navigationDelegate = context.coordinator webView.configuration.userContentController = contentController @@ -170,17 +170,17 @@ public enum WebViewManager { if annotationSaveTransactionID != context.coordinator.lastSavedAnnotationID { context.coordinator.lastSavedAnnotationID = annotationSaveTransactionID - (webView as? WebView)?.dispatchEvent(.saveAnnotation(annotation: annotation)) + (webView as? OmnivoreWebView)?.dispatchEvent(.saveAnnotation(annotation: annotation)) } if sendIncreaseFontSignal { sendIncreaseFontSignal = false - (webView as? WebView)?.updateFontSize() + (webView as? OmnivoreWebView)?.updateFontSize() } if sendDecreaseFontSignal { sendDecreaseFontSignal = false - (webView as? WebView)?.updateFontSize() + (webView as? OmnivoreWebView)?.updateFontSize() } } } diff --git a/apple/OmnivoreKit/Sources/Views/Web/BasicWebAppView.swift b/apple/OmnivoreKit/Sources/Views/Web/BasicWebAppView.swift index d03d5c58f..1baaf13d0 100644 --- a/apple/OmnivoreKit/Sources/Views/Web/BasicWebAppView.swift +++ b/apple/OmnivoreKit/Sources/Views/Web/BasicWebAppView.swift @@ -47,7 +47,7 @@ import WebKit } public func makeNSView(context _: Context) -> WKWebView { - let webView = WebView(frame: CGRect.zero) + let webView = OmnivoreWebView(frame: CGRect.zero) if let url = request.url { // Dark mode is still rendering a white background on mac for some reason. // Forcing light mode for now until we figure out a fix From 93d3485e40543c8e5ac1ec946502563e00c0a620 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 28 Jun 2022 16:21:42 -0700 Subject: [PATCH 02/10] remove old web reader code --- .../App/Views/Home/HomeFeedViewIOS.swift | 1 + .../App/Views/LinkItemDetailView.swift | 140 +------ .../App/Views/RootView/RootViewModel.swift | 16 - .../App/Views/WebReader/WebReader.swift | 160 +++++++ .../Views/WebReader/WebReaderContainer.swift | 392 +++++++++--------- .../WebReader/WebReaderLoadingContainer.swift | 82 ++-- .../Sources/Views/Article/SafariView.swift | 35 ++ .../Sources/Views/Article/WebAppView.swift | 187 --------- .../Views/Article/WebAppViewCoordinator.swift | 153 ------- .../Views/Article/WebAppWrapperView.swift | 127 ------ .../Views/Article/WebViewManager.swift | 30 ++ .../Sources/Views/Web/BasicWebAppView.swift | 10 + 12 files changed, 482 insertions(+), 851 deletions(-) create mode 100644 apple/OmnivoreKit/Sources/Views/Article/SafariView.swift delete mode 100644 apple/OmnivoreKit/Sources/Views/Article/WebAppView.swift delete mode 100644 apple/OmnivoreKit/Sources/Views/Article/WebAppViewCoordinator.swift delete mode 100644 apple/OmnivoreKit/Sources/Views/Article/WebAppWrapperView.swift create mode 100644 apple/OmnivoreKit/Sources/Views/Article/WebViewManager.swift diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index bb53c910c..abd162beb 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -22,6 +22,7 @@ import Views ZStack { if let linkRequest = viewModel.linkRequest { NavigationLink( + // TODO: add alt for macOS destination: WebReaderLoadingContainer(requestID: linkRequest.serverID), tag: linkRequest, selection: $viewModel.linkRequest diff --git a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift index cde19d366..8ba1a95b1 100644 --- a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift @@ -8,7 +8,6 @@ import Views @MainActor final class LinkItemDetailViewModel: ObservableObject { let pdfItem: PDFItem? let item: LinkedItem? - @Published var webAppWrapperViewModel: WebAppWrapperViewModel? init(linkedItemObjectID: NSManagedObjectID, dataService: DataService) { if let linkedItem = dataService.viewContext.object(with: linkedItemObjectID) as? LinkedItem { @@ -42,32 +41,6 @@ import Views ) } - func loadWebAppWrapper(dataService: DataService, rawAuthCookie: String?) async { - let viewer: Viewer? = await { - if let currentViewer = dataService.currentViewer { - return currentViewer - } - - guard let viewerObjectID = try? await dataService.fetchViewer() else { return nil } - - var result: Viewer? - - await dataService.viewContext.perform { - result = dataService.viewContext.object(with: viewerObjectID) as? Viewer - } - - return result - }() - - if let viewer = viewer { - createWebAppWrapperViewModel( - username: viewer.unwrappedUsername, - dataService: dataService, - rawAuthCookie: rawAuthCookie - ) - } - } - func trackReadEvent() { guard let itemID = item?.unwrappedID ?? pdfItem?.itemID else { return } guard let slug = item?.unwrappedSlug ?? pdfItem?.slug else { return } @@ -89,23 +62,6 @@ import Views var isItemArchived: Bool { item?.isArchived ?? pdfItem?.isArchived ?? false } - - private func createWebAppWrapperViewModel(username: String, dataService: DataService, rawAuthCookie: String?) { - guard let slug = item?.unwrappedSlug ?? pdfItem?.slug else { return } - let baseURL = dataService.appEnvironment.webAppBaseURL - - let urlRequest = URLRequest.webRequest( - baseURL: dataService.appEnvironment.webAppBaseURL, - urlPath: "/app/\(username)/\(slug)", - queryParams: ["isAppEmbedView": "true", "highlightBarDisabled": isMacApp ? "false" : "true"] - ) - - webAppWrapperViewModel = WebAppWrapperViewModel( - webViewURLRequest: urlRequest, - baseURL: baseURL, - rawAuthCookie: rawAuthCookie - ) - } } struct LinkItemDetailView: View { @@ -144,13 +100,6 @@ struct LinkItemDetailView: View { ) } - var fontAdjustmentPopoverView: some View { - FontSizeAdjustmentPopoverView( - increaseFontAction: { viewModel.webAppWrapperViewModel?.sendIncreaseFontSignal = true }, - decreaseFontAction: { viewModel.webAppWrapperViewModel?.sendDecreaseFontSignal = true } - ) - } - // We always want this hidden but setting it to false initially // fixes a bug where SwiftUI searchable will always show the nav bar // if the search field is active when pushing. @@ -174,8 +123,11 @@ struct LinkItemDetailView: View { } } #else - fixedNavBarReader - .task { viewModel.trackReadEvent() } + // TODO: make pdf item work for macos + if let item = viewModel.item { + WebReaderContainerView(item: item) + .task { viewModel.trackReadEvent() } + } #endif } @@ -249,61 +201,6 @@ struct LinkItemDetailView: View { } } - #if os(iOS) - @ViewBuilder private var hidingNavBarReader: some View { - if let webAppWrapperViewModel = viewModel.webAppWrapperViewModel { - ZStack { - WebAppWrapperView( - viewModel: webAppWrapperViewModel, - navBarVisibilityRatioUpdater: { - if $0 < 1 { - showFontSizePopover = false - } - navBarVisibilityRatio = $0 - } - ) - if showFontSizePopover { - VStack { - Color.clear - .contentShape(Rectangle()) - .frame(height: LinkItemDetailView.navBarHeight) - HStack { - Spacer() - fontAdjustmentPopoverView - .background(Color.appButtonBackground) - .cornerRadius(8) - .padding(.trailing, 44) - } - Spacer() - } - .background( - Color.clear - .contentShape(Rectangle()) - .onTapGesture { - showFontSizePopover = false - } - ) - } - VStack(spacing: 0) { - navBar - Spacer() - } - } - } else { - VStack(spacing: 0) { - navBar - Spacer() - } - .task { - await viewModel.loadWebAppWrapper( - dataService: dataService, - rawAuthCookie: authenticator.omnivoreAuthCookieString - ) - } - } - } - #endif - @ViewBuilder private var fixedNavBarReader: some View { if let pdfItem = viewModel.pdfItem, let pdfURL = pdfItem.pdfURL { #if os(iOS) @@ -312,39 +209,12 @@ struct LinkItemDetailView: View { #elseif os(macOS) PDFWrapperView(pdfURL: pdfURL) #endif - } else if let webAppWrapperViewModel = viewModel.webAppWrapperViewModel { - WebAppWrapperView(viewModel: webAppWrapperViewModel) - .toolbar { - ToolbarItem(placement: .automatic) { - Button( - action: { showFontSizePopover = true }, - label: { - Image(systemName: "textformat.size") - } - ) - #if os(iOS) - .fittedPopover(isPresented: $showFontSizePopover) { - fontAdjustmentPopoverView - } - #else - .popover(isPresented: $showFontSizePopover) { - fontAdjustmentPopoverView - } - #endif - } - } } else { HStack(alignment: .center) { Spacer() Text("Loading...") Spacer() } - .task { - await viewModel.loadWebAppWrapper( - dataService: dataService, - rawAuthCookie: authenticator.omnivoreAuthCookieString - ) - } } } } diff --git a/apple/OmnivoreKit/Sources/App/Views/RootView/RootViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/RootView/RootViewModel.swift index b598fa014..9acf9a501 100644 --- a/apple/OmnivoreKit/Sources/App/Views/RootView/RootViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/RootView/RootViewModel.swift @@ -32,22 +32,6 @@ public final class RootViewModel: ObservableObject { #endif } - func webAppWrapperViewModel(webLinkPath: String) -> WebAppWrapperViewModel { - let baseURL = services.dataService.appEnvironment.webAppBaseURL - - let urlRequest = URLRequest.webRequest( - baseURL: services.dataService.appEnvironment.webAppBaseURL, - urlPath: webLinkPath, - queryParams: ["isAppEmbedView": "true", "highlightBarDisabled": isMacApp ? "false" : "true"] - ) - - return WebAppWrapperViewModel( - webViewURLRequest: urlRequest, - baseURL: baseURL, - rawAuthCookie: services.authenticator.omnivoreAuthCookieString - ) - } - func triggerPushNotificationRequestIfNeeded() { guard FeatureFlag.enablePushNotifications else { return } diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift index bafbaf6d1..1a7468497 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift @@ -167,4 +167,164 @@ import WebKit ) } } +#else + struct WebReader: NSViewRepresentable { + let item: LinkedItem + let articleContent: ArticleContent + let openLinkAction: (URL) -> Void + let webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void + let navBarVisibilityRatioUpdater: (Double) -> Void + + @Binding var updateFontFamilyActionID: UUID? + @Binding var updateFontActionID: UUID? + @Binding var updateTextContrastActionID: UUID? + @Binding var updateMaxWidthActionID: UUID? + @Binding var updateLineHeightActionID: UUID? + @Binding var annotationSaveTransactionID: UUID? + @Binding var showNavBarActionID: UUID? + @Binding var shareActionID: UUID? + @Binding var annotation: String + + func makeCoordinator() -> WebReaderCoordinator { + WebReaderCoordinator() + } + + func fontSize() -> Int { + let storedSize = UserDefaults.standard.integer(forKey: UserDefaultKey.preferredWebFontSize.rawValue) + return storedSize <= 1 ? Int(NSFont.userFont(ofSize: 16)?.pointSize ?? 16) : storedSize + } + + func lineHeight() -> Int { + let storedSize = UserDefaults.standard.integer(forKey: UserDefaultKey.preferredWebLineSpacing.rawValue) + return storedSize <= 1 ? 150 : storedSize + } + + func maxWidthPercentage() -> Int { + let storedSize = UserDefaults.standard.integer(forKey: UserDefaultKey.preferredWebMaxWidthPercentage.rawValue) + return storedSize <= 1 ? 100 : storedSize + } + + func makeNSView(context: Context) -> WKWebView { + let webView = WebViewManager.shared() + let contentController = WKUserContentController() + + webView.navigationDelegate = context.coordinator + webView.configuration.userContentController = contentController + webView.setValue(false, forKey: "drawsBackground") + + for action in WebViewAction.allCases { + webView.configuration.userContentController.add(context.coordinator, name: action.rawValue) + } + + webView.configuration.userContentController.removeAllScriptMessageHandlers() + +// webView.configuration.userContentController.add(webView, name: "viewerAction") + + webView.configuration.userContentController.addScriptMessageHandler( + context.coordinator, contentWorld: .page, name: "articleAction" + ) + + context.coordinator.linkHandler = openLinkAction + context.coordinator.webViewActionHandler = webViewActionHandler + context.coordinator.updateNavBarVisibilityRatio = navBarVisibilityRatioUpdater + loadContent(webView: webView) + + return webView + } + + // swiftlint:disable:next cyclomatic_complexity + func updateNSView(_ webView: WKWebView, context: Context) { + if annotationSaveTransactionID != context.coordinator.lastSavedAnnotationID { + context.coordinator.lastSavedAnnotationID = annotationSaveTransactionID + (webView as? OmnivoreWebView)?.dispatchEvent(.saveAnnotation(annotation: annotation)) + } + + if updateFontFamilyActionID != context.coordinator.previousUpdateFontFamilyActionID { + context.coordinator.previousUpdateFontFamilyActionID = updateFontFamilyActionID + (webView as? OmnivoreWebView)?.updateFontFamily() + } + + if updateFontActionID != context.coordinator.previousUpdateFontActionID { + context.coordinator.previousUpdateFontActionID = updateFontActionID + (webView as? OmnivoreWebView)?.updateFontSize() + } + + if updateTextContrastActionID != context.coordinator.previousUpdateTextContrastActionID { + context.coordinator.previousUpdateTextContrastActionID = updateTextContrastActionID + (webView as? OmnivoreWebView)?.updateTextContrast() + } + + if updateMaxWidthActionID != context.coordinator.previousUpdateMaxWidthActionID { + context.coordinator.previousUpdateMaxWidthActionID = updateMaxWidthActionID + (webView as? OmnivoreWebView)?.updateMaxWidthPercentage() + } + + if updateLineHeightActionID != context.coordinator.previousUpdateLineHeightActionID { + context.coordinator.previousUpdateLineHeightActionID = updateLineHeightActionID + (webView as? OmnivoreWebView)?.updateLineHeight() + } + + if showNavBarActionID != context.coordinator.previousShowNavBarActionID { + context.coordinator.previousShowNavBarActionID = showNavBarActionID + context.coordinator.showNavBar() + } + + if shareActionID != context.coordinator.previousShareActionID { + context.coordinator.previousShareActionID = shareActionID + (webView as? OmnivoreWebView)?.shareOriginalItem() + } + + // If the webview had been terminated `needsReload` will have been set to true + if context.coordinator.needsReload { + loadContent(webView: webView) + context.coordinator.needsReload = false + return + } + + if webView.isLoading { return } + + // If the root element is not detected then `WKWebView` may have unloaded the content + // so we need to load it again. + webView.evaluateJavaScript("document.getElementById('root') ? true : false") { hasRootElement, _ in + guard let hasRootElement = hasRootElement as? Bool else { return } + + if !hasRootElement { + DispatchQueue.main.async { + loadContent(webView: webView) + } + } + } + } + + func loadContent(webView: WKWebView) { + let fontFamilyValue = UserDefaults.standard.string(forKey: UserDefaultKey.preferredWebFont.rawValue) + + let prefersHighContrastText: Bool = { + let key = UserDefaultKey.prefersHighContrastWebFont.rawValue + if UserDefaults.standard.object(forKey: key) != nil { + return UserDefaults.standard.bool(forKey: key) + } else { + return true + } + }() + + let fontFamily = fontFamilyValue.flatMap { WebFont(rawValue: $0) } ?? .inter + + webView.loadHTMLString( + WebReaderContent( + item: item, + articleContent: articleContent, + isDark: NSApp.effectiveAppearance.name == NSAppearance.Name.darkAqua, + fontSize: fontSize(), + lineHeight: lineHeight(), + maxWidthPercentage: maxWidthPercentage(), + fontFamily: fontFamily, + prefersHighContrastText: prefersHighContrastText + ) + .styledContent, + baseURL: ViewsPackage.bundleURL + ) + } + } + #endif diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift index 5bc1db9ef..54019c938 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift @@ -4,209 +4,213 @@ import SwiftUI import Views import WebKit -#if os(iOS) - struct WebReaderContainerView: View { - let item: LinkedItem +struct WebReaderContainerView: View { + let item: LinkedItem - @State private var showPreferencesPopover = false - @State private var showLabelsModal = false - @State private var showTitleEdit = false - @State var showHighlightAnnotationModal = false - @State var safariWebLink: SafariWebLink? - @State private var navBarVisibilityRatio = 1.0 - @State private var showDeleteConfirmation = false - @State private var progressViewOpacity = 0.0 - @State var updateFontFamilyActionID: UUID? - @State var updateFontActionID: UUID? - @State var updateTextContrastActionID: UUID? - @State var updateMaxWidthActionID: UUID? - @State var updateLineHeightActionID: UUID? - @State var annotationSaveTransactionID: UUID? - @State var showNavBarActionID: UUID? - @State var shareActionID: UUID? - @State var annotation = String() + @State private var showPreferencesPopover = false + @State private var showLabelsModal = false + @State private var showTitleEdit = false + @State var showHighlightAnnotationModal = false + @State var safariWebLink: SafariWebLink? + @State private var navBarVisibilityRatio = 1.0 + @State private var showDeleteConfirmation = false + @State private var progressViewOpacity = 0.0 + @State var updateFontFamilyActionID: UUID? + @State var updateFontActionID: UUID? + @State var updateTextContrastActionID: UUID? + @State var updateMaxWidthActionID: UUID? + @State var updateLineHeightActionID: UUID? + @State var annotationSaveTransactionID: UUID? + @State var showNavBarActionID: UUID? + @State var shareActionID: UUID? + @State var annotation = String() - @EnvironmentObject var dataService: DataService - @Environment(\.presentationMode) var presentationMode: Binding - @StateObject var viewModel = WebReaderViewModel() + @EnvironmentObject var dataService: DataService + @Environment(\.presentationMode) var presentationMode: Binding + @StateObject var viewModel = WebReaderViewModel() - func webViewActionHandler(message: WKScriptMessage, replyHandler: WKScriptMessageReplyHandler?) { - if let replyHandler = replyHandler { - viewModel.webViewActionWithReplyHandler( - message: message, - replyHandler: replyHandler, - dataService: dataService - ) - return - } - - if message.name == WebViewAction.highlightAction.rawValue { - handleHighlightAction(message: message) - } + func webViewActionHandler(message: WKScriptMessage, replyHandler: WKScriptMessageReplyHandler?) { + if let replyHandler = replyHandler { + viewModel.webViewActionWithReplyHandler( + message: message, + replyHandler: replyHandler, + dataService: dataService + ) + return } - private func handleHighlightAction(message: WKScriptMessage) { - guard let messageBody = message.body as? [String: String] else { return } - guard let actionID = messageBody["actionID"] else { return } - - switch actionID { - case "annotate": - annotation = messageBody["annotation"] ?? "" - showHighlightAnnotationModal = true - default: - break - } + if message.name == WebViewAction.highlightAction.rawValue { + handleHighlightAction(message: message) } + } - var navBar: some View { - HStack(alignment: .center) { - Button( - action: { self.presentationMode.wrappedValue.dismiss() }, - label: { - Image(systemName: "chevron.backward") - .font(.appTitleTwo) - .foregroundColor(.appGrayTextContrast) - .padding(.horizontal) - } - ) - .scaleEffect(navBarVisibilityRatio) - Spacer() - Button( - action: { showPreferencesPopover.toggle() }, - label: { - Image(systemName: "textformat.size") - .font(.appTitleTwo) - } - ) - .padding(.horizontal) - .scaleEffect(navBarVisibilityRatio) - Menu( - content: { - Group { - Button( - action: { showTitleEdit = true }, - label: { Label("Edit Title/Description", systemImage: "textbox") } - ) - Button( - action: { showLabelsModal = true }, - label: { Label("Edit Labels", systemImage: "tag") } - ) - Button( - action: { - dataService.archiveLink(objectID: item.objectID, archived: !item.isArchived) - presentationMode.wrappedValue.dismiss() - Snackbar.show(message: !item.isArchived ? "Link archived" : "Link moved to Inbox") - }, - label: { - Label( - item.isArchived ? "Unarchive" : "Archive", - systemImage: item.isArchived ? "tray.and.arrow.down.fill" : "archivebox" - ) - } - ) - Button( - action: { shareActionID = UUID() }, - label: { Label("Share Original", systemImage: "square.and.arrow.up") } - ) - Button( - action: { showDeleteConfirmation = true }, - label: { Label("Delete", systemImage: "trash") } - ) - } - }, - label: { - Image.profile - .padding(.horizontal) - .scaleEffect(navBarVisibilityRatio) - } - ) - } - .frame(height: readerViewNavBarHeight * navBarVisibilityRatio) - .opacity(navBarVisibilityRatio) - .background(Color.systemBackground) - .alert("Are you sure?", isPresented: $showDeleteConfirmation) { - Button("Remove Link", role: .destructive) { - Snackbar.show(message: "Link removed") - dataService.removeLink(objectID: item.objectID) - presentationMode.wrappedValue.dismiss() + private func handleHighlightAction(message: WKScriptMessage) { + guard let messageBody = message.body as? [String: String] else { return } + guard let actionID = messageBody["actionID"] else { return } + + switch actionID { + case "annotate": + annotation = messageBody["annotation"] ?? "" + showHighlightAnnotationModal = true + default: + break + } + } + + var navBar: some View { + HStack(alignment: .center) { + Button( + action: { self.presentationMode.wrappedValue.dismiss() }, + label: { + Image(systemName: "chevron.backward") + .font(.appTitleTwo) + .foregroundColor(.appGrayTextContrast) + .padding(.horizontal) } - Button("Cancel", role: .cancel, action: {}) - } - .sheet(isPresented: $showLabelsModal) { - ApplyLabelsView(mode: .item(item), onSave: { _ in showLabelsModal = false }) - } - .sheet(isPresented: $showTitleEdit) { - LinkedItemTitleEditView(item: item) - } - } - - var body: some View { - ZStack { - if let articleContent = viewModel.articleContent { - WebReader( - item: item, - articleContent: articleContent, - openLinkAction: { - #if os(macOS) - NSWorkspace.shared.open($0) - #elseif os(iOS) - safariWebLink = SafariWebLink(id: UUID(), url: $0) - #endif - }, - webViewActionHandler: webViewActionHandler, - navBarVisibilityRatioUpdater: { - navBarVisibilityRatio = $0 - }, - updateFontFamilyActionID: $updateFontFamilyActionID, - updateFontActionID: $updateFontActionID, - updateTextContrastActionID: $updateTextContrastActionID, - updateMaxWidthActionID: $updateMaxWidthActionID, - updateLineHeightActionID: $updateLineHeightActionID, - annotationSaveTransactionID: $annotationSaveTransactionID, - showNavBarActionID: $showNavBarActionID, - shareActionID: $shareActionID, - annotation: $annotation - ) - .onTapGesture { - withAnimation { - navBarVisibilityRatio = 1 - showNavBarActionID = UUID() - } + ) + .scaleEffect(navBarVisibilityRatio) + Spacer() + Button( + action: { showPreferencesPopover.toggle() }, + label: { + Image(systemName: "textformat.size") + .font(.appTitleTwo) + } + ) + .padding(.horizontal) + .scaleEffect(navBarVisibilityRatio) + Menu( + content: { + Group { + Button( + action: { showTitleEdit = true }, + label: { Label("Edit Title/Description", systemImage: "textbox") } + ) + Button( + action: { showLabelsModal = true }, + label: { Label("Edit Labels", systemImage: "tag") } + ) + Button( + action: { + dataService.archiveLink(objectID: item.objectID, archived: !item.isArchived) + presentationMode.wrappedValue.dismiss() + Snackbar.show(message: !item.isArchived ? "Link archived" : "Link moved to Inbox") + }, + label: { + Label( + item.isArchived ? "Unarchive" : "Archive", + systemImage: item.isArchived ? "tray.and.arrow.down.fill" : "archivebox" + ) + } + ) + Button( + action: { shareActionID = UUID() }, + label: { Label("Share Original", systemImage: "square.and.arrow.up") } + ) + Button( + action: { showDeleteConfirmation = true }, + label: { Label("Delete", systemImage: "trash") } + ) } + }, + label: { + Image.profile + .padding(.horizontal) + .scaleEffect(navBarVisibilityRatio) + } + ) + } + .frame(height: readerViewNavBarHeight * navBarVisibilityRatio) + .opacity(navBarVisibilityRatio) + .background(Color.systemBackground) + .alert("Are you sure?", isPresented: $showDeleteConfirmation) { + Button("Remove Link", role: .destructive) { + Snackbar.show(message: "Link removed") + dataService.removeLink(objectID: item.objectID) + presentationMode.wrappedValue.dismiss() + } + Button("Cancel", role: .cancel, action: {}) + } + .sheet(isPresented: $showLabelsModal) { + ApplyLabelsView(mode: .item(item), onSave: { _ in showLabelsModal = false }) + } + .sheet(isPresented: $showTitleEdit) { + LinkedItemTitleEditView(item: item) + } + } + + var body: some View { + ZStack { + if let articleContent = viewModel.articleContent { + WebReader( + item: item, + articleContent: articleContent, + openLinkAction: { + #if os(macOS) + NSWorkspace.shared.open($0) + #elseif os(iOS) + safariWebLink = SafariWebLink(id: UUID(), url: $0) + #endif + }, + webViewActionHandler: webViewActionHandler, + navBarVisibilityRatioUpdater: { + navBarVisibilityRatio = $0 + }, + updateFontFamilyActionID: $updateFontFamilyActionID, + updateFontActionID: $updateFontActionID, + updateTextContrastActionID: $updateTextContrastActionID, + updateMaxWidthActionID: $updateMaxWidthActionID, + updateLineHeightActionID: $updateLineHeightActionID, + annotationSaveTransactionID: $annotationSaveTransactionID, + showNavBarActionID: $showNavBarActionID, + shareActionID: $shareActionID, + annotation: $annotation + ) + .onTapGesture { + withAnimation { + navBarVisibilityRatio = 1 + showNavBarActionID = UUID() + } + } + #if os(iOS) + // TODO: fix for mac .fullScreenCover(item: $safariWebLink) { SafariView(url: $0.url) } - .sheet(isPresented: $showHighlightAnnotationModal) { - HighlightAnnotationSheet( - annotation: $annotation, - onSave: { - annotationSaveTransactionID = UUID() - showHighlightAnnotationModal = false - }, - onCancel: { - showHighlightAnnotationModal = false - } - ) + #endif + .sheet(isPresented: $showHighlightAnnotationModal) { + HighlightAnnotationSheet( + annotation: $annotation, + onSave: { + annotationSaveTransactionID = UUID() + showHighlightAnnotationModal = false + }, + onCancel: { + showHighlightAnnotationModal = false + } + ) + } + } else if let errorMessage = viewModel.errorMessage { + Text(errorMessage).padding() + } else { + ProgressView() + .opacity(progressViewOpacity) + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000)) { + progressViewOpacity = 1 + } + } + .task { + await viewModel.loadContent(dataService: dataService, itemID: item.unwrappedID) } - } else if let errorMessage = viewModel.errorMessage { - Text(errorMessage).padding() - } else { - ProgressView() - .opacity(progressViewOpacity) - .onAppear { - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000)) { - progressViewOpacity = 1 - } - } - .task { - await viewModel.loadContent(dataService: dataService, itemID: item.unwrappedID) - } - } - VStack(spacing: 0) { - navBar - Spacer() - } } + VStack(spacing: 0) { + navBar + Spacer() + } + } + #if os(iOS) + // TODO: implement for mac .formSheet(isPresented: $showPreferencesPopover, useSmallDetent: false) { WebPreferencesPopoverView( updateFontFamilyAction: { updateFontFamilyActionID = UUID() }, @@ -217,10 +221,10 @@ import WebKit dismissAction: { showPreferencesPopover = false } ) } - .onDisappear { - // Clear the shared webview content when exiting - WebViewManager.shared().loadHTMLString("", baseURL: nil) - } + #endif + .onDisappear { + // Clear the shared webview content when exiting + WebViewManager.shared().loadHTMLString("", baseURL: nil) } } -#endif +} diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderLoadingContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderLoadingContainer.swift index 94265cf37..a89b39bef 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderLoadingContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderLoadingContainer.swift @@ -4,56 +4,60 @@ import Services import SwiftUI import Utils -#if os(iOS) - @MainActor final class WebReaderLoadingContainerViewModel: ObservableObject { - @Published var item: LinkedItem? - @Published var errorMessage: String? +@MainActor final class WebReaderLoadingContainerViewModel: ObservableObject { + @Published var item: LinkedItem? + @Published var errorMessage: String? - func loadItem(dataService: DataService, requestID: String) async { - guard let objectID = try? await dataService.loadItemContentUsingRequestID(requestID: requestID) else { return } - item = dataService.viewContext.object(with: objectID) as? LinkedItem - } - - func trackReadEvent() { - guard let item = item else { return } - - EventTracker.track( - .linkRead( - linkID: item.unwrappedID, - slug: item.unwrappedSlug, - originalArticleURL: item.unwrappedPageURLString - ) - ) - } + func loadItem(dataService: DataService, requestID: String) async { + guard let objectID = try? await dataService.loadItemContentUsingRequestID(requestID: requestID) else { return } + item = dataService.viewContext.object(with: objectID) as? LinkedItem } - public struct WebReaderLoadingContainer: View { - let requestID: String + func trackReadEvent() { + guard let item = item else { return } - @EnvironmentObject var dataService: DataService - @StateObject var viewModel = WebReaderLoadingContainerViewModel() + EventTracker.track( + .linkRead( + linkID: item.unwrappedID, + slug: item.unwrappedSlug, + originalArticleURL: item.unwrappedPageURLString + ) + ) + } +} - public var body: some View { - if let item = viewModel.item { - if let pdfItem = PDFItem.make(item: item) { +public struct WebReaderLoadingContainer: View { + let requestID: String + + @EnvironmentObject var dataService: DataService + @StateObject var viewModel = WebReaderLoadingContainerViewModel() + + public var body: some View { + if let item = viewModel.item { + if let pdfItem = PDFItem.make(item: item) { + #if os(iOS) PDFViewer(viewModel: PDFViewerViewModel(pdfItem: pdfItem)) .navigationBarHidden(true) .navigationViewStyle(.stack) .accentColor(.appGrayTextContrast) .task { viewModel.trackReadEvent() } - } else { - WebReaderContainerView(item: item) - .navigationBarHidden(true) - .navigationViewStyle(.stack) - .accentColor(.appGrayTextContrast) - .task { viewModel.trackReadEvent() } - } - } else if let errorMessage = viewModel.errorMessage { - Text(errorMessage) + #else + // TODO: implement pdf view for macOS + #endif } else { - ProgressView() - .task { await viewModel.loadItem(dataService: dataService, requestID: requestID) } + WebReaderContainerView(item: item) + #if os(iOS) + .navigationBarHidden(true) + .navigationViewStyle(.stack) + #endif + .accentColor(.appGrayTextContrast) + .task { viewModel.trackReadEvent() } } + } else if let errorMessage = viewModel.errorMessage { + Text(errorMessage) + } else { + ProgressView() + .task { await viewModel.loadItem(dataService: dataService, requestID: requestID) } } } -#endif +} diff --git a/apple/OmnivoreKit/Sources/Views/Article/SafariView.swift b/apple/OmnivoreKit/Sources/Views/Article/SafariView.swift new file mode 100644 index 000000000..2f7bc16e4 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Article/SafariView.swift @@ -0,0 +1,35 @@ +import SafariServices +import SwiftUI +import WebKit + +#if os(iOS) + public struct SafariView: UIViewControllerRepresentable { + let url: URL + + public init(url: URL) { + self.url = url + } + + public func makeUIViewController( + context _: UIViewControllerRepresentableContext + ) -> SFSafariViewController { + SFSafariViewController(url: url) + } + + // swiftlint:disable:next line_length + public func updateUIViewController(_: SFSafariViewController, context _: UIViewControllerRepresentableContext) {} + } + +#elseif os(macOS) + public struct SafariView: View { + let url: URL + + public init(url: URL) { + self.url = url + } + + public var body: some View { + Color.clear + } + } +#endif diff --git a/apple/OmnivoreKit/Sources/Views/Article/WebAppView.swift b/apple/OmnivoreKit/Sources/Views/Article/WebAppView.swift deleted file mode 100644 index bb390ac1b..000000000 --- a/apple/OmnivoreKit/Sources/Views/Article/WebAppView.swift +++ /dev/null @@ -1,187 +0,0 @@ -import Models -import SwiftUI -import Utils -import WebKit - -public let readerViewNavBarHeight = 50.0 - -enum WebViewConfigurationManager { - private static let processPool = WKProcessPool() - static func create() -> WKWebViewConfiguration { - let config = WKWebViewConfiguration() - config.processPool = processPool - #if os(iOS) - config.allowsInlineMediaPlayback = true - #endif - config.mediaTypesRequiringUserActionForPlayback = .audio - return config - } -} - -public enum WebViewManager { - public static let sharedView = create() - public static func shared() -> OmnivoreWebView { - sharedView - } - - public static func create() -> OmnivoreWebView { - OmnivoreWebView(frame: CGRect.zero, configuration: WebViewConfigurationManager.create()) - } -} - -#if os(iOS) - struct WebAppView: UIViewRepresentable { - let request: URLRequest - let baseURL: URL - let rawAuthCookie: String? - let openLinkAction: (URL) -> Void - let webViewActionHandler: (WKScriptMessage) -> Void - let navBarVisibilityRatioUpdater: (Double) -> Void - @Binding var annotation: String - @Binding var annotationSaveTransactionID: UUID? - @Binding var sendIncreaseFontSignal: Bool - @Binding var sendDecreaseFontSignal: Bool - - func makeCoordinator() -> WebAppViewCoordinator { - WebAppViewCoordinator() - } - - func fontSize() -> Int { - let storedSize = UserDefaults.standard.integer(forKey: "preferredWebFontSize") - return storedSize <= 1 ? UITraitCollection.current.preferredWebFontSize : storedSize - } - - func makeUIView(context: Context) -> WKWebView { - let webView = WebViewManager.create() - let contentController = WKUserContentController() - - webView.navigationDelegate = context.coordinator - webView.isOpaque = false - webView.backgroundColor = .clear - webView.configuration.userContentController = contentController - webView.scrollView.delegate = context.coordinator - webView.scrollView.contentInset.top = readerViewNavBarHeight - webView.scrollView.verticalScrollIndicatorInsets.top = readerViewNavBarHeight - - for action in WebViewAction.allCases { - webView.configuration.userContentController.add(context.coordinator, name: action.rawValue) - } - - webView.configuration.userContentController.add(webView, name: "viewerAction") - - webView.configureForOmnivoreAppEmbed( - config: WebViewConfig( - url: baseURL, - themeId: UITraitCollection.current.userInterfaceStyle == .dark ? "Gray" : "LightGray", - margin: 0, - fontSize: fontSize(), - fontFamily: "inter", - rawAuthCookie: rawAuthCookie - ) - ) - - context.coordinator.linkHandler = openLinkAction - context.coordinator.webViewActionHandler = webViewActionHandler - context.coordinator.updateNavBarVisibilityRatio = navBarVisibilityRatioUpdater - - return webView - } - - func updateUIView(_ webView: WKWebView, context: Context) { - if context.coordinator.needsReload { - webView.load(request) - context.coordinator.needsReload = false - } - - if annotationSaveTransactionID != context.coordinator.lastSavedAnnotationID { - context.coordinator.lastSavedAnnotationID = annotationSaveTransactionID - (webView as? OmnivoreWebView)?.dispatchEvent(.saveAnnotation(annotation: annotation)) - } - - if sendIncreaseFontSignal { - sendIncreaseFontSignal = false - (webView as? OmnivoreWebView)?.updateFontSize() - } - - if sendDecreaseFontSignal { - sendDecreaseFontSignal = false - (webView as? OmnivoreWebView)?.updateFontSize() - } - } - } -#endif - -#if os(macOS) - struct WebAppView: NSViewRepresentable { - let request: URLRequest - let baseURL: URL - let rawAuthCookie: String? - let openLinkAction: (URL) -> Void - let webViewActionHandler: (WKScriptMessage) -> Void - let navBarVisibilityRatioUpdater: (Double) -> Void - @Binding var annotation: String - @Binding var annotationSaveTransactionID: UUID? - @Binding var sendIncreaseFontSignal: Bool - @Binding var sendDecreaseFontSignal: Bool - - func makeCoordinator() -> WebAppViewCoordinator { - WebAppViewCoordinator() - } - - func fontSize() -> Int { - let storedSize = UserDefaults.standard.integer(forKey: UserDefaultKey.preferredWebFontSize.rawValue) - return storedSize <= 1 ? Int(NSFont.userFont(ofSize: 16)?.pointSize ?? 16) : storedSize - } - - func makeNSView(context: Context) -> WKWebView { - let contentController = WKUserContentController() - let webView = OmnivoreWebView(frame: CGRect.zero) - - webView.navigationDelegate = context.coordinator - webView.configuration.userContentController = contentController - webView.setValue(false, forKey: "drawsBackground") - - for action in WebViewAction.allCases { - webView.configuration.userContentController.add(context.coordinator, name: action.rawValue) - } - - webView.configureForOmnivoreAppEmbed( - config: WebViewConfig( - url: baseURL, - themeId: NSApp.effectiveAppearance.name == NSAppearance.Name.darkAqua ? "Gray" : "LightGray", - margin: 0, - fontSize: fontSize(), - fontFamily: "inter", - rawAuthCookie: rawAuthCookie - ) - ) - - context.coordinator.linkHandler = openLinkAction - context.coordinator.webViewActionHandler = webViewActionHandler - - return webView - } - - func updateNSView(_ webView: WKWebView, context: Context) { - if context.coordinator.needsReload { - webView.load(request) - context.coordinator.needsReload = false - } - - if annotationSaveTransactionID != context.coordinator.lastSavedAnnotationID { - context.coordinator.lastSavedAnnotationID = annotationSaveTransactionID - (webView as? OmnivoreWebView)?.dispatchEvent(.saveAnnotation(annotation: annotation)) - } - - if sendIncreaseFontSignal { - sendIncreaseFontSignal = false - (webView as? OmnivoreWebView)?.updateFontSize() - } - - if sendDecreaseFontSignal { - sendDecreaseFontSignal = false - (webView as? OmnivoreWebView)?.updateFontSize() - } - } - } -#endif diff --git a/apple/OmnivoreKit/Sources/Views/Article/WebAppViewCoordinator.swift b/apple/OmnivoreKit/Sources/Views/Article/WebAppViewCoordinator.swift deleted file mode 100644 index 430d17591..000000000 --- a/apple/OmnivoreKit/Sources/Views/Article/WebAppViewCoordinator.swift +++ /dev/null @@ -1,153 +0,0 @@ -import SwiftUI -import WebKit - -final class WebAppViewCoordinator: NSObject { - var webViewActionHandler: (WKScriptMessage) -> Void = { _ in } - var linkHandler: (URL) -> Void = { _ in } - var needsReload = true - var lastSavedAnnotationID: UUID? - var updateNavBarVisibilityRatio: (Double) -> Void = { _ in } - private var yOffsetAtStartOfDrag: Double? - private var lastYOffset: Double = 0 - private var hasDragged = false - private var isNavBarHidden = false - - override init() { - super.init() - } - - var navBarVisibilityRatio: Double = 1.0 { - didSet { - isNavBarHidden = navBarVisibilityRatio == 0 - updateNavBarVisibilityRatio(navBarVisibilityRatio) - } - } -} - -extension WebAppViewCoordinator: WKScriptMessageHandler { - func userContentController(_: WKUserContentController, didReceive message: WKScriptMessage) { - webViewActionHandler(message) - } -} - -extension WebAppViewCoordinator: WKNavigationDelegate { - // swiftlint:disable:next line_length - func webView(_: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { - if navigationAction.navigationType == .linkActivated { - if let linkURL = navigationAction.request.url { - linkHandler(linkURL) - } - decisionHandler(.cancel) - } else { - decisionHandler(.allow) - } - } - - func webView(_ webView: WKWebView, didFinish _: WKNavigation!) { - #if os(iOS) - webView.isOpaque = true - webView.backgroundColor = .systemBackground - #endif - } -} - -#if os(iOS) - extension WebAppViewCoordinator: UIScrollViewDelegate { - func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { - hasDragged = true - yOffsetAtStartOfDrag = scrollView.contentOffset.y + scrollView.contentInset.top - } - - func scrollViewDidScroll(_ scrollView: UIScrollView) { - guard hasDragged else { return } - - let yOffset = scrollView.contentOffset.y - - if yOffset == 0 { - scrollView.contentInset.top = readerViewNavBarHeight - navBarVisibilityRatio = 1 - return - } - - if yOffset < 0 { - navBarVisibilityRatio = 1 - scrollView.contentInset.top = readerViewNavBarHeight - return - } - - if yOffset < readerViewNavBarHeight { - let isScrollingUp = yOffsetAtStartOfDrag ?? 0 > yOffset - navBarVisibilityRatio = isScrollingUp || yOffset < 0 ? 1 : min(1, 1 - (yOffset / readerViewNavBarHeight)) - scrollView.contentInset.top = navBarVisibilityRatio * readerViewNavBarHeight - return - } - - guard let yOffsetAtStartOfDrag = yOffsetAtStartOfDrag else { return } - - if yOffset > yOffsetAtStartOfDrag, !isNavBarHidden { - let translation = yOffset - yOffsetAtStartOfDrag - let ratio = translation < readerViewNavBarHeight ? 1 - (translation / readerViewNavBarHeight) : 0 - navBarVisibilityRatio = min(ratio, 1) - scrollView.contentInset.top = navBarVisibilityRatio * readerViewNavBarHeight - } - } - - func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { - if decelerate, scrollView.contentOffset.y + scrollView.contentInset.top < (yOffsetAtStartOfDrag ?? 0) { - scrollView.contentInset.top = readerViewNavBarHeight - navBarVisibilityRatio = 1 - } - } - - func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool { - scrollView.contentInset.top = readerViewNavBarHeight - navBarVisibilityRatio = 1 - return false - } - } -#endif - -struct WebViewConfig { - let url: URL - let themeId: String - let margin: Int - let fontSize: Int - let fontFamily: String - let rawAuthCookie: String? -} - -extension WKWebView { - func configureForOmnivoreAppEmbed(config: WebViewConfig) { - // Set cookies to pass article preferences to web view - injectCookie(cookieString: "theme=\(config.themeId); Max-Age=31536000;", url: config.url) - injectCookie(cookieString: "margin=\(config.margin); Max-Age=31536000;", url: config.url) - injectCookie(cookieString: "fontSize=\(config.fontSize); Max-Age=31536000;", url: config.url) - injectCookie(cookieString: "fontFamily=\(config.fontFamily); Max-Age=31536000;", url: config.url) - - let authToken = extractAuthToken(rawAuthCookie: config.rawAuthCookie, url: config.url) - - if let authToken = authToken { - injectCookie(cookieString: "authToken=\(authToken); Max-Age=31536000;", url: config.url) - } else { - injectCookie(cookieString: config.rawAuthCookie, url: config.url) - } - } - - func injectCookie(cookieString: String?, url: URL) { - if let cookieString = cookieString { - for cookie in HTTPCookie.cookies(withResponseHeaderFields: ["Set-Cookie": cookieString], for: url) { - configuration.websiteDataStore.httpCookieStore.setCookie(cookie) {} - } - } - } -} - -// Temp utility until we swap out the web build used in prod. -// Once we do that we can just pass in the authToken directly -// and remove the rawAuthCookie param. -// This util just makes it easier to feature flag the web build used -private func extractAuthToken(rawAuthCookie: String?, url: URL) -> String? { - guard let rawAuthCookie = rawAuthCookie else { return nil } - let cookies = HTTPCookie.cookies(withResponseHeaderFields: ["Set-Cookie": rawAuthCookie], for: url) - return cookies.first?.value -} diff --git a/apple/OmnivoreKit/Sources/Views/Article/WebAppWrapperView.swift b/apple/OmnivoreKit/Sources/Views/Article/WebAppWrapperView.swift deleted file mode 100644 index 29b3f2f8f..000000000 --- a/apple/OmnivoreKit/Sources/Views/Article/WebAppWrapperView.swift +++ /dev/null @@ -1,127 +0,0 @@ -import SafariServices -import SwiftUI -import WebKit - -public final class WebAppWrapperViewModel: ObservableObject { - public enum Action { - case shareHighlight(highlightID: String) - } - - let webViewURLRequest: URLRequest - let baseURL: URL - let rawAuthCookie: String? - - @Published public var sendIncreaseFontSignal: Bool = false - @Published public var sendDecreaseFontSignal: Bool = false - - public init(webViewURLRequest: URLRequest, baseURL: URL, rawAuthCookie: String?) { - self.webViewURLRequest = webViewURLRequest - self.rawAuthCookie = rawAuthCookie - self.baseURL = baseURL - } -} - -public struct WebAppWrapperView: View { - struct SafariWebLink: Identifiable { - let id: UUID - let url: URL - } - - @ObservedObject private var viewModel: WebAppWrapperViewModel - @State var showHighlightAnnotationModal = false - @State private var annotation = String() - @State var annotationSaveTransactionID: UUID? - @State var safariWebLink: SafariWebLink? - let navBarVisibilityRatioUpdater: (Double) -> Void - - public init(viewModel: WebAppWrapperViewModel, navBarVisibilityRatioUpdater: ((Double) -> Void)? = nil) { - self.viewModel = viewModel - self.navBarVisibilityRatioUpdater = navBarVisibilityRatioUpdater ?? { _ in } - } - - public var body: some View { - VStack { - WebAppView( - request: viewModel.webViewURLRequest, - baseURL: viewModel.baseURL, - rawAuthCookie: viewModel.rawAuthCookie, - openLinkAction: { - #if os(macOS) - NSWorkspace.shared.open($0) - #elseif os(iOS) - safariWebLink = SafariWebLink(id: UUID(), url: $0) - #endif - }, - webViewActionHandler: webViewActionHandler, - navBarVisibilityRatioUpdater: navBarVisibilityRatioUpdater, - annotation: $annotation, - annotationSaveTransactionID: $annotationSaveTransactionID, - sendIncreaseFontSignal: $viewModel.sendIncreaseFontSignal, - sendDecreaseFontSignal: $viewModel.sendDecreaseFontSignal - ) - } - .sheet(item: $safariWebLink) { - SafariView(url: $0.url) - } - .sheet(isPresented: $showHighlightAnnotationModal) { - HighlightAnnotationSheet( - annotation: $annotation, - onSave: { - annotationSaveTransactionID = UUID() - showHighlightAnnotationModal = false - }, - onCancel: { - showHighlightAnnotationModal = false - } - ) - } - } - - func webViewActionHandler(message: WKScriptMessage) { - if message.name == WebViewAction.highlightAction.rawValue { - handleHighlightAction(message: message) - } - } - - private func handleHighlightAction(message: WKScriptMessage) { - guard let messageBody = message.body as? [String: String] else { return } - guard let actionID = messageBody["actionID"] else { return } - - if actionID == "annotate" { - annotation = messageBody["annotation"] ?? "" - showHighlightAnnotationModal = true - } - } -} - -#if os(iOS) - public struct SafariView: UIViewControllerRepresentable { - let url: URL - - public init(url: URL) { - self.url = url - } - - public func makeUIViewController( - context _: UIViewControllerRepresentableContext - ) -> SFSafariViewController { - SFSafariViewController(url: url) - } - - // swiftlint:disable:next line_length - public func updateUIViewController(_: SFSafariViewController, context _: UIViewControllerRepresentableContext) {} - } - -#elseif os(macOS) - public struct SafariView: View { - let url: URL - - public init(url: URL) { - self.url = url - } - - public var body: some View { - Color.clear - } - } -#endif diff --git a/apple/OmnivoreKit/Sources/Views/Article/WebViewManager.swift b/apple/OmnivoreKit/Sources/Views/Article/WebViewManager.swift new file mode 100644 index 000000000..022b1bce1 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Article/WebViewManager.swift @@ -0,0 +1,30 @@ +import Models +import SwiftUI +import Utils +import WebKit + +public let readerViewNavBarHeight = 50.0 + +enum WebViewConfigurationManager { + private static let processPool = WKProcessPool() + static func create() -> WKWebViewConfiguration { + let config = WKWebViewConfiguration() + config.processPool = processPool + #if os(iOS) + config.allowsInlineMediaPlayback = true + #endif + config.mediaTypesRequiringUserActionForPlayback = .audio + return config + } +} + +public enum WebViewManager { + public static let sharedView = create() + public static func shared() -> OmnivoreWebView { + sharedView + } + + public static func create() -> OmnivoreWebView { + OmnivoreWebView(frame: CGRect.zero, configuration: WebViewConfigurationManager.create()) + } +} diff --git a/apple/OmnivoreKit/Sources/Views/Web/BasicWebAppView.swift b/apple/OmnivoreKit/Sources/Views/Web/BasicWebAppView.swift index 1baaf13d0..7d9df8561 100644 --- a/apple/OmnivoreKit/Sources/Views/Web/BasicWebAppView.swift +++ b/apple/OmnivoreKit/Sources/Views/Web/BasicWebAppView.swift @@ -73,3 +73,13 @@ public final class BasicWebAppViewCoordinator: NSObject { super.init() } } + +extension WKWebView { + func injectCookie(cookieString: String?, url: URL) { + if let cookieString = cookieString { + for cookie in HTTPCookie.cookies(withResponseHeaderFields: ["Set-Cookie": cookieString], for: url) { + configuration.websiteDataStore.httpCookieStore.setCookie(cookie) {} + } + } + } +} From 43a20abcd55041d7558ac9769d1321c38b30d148 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 28 Jun 2022 17:03:58 -0700 Subject: [PATCH 03/10] try to load htmlString on mac app --- .../App/Views/WebReader/WebReader.swift | 32 ++++++++++--------- .../Views/WebReader/WebReaderContent.swift | 1 + 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift index 1a7468497..4766b8924 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift @@ -149,7 +149,7 @@ import WebKit } }() - let fontFamily = fontFamilyValue.flatMap { WebFont(rawValue: $0) } ?? .inter + let fontFamily = fontFamilyValue.flatMap { WebFont(rawValue: $0) } ?? .system webView.loadHTMLString( WebReaderContent( @@ -308,22 +308,24 @@ import WebKit } }() - let fontFamily = fontFamilyValue.flatMap { WebFont(rawValue: $0) } ?? .inter + let fontFamily = fontFamilyValue.flatMap { WebFont(rawValue: $0) } ?? .system - webView.loadHTMLString( - WebReaderContent( - item: item, - articleContent: articleContent, - isDark: NSApp.effectiveAppearance.name == NSAppearance.Name.darkAqua, - fontSize: fontSize(), - lineHeight: lineHeight(), - maxWidthPercentage: maxWidthPercentage(), - fontFamily: fontFamily, - prefersHighContrastText: prefersHighContrastText - ) - .styledContent, - baseURL: ViewsPackage.bundleURL + let htmlString = WebReaderContent( + item: item, + articleContent: articleContent, + isDark: NSApp.effectiveAppearance.name == NSAppearance.Name.darkAqua, + fontSize: fontSize(), + lineHeight: lineHeight(), + maxWidthPercentage: maxWidthPercentage(), + fontFamily: fontFamily, + prefersHighContrastText: prefersHighContrastText ) + .styledContent + +// webView.loadFileURL(ViewsPackage.bundleURL, allowingReadAccessTo: ViewsPackage.bundleURL) + // macOS isn't loading the resources at the baseURL here for some reason + // maybe it needs the `allowingReadAccessTo` permission? + webView.loadHTMLString(htmlString, baseURL: ViewsPackage.bundleURL) } } diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift index aa354ef88..c5b22ec30 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift @@ -51,6 +51,7 @@ struct WebReaderContent {
+
HIIIIII
From e48a3027f6108888bd1122f31d3b4fb439b19bca Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 29 Jun 2022 15:58:16 -0700 Subject: [PATCH 04/10] use resourceURL for locating bundled web reader web resources --- .../OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift | 5 +---- apple/OmnivoreKit/Sources/Views/BundleFinder.swift | 4 ++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift index 4766b8924..f2a0869ea 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift @@ -322,10 +322,7 @@ import WebKit ) .styledContent -// webView.loadFileURL(ViewsPackage.bundleURL, allowingReadAccessTo: ViewsPackage.bundleURL) - // macOS isn't loading the resources at the baseURL here for some reason - // maybe it needs the `allowingReadAccessTo` permission? - webView.loadHTMLString(htmlString, baseURL: ViewsPackage.bundleURL) + webView.loadHTMLString(htmlString, baseURL: ViewsPackage.resourceURL) } } diff --git a/apple/OmnivoreKit/Sources/Views/BundleFinder.swift b/apple/OmnivoreKit/Sources/Views/BundleFinder.swift index 883b67bb4..f9954e97f 100644 --- a/apple/OmnivoreKit/Sources/Views/BundleFinder.swift +++ b/apple/OmnivoreKit/Sources/Views/BundleFinder.swift @@ -5,4 +5,8 @@ public enum ViewsPackage { public static var bundleURL: URL { Bundle.module.bundleURL } + + public static var resourceURL: URL { + Bundle.module.resourceURL ?? bundleURL + } } From 6721a0681c67650904de924f05d3cecde27840b2 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 29 Jun 2022 20:08:42 -0700 Subject: [PATCH 05/10] remove duplicate platform code in webreader --- .../App/Views/WebReader/WebReader.swift | 462 +++++++----------- .../Sources/Utils/CrossOSHelpers.swift | 2 + 2 files changed, 171 insertions(+), 293 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift index f2a0869ea..db0b79242 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift @@ -4,326 +4,202 @@ import Utils import Views import WebKit -#if os(iOS) - struct WebReader: UIViewRepresentable { - let item: LinkedItem - let articleContent: ArticleContent - let openLinkAction: (URL) -> Void - let webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void - let navBarVisibilityRatioUpdater: (Double) -> Void +struct WebReader: PlatformViewRepresentable { + let item: LinkedItem + let articleContent: ArticleContent + let openLinkAction: (URL) -> Void + let webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void + let navBarVisibilityRatioUpdater: (Double) -> Void - @Binding var updateFontFamilyActionID: UUID? - @Binding var updateFontActionID: UUID? - @Binding var updateTextContrastActionID: UUID? - @Binding var updateMaxWidthActionID: UUID? - @Binding var updateLineHeightActionID: UUID? - @Binding var annotationSaveTransactionID: UUID? - @Binding var showNavBarActionID: UUID? - @Binding var shareActionID: UUID? - @Binding var annotation: String + @Binding var updateFontFamilyActionID: UUID? + @Binding var updateFontActionID: UUID? + @Binding var updateTextContrastActionID: UUID? + @Binding var updateMaxWidthActionID: UUID? + @Binding var updateLineHeightActionID: UUID? + @Binding var annotationSaveTransactionID: UUID? + @Binding var showNavBarActionID: UUID? + @Binding var shareActionID: UUID? + @Binding var annotation: String - func makeCoordinator() -> WebReaderCoordinator { - WebReaderCoordinator() - } + func makeCoordinator() -> WebReaderCoordinator { + WebReaderCoordinator() + } - func fontSize() -> Int { - let storedSize = UserDefaults.standard.integer(forKey: UserDefaultKey.preferredWebFontSize.rawValue) + func fontSize() -> Int { + let storedSize = UserDefaults.standard.integer(forKey: UserDefaultKey.preferredWebFontSize.rawValue) + #if os(iOS) return storedSize <= 1 ? UITraitCollection.current.preferredWebFontSize : storedSize - } + #else + return storedSize <= 1 ? Int(NSFont.userFont(ofSize: 16)?.pointSize ?? 16) : storedSize + #endif + } - func lineHeight() -> Int { - let storedSize = UserDefaults.standard.integer(forKey: UserDefaultKey.preferredWebLineSpacing.rawValue) - return storedSize <= 1 ? 150 : storedSize - } + func lineHeight() -> Int { + let storedSize = UserDefaults.standard.integer(forKey: UserDefaultKey.preferredWebLineSpacing.rawValue) + return storedSize <= 1 ? 150 : storedSize + } - func maxWidthPercentage() -> Int { - let storedSize = UserDefaults.standard.integer(forKey: UserDefaultKey.preferredWebMaxWidthPercentage.rawValue) - return storedSize <= 1 ? 100 : storedSize - } + func maxWidthPercentage() -> Int { + let storedSize = UserDefaults.standard.integer(forKey: UserDefaultKey.preferredWebMaxWidthPercentage.rawValue) + return storedSize <= 1 ? 100 : storedSize + } - func makeUIView(context: Context) -> WKWebView { - let webView = WebViewManager.shared() - let contentController = WKUserContentController() + private func makePlatformView(context: Context) -> WKWebView { + let webView = WebViewManager.shared() + let contentController = WKUserContentController() - webView.navigationDelegate = context.coordinator + webView.navigationDelegate = context.coordinator + #if os(iOS) webView.isOpaque = false webView.backgroundColor = .clear - webView.configuration.userContentController = contentController webView.scrollView.delegate = context.coordinator webView.scrollView.contentInset.top = readerViewNavBarHeight webView.scrollView.verticalScrollIndicatorInsets.top = readerViewNavBarHeight - - webView.configuration.userContentController.removeAllScriptMessageHandlers() - - for action in WebViewAction.allCases { - webView.configuration.userContentController.add(context.coordinator, name: action.rawValue) - } - + // TODO: unsure if needed for macos webView.configuration.userContentController.add(webView, name: "viewerAction") + #else + webView.setValue(false, forKey: "drawsBackground") + #endif + webView.configuration.userContentController = contentController - webView.configuration.userContentController.addScriptMessageHandler( - context.coordinator, contentWorld: .page, name: "articleAction" - ) + webView.configuration.userContentController.removeAllScriptMessageHandlers() - context.coordinator.linkHandler = openLinkAction - context.coordinator.webViewActionHandler = webViewActionHandler - context.coordinator.updateNavBarVisibilityRatio = navBarVisibilityRatioUpdater + for action in WebViewAction.allCases { + webView.configuration.userContentController.add(context.coordinator, name: action.rawValue) + } + + webView.configuration.userContentController.addScriptMessageHandler( + context.coordinator, contentWorld: .page, name: "articleAction" + ) + + context.coordinator.linkHandler = openLinkAction + context.coordinator.webViewActionHandler = webViewActionHandler + context.coordinator.updateNavBarVisibilityRatio = navBarVisibilityRatioUpdater + loadContent(webView: webView) + + return webView + } + + // swiftlint:disable:next cyclomatic_complexity + private func updatePlatformView(_ webView: WKWebView, context: Context) { + if annotationSaveTransactionID != context.coordinator.lastSavedAnnotationID { + context.coordinator.lastSavedAnnotationID = annotationSaveTransactionID + (webView as? OmnivoreWebView)?.dispatchEvent(.saveAnnotation(annotation: annotation)) + } + + if updateFontFamilyActionID != context.coordinator.previousUpdateFontFamilyActionID { + context.coordinator.previousUpdateFontFamilyActionID = updateFontFamilyActionID + (webView as? OmnivoreWebView)?.updateFontFamily() + } + + if updateFontActionID != context.coordinator.previousUpdateFontActionID { + context.coordinator.previousUpdateFontActionID = updateFontActionID + (webView as? OmnivoreWebView)?.updateFontSize() + } + + if updateTextContrastActionID != context.coordinator.previousUpdateTextContrastActionID { + context.coordinator.previousUpdateTextContrastActionID = updateTextContrastActionID + (webView as? OmnivoreWebView)?.updateTextContrast() + } + + if updateMaxWidthActionID != context.coordinator.previousUpdateMaxWidthActionID { + context.coordinator.previousUpdateMaxWidthActionID = updateMaxWidthActionID + (webView as? OmnivoreWebView)?.updateMaxWidthPercentage() + } + + if updateLineHeightActionID != context.coordinator.previousUpdateLineHeightActionID { + context.coordinator.previousUpdateLineHeightActionID = updateLineHeightActionID + (webView as? OmnivoreWebView)?.updateLineHeight() + } + + if showNavBarActionID != context.coordinator.previousShowNavBarActionID { + context.coordinator.previousShowNavBarActionID = showNavBarActionID + context.coordinator.showNavBar() + } + + if shareActionID != context.coordinator.previousShareActionID { + context.coordinator.previousShareActionID = shareActionID + (webView as? OmnivoreWebView)?.shareOriginalItem() + } + + // If the webview had been terminated `needsReload` will have been set to true + if context.coordinator.needsReload { loadContent(webView: webView) - - return webView + context.coordinator.needsReload = false + return + } + + if webView.isLoading { return } + + // If the root element is not detected then `WKWebView` may have unloaded the content + // so we need to load it again. + webView.evaluateJavaScript("document.getElementById('root') ? true : false") { hasRootElement, _ in + guard let hasRootElement = hasRootElement as? Bool else { return } + + if !hasRootElement { + DispatchQueue.main.async { + loadContent(webView: webView) + } + } + } + } + + private func loadContent(webView: WKWebView) { + let fontFamilyValue = UserDefaults.standard.string(forKey: UserDefaultKey.preferredWebFont.rawValue) + + let prefersHighContrastText: Bool = { + let key = UserDefaultKey.prefersHighContrastWebFont.rawValue + if UserDefaults.standard.object(forKey: key) != nil { + return UserDefaults.standard.bool(forKey: key) + } else { + return true + } + }() + + let fontFamily = fontFamilyValue.flatMap { WebFont(rawValue: $0) } ?? .system + + let htmlString = WebReaderContent( + item: item, + articleContent: articleContent, + isDark: isDarkMode, + fontSize: fontSize(), + lineHeight: lineHeight(), + maxWidthPercentage: maxWidthPercentage(), + fontFamily: fontFamily, + prefersHighContrastText: prefersHighContrastText + ) + .styledContent + + webView.loadHTMLString(htmlString, baseURL: ViewsPackage.resourceURL) + } + + var isDarkMode: Bool { + #if os(iOS) + UITraitCollection.current.userInterfaceStyle == .dark + #else + NSApp.effectiveAppearance.name == NSAppearance.Name.darkAqua + #endif + } +} + +#if os(iOS) + extension WebReader { + func makeUIView(context: Context) -> WKWebView { + makePlatformView(context: context) } - // swiftlint:disable:next cyclomatic_complexity func updateUIView(_ webView: WKWebView, context: Context) { - if annotationSaveTransactionID != context.coordinator.lastSavedAnnotationID { - context.coordinator.lastSavedAnnotationID = annotationSaveTransactionID - (webView as? OmnivoreWebView)?.dispatchEvent(.saveAnnotation(annotation: annotation)) - } - - if updateFontFamilyActionID != context.coordinator.previousUpdateFontFamilyActionID { - context.coordinator.previousUpdateFontFamilyActionID = updateFontFamilyActionID - (webView as? OmnivoreWebView)?.updateFontFamily() - } - - if updateFontActionID != context.coordinator.previousUpdateFontActionID { - context.coordinator.previousUpdateFontActionID = updateFontActionID - (webView as? OmnivoreWebView)?.updateFontSize() - } - - if updateTextContrastActionID != context.coordinator.previousUpdateTextContrastActionID { - context.coordinator.previousUpdateTextContrastActionID = updateTextContrastActionID - (webView as? OmnivoreWebView)?.updateTextContrast() - } - - if updateMaxWidthActionID != context.coordinator.previousUpdateMaxWidthActionID { - context.coordinator.previousUpdateMaxWidthActionID = updateMaxWidthActionID - (webView as? OmnivoreWebView)?.updateMaxWidthPercentage() - } - - if updateLineHeightActionID != context.coordinator.previousUpdateLineHeightActionID { - context.coordinator.previousUpdateLineHeightActionID = updateLineHeightActionID - (webView as? OmnivoreWebView)?.updateLineHeight() - } - - if showNavBarActionID != context.coordinator.previousShowNavBarActionID { - context.coordinator.previousShowNavBarActionID = showNavBarActionID - context.coordinator.showNavBar() - } - - if shareActionID != context.coordinator.previousShareActionID { - context.coordinator.previousShareActionID = shareActionID - (webView as? OmnivoreWebView)?.shareOriginalItem() - } - - // If the webview had been terminated `needsReload` will have been set to true - if context.coordinator.needsReload { - loadContent(webView: webView) - context.coordinator.needsReload = false - return - } - - if webView.isLoading { return } - - // If the root element is not detected then `WKWebView` may have unloaded the content - // so we need to load it again. - webView.evaluateJavaScript("document.getElementById('root') ? true : false") { hasRootElement, _ in - guard let hasRootElement = hasRootElement as? Bool else { return } - - if !hasRootElement { - DispatchQueue.main.async { - loadContent(webView: webView) - } - } - } - } - - func loadContent(webView: WKWebView) { - let fontFamilyValue = UserDefaults.standard.string(forKey: UserDefaultKey.preferredWebFont.rawValue) - - let prefersHighContrastText: Bool = { - let key = UserDefaultKey.prefersHighContrastWebFont.rawValue - if UserDefaults.standard.object(forKey: key) != nil { - return UserDefaults.standard.bool(forKey: key) - } else { - return true - } - }() - - let fontFamily = fontFamilyValue.flatMap { WebFont(rawValue: $0) } ?? .system - - webView.loadHTMLString( - WebReaderContent( - item: item, - articleContent: articleContent, - isDark: UITraitCollection.current.userInterfaceStyle == .dark, - fontSize: fontSize(), - lineHeight: lineHeight(), - maxWidthPercentage: maxWidthPercentage(), - fontFamily: fontFamily, - prefersHighContrastText: prefersHighContrastText - ) - .styledContent, - baseURL: ViewsPackage.bundleURL - ) + updatePlatformView(webView, context: context) } } #else - struct WebReader: NSViewRepresentable { - let item: LinkedItem - let articleContent: ArticleContent - let openLinkAction: (URL) -> Void - let webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void - let navBarVisibilityRatioUpdater: (Double) -> Void - - @Binding var updateFontFamilyActionID: UUID? - @Binding var updateFontActionID: UUID? - @Binding var updateTextContrastActionID: UUID? - @Binding var updateMaxWidthActionID: UUID? - @Binding var updateLineHeightActionID: UUID? - @Binding var annotationSaveTransactionID: UUID? - @Binding var showNavBarActionID: UUID? - @Binding var shareActionID: UUID? - @Binding var annotation: String - - func makeCoordinator() -> WebReaderCoordinator { - WebReaderCoordinator() - } - - func fontSize() -> Int { - let storedSize = UserDefaults.standard.integer(forKey: UserDefaultKey.preferredWebFontSize.rawValue) - return storedSize <= 1 ? Int(NSFont.userFont(ofSize: 16)?.pointSize ?? 16) : storedSize - } - - func lineHeight() -> Int { - let storedSize = UserDefaults.standard.integer(forKey: UserDefaultKey.preferredWebLineSpacing.rawValue) - return storedSize <= 1 ? 150 : storedSize - } - - func maxWidthPercentage() -> Int { - let storedSize = UserDefaults.standard.integer(forKey: UserDefaultKey.preferredWebMaxWidthPercentage.rawValue) - return storedSize <= 1 ? 100 : storedSize - } - + extension WebReader { func makeNSView(context: Context) -> WKWebView { - let webView = WebViewManager.shared() - let contentController = WKUserContentController() - - webView.navigationDelegate = context.coordinator - webView.configuration.userContentController = contentController - webView.setValue(false, forKey: "drawsBackground") - - for action in WebViewAction.allCases { - webView.configuration.userContentController.add(context.coordinator, name: action.rawValue) - } - - webView.configuration.userContentController.removeAllScriptMessageHandlers() - -// webView.configuration.userContentController.add(webView, name: "viewerAction") - - webView.configuration.userContentController.addScriptMessageHandler( - context.coordinator, contentWorld: .page, name: "articleAction" - ) - - context.coordinator.linkHandler = openLinkAction - context.coordinator.webViewActionHandler = webViewActionHandler - context.coordinator.updateNavBarVisibilityRatio = navBarVisibilityRatioUpdater - loadContent(webView: webView) - - return webView + makePlatformView(context: context) } - // swiftlint:disable:next cyclomatic_complexity func updateNSView(_ webView: WKWebView, context: Context) { - if annotationSaveTransactionID != context.coordinator.lastSavedAnnotationID { - context.coordinator.lastSavedAnnotationID = annotationSaveTransactionID - (webView as? OmnivoreWebView)?.dispatchEvent(.saveAnnotation(annotation: annotation)) - } - - if updateFontFamilyActionID != context.coordinator.previousUpdateFontFamilyActionID { - context.coordinator.previousUpdateFontFamilyActionID = updateFontFamilyActionID - (webView as? OmnivoreWebView)?.updateFontFamily() - } - - if updateFontActionID != context.coordinator.previousUpdateFontActionID { - context.coordinator.previousUpdateFontActionID = updateFontActionID - (webView as? OmnivoreWebView)?.updateFontSize() - } - - if updateTextContrastActionID != context.coordinator.previousUpdateTextContrastActionID { - context.coordinator.previousUpdateTextContrastActionID = updateTextContrastActionID - (webView as? OmnivoreWebView)?.updateTextContrast() - } - - if updateMaxWidthActionID != context.coordinator.previousUpdateMaxWidthActionID { - context.coordinator.previousUpdateMaxWidthActionID = updateMaxWidthActionID - (webView as? OmnivoreWebView)?.updateMaxWidthPercentage() - } - - if updateLineHeightActionID != context.coordinator.previousUpdateLineHeightActionID { - context.coordinator.previousUpdateLineHeightActionID = updateLineHeightActionID - (webView as? OmnivoreWebView)?.updateLineHeight() - } - - if showNavBarActionID != context.coordinator.previousShowNavBarActionID { - context.coordinator.previousShowNavBarActionID = showNavBarActionID - context.coordinator.showNavBar() - } - - if shareActionID != context.coordinator.previousShareActionID { - context.coordinator.previousShareActionID = shareActionID - (webView as? OmnivoreWebView)?.shareOriginalItem() - } - - // If the webview had been terminated `needsReload` will have been set to true - if context.coordinator.needsReload { - loadContent(webView: webView) - context.coordinator.needsReload = false - return - } - - if webView.isLoading { return } - - // If the root element is not detected then `WKWebView` may have unloaded the content - // so we need to load it again. - webView.evaluateJavaScript("document.getElementById('root') ? true : false") { hasRootElement, _ in - guard let hasRootElement = hasRootElement as? Bool else { return } - - if !hasRootElement { - DispatchQueue.main.async { - loadContent(webView: webView) - } - } - } - } - - func loadContent(webView: WKWebView) { - let fontFamilyValue = UserDefaults.standard.string(forKey: UserDefaultKey.preferredWebFont.rawValue) - - let prefersHighContrastText: Bool = { - let key = UserDefaultKey.prefersHighContrastWebFont.rawValue - if UserDefaults.standard.object(forKey: key) != nil { - return UserDefaults.standard.bool(forKey: key) - } else { - return true - } - }() - - let fontFamily = fontFamilyValue.flatMap { WebFont(rawValue: $0) } ?? .system - - let htmlString = WebReaderContent( - item: item, - articleContent: articleContent, - isDark: NSApp.effectiveAppearance.name == NSAppearance.Name.darkAqua, - fontSize: fontSize(), - lineHeight: lineHeight(), - maxWidthPercentage: maxWidthPercentage(), - fontFamily: fontFamily, - prefersHighContrastText: prefersHighContrastText - ) - .styledContent - - webView.loadHTMLString(htmlString, baseURL: ViewsPackage.resourceURL) + updatePlatformView(webView, context: context) } } - #endif diff --git a/apple/OmnivoreKit/Sources/Utils/CrossOSHelpers.swift b/apple/OmnivoreKit/Sources/Utils/CrossOSHelpers.swift index f81324cd5..b26677c53 100644 --- a/apple/OmnivoreKit/Sources/Utils/CrossOSHelpers.swift +++ b/apple/OmnivoreKit/Sources/Utils/CrossOSHelpers.swift @@ -3,6 +3,7 @@ import SwiftUI #if os(iOS) import UIKit public typealias PlatformViewController = UIViewController + public typealias PlatformViewRepresentable = UIViewRepresentable public typealias PlatformHostingController = UIHostingController let osVersion = UIDevice.current.systemVersion public let userAgent = "ios-\(osVersion)" @@ -10,6 +11,7 @@ import SwiftUI import AppKit public typealias PlatformViewController = NSViewController public typealias PlatformHostingController = NSHostingController + public typealias PlatformViewRepresentable = NSViewRepresentable let osVersion = ProcessInfo.processInfo.operatingSystemVersion public let userAgent = "macos-\(osVersion)" From aa79137223efd05780de988d83ae3b8bbc9452b1 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 29 Jun 2022 20:23:54 -0700 Subject: [PATCH 06/10] fix bug where viewerAction is overridden on webreader --- .../OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift index db0b79242..ed977dd06 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift @@ -49,6 +49,9 @@ struct WebReader: PlatformViewRepresentable { let contentController = WKUserContentController() webView.navigationDelegate = context.coordinator + webView.configuration.userContentController = contentController + webView.configuration.userContentController.removeAllScriptMessageHandlers() + #if os(iOS) webView.isOpaque = false webView.backgroundColor = .clear @@ -60,9 +63,6 @@ struct WebReader: PlatformViewRepresentable { #else webView.setValue(false, forKey: "drawsBackground") #endif - webView.configuration.userContentController = contentController - - webView.configuration.userContentController.removeAllScriptMessageHandlers() for action in WebViewAction.allCases { webView.configuration.userContentController.add(context.coordinator, name: action.rawValue) From af6f8dd4169a6c0e4523ac2fbb4b48baf25877ee Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 29 Jun 2022 22:09:52 -0700 Subject: [PATCH 07/10] enable highlight bar for macos web reader --- apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift | 1 - .../Sources/App/Views/WebReader/WebReaderContent.swift | 1 + apple/OmnivoreKit/Sources/Views/Resources/bundle.js | 2 +- packages/appreader/src/index.jsx | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift index ed977dd06..265196a09 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift @@ -58,7 +58,6 @@ struct WebReader: PlatformViewRepresentable { webView.scrollView.delegate = context.coordinator webView.scrollView.contentInset.top = readerViewNavBarHeight webView.scrollView.verticalScrollIndicatorInsets.top = readerViewNavBarHeight - // TODO: unsure if needed for macos webView.configuration.userContentController.add(webView, name: "viewerAction") #else webView.setValue(false, forKey: "drawsBackground") diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift index c5b22ec30..389d2a542 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift @@ -87,6 +87,7 @@ struct WebReaderContent { window.lineHeight = \(lineHeight) window.localStorage.setItem("theme", "\(themeKey)") window.prefersHighContrastFont = \(prefersHighContrastText) + window.enableHighlightBar = \(isMacApp) diff --git a/apple/OmnivoreKit/Sources/Views/Resources/bundle.js b/apple/OmnivoreKit/Sources/Views/Resources/bundle.js index fb96702ce..c602455de 100644 --- a/apple/OmnivoreKit/Sources/Views/Resources/bundle.js +++ b/apple/OmnivoreKit/Sources/Views/Resources/bundle.js @@ -1,2 +1,2 @@ /*! For license information please see bundle.js.LICENSE.txt */ -(()=>{var e,t,n={7162:(e,t,n)=>{e.exports=n(5047)},6279:function(e,t){var n="undefined"!=typeof self?self:this,r=function(){function e(){this.fetch=!1,this.DOMException=n.DOMException}return e.prototype=n,new e}();!function(e){!function(t){var n="URLSearchParams"in e,r="Symbol"in e&&"iterator"in Symbol,o="FileReader"in e&&"Blob"in e&&function(){try{return new Blob,!0}catch(e){return!1}}(),i="FormData"in e,a="ArrayBuffer"in e;if(a)var l=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],s=ArrayBuffer.isView||function(e){return e&&l.indexOf(Object.prototype.toString.call(e))>-1};function u(e){if("string"!=typeof e&&(e=String(e)),/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(e))throw new TypeError("Invalid character in header field name");return e.toLowerCase()}function c(e){return"string"!=typeof e&&(e=String(e)),e}function f(e){var t={next:function(){var t=e.shift();return{done:void 0===t,value:t}}};return r&&(t[Symbol.iterator]=function(){return t}),t}function d(e){this.map={},e instanceof d?e.forEach((function(e,t){this.append(t,e)}),this):Array.isArray(e)?e.forEach((function(e){this.append(e[0],e[1])}),this):e&&Object.getOwnPropertyNames(e).forEach((function(t){this.append(t,e[t])}),this)}function h(e){if(e.bodyUsed)return Promise.reject(new TypeError("Already read"));e.bodyUsed=!0}function p(e){return new Promise((function(t,n){e.onload=function(){t(e.result)},e.onerror=function(){n(e.error)}}))}function g(e){var t=new FileReader,n=p(t);return t.readAsArrayBuffer(e),n}function m(e){if(e.slice)return e.slice(0);var t=new Uint8Array(e.byteLength);return t.set(new Uint8Array(e)),t.buffer}function v(){return this.bodyUsed=!1,this._initBody=function(e){var t;this._bodyInit=e,e?"string"==typeof e?this._bodyText=e:o&&Blob.prototype.isPrototypeOf(e)?this._bodyBlob=e:i&&FormData.prototype.isPrototypeOf(e)?this._bodyFormData=e:n&&URLSearchParams.prototype.isPrototypeOf(e)?this._bodyText=e.toString():a&&o&&(t=e)&&DataView.prototype.isPrototypeOf(t)?(this._bodyArrayBuffer=m(e.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):a&&(ArrayBuffer.prototype.isPrototypeOf(e)||s(e))?this._bodyArrayBuffer=m(e):this._bodyText=e=Object.prototype.toString.call(e):this._bodyText="",this.headers.get("content-type")||("string"==typeof e?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):n&&URLSearchParams.prototype.isPrototypeOf(e)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},o&&(this.blob=function(){var e=h(this);if(e)return e;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(new Blob([this._bodyArrayBuffer]));if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){return this._bodyArrayBuffer?h(this)||Promise.resolve(this._bodyArrayBuffer):this.blob().then(g)}),this.text=function(){var e,t,n,r=h(this);if(r)return r;if(this._bodyBlob)return e=this._bodyBlob,n=p(t=new FileReader),t.readAsText(e),n;if(this._bodyArrayBuffer)return Promise.resolve(function(e){for(var t=new Uint8Array(e),n=new Array(t.length),r=0;r-1?r:n),this.mode=t.mode||this.mode||null,this.signal=t.signal||this.signal,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&o)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(o)}function w(e){var t=new FormData;return e.trim().split("&").forEach((function(e){if(e){var n=e.split("="),r=n.shift().replace(/\+/g," "),o=n.join("=").replace(/\+/g," ");t.append(decodeURIComponent(r),decodeURIComponent(o))}})),t}function x(e,t){t||(t={}),this.type="default",this.status=void 0===t.status?200:t.status,this.ok=this.status>=200&&this.status<300,this.statusText="statusText"in t?t.statusText:"OK",this.headers=new d(t.headers),this.url=t.url||"",this._initBody(e)}b.prototype.clone=function(){return new b(this,{body:this._bodyInit})},v.call(b.prototype),v.call(x.prototype),x.prototype.clone=function(){return new x(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new d(this.headers),url:this.url})},x.error=function(){var e=new x(null,{status:0,statusText:""});return e.type="error",e};var E=[301,302,303,307,308];x.redirect=function(e,t){if(-1===E.indexOf(t))throw new RangeError("Invalid status code");return new x(null,{status:t,headers:{location:e}})},t.DOMException=e.DOMException;try{new t.DOMException}catch(e){t.DOMException=function(e,t){this.message=e,this.name=t;var n=Error(e);this.stack=n.stack},t.DOMException.prototype=Object.create(Error.prototype),t.DOMException.prototype.constructor=t.DOMException}function k(e,n){return new Promise((function(r,i){var a=new b(e,n);if(a.signal&&a.signal.aborted)return i(new t.DOMException("Aborted","AbortError"));var l=new XMLHttpRequest;function s(){l.abort()}l.onload=function(){var e,t,n={status:l.status,statusText:l.statusText,headers:(e=l.getAllResponseHeaders()||"",t=new d,e.replace(/\r?\n[\t ]+/g," ").split(/\r?\n/).forEach((function(e){var n=e.split(":"),r=n.shift().trim();if(r){var o=n.join(":").trim();t.append(r,o)}})),t)};n.url="responseURL"in l?l.responseURL:n.headers.get("X-Request-URL");var o="response"in l?l.response:l.responseText;r(new x(o,n))},l.onerror=function(){i(new TypeError("Network request failed"))},l.ontimeout=function(){i(new TypeError("Network request failed"))},l.onabort=function(){i(new t.DOMException("Aborted","AbortError"))},l.open(a.method,a.url,!0),"include"===a.credentials?l.withCredentials=!0:"omit"===a.credentials&&(l.withCredentials=!1),"responseType"in l&&o&&(l.responseType="blob"),a.headers.forEach((function(e,t){l.setRequestHeader(t,e)})),a.signal&&(a.signal.addEventListener("abort",s),l.onreadystatechange=function(){4===l.readyState&&a.signal.removeEventListener("abort",s)}),l.send(void 0===a._bodyInit?null:a._bodyInit)}))}k.polyfill=!0,e.fetch||(e.fetch=k,e.Headers=d,e.Request=b,e.Response=x),t.Headers=d,t.Request=b,t.Response=x,t.fetch=k,Object.defineProperty(t,"__esModule",{value:!0})}({})}(r),r.fetch.ponyfill=!0,delete r.fetch.polyfill;var o=r;(t=o.fetch).default=o.fetch,t.fetch=o.fetch,t.Headers=o.Headers,t.Request=o.Request,t.Response=o.Response,e.exports=t},1427:e=>{var t=function(){this.Diff_Timeout=1,this.Diff_EditCost=4,this.Match_Threshold=.5,this.Match_Distance=1e3,this.Patch_DeleteThreshold=.5,this.Patch_Margin=4,this.Match_MaxBits=32},n=-1;t.Diff=function(e,t){return[e,t]},t.prototype.diff_main=function(e,n,r,o){void 0===o&&(o=this.Diff_Timeout<=0?Number.MAX_VALUE:(new Date).getTime()+1e3*this.Diff_Timeout);var i=o;if(null==e||null==n)throw new Error("Null input. (diff_main)");if(e==n)return e?[new t.Diff(0,e)]:[];void 0===r&&(r=!0);var a=r,l=this.diff_commonPrefix(e,n),s=e.substring(0,l);e=e.substring(l),n=n.substring(l),l=this.diff_commonSuffix(e,n);var u=e.substring(e.length-l);e=e.substring(0,e.length-l),n=n.substring(0,n.length-l);var c=this.diff_compute_(e,n,a,i);return s&&c.unshift(new t.Diff(0,s)),u&&c.push(new t.Diff(0,u)),this.diff_cleanupMerge(c),c},t.prototype.diff_compute_=function(e,r,o,i){var a;if(!e)return[new t.Diff(1,r)];if(!r)return[new t.Diff(n,e)];var l=e.length>r.length?e:r,s=e.length>r.length?r:e,u=l.indexOf(s);if(-1!=u)return a=[new t.Diff(1,l.substring(0,u)),new t.Diff(0,s),new t.Diff(1,l.substring(u+s.length))],e.length>r.length&&(a[0][0]=a[2][0]=n),a;if(1==s.length)return[new t.Diff(n,e),new t.Diff(1,r)];var c=this.diff_halfMatch_(e,r);if(c){var f=c[0],d=c[1],h=c[2],p=c[3],g=c[4],m=this.diff_main(f,h,o,i),v=this.diff_main(d,p,o,i);return m.concat([new t.Diff(0,g)],v)}return o&&e.length>100&&r.length>100?this.diff_lineMode_(e,r,i):this.diff_bisect_(e,r,i)},t.prototype.diff_lineMode_=function(e,r,o){var i=this.diff_linesToChars_(e,r);e=i.chars1,r=i.chars2;var a=i.lineArray,l=this.diff_main(e,r,!1,o);this.diff_charsToLines_(l,a),this.diff_cleanupSemantic(l),l.push(new t.Diff(0,""));for(var s=0,u=0,c=0,f="",d="";s=1&&c>=1){l.splice(s-u-c,u+c),s=s-u-c;for(var h=this.diff_main(f,d,!1,o),p=h.length-1;p>=0;p--)l.splice(s,0,h[p]);s+=h.length}c=0,u=0,f="",d=""}s++}return l.pop(),l},t.prototype.diff_bisect_=function(e,r,o){for(var i=e.length,a=r.length,l=Math.ceil((i+a)/2),s=l,u=2*l,c=new Array(u),f=new Array(u),d=0;do);b++){for(var w=-b+g;w<=b-m;w+=2){for(var x=s+w,E=(O=w==-b||w!=b&&c[x-1]i)m+=2;else if(E>a)g+=2;else if(p&&(_=s+h-w)>=0&&_=(S=i-f[_]))return this.diff_bisectSplit_(e,r,O,E,o)}for(var k=-b+v;k<=b-y;k+=2){for(var S,_=s+k,C=(S=k==-b||k!=b&&f[_-1]i)y+=2;else if(C>a)v+=2;else if(!p){var O;if((x=s+h-k)>=0&&x=(S=i-S))return this.diff_bisectSplit_(e,r,O,E,o)}}}return[new t.Diff(n,e),new t.Diff(1,r)]},t.prototype.diff_bisectSplit_=function(e,t,n,r,o){var i=e.substring(0,n),a=t.substring(0,r),l=e.substring(n),s=t.substring(r),u=this.diff_main(i,a,!1,o),c=this.diff_main(l,s,!1,o);return u.concat(c)},t.prototype.diff_linesToChars_=function(e,t){var n=[],r={};function o(e){for(var t="",o=0,a=-1,l=n.length;ar?e=e.substring(n-r):nt.length?e:t,r=e.length>t.length?t:e;if(n.length<4||2*r.length=e.length?[r,i,a,l,c]:null}var a,l,s,u,c,f=i(n,r,Math.ceil(n.length/4)),d=i(n,r,Math.ceil(n.length/2));return f||d?(a=d?f&&f[4].length>d[4].length?f:d:f,e.length>t.length?(l=a[0],s=a[1],u=a[2],c=a[3]):(u=a[0],c=a[1],l=a[2],s=a[3]),[l,s,u,c,a[4]]):null},t.prototype.diff_cleanupSemantic=function(e){for(var r=!1,o=[],i=0,a=null,l=0,s=0,u=0,c=0,f=0;l0?o[i-1]:-1,s=0,u=0,c=0,f=0,a=null,r=!0)),l++;for(r&&this.diff_cleanupMerge(e),this.diff_cleanupSemanticLossless(e),l=1;l=g?(p>=d.length/2||p>=h.length/2)&&(e.splice(l,0,new t.Diff(0,h.substring(0,p))),e[l-1][1]=d.substring(0,d.length-p),e[l+1][1]=h.substring(p),l++):(g>=d.length/2||g>=h.length/2)&&(e.splice(l,0,new t.Diff(0,d.substring(0,g))),e[l-1][0]=1,e[l-1][1]=h.substring(0,h.length-g),e[l+1][0]=n,e[l+1][1]=d.substring(g),l++),l++}l++}},t.prototype.diff_cleanupSemanticLossless=function(e){function n(e,n){if(!e||!n)return 6;var r=e.charAt(e.length-1),o=n.charAt(0),i=r.match(t.nonAlphaNumericRegex_),a=o.match(t.nonAlphaNumericRegex_),l=i&&r.match(t.whitespaceRegex_),s=a&&o.match(t.whitespaceRegex_),u=l&&r.match(t.linebreakRegex_),c=s&&o.match(t.linebreakRegex_),f=u&&e.match(t.blanklineEndRegex_),d=c&&n.match(t.blanklineStartRegex_);return f||d?5:u||c?4:i&&!l&&s?3:l||s?2:i||a?1:0}for(var r=1;r=d&&(d=h,u=o,c=i,f=a)}e[r-1][1]!=u&&(u?e[r-1][1]=u:(e.splice(r-1,1),r--),e[r][1]=c,f?e[r+1][1]=f:(e.splice(r+1,1),r--))}r++}},t.nonAlphaNumericRegex_=/[^a-zA-Z0-9]/,t.whitespaceRegex_=/\s/,t.linebreakRegex_=/[\r\n]/,t.blanklineEndRegex_=/\n\r?\n$/,t.blanklineStartRegex_=/^\r?\n\r?\n/,t.prototype.diff_cleanupEfficiency=function(e){for(var r=!1,o=[],i=0,a=null,l=0,s=!1,u=!1,c=!1,f=!1;l0?o[i-1]:-1,c=f=!1),r=!0)),l++;r&&this.diff_cleanupMerge(e)},t.prototype.diff_cleanupMerge=function(e){e.push(new t.Diff(0,""));for(var r,o=0,i=0,a=0,l="",s="";o1?(0!==i&&0!==a&&(0!==(r=this.diff_commonPrefix(s,l))&&(o-i-a>0&&0==e[o-i-a-1][0]?e[o-i-a-1][1]+=s.substring(0,r):(e.splice(0,0,new t.Diff(0,s.substring(0,r))),o++),s=s.substring(r),l=l.substring(r)),0!==(r=this.diff_commonSuffix(s,l))&&(e[o][1]=s.substring(s.length-r)+e[o][1],s=s.substring(0,s.length-r),l=l.substring(0,l.length-r))),o-=i+a,e.splice(o,i+a),l.length&&(e.splice(o,0,new t.Diff(n,l)),o++),s.length&&(e.splice(o,0,new t.Diff(1,s)),o++),o++):0!==o&&0==e[o-1][0]?(e[o-1][1]+=e[o][1],e.splice(o,1)):o++,a=0,i=0,l="",s=""}""===e[e.length-1][1]&&e.pop();var u=!1;for(o=1;ot));r++)a=o,l=i;return e.length!=r&&e[r][0]===n?l:l+(t-a)},t.prototype.diff_prettyHtml=function(e){for(var t=[],r=/&/g,o=//g,a=/\n/g,l=0;l");switch(s){case 1:t[l]=''+u+"";break;case n:t[l]=''+u+"";break;case 0:t[l]=""+u+""}}return t.join("")},t.prototype.diff_text1=function(e){for(var t=[],n=0;nthis.Match_MaxBits)throw new Error("Pattern too long for this browser.");var r=this.match_alphabet_(t),o=this;function i(e,r){var i=e/t.length,a=Math.abs(n-r);return o.Match_Distance?i+a/o.Match_Distance:a?1:i}var a=this.Match_Threshold,l=e.indexOf(t,n);-1!=l&&(a=Math.min(i(0,l),a),-1!=(l=e.lastIndexOf(t,n+t.length))&&(a=Math.min(i(0,l),a)));var s,u,c=1<=p;v--){var y=r[e.charAt(v-1)];if(m[v]=0===h?(m[v+1]<<1|1)&y:(m[v+1]<<1|1)&y|(f[v+1]|f[v])<<1|1|f[v+1],m[v]&c){var b=i(h,v-1);if(b<=a){if(a=b,!((l=v-1)>n))break;p=Math.max(1,2*n-l)}}}if(i(h+1,n)>a)break;f=m}return l},t.prototype.match_alphabet_=function(e){for(var t={},n=0;n2&&(this.diff_cleanupSemantic(a),this.diff_cleanupEfficiency(a));else if(e&&"object"==typeof e&&void 0===r&&void 0===o)a=e,i=this.diff_text1(a);else if("string"==typeof e&&r&&"object"==typeof r&&void 0===o)i=e,a=r;else{if("string"!=typeof e||"string"!=typeof r||!o||"object"!=typeof o)throw new Error("Unknown call format to patch_make.");i=e,a=o}if(0===a.length)return[];for(var l=[],s=new t.patch_obj,u=0,c=0,f=0,d=i,h=i,p=0;p=2*this.Patch_Margin&&u&&(this.patch_addContext_(s,d),l.push(s),s=new t.patch_obj,u=0,d=h,c=f)}1!==g&&(c+=m.length),g!==n&&(f+=m.length)}return u&&(this.patch_addContext_(s,d),l.push(s)),l},t.prototype.patch_deepCopy=function(e){for(var n=[],r=0;rthis.Match_MaxBits?-1!=(l=this.match_main(t,c.substring(0,this.Match_MaxBits),u))&&(-1==(f=this.match_main(t,c.substring(c.length-this.Match_MaxBits),u+c.length-this.Match_MaxBits))||l>=f)&&(l=-1):l=this.match_main(t,c,u),-1==l)i[a]=!1,o-=e[a].length2-e[a].length1;else if(i[a]=!0,o=l-u,c==(s=-1==f?t.substring(l,l+c.length):t.substring(l,f+this.Match_MaxBits)))t=t.substring(0,l)+this.diff_text2(e[a].diffs)+t.substring(l+c.length);else{var d=this.diff_main(c,s,!1);if(c.length>this.Match_MaxBits&&this.diff_levenshtein(d)/c.length>this.Patch_DeleteThreshold)i[a]=!1;else{this.diff_cleanupSemanticLossless(d);for(var h,p=0,g=0;ga[0][1].length){var l=n-a[0][1].length;a[0][1]=r.substring(a[0][1].length)+a[0][1],i.start1-=l,i.start2-=l,i.length1+=l,i.length2+=l}return 0==(a=(i=e[e.length-1]).diffs).length||0!=a[a.length-1][0]?(a.push(new t.Diff(0,r)),i.length1+=n,i.length2+=n):n>a[a.length-1][1].length&&(l=n-a[a.length-1][1].length,a[a.length-1][1]+=r.substring(0,l),i.length1+=l,i.length2+=l),r},t.prototype.patch_splitMax=function(e){for(var r=this.Match_MaxBits,o=0;o2*r?(u.length1+=d.length,a+=d.length,c=!1,u.diffs.push(new t.Diff(f,d)),i.diffs.shift()):(d=d.substring(0,r-u.length1-this.Patch_Margin),u.length1+=d.length,a+=d.length,0===f?(u.length2+=d.length,l+=d.length):c=!1,u.diffs.push(new t.Diff(f,d)),d==i.diffs[0][1]?i.diffs.shift():i.diffs[0][1]=i.diffs[0][1].substring(d.length))}s=(s=this.diff_text2(u.diffs)).substring(s.length-this.Patch_Margin);var h=this.diff_text1(i.diffs).substring(0,this.Patch_Margin);""!==h&&(u.length1+=h.length,u.length2+=h.length,0!==u.diffs.length&&0===u.diffs[u.diffs.length-1][0]?u.diffs[u.diffs.length-1][1]+=h:u.diffs.push(new t.Diff(0,h))),c||e.splice(++o,0,u)}}},t.prototype.patch_toText=function(e){for(var t=[],n=0;n{"use strict";e.exports=function(e){var t=e.uri,n=e.name,r=e.type;this.uri=t,this.name=n,this.type=r}},2929:(e,t,n)=>{"use strict";var r=n(1278);e.exports=function e(t,n,o){var i;void 0===n&&(n=""),void 0===o&&(o=r);var a=new Map;function l(e,t){var n=a.get(t);n?n.push.apply(n,e):a.set(t,e)}if(o(t))i=null,l([n],t);else{var s=n?n+".":"";if("undefined"!=typeof FileList&&t instanceof FileList)i=Array.prototype.map.call(t,(function(e,t){return l([""+s+t],e),null}));else if(Array.isArray(t))i=t.map((function(t,n){var r=e(t,""+s+n,o);return r.files.forEach(l),r.clone}));else if(t&&t.constructor===Object)for(var u in i={},t){var c=e(t[u],""+s+u,o);c.files.forEach(l),i[u]=c.clone}else i=t}return{clone:i,files:a}}},9384:(e,t,n)=>{"use strict";t.ReactNativeFile=n(7570),t.extractFiles=n(2929),t.isExtractableFile=n(1278)},1278:(e,t,n)=>{"use strict";var r=n(7570);e.exports=function(e){return"undefined"!=typeof File&&e instanceof File||"undefined"!=typeof Blob&&e instanceof Blob||e instanceof r}},1688:e=>{e.exports="object"==typeof self?self.FormData:window.FormData},8749:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});var o=n(9384),i=r(n(1688)),a=function(e){return o.isExtractableFile(e)||null!==e&&"object"==typeof e&&"function"==typeof e.pipe};t.default=function(e,t,n){var r=o.extractFiles({query:e,variables:t,operationName:n},"",a),l=r.clone,s=r.files;if(0===s.size){if(!Array.isArray(e))return JSON.stringify(l);if(void 0!==t&&!Array.isArray(t))throw new Error("Cannot create request body with given variable type, array expected");var u=e.reduce((function(e,n,r){return e.push({query:n,variables:t?t[r]:void 0}),e}),[]);return JSON.stringify(u)}var c=new("undefined"==typeof FormData?i.default:FormData);c.append("operations",JSON.stringify(l));var f={},d=0;return s.forEach((function(e){f[++d]=e})),c.append("map",JSON.stringify(f)),d=0,s.forEach((function(e,t){c.append(""+ ++d,t)})),c}},6647:function(e,t,n){"use strict";var r=this&&this.__assign||function(){return r=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){var t=e.prototype.toJSON;"function"==typeof t||(0,r.default)(0),e.prototype.inspect=t,o.default&&(e.prototype[o.default]=t)};var r=i(n(5006)),o=i(n(8019));function i(e){return e&&e.__esModule?e:{default:e}}},8048:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){return a(e,[])};var r,o=(r=n(8019))&&r.__esModule?r:{default:r};function i(e){return i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},i(e)}function a(e,t){switch(i(e)){case"string":return JSON.stringify(e);case"function":return e.name?"[function ".concat(e.name,"]"):"[function]";case"object":return null===e?"null":function(e,t){if(-1!==t.indexOf(e))return"[Circular]";var n=[].concat(t,[e]),r=function(e){var t=e[String(o.default)];return"function"==typeof t?t:"function"==typeof e.inspect?e.inspect:void 0}(e);if(void 0!==r){var i=r.call(e);if(i!==e)return"string"==typeof i?i:a(i,n)}else if(Array.isArray(e))return function(e,t){if(0===e.length)return"[]";if(t.length>2)return"[Array]";for(var n=Math.min(10,e.length),r=e.length-n,o=[],i=0;i1&&o.push("... ".concat(r," more items")),"["+o.join(", ")+"]"}(e,n);return function(e,t){var n=Object.keys(e);return 0===n.length?"{}":t.length>2?"["+function(e){var t=Object.prototype.toString.call(e).replace(/^\[object /,"").replace(/]$/,"");if("Object"===t&&"function"==typeof e.constructor){var n=e.constructor.name;if("string"==typeof n&&""!==n)return n}return t}(e)+"]":"{ "+n.map((function(n){return n+": "+a(e[n],t)})).join(", ")+" }"}(e,n)}(e,t);default:return String(e)}}},5006:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){if(!Boolean(e))throw new Error(null!=t?t:"Unexpected invariant triggered.")}},8019:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):void 0;t.default=n},4560:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isNode=function(e){return null!=e&&"string"==typeof e.kind},t.Token=t.Location=void 0;var r,o=(r=n(2678))&&r.__esModule?r:{default:r},i=function(){function e(e,t,n){this.start=e.start,this.end=t.end,this.startToken=e,this.endToken=t,this.source=n}return e.prototype.toJSON=function(){return{start:this.start,end:this.end}},e}();t.Location=i,(0,o.default)(i);var a=function(){function e(e,t,n,r,o,i,a){this.kind=e,this.start=t,this.end=n,this.line=r,this.column=o,this.value=a,this.prev=i,this.next=null}return e.prototype.toJSON=function(){return{kind:this.kind,value:this.value,line:this.line,column:this.column}},e}();t.Token=a,(0,o.default)(a)},9501:(e,t)=>{"use strict";function n(e){for(var t=0;ta&&n(t[l-1]);)--l;return t.slice(a,l).join("\n")},t.getBlockStringIndentation=r,t.printBlockString=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=-1===e.indexOf("\n"),o=" "===e[0]||"\t"===e[0],i='"'===e[e.length-1],a="\\"===e[e.length-1],l=!r||i||a||n,s="";return!l||r&&o||(s+="\n"+t),s+=t?e.replace(/\n/g,"\n"+t):e,l&&(s+="\n"),'"""'+s.replace(/"""/g,'\\"""')+'"""'}},3083:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.print=function(e){return(0,r.visit)(e,{leave:i})};var r=n(2624),o=n(9501),i={Name:function(e){return e.value},Variable:function(e){return"$"+e.name},Document:function(e){return l(e.definitions,"\n\n")+"\n"},OperationDefinition:function(e){var t=e.operation,n=e.name,r=u("(",l(e.variableDefinitions,", "),")"),o=l(e.directives," "),i=e.selectionSet;return n||o||r||"query"!==t?l([t,l([n,r]),o,i]," "):i},VariableDefinition:function(e){var t=e.variable,n=e.type,r=e.defaultValue,o=e.directives;return t+": "+n+u(" = ",r)+u(" ",l(o," "))},SelectionSet:function(e){return s(e.selections)},Field:function(e){var t=e.alias,n=e.name,r=e.arguments,o=e.directives,i=e.selectionSet,a=u("",t,": ")+n,s=a+u("(",l(r,", "),")");return s.length>80&&(s=a+u("(\n",c(l(r,"\n")),"\n)")),l([s,l(o," "),i]," ")},Argument:function(e){return e.name+": "+e.value},FragmentSpread:function(e){return"..."+e.name+u(" ",l(e.directives," "))},InlineFragment:function(e){var t=e.typeCondition,n=e.directives,r=e.selectionSet;return l(["...",u("on ",t),l(n," "),r]," ")},FragmentDefinition:function(e){var t=e.name,n=e.typeCondition,r=e.variableDefinitions,o=e.directives,i=e.selectionSet;return"fragment ".concat(t).concat(u("(",l(r,", "),")")," ")+"on ".concat(n," ").concat(u("",l(o," ")," "))+i},IntValue:function(e){return e.value},FloatValue:function(e){return e.value},StringValue:function(e,t){var n=e.value;return e.block?(0,o.printBlockString)(n,"description"===t?"":" "):JSON.stringify(n)},BooleanValue:function(e){return e.value?"true":"false"},NullValue:function(){return"null"},EnumValue:function(e){return e.value},ListValue:function(e){return"["+l(e.values,", ")+"]"},ObjectValue:function(e){return"{"+l(e.fields,", ")+"}"},ObjectField:function(e){return e.name+": "+e.value},Directive:function(e){return"@"+e.name+u("(",l(e.arguments,", "),")")},NamedType:function(e){return e.name},ListType:function(e){return"["+e.type+"]"},NonNullType:function(e){return e.type+"!"},SchemaDefinition:a((function(e){var t=e.directives,n=e.operationTypes;return l(["schema",l(t," "),s(n)]," ")})),OperationTypeDefinition:function(e){return e.operation+": "+e.type},ScalarTypeDefinition:a((function(e){return l(["scalar",e.name,l(e.directives," ")]," ")})),ObjectTypeDefinition:a((function(e){var t=e.name,n=e.interfaces,r=e.directives,o=e.fields;return l(["type",t,u("implements ",l(n," & ")),l(r," "),s(o)]," ")})),FieldDefinition:a((function(e){var t=e.name,n=e.arguments,r=e.type,o=e.directives;return t+(d(n)?u("(\n",c(l(n,"\n")),"\n)"):u("(",l(n,", "),")"))+": "+r+u(" ",l(o," "))})),InputValueDefinition:a((function(e){var t=e.name,n=e.type,r=e.defaultValue,o=e.directives;return l([t+": "+n,u("= ",r),l(o," ")]," ")})),InterfaceTypeDefinition:a((function(e){var t=e.name,n=e.interfaces,r=e.directives,o=e.fields;return l(["interface",t,u("implements ",l(n," & ")),l(r," "),s(o)]," ")})),UnionTypeDefinition:a((function(e){var t=e.name,n=e.directives,r=e.types;return l(["union",t,l(n," "),r&&0!==r.length?"= "+l(r," | "):""]," ")})),EnumTypeDefinition:a((function(e){var t=e.name,n=e.directives,r=e.values;return l(["enum",t,l(n," "),s(r)]," ")})),EnumValueDefinition:a((function(e){return l([e.name,l(e.directives," ")]," ")})),InputObjectTypeDefinition:a((function(e){var t=e.name,n=e.directives,r=e.fields;return l(["input",t,l(n," "),s(r)]," ")})),DirectiveDefinition:a((function(e){var t=e.name,n=e.arguments,r=e.repeatable,o=e.locations;return"directive @"+t+(d(n)?u("(\n",c(l(n,"\n")),"\n)"):u("(",l(n,", "),")"))+(r?" repeatable":"")+" on "+l(o," | ")})),SchemaExtension:function(e){var t=e.directives,n=e.operationTypes;return l(["extend schema",l(t," "),s(n)]," ")},ScalarTypeExtension:function(e){return l(["extend scalar",e.name,l(e.directives," ")]," ")},ObjectTypeExtension:function(e){var t=e.name,n=e.interfaces,r=e.directives,o=e.fields;return l(["extend type",t,u("implements ",l(n," & ")),l(r," "),s(o)]," ")},InterfaceTypeExtension:function(e){var t=e.name,n=e.interfaces,r=e.directives,o=e.fields;return l(["extend interface",t,u("implements ",l(n," & ")),l(r," "),s(o)]," ")},UnionTypeExtension:function(e){var t=e.name,n=e.directives,r=e.types;return l(["extend union",t,l(n," "),r&&0!==r.length?"= "+l(r," | "):""]," ")},EnumTypeExtension:function(e){var t=e.name,n=e.directives,r=e.values;return l(["extend enum",t,l(n," "),s(r)]," ")},InputObjectTypeExtension:function(e){var t=e.name,n=e.directives,r=e.fields;return l(["extend input",t,l(n," "),s(r)]," ")}};function a(e){return function(t){return l([t.description,e(t)],"\n")}}function l(e){var t,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return null!==(t=null==e?void 0:e.filter((function(e){return e})).join(n))&&void 0!==t?t:""}function s(e){return u("{\n",c(l(e,"\n")),"\n}")}function u(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";return null!=t&&""!==t?e+t+n:""}function c(e){return u(" ",e.replace(/\n/g,"\n "))}function f(e){return-1!==e.indexOf("\n")}function d(e){return null!=e&&e.some(f)}},2624:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.visit=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:a,r=void 0,u=Array.isArray(e),c=[e],f=-1,d=[],h=void 0,p=void 0,g=void 0,m=[],v=[],y=e;do{var b=++f===c.length,w=b&&0!==d.length;if(b){if(p=0===v.length?void 0:m[m.length-1],h=g,g=v.pop(),w){if(u)h=h.slice();else{for(var x={},E=0,k=Object.keys(h);E{var r=n(7772).Symbol;e.exports=r},3366:(e,t,n)=>{var r=n(857),o=n(2107),i=n(7157),a=r?r.toStringTag:void 0;e.exports=function(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":a&&a in Object(e)?o(e):i(e)}},1704:(e,t,n)=>{var r=n(2153),o=/^\s+/;e.exports=function(e){return e?e.slice(0,r(e)+1).replace(o,""):e}},1242:(e,t,n)=>{var r="object"==typeof n.g&&n.g&&n.g.Object===Object&&n.g;e.exports=r},2107:(e,t,n)=>{var r=n(857),o=Object.prototype,i=o.hasOwnProperty,a=o.toString,l=r?r.toStringTag:void 0;e.exports=function(e){var t=i.call(e,l),n=e[l];try{e[l]=void 0;var r=!0}catch(e){}var o=a.call(e);return r&&(t?e[l]=n:delete e[l]),o}},7157:e=>{var t=Object.prototype.toString;e.exports=function(e){return t.call(e)}},7772:(e,t,n)=>{var r=n(1242),o="object"==typeof self&&self&&self.Object===Object&&self,i=r||o||Function("return this")();e.exports=i},2153:e=>{var t=/\s/;e.exports=function(e){for(var n=e.length;n--&&t.test(e.charAt(n)););return n}},4073:(e,t,n)=>{var r=n(9259),o=n(1100),i=n(7642),a=Math.max,l=Math.min;e.exports=function(e,t,n){var s,u,c,f,d,h,p=0,g=!1,m=!1,v=!0;if("function"!=typeof e)throw new TypeError("Expected a function");function y(t){var n=s,r=u;return s=u=void 0,p=t,f=e.apply(r,n)}function b(e){return p=e,d=setTimeout(x,t),g?y(e):f}function w(e){var n=e-h;return void 0===h||n>=t||n<0||m&&e-p>=c}function x(){var e=o();if(w(e))return E(e);d=setTimeout(x,function(e){var n=t-(e-h);return m?l(n,c-(e-p)):n}(e))}function E(e){return d=void 0,v&&s?y(e):(s=u=void 0,f)}function k(){var e=o(),n=w(e);if(s=arguments,u=this,h=e,n){if(void 0===d)return b(h);if(m)return clearTimeout(d),d=setTimeout(x,t),y(h)}return void 0===d&&(d=setTimeout(x,t)),f}return t=i(t)||0,r(n)&&(g=!!n.leading,c=(m="maxWait"in n)?a(i(n.maxWait)||0,t):c,v="trailing"in n?!!n.trailing:v),k.cancel=function(){void 0!==d&&clearTimeout(d),p=0,s=h=u=d=void 0},k.flush=function(){return void 0===d?f:E(o())},k}},9259:e=>{e.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},5125:e=>{e.exports=function(e){return null!=e&&"object"==typeof e}},4795:(e,t,n)=>{var r=n(3366),o=n(5125);e.exports=function(e){return"symbol"==typeof e||o(e)&&"[object Symbol]"==r(e)}},1100:(e,t,n)=>{var r=n(7772);e.exports=function(){return r.Date.now()}},7642:(e,t,n)=>{var r=n(1704),o=n(9259),i=n(4795),a=/^[-+]0x[0-9a-f]+$/i,l=/^0b[01]+$/i,s=/^0o[0-7]+$/i,u=parseInt;e.exports=function(e){if("number"==typeof e)return e;if(i(e))return NaN;if(o(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=o(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=r(e);var n=l.test(e);return n||s.test(e)?u(e.slice(2),n?2:8):a.test(e)?NaN:+e}},104:(e,t,n)=>{"use strict";(function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)if(Object.prototype.hasOwnProperty.call(e,n)){var r=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(e,n):{};r.get||r.set?Object.defineProperty(t,n,r):t[n]=e[n]}t.default=e})(n(2784)),(r=n(5977))&&r.__esModule,n(8467),n(3321),n(4329);var r,o,i=(n(1624),n(1119));null===(o=window.omnivoreEnv.__NEXT_IMAGE_OPTS)||void 0===o||o.experimentalLayoutRaw;window.omnivoreEnv.__NEXT_IMAGE_OPTS,new Set;new Map;"undefined"==typeof window&&(n.g.__NEXT_IMAGE_IMPORTED=!0);new Map([["default",function({config:e,src:t,width:n,quality:r}){return t.endsWith(".svg")&&!e.dangerouslyAllowSVG?t:`${i.normalizePathTrailingSlash(e.path)}?url=${encodeURIComponent(t)}&w=${n}&q=${r||75}`}],["imgix",function({config:e,src:t,width:n,quality:r}){const o=new URL(`${e.path}${a(t)}`),i=o.searchParams;return i.set("auto",i.get("auto")||"format"),i.set("fit",i.get("fit")||"max"),i.set("w",i.get("w")||n.toString()),r&&i.set("q",r.toString()),o.href}],["cloudinary",function({config:e,src:t,width:n,quality:r}){const o=["f_auto","c_limit","w_"+n,"q_"+(r||"auto")].join(",")+"/";return`${e.path}${o}${a(t)}`}],["akamai",function({config:e,src:t,width:n}){return`${e.path}${a(t)}?imwidth=${n}`}],["custom",function({src:e}){throw new Error(`Image with src "${e}" is missing "loader" prop.\nRead more: https://nextjs.org/docs/messages/next-image-missing-loader`)}]]);function a(e){return"/"===e[0]?e.slice(1):e}},4529:(e,t,n)=>{"use strict";var r;(r=n(2784))&&r.__esModule,n(6640),n(9518),n(3321)},1119:(e,t)=>{"use strict";function n(e){return e.endsWith("/")&&"/"!==e?e.slice(0,-1):e}Object.defineProperty(t,"__esModule",{value:!0}),t.removePathTrailingSlash=n,t.normalizePathTrailingSlash=void 0;const r=window.omnivoreEnv.__NEXT_TRAILING_SLASH?e=>/\.[^/]+\/?$/.test(e)?n(e):e.endsWith("/")?e:e+"/":n;t.normalizePathTrailingSlash=r},1976:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.cancelIdleCallback=t.requestIdleCallback=void 0;const n="undefined"!=typeof self&&self.requestIdleCallback&&self.requestIdleCallback.bind(window)||function(e){let t=Date.now();return setTimeout((function(){e({didTimeout:!1,timeRemaining:function(){return Math.max(0,50-(Date.now()-t))}})}),1)};t.requestIdleCallback=n;const r="undefined"!=typeof self&&self.cancelIdleCallback&&self.cancelIdleCallback.bind(window)||function(e){return clearTimeout(e)};t.cancelIdleCallback=r},7928:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.markAssetError=u,t.isAssetError=function(e){return e&&s in e},t.getClientBuildManifest=f,t.getMiddlewareManifest=function(){return self.__MIDDLEWARE_MANIFEST?Promise.resolve(self.__MIDDLEWARE_MANIFEST):c(new Promise((e=>{const t=self.__MIDDLEWARE_MANIFEST_CB;self.__MIDDLEWARE_MANIFEST_CB=()=>{e(self.__MIDDLEWARE_MANIFEST),t&&t()}})),i,u(new Error("Failed to load client middleware manifest")))},t.createRouteLoader=function(e){const t=new Map,n=new Map,r=new Map,s=new Map;function f(e){{let t=n.get(e);return t||(document.querySelector(`script[src^="${e}"]`)?Promise.resolve():(n.set(e,t=function(e,t){return new Promise(((n,r)=>{(t=document.createElement("script")).onload=n,t.onerror=()=>r(u(new Error(`Failed to load script: ${e}`))),t.crossOrigin=window.omnivoreEnv.__NEXT_CROSS_ORIGIN,t.src=e,document.body.appendChild(t)}))}(e)),t))}}function h(e){let t=r.get(e);return t||(r.set(e,t=fetch(e).then((t=>{if(!t.ok)throw new Error(`Failed to load stylesheet: ${e}`);return t.text().then((t=>({href:e,content:t})))})).catch((e=>{throw u(e)}))),t)}return{whenEntrypoint:e=>a(e,t),onEntrypoint(e,n){(n?Promise.resolve().then((()=>n())).then((e=>({component:e&&e.default||e,exports:e})),(e=>({error:e}))):Promise.resolve(void 0)).then((n=>{const r=t.get(e);r&&"resolve"in r?n&&(t.set(e,n),r.resolve(n)):(n?t.set(e,n):t.delete(e),s.delete(e))}))},loadRoute(n,r){return a(n,s,(()=>c(d(e,n).then((({scripts:e,css:r})=>Promise.all([t.has(n)?[]:Promise.all(e.map(f)),Promise.all(r.map(h))]))).then((e=>this.whenEntrypoint(n).then((t=>({entrypoint:t,styles:e[1]}))))),i,u(new Error(`Route did not complete loading: ${n}`))).then((({entrypoint:e,styles:t})=>{const n=Object.assign({styles:t},e);return"error"in e?e:n})).catch((e=>{if(r)throw e;return{error:e}})).finally((()=>{}))))},prefetch(t){let n;return(n=navigator.connection)&&(n.saveData||/2g/.test(n.effectiveType))?Promise.resolve():d(e,t).then((e=>Promise.all(l?e.scripts.map((e=>{return t=e,n="script",new Promise(((e,o)=>{const i=`\n link[rel="prefetch"][href^="${t}"],\n link[rel="preload"][href^="${t}"],\n script[src^="${t}"]`;if(document.querySelector(i))return e();(r=document.createElement("link")).as=n,r.rel="prefetch",r.crossOrigin=window.omnivoreEnv.__NEXT_CROSS_ORIGIN,r.onload=e,r.onerror=o,r.href=t,document.head.appendChild(r)}));var t,n,r})):[]))).then((()=>{o.requestIdleCallback((()=>this.loadRoute(t,!0).catch((()=>{}))))})).catch((()=>{}))}}},(r=n(9983))&&r.__esModule;var r,o=n(1976);const i=3800;function a(e,t,n){let r,o=t.get(e);if(o)return"future"in o?o.future:Promise.resolve(o);const i=new Promise((e=>{r=e}));return t.set(e,o={resolve:r,future:i}),n?n().then((e=>(r(e),e))).catch((n=>{throw t.delete(e),n})):i}const l=function(e){try{return e=document.createElement("link"),!!window.MSInputMethodContext&&!!document.documentMode||e.relList.supports("prefetch")}catch(e){return!1}}(),s=Symbol("ASSET_LOAD_ERROR");function u(e){return Object.defineProperty(e,s,{})}function c(e,t,n){return new Promise(((r,i)=>{let a=!1;e.then((e=>{a=!0,r(e)})).catch(i),o.requestIdleCallback((()=>setTimeout((()=>{a||i(n)}),t)))}))}function f(){return self.__BUILD_MANIFEST?Promise.resolve(self.__BUILD_MANIFEST):c(new Promise((e=>{const t=self.__BUILD_MANIFEST_CB;self.__BUILD_MANIFEST_CB=()=>{e(self.__BUILD_MANIFEST),t&&t()}})),i,u(new Error("Failed to load client build manifest")))}function d(e,t){return f().then((n=>{if(!(t in n))throw u(new Error(`Failed to lookup route: ${t}`));const r=n[t].map((t=>e+"/_next/"+encodeURI(t)));return{scripts:r.filter((e=>e.endsWith(".js"))),css:r.filter((e=>e.endsWith(".css")))}}))}},9518:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"Router",{enumerable:!0,get:function(){return o.default}}),Object.defineProperty(t,"withRouter",{enumerable:!0,get:function(){return l.default}}),t.useRouter=function(){return r.default.useContext(i.RouterContext)},t.createRouter=function(...e){return u.router=new o.default(...e),u.readyCallbacks.forEach((e=>e())),u.readyCallbacks=[],u.router},t.makePublicRouterInstance=function(e){const t=e,n={};for(const e of c)"object"!=typeof t[e]?n[e]=t[e]:n[e]=Object.assign(Array.isArray(t[e])?[]:{},t[e]);return n.events=o.default.events,f.forEach((e=>{n[e]=(...n)=>t[e](...n)})),n},t.default=void 0;var r=s(n(2784)),o=s(n(6640)),i=n(6510),a=s(n(274)),l=s(n(9564));function s(e){return e&&e.__esModule?e:{default:e}}const u={router:null,readyCallbacks:[],ready(e){if(this.router)return e();"undefined"!=typeof window&&this.readyCallbacks.push(e)}},c=["pathname","route","query","asPath","components","isFallback","basePath","locale","locales","defaultLocale","isReady","isPreview","isLocaleDomain","domainLocales"],f=["push","replace","reload","back","prefetch","beforePopState"];function d(){if(!u.router)throw new Error('No router instance found.\nYou should only use "next/router" on the client side of your app.\n');return u.router}Object.defineProperty(u,"events",{get:()=>o.default.events}),c.forEach((e=>{Object.defineProperty(u,e,{get:()=>d()[e]})})),f.forEach((e=>{u[e]=(...t)=>d()[e](...t)})),["routeChangeStart","beforeHistoryChange","routeChangeComplete","routeChangeError","hashChangeStart","hashChangeComplete"].forEach((e=>{u.ready((()=>{o.default.events.on(e,((...t)=>{const n=`on${e.charAt(0).toUpperCase()}${e.substring(1)}`,r=u;if(r[n])try{r[n](...t)}catch(e){console.error(`Error when running the Router event: ${n}`),console.error(a.default(e)?`${e.message}\n${e.stack}`:e+"")}}))}))}));var h=u;t.default=h},3321:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.useIntersection=function({rootRef:e,rootMargin:t,disabled:n}){const s=n||!i,u=r.useRef(),[c,f]=r.useState(!1),[d,h]=r.useState(e?e.current:null),p=r.useCallback((e=>{u.current&&(u.current(),u.current=void 0),s||c||e&&e.tagName&&(u.current=function(e,t,n){const{id:r,observer:o,elements:i}=function(e){const t={root:e.root||null,margin:e.rootMargin||""};let n,r=l.find((e=>e.root===t.root&&e.margin===t.margin));if(r?n=a.get(r):(n=a.get(t),l.push(t)),n)return n;const o=new Map,i=new IntersectionObserver((e=>{e.forEach((e=>{const t=o.get(e.target),n=e.isIntersecting||e.intersectionRatio>0;t&&n&&t(n)}))}),e);return a.set(t,n={id:t,observer:i,elements:o}),n}(n);return i.set(e,(e=>e&&f(e))),o.observe(e),function(){if(i.delete(e),o.unobserve(e),0===i.size){o.disconnect(),a.delete(r);let e=l.findIndex((e=>e.root===r.root&&e.margin===r.margin));e>-1&&l.splice(e,1)}}}(e,0,{root:d,rootMargin:t}))}),[s,d,t,c]);return r.useEffect((()=>{if(!i&&!c){const e=o.requestIdleCallback((()=>f(!0)));return()=>o.cancelIdleCallback(e)}}),[c]),r.useEffect((()=>{e&&h(e.current)}),[e]),[p,c]};var r=n(2784),o=n(1976);const i="undefined"!=typeof IntersectionObserver,a=new Map,l=[]},9564:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){function t(t){return o.default.createElement(e,Object.assign({router:i.useRouter()},t))}return t.getInitialProps=e.getInitialProps,t.origGetInitialProps=e.origGetInitialProps,t};var r,o=(r=n(2784))&&r.__esModule?r:{default:r},i=n(9518)},9264:(e,t)=>{"use strict";function n(e,t){void 0===t&&(t={});for(var n=function(e){for(var t=[],n=0;n=48&&s<=57||s>=65&&s<=90||s>=97&&s<=122||95===s))break;a+=e[l++]}if(!a)throw new TypeError("Missing parameter name at "+n);t.push({type:"NAME",index:n,value:a}),n=l}else t.push({type:"CLOSE",index:n,value:e[n++]});else t.push({type:"OPEN",index:n,value:e[n++]});else t.push({type:"ESCAPED_CHAR",index:n++,value:e[n++]});else t.push({type:"MODIFIER",index:n,value:e[n++]})}return t.push({type:"END",index:n,value:""}),t}(e),r=t.prefixes,o=void 0===r?"./":r,a="[^"+i(t.delimiter||"/#?")+"]+?",l=[],s=0,u=0,c="",f=function(e){if(u-1:void 0===E;o||(g+="(?:"+p+"(?="+h+"))?"),k||(g+="(?="+p+"|"+h+")")}return new RegExp(g,a(n))}function s(e,t,r){return e instanceof RegExp?function(e,t){if(!t)return e;var n=e.source.match(/\((?!\?)/g);if(n)for(var r=0;r{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=o,t.getProperError=function(e){return o(e)?e:new Error(r.isPlainObject(e)?JSON.stringify(e):e+"")};var r=n(9910);function o(e){return"object"==typeof e&&null!==e&&"name"in e&&"message"in e}},4863:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.normalizePathSep=o,t.denormalizePagePath=function(e){return(e=o(e)).startsWith("/index/")&&!r.isDynamicRoute(e)?e=e.slice(6):"/index"===e&&(e="/"),e};var r=n(9150);function o(e){return e.replace(/\\/g,"/")}},1719:(e,t,n)=>{"use strict";var r;Object.defineProperty(t,"__esModule",{value:!0}),t.AmpStateContext=void 0;const o=((r=n(2784))&&r.__esModule?r:{default:r}).default.createContext({});t.AmpStateContext=o},9739:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isInAmpMode=a,t.useAmp=function(){return a(o.default.useContext(i.AmpStateContext))};var r,o=(r=n(2784))&&r.__esModule?r:{default:r},i=n(1719);function a({ampFirst:e=!1,hybrid:t=!1,hasQuery:n=!1}={}){return e||t&&n}},8058:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.escapeStringRegexp=function(e){return e.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&")}},7177:(e,t,n)=>{"use strict";var r;Object.defineProperty(t,"__esModule",{value:!0}),t.HeadManagerContext=void 0;const o=((r=n(2784))&&r.__esModule?r:{default:r}).default.createContext({});t.HeadManagerContext=o},5977:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.defaultHead=u,t.default=void 0;var r,o=function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)if(Object.prototype.hasOwnProperty.call(e,n)){var r=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(e,n):{};r.get||r.set?Object.defineProperty(t,n,r):t[n]=e[n]}return t.default=e,t}(n(2784)),i=(r=n(1889))&&r.__esModule?r:{default:r},a=n(1719),l=n(7177),s=n(9739);function u(e=!1){const t=[o.default.createElement("meta",{charSet:"utf-8"})];return e||t.push(o.default.createElement("meta",{name:"viewport",content:"width=device-width"})),t}function c(e,t){return"string"==typeof t||"number"==typeof t?e:t.type===o.default.Fragment?e.concat(o.default.Children.toArray(t.props.children).reduce(((e,t)=>"string"==typeof t||"number"==typeof t?e:e.concat(t)),[])):e.concat(t)}n(1624);const f=["name","httpEquiv","charSet","itemProp"];function d(e,t){return e.reduce(((e,t)=>{const n=o.default.Children.toArray(t.props.children);return e.concat(n)}),[]).reduce(c,[]).reverse().concat(u(t.inAmpMode)).filter(function(){const e=new Set,t=new Set,n=new Set,r={};return o=>{let i=!0,a=!1;if(o.key&&"number"!=typeof o.key&&o.key.indexOf("$")>0){a=!0;const t=o.key.slice(o.key.indexOf("$")+1);e.has(t)?i=!1:e.add(t)}switch(o.type){case"title":case"base":t.has(o.type)?i=!1:t.add(o.type);break;case"meta":for(let e=0,t=f.length;e{const r=e.key||n;if(window.omnivoreEnv.__NEXT_OPTIMIZE_FONTS&&!t.inAmpMode&&"link"===e.type&&e.props.href&&["https://fonts.googleapis.com/css","https://use.typekit.net/"].some((t=>e.props.href.startsWith(t)))){const t={...e.props||{}};return t["data-href"]=t.href,t.href=void 0,t["data-optimized-fonts"]=!0,o.default.cloneElement(e,t)}return o.default.cloneElement(e,{key:r})}))}t.default=function({children:e}){const t=o.useContext(a.AmpStateContext),n=o.useContext(l.HeadManagerContext);return o.default.createElement(i.default,{reduceComponentsToState:d,headManager:n,inAmpMode:s.isInAmpMode(t)},e)}},927:(e,t)=>{"use strict";t.D=function(e,t,n){let r;if(e){n&&(n=n.toLowerCase());for(const a of e){var o,i;if(t===(null===(o=a.domain)||void 0===o?void 0:o.split(":")[0].toLowerCase())||n===a.defaultLocale.toLowerCase()||(null===(i=a.locales)||void 0===i?void 0:i.some((e=>e.toLowerCase()===n)))){r=a;break}}}return r}},816:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.normalizeLocalePath=function(e,t){let n;const r=e.split("/");return(t||[]).some((t=>!(!r[1]||r[1].toLowerCase()!==t.toLowerCase()||(n=t,r.splice(1,1),e=r.join("/")||"/",0)))),{pathname:e,detectedLocale:n}}},4329:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ImageConfigContext=void 0;var r,o=(r=n(2784))&&r.__esModule?r:{default:r},i=n(8467);const a=o.default.createContext(i.imageConfigDefault);t.ImageConfigContext=a},8467:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.imageConfigDefault=t.VALID_LOADERS=void 0,t.VALID_LOADERS=["default","imgix","cloudinary","akamai","custom"];t.imageConfigDefault={deviceSizes:[640,750,828,1080,1200,1920,2048,3840],imageSizes:[16,32,48,64,96,128,256,384],path:"/_next/image",loader:"default",domains:[],disableStaticImages:!1,minimumCacheTTL:60,formats:["image/webp"],dangerouslyAllowSVG:!1,contentSecurityPolicy:"script-src 'none'; frame-src 'none'; sandbox;"}},9910:(e,t)=>{"use strict";function n(e){return Object.prototype.toString.call(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.getObjectClassLabel=n,t.isPlainObject=function(e){if("[object Object]"!==n(e))return!1;const t=Object.getPrototypeOf(e);return null===t||t===Object.prototype}},7471:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(){const e=Object.create(null);return{on(t,n){(e[t]||(e[t]=[])).push(n)},off(t,n){e[t]&&e[t].splice(e[t].indexOf(n)>>>0,1)},emit(t,...n){(e[t]||[]).slice().map((e=>{e(...n)}))}}}},6510:(e,t,n)=>{"use strict";var r;Object.defineProperty(t,"__esModule",{value:!0}),t.RouterContext=void 0;const o=((r=n(2784))&&r.__esModule?r:{default:r}).default.createContext(null);t.RouterContext=o},6640:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getDomainLocale=function(e,t,n,r){if(window.omnivoreEnv.__NEXT_I18N_SUPPORT){t=t||l.normalizeLocalePath(e,n).detectedLocale;const o=b(r,void 0,t);return!!o&&`http${o.http?"":"s"}://${o.domain}${w||""}${t===o.defaultLocale?"":`/${t}`}${e}`}return!1},t.addLocale=k,t.delLocale=S,t.hasBasePath=C,t.addBasePath=O,t.delBasePath=P,t.isLocalURL=T,t.interpolateAs=R,t.resolveHref=j,t.default=void 0;var r=n(1119),o=n(7928),i=function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)if(Object.prototype.hasOwnProperty.call(e,n)){var r=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(e,n):{};r.get||r.set?Object.defineProperty(t,n,r):t[n]=e[n]}return t.default=e,t}(n(274)),a=n(4863),l=n(816),s=y(n(7471)),u=n(1624),c=n(7482),f=n(1577),d=n(646),h=y(n(5317)),p=n(3107),g=n(4794),m=n(6555),v=n(3948);function y(e){return e&&e.__esModule?e:{default:e}}let b;window.omnivoreEnv.__NEXT_I18N_SUPPORT&&(b=n(927).D);const w=window.omnivoreEnv.__NEXT_ROUTER_BASEPATH||"";function x(){return Object.assign(new Error("Route Cancelled"),{cancelled:!0})}function E(e,t){if(!e.startsWith("/")||!t)return e;const n=_(e);return r.normalizePathTrailingSlash(`${t}${n}`)+e.slice(n.length)}function k(e,t,n){if(window.omnivoreEnv.__NEXT_I18N_SUPPORT){const r=_(e).toLowerCase(),o=t&&t.toLowerCase();return t&&t!==n&&!r.startsWith("/"+o+"/")&&r!=="/"+o?E(e,"/"+t):e}return e}function S(e,t){if(window.omnivoreEnv.__NEXT_I18N_SUPPORT){const n=_(e),r=n.toLowerCase(),o=t&&t.toLowerCase();return t&&(r.startsWith("/"+o+"/")||r==="/"+o)?(n.length===t.length+1?"/":"")+e.slice(t.length+1):e}return e}function _(e){const t=e.indexOf("?"),n=e.indexOf("#");return(t>-1||n>-1)&&(e=e.substring(0,t>-1?t:n)),e}function C(e){return(e=_(e))===w||e.startsWith(w+"/")}function O(e){return E(e,w)}function P(e){return(e=e.slice(w.length)).startsWith("/")||(e=`/${e}`),e}function T(e){if(e.startsWith("/")||e.startsWith("#")||e.startsWith("?"))return!0;try{const t=u.getLocationOrigin(),n=new URL(e,t);return n.origin===t&&C(n.pathname)}catch(e){return!1}}function R(e,t,n){let r="";const o=g.getRouteRegex(e),i=o.groups,a=(t!==e?p.getRouteMatcher(o)(t):"")||n;r=e;const l=Object.keys(i);return l.every((e=>{let t=a[e]||"";const{repeat:n,optional:o}=i[e];let l=`[${n?"...":""}${e}]`;return o&&(l=`${t?"":"/"}[${l}]`),n&&!Array.isArray(t)&&(t=[t]),(o||e in a)&&(r=r.replace(l,n?t.map((e=>encodeURIComponent(e))).join("/"):encodeURIComponent(t))||"/")}))||(r=""),{params:l,result:r}}function L(e,t){const n={};return Object.keys(e).forEach((r=>{t.includes(r)||(n[r]=e[r])})),n}function j(e,t,n){let o,i="string"==typeof t?t:m.formatWithValidation(t);const a=i.match(/^[a-zA-Z]{1,}:\/\//),l=a?i.slice(a[0].length):i;if((l.split("?")[0]||"").match(/(\/\/|\\)/)){console.error(`Invalid href passed to next/router: ${i}, repeated forward-slashes (//) or backslashes \\ are not valid in the href`);const e=u.normalizeRepeatedSlashes(l);i=(a?a[0]:"")+e}if(!T(i))return n?[i]:i;try{o=new URL(i.startsWith("#")?e.asPath:e.pathname,"http://n")}catch(e){o=new URL("/","http://n")}try{const e=new URL(i,o);e.pathname=r.normalizePathTrailingSlash(e.pathname);let t="";if(c.isDynamicRoute(e.pathname)&&e.searchParams&&n){const n=d.searchParamsToUrlQuery(e.searchParams),{result:r,params:o}=R(e.pathname,e.pathname,n);r&&(t=m.formatWithValidation({pathname:r,hash:e.hash,query:L(n,o)}))}const a=e.origin===o.origin?e.href.slice(e.origin.length):e.href;return n?[a,t||a]:a}catch(e){return n?[i]:i}}function A(e){const t=u.getLocationOrigin();return e.startsWith(t)?e.substring(t.length):e}function M(e,t,n){let[r,o]=j(e,t,!0);const i=u.getLocationOrigin(),a=r.startsWith(i),l=o&&o.startsWith(i);r=A(r),o=o?A(o):o;const s=a?r:O(r),c=n?A(j(e,n)):o||r;return{url:s,as:l?c:O(c)}}function I(e,t){const n=r.removePathTrailingSlash(a.denormalizePagePath(e));return"/404"===n||"/_error"===n?e:(t.includes(n)||t.some((t=>{if(c.isDynamicRoute(t)&&g.getRouteRegex(t).re.test(n))return e=t,!0})),r.removePathTrailingSlash(e))}const D=window.omnivoreEnv.__NEXT_SCROLL_RESTORATION&&"undefined"!=typeof window&&"scrollRestoration"in window.history&&!!function(){try{let e="__next";return sessionStorage.setItem(e,e),sessionStorage.removeItem(e),!0}catch(e){}}(),N=Symbol("SSG_DATA_NOT_FOUND");function F(e,t,n){return fetch(e,{credentials:"same-origin"}).then((r=>{if(!r.ok){if(t>1&&r.status>=500)return F(e,t-1,n);if(404===r.status)return r.json().then((e=>{if(e.notFound)return{notFound:N};throw new Error("Failed to load static props")}));throw new Error("Failed to load static props")}return n.text?r.text():r.json()}))}function z(e,t,n,r,i){const{href:a}=new URL(e,window.location.href);return void 0!==r[a]?r[a]:r[a]=F(e,t?3:1,{text:n}).catch((e=>{throw t||o.markAssetError(e),e})).then((e=>(i||delete r[a],e))).catch((e=>{throw delete r[a],e}))}class ${constructor(e,t,n,{initialProps:o,pageLoader:i,App:a,wrapApp:l,Component:s,err:d,subscription:h,isFallback:p,locale:g,locales:v,defaultLocale:y,domainLocales:x,isPreview:E,isRsc:k}){this.sdc={},this.sdr={},this.sde={},this._idx=0,this.onPopState=e=>{const t=e.state;if(!t){const{pathname:e,query:t}=this;return void this.changeState("replaceState",m.formatWithValidation({pathname:O(e),query:t}),u.getURL())}if(!t.__N)return;let n;const{url:r,as:o,options:i,idx:a}=t;if(window.omnivoreEnv.__NEXT_SCROLL_RESTORATION&&D&&this._idx!==a){try{sessionStorage.setItem("__next_scroll_"+this._idx,JSON.stringify({x:self.pageXOffset,y:self.pageYOffset}))}catch{}try{const e=sessionStorage.getItem("__next_scroll_"+a);n=JSON.parse(e)}catch{n={x:0,y:0}}}this._idx=a;const{pathname:l}=f.parseRelativeUrl(r);this.isSsr&&o===O(this.asPath)&&l===O(this.pathname)||this._bps&&!this._bps(t)||this.change("replaceState",r,o,Object.assign({},i,{shallow:i.shallow&&this._shallow,locale:i.locale||this.defaultLocale}),n)};const S=r.removePathTrailingSlash(e);this.components={},"/_error"!==e&&(this.components[S]={Component:s,initial:!0,props:o,err:d,__N_SSG:o&&o.__N_SSG,__N_SSP:o&&o.__N_SSP,__N_RSC:!!k}),this.components["/_app"]={Component:a,styleSheets:[]},this.events=$.events,this.pageLoader=i;const _=c.isDynamicRoute(e)&&self.__NEXT_DATA__.autoExport;if(this.basePath=w,this.sub=h,this.clc=null,this._wrapApp=l,this.isSsr=!0,this.isLocaleDomain=!1,this.isReady=!(!(self.__NEXT_DATA__.gssp||self.__NEXT_DATA__.gip||self.__NEXT_DATA__.appGip&&!self.__NEXT_DATA__.gsp)&&(_||self.location.search||window.omnivoreEnv.__NEXT_HAS_REWRITES)),window.omnivoreEnv.__NEXT_I18N_SUPPORT&&(this.locales=v,this.defaultLocale=y,this.domainLocales=x,this.isLocaleDomain=!!b(x,self.location.hostname)),this.state={route:S,pathname:e,query:t,asPath:_?e:n,isPreview:!!E,locale:window.omnivoreEnv.__NEXT_I18N_SUPPORT?g:void 0,isFallback:p},"undefined"!=typeof window){if(!n.startsWith("//")){const r={locale:g};r._shouldResolveHref=n!==e,this.changeState("replaceState",m.formatWithValidation({pathname:O(e),query:t}),u.getURL(),r)}window.addEventListener("popstate",this.onPopState),window.omnivoreEnv.__NEXT_SCROLL_RESTORATION&&D&&(window.history.scrollRestoration="manual")}}reload(){window.location.reload()}back(){window.history.back()}push(e,t,n={}){if(window.omnivoreEnv.__NEXT_SCROLL_RESTORATION&&D)try{sessionStorage.setItem("__next_scroll_"+this._idx,JSON.stringify({x:self.pageXOffset,y:self.pageYOffset}))}catch{}return({url:e,as:t}=M(this,e,t)),this.change("pushState",e,t,n)}replace(e,t,n={}){return({url:e,as:t}=M(this,e,t)),this.change("replaceState",e,t,n)}async change(e,t,n,a,s){if(!T(t))return window.location.href=t,!1;const d=a._h||a._shouldResolveHref||_(t)===_(n),v={...this.state};a._h&&(this.isReady=!0);const y=v.locale;if(window.omnivoreEnv.__NEXT_I18N_SUPPORT){v.locale=!1===a.locale?this.defaultLocale:a.locale||v.locale,void 0===a.locale&&(a.locale=v.locale);const e=f.parseRelativeUrl(C(n)?P(n):n),r=l.normalizeLocalePath(e.pathname,this.locales);r.detectedLocale&&(v.locale=r.detectedLocale,e.pathname=O(e.pathname),n=m.formatWithValidation(e),t=O(l.normalizeLocalePath(C(t)?P(t):t,this.locales).pathname));let o=!1;var w;window.omnivoreEnv.__NEXT_I18N_SUPPORT&&((null===(w=this.locales)||void 0===w?void 0:w.includes(v.locale))||(e.pathname=k(e.pathname,v.locale),window.location.href=m.formatWithValidation(e),o=!0));const i=b(this.domainLocales,void 0,v.locale);if(window.omnivoreEnv.__NEXT_I18N_SUPPORT&&!o&&i&&this.isLocaleDomain&&self.location.hostname!==i.domain){const e=P(n);window.location.href=`http${i.http?"":"s"}://${i.domain}${O(`${v.locale===i.defaultLocale?"":`/${v.locale}`}${"/"===e?"":e}`||"/")}`,o=!0}if(o)return new Promise((()=>{}))}a._h||(this.isSsr=!1),u.ST&&performance.mark("routeChange");const{shallow:x=!1,scroll:E=!0}=a,j={shallow:x};this._inFlightRoute&&this.abortComponentLoad(this._inFlightRoute,j),n=O(k(C(n)?P(n):n,a.locale,this.defaultLocale));const A=S(C(n)?P(n):n,v.locale);this._inFlightRoute=n;let D=y!==v.locale;if(!a._h&&this.onlyAHashChange(A)&&!D)return v.asPath=A,$.events.emit("hashChangeStart",n,j),this.changeState(e,t,n,{...a,scroll:!1}),E&&this.scrollToHash(A),this.set(v,this.components[v.route],null),$.events.emit("hashChangeComplete",n,j),!0;let F,z,B=f.parseRelativeUrl(t),{pathname:U,query:W}=B;try{[F,{__rewrites:z}]=await Promise.all([this.pageLoader.getPageList(),o.getClientBuildManifest(),this.pageLoader.getMiddlewareList()])}catch(e){return window.location.href=n,!1}this.urlIsNew(A)||D||(e="replaceState");let H=n;if(U=U?r.removePathTrailingSlash(P(U)):U,d&&"/_error"!==U)if(a._shouldResolveHref=!0,window.omnivoreEnv.__NEXT_HAS_REWRITES&&n.startsWith("/")){const e=h.default(O(k(A,v.locale)),F,z,W,(e=>I(e,F)),this.locales);if(e.externalDest)return location.href=n,!0;H=e.asPath,e.matchedPage&&e.resolvedHref&&(U=e.resolvedHref,B.pathname=O(U),t=m.formatWithValidation(B))}else B.pathname=I(U,F),B.pathname!==U&&(U=B.pathname,B.pathname=O(U),t=m.formatWithValidation(B));if(!T(n))return window.location.href=n,!1;if(H=S(P(H),v.locale),(!a.shallow||1===a._h)&&(1!==a._h||c.isDynamicRoute(r.removePathTrailingSlash(U)))){const r=await this._preflightRequest({as:n,cache:!0,pages:F,pathname:U,query:W,locale:v.locale,isPreview:v.isPreview});if("rewrite"===r.type)W={...W,...r.parsedAs.query},H=r.asPath,U=r.resolvedHref,B.pathname=r.resolvedHref,t=m.formatWithValidation(B);else{if("redirect"===r.type&&r.newAs)return this.change(e,r.newUrl,r.newAs,a);if("redirect"===r.type&&r.destination)return window.location.href=r.destination,new Promise((()=>{}));if("refresh"===r.type&&n!==window.location.pathname)return window.location.href=n,new Promise((()=>{}))}}const V=r.removePathTrailingSlash(U);if(c.isDynamicRoute(V)){const e=f.parseRelativeUrl(H),r=e.pathname,o=g.getRouteRegex(V),i=p.getRouteMatcher(o)(r),a=V===r,l=a?R(V,r,W):{};if(!i||a&&!l.result){const e=Object.keys(o.groups).filter((e=>!W[e]));if(e.length>0)throw new Error((a?`The provided \`href\` (${t}) value is missing query values (${e.join(", ")}) to be interpolated properly. `:`The provided \`as\` value (${r}) is incompatible with the \`href\` value (${V}). `)+"Read more: https://nextjs.org/docs/messages/"+(a?"href-interpolation-failed":"incompatible-href-as"))}else a?n=m.formatWithValidation(Object.assign({},e,{pathname:l.result,query:L(W,l.params)})):Object.assign(W,i)}$.events.emit("routeChangeStart",n,j);try{var q,X;let r=await this.getRouteInfo(V,U,W,n,H,j,v.locale,v.isPreview),{error:o,props:i,__N_SSG:l,__N_SSP:u}=r;if((l||u)&&i){if(i.pageProps&&i.pageProps.__N_REDIRECT){const t=i.pageProps.__N_REDIRECT;if(t.startsWith("/")&&!1!==i.pageProps.__N_REDIRECT_BASE_PATH){const n=f.parseRelativeUrl(t);n.pathname=I(n.pathname,F);const{url:r,as:o}=M(this,t,t);return this.change(e,r,o,a)}return window.location.href=t,new Promise((()=>{}))}if(v.isPreview=!!i.__N_PREVIEW,i.notFound===N){let e;try{await this.fetchComponent("/404"),e="/404"}catch(t){e="/_error"}r=await this.getRouteInfo(e,e,W,n,H,{shallow:!1},v.locale,v.isPreview)}}$.events.emit("beforeHistoryChange",n,j),this.changeState(e,t,n,a),a._h&&"/_error"===U&&500===(null===(q=self.__NEXT_DATA__.props)||void 0===q||null===(X=q.pageProps)||void 0===X?void 0:X.statusCode)&&(null==i?void 0:i.pageProps)&&(i.pageProps.statusCode=500);const c=a.shallow&&v.route===V;var K;const d=(null!==(K=a.scroll)&&void 0!==K?K:!c)?{x:0,y:0}:null;if(await this.set({...v,route:V,pathname:U,query:W,asPath:A,isFallback:!1},r,null!=s?s:d).catch((e=>{if(!e.cancelled)throw e;o=o||e})),o)throw $.events.emit("routeChangeError",o,A,j),o;return window.omnivoreEnv.__NEXT_I18N_SUPPORT&&v.locale&&(document.documentElement.lang=v.locale),$.events.emit("routeChangeComplete",n,j),!0}catch(e){if(i.default(e)&&e.cancelled)return!1;throw e}}changeState(e,t,n,r={}){"pushState"===e&&u.getURL()===n||(this._shallow=r.shallow,window.history[e]({url:t,as:n,options:r,__N:!0,idx:this._idx="pushState"!==e?this._idx:this._idx+1},"",n))}async handleRouteInfoError(e,t,n,r,a,l){if(e.cancelled)throw e;if(o.isAssetError(e)||l)throw $.events.emit("routeChangeError",e,r,a),window.location.href=r,x();try{let r,o,i;void 0!==r&&void 0!==o||({page:r,styleSheets:o}=await this.fetchComponent("/_error"));const a={props:i,Component:r,styleSheets:o,err:e,error:e};if(!a.props)try{a.props=await this.getInitialProps(r,{err:e,pathname:t,query:n})}catch(e){console.error("Error in error page `getInitialProps`: ",e),a.props={}}return a}catch(e){return this.handleRouteInfoError(i.default(e)?e:new Error(e+""),t,n,r,a,!0)}}async getRouteInfo(e,t,n,r,o,a,l,s){try{const i=this.components[e];if(a.shallow&&i&&this.route===e)return i;let u;i&&!("initial"in i)&&(u=i);const c=u||await this.fetchComponent(e).then((e=>({Component:e.page,styleSheets:e.styleSheets,__N_SSG:e.mod.__N_SSG,__N_SSP:e.mod.__N_SSP,__N_RSC:!!e.mod.__next_rsc__}))),{Component:f,__N_SSG:d,__N_SSP:h,__N_RSC:p}=c;let g;(d||h||p)&&(g=this.pageLoader.getDataHref({href:m.formatWithValidation({pathname:t,query:n}),asPath:o,ssg:d,rsc:p,locale:l}));const v=await this._getData((()=>d||h?z(g,this.isSsr,!1,d?this.sdc:this.sdr,!!d&&!s):this.getInitialProps(f,{pathname:t,query:n,asPath:r,locale:l,locales:this.locales,defaultLocale:this.defaultLocale})));if(p){const{fresh:e,data:t}=await this._getData((()=>this._getFlightData(g)));v.pageProps=Object.assign(v.pageProps,{__flight_serialized__:t,__flight_fresh__:e})}return c.props=v,this.components[e]=c,c}catch(e){return this.handleRouteInfoError(i.getProperError(e),t,n,r,a)}}set(e,t,n){return this.state=e,this.sub(t,this.components["/_app"].Component,n)}beforePopState(e){this._bps=e}onlyAHashChange(e){if(!this.asPath)return!1;const[t,n]=this.asPath.split("#"),[r,o]=e.split("#");return!(!o||t!==r||n!==o)||t===r&&n!==o}scrollToHash(e){const[,t=""]=e.split("#");if(""===t||"top"===t)return void window.scrollTo(0,0);const n=document.getElementById(t);if(n)return void n.scrollIntoView();const r=document.getElementsByName(t)[0];r&&r.scrollIntoView()}urlIsNew(e){return this.asPath!==e}async prefetch(e,t=e,n={}){let i=f.parseRelativeUrl(e),{pathname:a,query:s}=i;if(window.omnivoreEnv.__NEXT_I18N_SUPPORT&&!1===n.locale){a=l.normalizeLocalePath(a,this.locales).pathname,i.pathname=a,e=m.formatWithValidation(i);let r=f.parseRelativeUrl(t);const o=l.normalizeLocalePath(r.pathname,this.locales);r.pathname=o.pathname,n.locale=o.detectedLocale||this.defaultLocale,t=m.formatWithValidation(r)}const u=await this.pageLoader.getPageList();let c=t;if(window.omnivoreEnv.__NEXT_HAS_REWRITES&&t.startsWith("/")){let n;({__rewrites:n}=await o.getClientBuildManifest());const r=h.default(O(k(t,this.locale)),u,n,i.query,(e=>I(e,u)),this.locales);if(r.externalDest)return;c=S(P(r.asPath),this.locale),r.matchedPage&&r.resolvedHref&&(a=r.resolvedHref,i.pathname=a,e=m.formatWithValidation(i))}else i.pathname=I(i.pathname,u),i.pathname!==a&&(a=i.pathname,i.pathname=a,e=m.formatWithValidation(i));const d=await this._preflightRequest({as:O(t),cache:!0,pages:u,pathname:a,query:s,locale:this.locale,isPreview:this.isPreview});"rewrite"===d.type&&(i.pathname=d.resolvedHref,a=d.resolvedHref,s={...s,...d.parsedAs.query},c=d.asPath,e=m.formatWithValidation(i));const p=r.removePathTrailingSlash(a);await Promise.all([this.pageLoader._isSsg(p).then((t=>!!t&&z(this.pageLoader.getDataHref({href:e,asPath:c,ssg:!0,locale:void 0!==n.locale?n.locale:this.locale}),!1,!1,this.sdc,!0))),this.pageLoader[n.priority?"loadPage":"prefetch"](p)])}async fetchComponent(e){let t=!1;const n=this.clc=()=>{t=!0},r=()=>{if(t){const t=new Error(`Abort fetching component for route: "${e}"`);throw t.cancelled=!0,t}n===this.clc&&(this.clc=null)};try{const t=await this.pageLoader.loadPage(e);return r(),t}catch(e){throw r(),e}}_getData(e){let t=!1;const n=()=>{t=!0};return this.clc=n,e().then((e=>{if(n===this.clc&&(this.clc=null),t){const e=new Error("Loading initial props cancelled");throw e.cancelled=!0,e}return e}))}_getFlightData(e){return z(e,!0,!0,this.sdc,!1).then((e=>({fresh:!0,data:e})))}async _preflightRequest(e){const t=_(e.as),n=S(C(t)?P(t):t,e.locale),o=(await this.pageLoader.getMiddlewareList()).map((([e,t])=>({page:e,ssr:t}))),i=v.getRoutingItems(e.pages,o);let a,s=!1;for(const e of i)if(e.match(n)){e.isMiddleware&&(s=!0);break}if(!s)return{type:"next"};try{a=await this._getPreflightData({preflightHref:e.as,shouldCache:e.cache,isPreview:e.isPreview})}catch(t){return{type:"redirect",destination:e.as}}if(a.rewrite){if(!a.rewrite.startsWith("/"))return{type:"redirect",destination:e.as};const t=f.parseRelativeUrl(l.normalizeLocalePath(C(a.rewrite)?P(a.rewrite):a.rewrite,this.locales).pathname),n=r.removePathTrailingSlash(t.pathname);let o,i;return e.pages.includes(n)?(o=!0,i=n):(i=I(n,e.pages),i!==t.pathname&&e.pages.includes(i)&&(o=!0)),{type:"rewrite",asPath:t.pathname,parsedAs:t,matchedPage:o,resolvedHref:i}}if(a.redirect){if(a.redirect.startsWith("/")){const e=r.removePathTrailingSlash(l.normalizeLocalePath(C(a.redirect)?P(a.redirect):a.redirect,this.locales).pathname),{url:t,as:n}=M(this,e,e);return{type:"redirect",newUrl:t,newAs:n}}return{type:"redirect",destination:a.redirect}}return a.refresh&&!a.ssr?{type:"refresh"}:{type:"next"}}_getPreflightData(e){const{preflightHref:t,shouldCache:n=!1,isPreview:r}=e,{href:o}=new URL(t,window.location.href);return!r&&n&&this.sde[o]?Promise.resolve(this.sde[o]):fetch(t,{method:"HEAD",credentials:"same-origin",headers:{"x-middleware-preflight":"1"}}).then((e=>{if(!e.ok)throw new Error("Failed to preflight request");return{cache:e.headers.get("x-middleware-cache"),redirect:e.headers.get("Location"),refresh:e.headers.has("x-middleware-refresh"),rewrite:e.headers.get("x-middleware-rewrite"),ssr:!!e.headers.get("x-middleware-ssr")}})).then((e=>(n&&"no-cache"!==e.cache&&(this.sde[o]=e),e))).catch((e=>{throw delete this.sde[o],e}))}getInitialProps(e,t){const{Component:n}=this.components["/_app"],r=this._wrapApp(n);return t.AppTree=r,u.loadGetInitialProps(n,{AppTree:r,Component:e,router:this,ctx:t})}abortComponentLoad(e,t){this.clc&&($.events.emit("routeChangeError",x(),e,t),this.clc(),this.clc=null)}get route(){return this.state.route}get pathname(){return this.state.pathname}get query(){return this.state.query}get asPath(){return this.state.asPath}get locale(){return this.state.locale}get isFallback(){return this.state.isFallback}get isPreview(){return this.state.isPreview}}t.default=$,$.events=s.default()},6555:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.formatUrl=i,t.formatWithValidation=function(e){return i(e)},t.urlObjectKeys=void 0;var r=function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)if(Object.prototype.hasOwnProperty.call(e,n)){var r=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(e,n):{};r.get||r.set?Object.defineProperty(t,n,r):t[n]=e[n]}return t.default=e,t}(n(646));const o=/https?|ftp|gopher|file/;function i(e){let{auth:t,hostname:n}=e,i=e.protocol||"",a=e.pathname||"",l=e.hash||"",s=e.query||"",u=!1;t=t?encodeURIComponent(t).replace(/%3A/i,":")+"@":"",e.host?u=t+e.host:n&&(u=t+(~n.indexOf(":")?`[${n}]`:n),e.port&&(u+=":"+e.port)),s&&"object"==typeof s&&(s=String(r.urlQueryToSearchParams(s)));let c=e.search||s&&`?${s}`||"";return i&&!i.endsWith(":")&&(i+=":"),e.slashes||(!i||o.test(i))&&!1!==u?(u="//"+(u||""),a&&"/"!==a[0]&&(a="/"+a)):u||(u=""),l&&"#"!==l[0]&&(l="#"+l),c&&"?"!==c[0]&&(c="?"+c),a=a.replace(/[?#]/g,encodeURIComponent),c=c.replace("#","%23"),`${i}${u}${a}${c}${l}`}t.urlObjectKeys=["auth","hash","host","hostname","href","path","pathname","port","protocol","query","search","slashes"]},9983:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t=""){return("/"===e?"/index":/^\/index(\/|$)/.test(e)?`/index${e}`:`${e}`)+t}},2763:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getMiddlewareRegex=function(e,t=!0){const n=r.getParametrizedRoute(e),i=o.test(e)?"(?!/api(?:/|$))":"";let a=t?"(?!_next($|/)).*":"",l=t?"(?:(/.*)?)":"";return"routeKeys"in n?"/"===n.parameterizedRoute?{groups:{},namedRegex:`^/${a}$`,re:new RegExp(`^/${a}$`),routeKeys:{}}:{groups:n.groups,namedRegex:`^${i}${n.namedParameterizedRoute}${l}$`,re:new RegExp(`^${i}${n.parameterizedRoute}${l}$`),routeKeys:n.routeKeys}:"/"===n.parameterizedRoute?{groups:{},re:new RegExp(`^/${a}$`)}:{groups:{},re:new RegExp(`^${i}${n.parameterizedRoute}${l}$`)}};var r=n(4794);const o=/^\/\[[^/]+?\](?=\/|$)/},9150:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"getMiddlewareRegex",{enumerable:!0,get:function(){return r.getMiddlewareRegex}}),Object.defineProperty(t,"getRouteMatcher",{enumerable:!0,get:function(){return o.getRouteMatcher}}),Object.defineProperty(t,"getRouteRegex",{enumerable:!0,get:function(){return i.getRouteRegex}}),Object.defineProperty(t,"getRoutingItems",{enumerable:!0,get:function(){return a.getRoutingItems}}),Object.defineProperty(t,"RoutingItem",{enumerable:!0,get:function(){return a.RoutingItem}}),Object.defineProperty(t,"getSortedRoutes",{enumerable:!0,get:function(){return l.getSortedRoutes}}),Object.defineProperty(t,"isDynamicRoute",{enumerable:!0,get:function(){return s.isDynamicRoute}});var r=n(2763),o=n(3107),i=n(4794),a=n(3948),l=n(9036),s=n(7482)},7482:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isDynamicRoute=function(e){return n.test(e)};const n=/\/\[[^/]+?\](?=\/|$)/},1577:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.parseRelativeUrl=function(e,t){const n=new URL("undefined"==typeof window?"http://n":r.getLocationOrigin()),i=t?new URL(t,n):n,{pathname:a,searchParams:l,search:s,hash:u,href:c,origin:f}=new URL(e,i);if(f!==n.origin)throw new Error(`invariant: invalid relative URL, router received ${e}`);return{pathname:a,query:o.searchParamsToUrlQuery(l),search:s,hash:u,href:c.slice(n.origin.length)}};var r=n(1624),o=n(646)},2011:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.parseUrl=function(e){if(e.startsWith("/"))return o.parseRelativeUrl(e);const t=new URL(e);return{hash:t.hash,hostname:t.hostname,href:t.href,pathname:t.pathname,port:t.port,protocol:t.protocol,query:r.searchParamsToUrlQuery(t.searchParams),search:t.search}};var r=n(646),o=n(1577)},1095:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.customRouteMatcherOptions=t.matcherOptions=t.pathToRegexp=void 0;var r=function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)if(Object.prototype.hasOwnProperty.call(e,n)){var r=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(e,n):{};r.get||r.set?Object.defineProperty(t,n,r):t[n]=e[n]}return t.default=e,t}(n(9264));t.pathToRegexp=r;const o={sensitive:!1,delimiter:"/"};t.matcherOptions=o;const i={...o,strict:!0};t.customRouteMatcherOptions=i,t.default=(e=!1)=>(t,n)=>{const a=[];let l=r.pathToRegexp(t,a,e?i:o);if(n){const e=n(l.source);l=new RegExp(e,l.flags)}const s=r.regexpToFunction(l,a);return(t,n)=>{const r=null!=t&&s(t);if(!r)return!1;if(e)for(const e of a)"number"==typeof e.name&&delete r.params[e.name];return{...n,...r.params}}}},9716:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.matchHas=function(e,t,n){const r={};return!!t.every((t=>{let o,i=t.key;switch(t.type){case"header":i=i.toLowerCase(),o=e.headers[i];break;case"cookie":o=e.cookies[t.key];break;case"query":o=n[i];break;case"host":{const{host:t}=(null==e?void 0:e.headers)||{};o=null==t?void 0:t.split(":")[0].toLowerCase();break}}if(!t.value&&o)return r[function(e){let t="";for(let n=0;n64&&r<91||r>96&&r<123)&&(t+=e[n])}return t}(i)]=o,!0;if(o){const e=new RegExp(`^${t.value}$`),n=Array.isArray(o)?o.slice(-1)[0].match(e):o.match(e);if(n)return Array.isArray(n)&&(n.groups?Object.keys(n.groups).forEach((e=>{r[e]=n.groups[e]})):"host"===t.type&&n[0]&&(r.host=n[0])),!0}return!1}))&&r},t.compileNonPath=a,t.prepareDestination=function(e){const t=Object.assign({},e.query);delete t.__nextLocale,delete t.__nextDefaultLocale;let n=e.destination;for(const r of Object.keys({...e.params,...t}))s=r,n=n.replace(new RegExp(`:${o.escapeStringRegexp(s)}`,"g"),`__ESC_COLON_${s}`);var s;const u=i.parseUrl(n),c=u.query,f=l(`${u.pathname}${u.hash||""}`),d=l(u.hostname||""),h=[],p=[];r.pathToRegexp(f,h),r.pathToRegexp(d,p);const g=[];h.forEach((e=>g.push(e.name))),p.forEach((e=>g.push(e.name)));const m=r.compile(f,{validate:!1}),v=r.compile(d,{validate:!1});for(const[t,n]of Object.entries(c))Array.isArray(n)?c[t]=n.map((t=>a(l(t),e.params))):c[t]=a(l(n),e.params);let y,b=Object.keys(e.params).filter((e=>"nextInternalLocale"!==e));if(e.appendParamsToQuery&&!b.some((e=>g.includes(e))))for(const t of b)t in c||(c[t]=e.params[t]);try{y=m(e.params);const[t,n]=y.split("#");u.hostname=v(e.params),u.pathname=t,u.hash=`${n?"#":""}${n||""}`,delete u.search}catch(e){if(e.message.match(/Expected .*? to not repeat, but got an array/))throw new Error("To use a multi-match in the destination you must add `*` at the end of the param name to signify it should repeat. https://nextjs.org/docs/messages/invalid-multi-match");throw e}return u.query={...t,...u.query},{newUrl:y,parsedDestination:u}};var r=n(9264),o=n(8058),i=n(2011);function a(e,t){if(!e.includes(":"))return e;for(const n of Object.keys(t))e.includes(`:${n}`)&&(e=e.replace(new RegExp(`:${n}\\*`,"g"),`:${n}--ESCAPED_PARAM_ASTERISKS`).replace(new RegExp(`:${n}\\?`,"g"),`:${n}--ESCAPED_PARAM_QUESTION`).replace(new RegExp(`:${n}\\+`,"g"),`:${n}--ESCAPED_PARAM_PLUS`).replace(new RegExp(`:${n}(?!\\w)`,"g"),`--ESCAPED_PARAM_COLON${n}`));return e=e.replace(/(:|\*|\?|\+|\(|\)|\{|\})/g,"\\$1").replace(/--ESCAPED_PARAM_PLUS/g,"+").replace(/--ESCAPED_PARAM_COLON/g,":").replace(/--ESCAPED_PARAM_QUESTION/g,"?").replace(/--ESCAPED_PARAM_ASTERISKS/g,"*"),r.compile(`/${e}`,{validate:!1})(t).slice(1)}function l(e){return e.replace(/__ESC_COLON_/gi,":")}},646:(e,t)=>{"use strict";function n(e){return"string"==typeof e||"number"==typeof e&&!isNaN(e)||"boolean"==typeof e?String(e):""}Object.defineProperty(t,"__esModule",{value:!0}),t.searchParamsToUrlQuery=function(e){const t={};return e.forEach(((e,n)=>{void 0===t[n]?t[n]=e:Array.isArray(t[n])?t[n].push(e):t[n]=[t[n],e]})),t},t.urlQueryToSearchParams=function(e){const t=new URLSearchParams;return Object.entries(e).forEach((([e,r])=>{Array.isArray(r)?r.forEach((r=>t.append(e,n(r)))):t.set(e,n(r))})),t},t.assign=function(e,...t){return t.forEach((t=>{Array.from(t.keys()).forEach((t=>e.delete(t))),t.forEach(((t,n)=>e.append(n,t)))})),e}},5317:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t,n,r,o,f){let d,h=!1,p=!1,g=s.parseRelativeUrl(e),m=a.removePathTrailingSlash(l.normalizeLocalePath(u.delBasePath(g.pathname),f).pathname);const v=n=>{let s=c(n.source)(g.pathname);if(n.has&&s){const e=i.matchHas({headers:{host:document.location.hostname},cookies:document.cookie.split("; ").reduce(((e,t)=>{const[n,...r]=t.split("=");return e[n]=r.join("="),e}),{})},n.has,g.query);e?Object.assign(s,e):s=!1}if(s){if(!n.destination)return p=!0,!0;const c=i.prepareDestination({appendParamsToQuery:!0,destination:n.destination,params:s,query:r});if(g=c.parsedDestination,e=c.newUrl,Object.assign(r,c.parsedDestination.query),m=a.removePathTrailingSlash(l.normalizeLocalePath(u.delBasePath(e),f).pathname),t.includes(m))return h=!0,d=m,!0;if(d=o(m),d!==e&&t.includes(d))return h=!0,!0}};let y=!1;for(let e=0;e{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getRouteMatcher=function(e){const{re:t,groups:n}=e;return e=>{const o=t.exec(e);if(!o)return!1;const i=e=>{try{return decodeURIComponent(e)}catch(e){throw new r.DecodeError("failed to decode param")}},a={};return Object.keys(n).forEach((e=>{const t=n[e],r=o[t.pos];void 0!==r&&(a[e]=~r.indexOf("/")?r.split("/").map((e=>i(e))):t.repeat?[i(r)]:i(r))})),a}};var r=n(1624)},4794:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getParametrizedRoute=i,t.getRouteRegex=function(e){const t=i(e);return"routeKeys"in t?{re:new RegExp(`^${t.parameterizedRoute}(?:/)?$`),groups:t.groups,routeKeys:t.routeKeys,namedRegex:`^${t.namedParameterizedRoute}(?:/)?$`}:{re:new RegExp(`^${t.parameterizedRoute}(?:/)?$`),groups:t.groups}};var r=n(8058);function o(e){const t=e.startsWith("[")&&e.endsWith("]");t&&(e=e.slice(1,-1));const n=e.startsWith("...");return n&&(e=e.slice(3)),{key:e,repeat:n,optional:t}}function i(e){const t=(e.replace(/\/$/,"")||"/").slice(1).split("/"),n={};let i=1;const a=t.map((e=>{if(e.startsWith("[")&&e.endsWith("]")){const{key:t,optional:r,repeat:a}=o(e.slice(1,-1));return n[t]={pos:i++,repeat:a,optional:r},a?r?"(?:/(.+?))?":"/(.+?)":"/([^/]+?)"}return`/${r.escapeStringRegexp(e)}`})).join("");if("undefined"==typeof window){let e=97,i=1;const l=()=>{let t="";for(let n=0;n122&&(i++,e=97);return t},s={};return{parameterizedRoute:a,namedParameterizedRoute:t.map((e=>{if(e.startsWith("[")&&e.endsWith("]")){const{key:t,optional:n,repeat:r}=o(e.slice(1,-1));let i=t.replace(/\W/g,""),a=!1;return(0===i.length||i.length>30)&&(a=!0),isNaN(parseInt(i.slice(0,1)))||(a=!0),a&&(i=l()),s[i]=t,r?n?`(?:/(?<${i}>.+?))?`:`/(?<${i}>.+?)`:`/(?<${i}>[^/]+?)`}return`/${r.escapeStringRegexp(e)}`})).join(""),groups:n,routeKeys:s}}return{parameterizedRoute:a,groups:n}}},3948:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getRoutingItems=function(e,t){const n=t.map((e=>`${e.page}/_middleware`)),s=new Map(t.map((e=>[e.page,e])));return a.getSortedRoutes([...e,...n]).map((e=>{if(e.endsWith(l)){const t=e.slice(0,-l.length)||"/",{ssr:n}=s.get(t);return{match:o.getRouteMatcher(r.getMiddlewareRegex(t,!n)),page:t,ssr:n,isMiddleware:!0}}return{match:o.getRouteMatcher(i.getRouteRegex(e)),page:e}}))};var r=n(2763),o=n(3107),i=n(4794),a=n(9036);const l="/_middleware"},9036:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getSortedRoutes=function(e){const t=new n;return e.forEach((e=>t.insert(e))),t.smoosh()};class n{insert(e){this._insert(e.split("/").filter(Boolean),[],!1)}smoosh(){return this._smoosh()}_smoosh(e="/"){const t=[...this.children.keys()].sort();null!==this.slugName&&t.splice(t.indexOf("[]"),1),null!==this.restSlugName&&t.splice(t.indexOf("[...]"),1),null!==this.optionalRestSlugName&&t.splice(t.indexOf("[[...]]"),1),this.isMiddleware&&t.splice(t.indexOf("_middleware"),1);const n=t.map((t=>this.children.get(t)._smoosh(`${e}${t}/`))).reduce(((e,t)=>[...e,...t]),[]);if(null!==this.slugName&&n.push(...this.children.get("[]")._smoosh(`${e}[${this.slugName}]/`)),!this.placeholder){const t="/"===e?"/":e.slice(0,-1);if(null!=this.optionalRestSlugName)throw new Error(`You cannot define a route with the same specificity as a optional catch-all route ("${t}" and "${t}[[...${this.optionalRestSlugName}]]").`);n.unshift(t)}return this.isMiddleware&&n.unshift(...this.children.get("_middleware")._smoosh(`${e}_middleware/`)),null!==this.restSlugName&&n.push(...this.children.get("[...]")._smoosh(`${e}[...${this.restSlugName}]/`)),null!==this.optionalRestSlugName&&n.push(...this.children.get("[[...]]")._smoosh(`${e}[[...${this.optionalRestSlugName}]]/`)),n}_insert(e,t,r){if(0===e.length)return void(this.placeholder=!1);if(r)throw new Error("Catch-all must be the last part of the URL.");let o=e[0];if(o.startsWith("[")&&o.endsWith("]")){let i=o.slice(1,-1),a=!1;if(i.startsWith("[")&&i.endsWith("]")&&(i=i.slice(1,-1),a=!0),i.startsWith("...")&&(i=i.substring(3),r=!0),i.startsWith("[")||i.endsWith("]"))throw new Error(`Segment names may not start or end with extra brackets ('${i}').`);if(i.startsWith("."))throw new Error(`Segment names may not start with erroneous periods ('${i}').`);function l(e,n){if(null!==e&&e!==n)throw new Error(`You cannot use different slug names for the same dynamic path ('${e}' !== '${n}').`);t.forEach((e=>{if(e===n)throw new Error(`You cannot have the same slug name "${n}" repeat within a single dynamic path`);if(e.replace(/\W/g,"")===o.replace(/\W/g,""))throw new Error(`You cannot have the slug names "${e}" and "${n}" differ only by non-word symbols within a single dynamic path`)})),t.push(n)}if(r)if(a){if(null!=this.restSlugName)throw new Error(`You cannot use both an required and optional catch-all route at the same level ("[...${this.restSlugName}]" and "${e[0]}" ).`);l(this.optionalRestSlugName,i),this.optionalRestSlugName=i,o="[[...]]"}else{if(null!=this.optionalRestSlugName)throw new Error(`You cannot use both an optional and required catch-all route at the same level ("[[...${this.optionalRestSlugName}]]" and "${e[0]}").`);l(this.restSlugName,i),this.restSlugName=i,o="[...]"}else{if(a)throw new Error(`Optional route parameters are not yet supported ("${e[0]}").`);l(this.slugName,i),this.slugName=i,o="[]"}}else"_middleware"===o&&1===e.length&&(this.isMiddleware=!0);this.children.has(o)||this.children.set(o,new n),this.children.get(o)._insert(e.slice(1),t,r)}constructor(){this.placeholder=!0,this.children=new Map,this.slugName=null,this.restSlugName=null,this.optionalRestSlugName=null,this.isMiddleware=!1}}},1889:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)if(Object.prototype.hasOwnProperty.call(e,n)){var r=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(e,n):{};r.get||r.set?Object.defineProperty(t,n,r):t[n]=e[n]}return t.default=e,t}(n(2784));const o="undefined"==typeof window;class i extends r.Component{constructor(e){super(e),this.emitChange=()=>{this._hasHeadManager&&this.props.headManager.updateHead(this.props.reduceComponentsToState([...this.props.headManager.mountedInstances],this.props))},this._hasHeadManager=this.props.headManager&&this.props.headManager.mountedInstances,o&&this._hasHeadManager&&(this.props.headManager.mountedInstances.add(this),this.emitChange())}componentDidMount(){this._hasHeadManager&&this.props.headManager.mountedInstances.add(this),this.emitChange()}componentDidUpdate(){this.emitChange()}componentWillUnmount(){this._hasHeadManager&&this.props.headManager.mountedInstances.delete(this),this.emitChange()}render(){return null}}t.default=i},1624:(e,t)=>{"use strict";function n(){const{protocol:e,hostname:t,port:n}=window.location;return`${e}//${t}${n?":"+n:""}`}function r(e){return"string"==typeof e?e:e.displayName||e.name||"Unknown"}function o(e){return e.finished||e.headersSent}Object.defineProperty(t,"__esModule",{value:!0}),t.execOnce=function(e){let t,n=!1;return(...r)=>(n||(n=!0,t=e(...r)),t)},t.getLocationOrigin=n,t.getURL=function(){const{href:e}=window.location,t=n();return e.substring(t.length)},t.getDisplayName=r,t.isResSent=o,t.normalizeRepeatedSlashes=function(e){const t=e.split("?");return t[0].replace(/\\/g,"/").replace(/\/\/+/g,"/")+(t[1]?`?${t.slice(1).join("?")}`:"")},t.loadGetInitialProps=async function e(t,n){const i=n.res||n.ctx&&n.ctx.res;if(!t.getInitialProps)return n.ctx&&n.Component?{pageProps:await e(n.Component,n.ctx)}:{};const a=await t.getInitialProps(n);if(i&&o(i))return a;if(!a){const e=`"${r(t)}.getInitialProps()" should resolve to an object. But found "${a}" instead.`;throw new Error(e)}return a},t.ST=t.SP=t.warnOnce=void 0,t.warnOnce=e=>{};const i="undefined"!=typeof performance;t.SP=i;const a=i&&"function"==typeof performance.mark&&"function"==typeof performance.measure;t.ST=a;class l extends Error{}t.DecodeError=l},6577:(e,t,n)=>{n(104)},9097:(e,t,n)=>{n(4529)},5632:(e,t,n)=>{e.exports=n(9518)},7320:e=>{"use strict";var t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;function o(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach((function(e){r[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(e){return!1}}()?Object.assign:function(e,i){for(var a,l,s=o(e),u=1;u{"use strict";var r=n(2784),o=n(7320),i=n(4616);function a(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n