Merge pull request #898 from omnivore-app/macos-web-reader
Mac App bundled reader
This commit is contained in:
@ -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
|
||||
|
||||
@ -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,39 +100,31 @@ 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.
|
||||
@State var hideNavBar = false
|
||||
|
||||
var body: some View {
|
||||
#if os(iOS)
|
||||
if viewModel.pdfItem != nil {
|
||||
fixedNavBarReader
|
||||
.navigationBarHidden(hideNavBar)
|
||||
.task {
|
||||
hideNavBar = true
|
||||
viewModel.trackReadEvent()
|
||||
}
|
||||
} else if let item = viewModel.item {
|
||||
WebReaderContainerView(item: item)
|
||||
.navigationBarHidden(hideNavBar)
|
||||
.task {
|
||||
hideNavBar = true
|
||||
viewModel.trackReadEvent()
|
||||
}
|
||||
}
|
||||
#else
|
||||
if viewModel.pdfItem != nil {
|
||||
fixedNavBarReader
|
||||
.task { viewModel.trackReadEvent() }
|
||||
#endif
|
||||
#if os(iOS)
|
||||
.navigationBarHidden(hideNavBar)
|
||||
#endif
|
||||
.task {
|
||||
hideNavBar = true
|
||||
viewModel.trackReadEvent()
|
||||
}
|
||||
} else if let item = viewModel.item {
|
||||
WebReaderContainerView(item: item)
|
||||
#if os(iOS)
|
||||
.navigationBarHidden(hideNavBar)
|
||||
#endif
|
||||
.task {
|
||||
hideNavBar = true
|
||||
viewModel.trackReadEvent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var navBar: some View {
|
||||
@ -249,61 +197,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 +205,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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 }
|
||||
|
||||
|
||||
@ -4,167 +4,201 @@ 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
|
||||
webView.configuration.userContentController = contentController
|
||||
webView.configuration.userContentController.removeAllScriptMessageHandlers()
|
||||
|
||||
#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)
|
||||
}
|
||||
|
||||
webView.configuration.userContentController.add(webView, name: "viewerAction")
|
||||
#else
|
||||
webView.setValue(false, forKey: "drawsBackground")
|
||||
#endif
|
||||
|
||||
webView.configuration.userContentController.addScriptMessageHandler(
|
||||
context.coordinator, contentWorld: .page, name: "articleAction"
|
||||
)
|
||||
for action in WebViewAction.allCases {
|
||||
webView.configuration.userContentController.add(context.coordinator, name: action.rawValue)
|
||||
}
|
||||
|
||||
context.coordinator.linkHandler = openLinkAction
|
||||
context.coordinator.webViewActionHandler = webViewActionHandler
|
||||
context.coordinator.updateNavBarVisibilityRatio = navBarVisibilityRatioUpdater
|
||||
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? WebView)?.dispatchEvent(.saveAnnotation(annotation: annotation))
|
||||
}
|
||||
|
||||
if updateFontFamilyActionID != context.coordinator.previousUpdateFontFamilyActionID {
|
||||
context.coordinator.previousUpdateFontFamilyActionID = updateFontFamilyActionID
|
||||
(webView as? WebView)?.updateFontFamily()
|
||||
}
|
||||
|
||||
if updateFontActionID != context.coordinator.previousUpdateFontActionID {
|
||||
context.coordinator.previousUpdateFontActionID = updateFontActionID
|
||||
(webView as? WebView)?.updateFontSize()
|
||||
}
|
||||
|
||||
if updateTextContrastActionID != context.coordinator.previousUpdateTextContrastActionID {
|
||||
context.coordinator.previousUpdateTextContrastActionID = updateTextContrastActionID
|
||||
(webView as? WebView)?.updateTextContrast()
|
||||
}
|
||||
|
||||
if updateMaxWidthActionID != context.coordinator.previousUpdateMaxWidthActionID {
|
||||
context.coordinator.previousUpdateMaxWidthActionID = updateMaxWidthActionID
|
||||
(webView as? WebView)?.updateMaxWidthPercentage()
|
||||
}
|
||||
|
||||
if updateLineHeightActionID != context.coordinator.previousUpdateLineHeightActionID {
|
||||
context.coordinator.previousUpdateLineHeightActionID = updateLineHeightActionID
|
||||
(webView as? WebView)?.updateLineHeight()
|
||||
}
|
||||
|
||||
if showNavBarActionID != context.coordinator.previousShowNavBarActionID {
|
||||
context.coordinator.previousShowNavBarActionID = showNavBarActionID
|
||||
context.coordinator.showNavBar()
|
||||
}
|
||||
|
||||
if shareActionID != context.coordinator.previousShareActionID {
|
||||
context.coordinator.previousShareActionID = shareActionID
|
||||
(webView as? WebView)?.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
updatePlatformView(webView, context: context)
|
||||
}
|
||||
}
|
||||
#else
|
||||
extension WebReader {
|
||||
func makeNSView(context: Context) -> WKWebView {
|
||||
makePlatformView(context: context)
|
||||
}
|
||||
|
||||
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: UITraitCollection.current.userInterfaceStyle == .dark,
|
||||
fontSize: fontSize(),
|
||||
lineHeight: lineHeight(),
|
||||
maxWidthPercentage: maxWidthPercentage(),
|
||||
fontFamily: fontFamily,
|
||||
prefersHighContrastText: prefersHighContrastText
|
||||
)
|
||||
.styledContent,
|
||||
baseURL: ViewsPackage.bundleURL
|
||||
)
|
||||
func updateNSView(_ webView: WKWebView, context: Context) {
|
||||
updatePlatformView(webView, context: context)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -4,62 +4,62 @@ 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<PresentationMode>
|
||||
@StateObject var viewModel = WebReaderViewModel()
|
||||
@EnvironmentObject var dataService: DataService
|
||||
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
|
||||
@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) {
|
||||
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) {
|
||||
#if os(iOS)
|
||||
Button(
|
||||
action: { self.presentationMode.wrappedValue.dismiss() },
|
||||
label: {
|
||||
@ -71,156 +71,187 @@ import WebKit
|
||||
)
|
||||
.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)
|
||||
#endif
|
||||
Button(
|
||||
action: { showPreferencesPopover.toggle() },
|
||||
label: {
|
||||
Image(systemName: "textformat.size")
|
||||
.font(.appTitleTwo)
|
||||
}
|
||||
)
|
||||
.padding(.horizontal)
|
||||
.scaleEffect(navBarVisibilityRatio)
|
||||
#if os(macOS)
|
||||
Spacer()
|
||||
#endif
|
||||
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)
|
||||
#if os(iOS)
|
||||
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: {
|
||||
#endif
|
||||
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: {
|
||||
#if os(iOS)
|
||||
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()
|
||||
#else
|
||||
Text("Options")
|
||||
#endif
|
||||
}
|
||||
Button("Cancel", role: .cancel, action: {})
|
||||
}
|
||||
.sheet(isPresented: $showLabelsModal) {
|
||||
ApplyLabelsView(mode: .item(item), onSave: { _ in showLabelsModal = false })
|
||||
}
|
||||
.sheet(isPresented: $showTitleEdit) {
|
||||
LinkedItemTitleEditView(item: item)
|
||||
}
|
||||
)
|
||||
#if os(macOS)
|
||||
.frame(maxWidth: 100)
|
||||
.padding(.trailing, 16)
|
||||
#endif
|
||||
}
|
||||
.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)
|
||||
#if os(iOS)
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
#endif
|
||||
}
|
||||
Button("Cancel", role: .cancel, action: {})
|
||||
}
|
||||
.sheet(isPresented: $showLabelsModal) {
|
||||
ApplyLabelsView(mode: .item(item), onSave: { _ in showLabelsModal = false })
|
||||
}
|
||||
.sheet(isPresented: $showTitleEdit) {
|
||||
LinkedItemTitleEditView(item: item)
|
||||
}
|
||||
#if os(macOS)
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
#endif
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
var webPreferencesPopoverView: some View {
|
||||
WebPreferencesPopoverView(
|
||||
updateFontFamilyAction: { updateFontFamilyActionID = UUID() },
|
||||
updateFontAction: { updateFontActionID = UUID() },
|
||||
updateTextContrastAction: { updateTextContrastActionID = UUID() },
|
||||
updateMaxWidthAction: { updateMaxWidthActionID = UUID() },
|
||||
updateLineHeightAction: { updateLineHeightActionID = UUID() },
|
||||
dismissAction: { showPreferencesPopover = false }
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
.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()
|
||||
}
|
||||
}
|
||||
.formSheet(isPresented: $showPreferencesPopover, useSmallDetent: false) {
|
||||
WebPreferencesPopoverView(
|
||||
updateFontFamilyAction: { updateFontFamilyActionID = UUID() },
|
||||
updateFontAction: { updateFontActionID = UUID() },
|
||||
updateTextContrastAction: { updateTextContrastActionID = UUID() },
|
||||
updateMaxWidthAction: { updateMaxWidthActionID = UUID() },
|
||||
updateLineHeightAction: { updateLineHeightActionID = UUID() },
|
||||
dismissAction: { showPreferencesPopover = false }
|
||||
)
|
||||
}
|
||||
.onDisappear {
|
||||
// Clear the shared webview content when exiting
|
||||
WebViewManager.shared().loadHTMLString("<html></html>", baseURL: nil)
|
||||
VStack(spacing: 0) {
|
||||
navBar
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
#if os(iOS)
|
||||
.formSheet(isPresented: $showPreferencesPopover, useSmallDetent: false) {
|
||||
webPreferencesPopoverView
|
||||
}
|
||||
#else
|
||||
.sheet(isPresented: $showPreferencesPopover) {
|
||||
webPreferencesPopoverView
|
||||
.frame(minWidth: 400, minHeight: 400)
|
||||
}
|
||||
#endif
|
||||
.onDisappear {
|
||||
// Clear the shared webview content when exiting
|
||||
WebViewManager.shared().loadHTMLString("<html></html>", baseURL: nil)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -51,6 +51,7 @@ struct WebReaderContent {
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" />
|
||||
<div>HIIIIII</div>
|
||||
<div id='_omnivore-htmlContent' style="display: none;">
|
||||
\(articleContent.htmlContent)
|
||||
</div>
|
||||
@ -86,6 +87,7 @@ struct WebReaderContent {
|
||||
window.lineHeight = \(lineHeight)
|
||||
window.localStorage.setItem("theme", "\(themeKey)")
|
||||
window.prefersHighContrastFont = \(prefersHighContrastText)
|
||||
window.enableHighlightBar = \(isMacApp)
|
||||
</script>
|
||||
<script src="bundle.js"></script>
|
||||
<script src="mathJaxConfiguration.js" id="MathJax-script"></script>
|
||||
|
||||
@ -3,57 +3,64 @@ import Models
|
||||
import Services
|
||||
import SwiftUI
|
||||
import Utils
|
||||
import Views
|
||||
|
||||
#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
|
||||
if let pdfURL = pdfItem.pdfURL {
|
||||
PDFWrapperView(pdfURL: pdfURL)
|
||||
}
|
||||
#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
|
||||
}
|
||||
|
||||
@ -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)"
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
35
apple/OmnivoreKit/Sources/Views/Article/SafariView.swift
Normal file
35
apple/OmnivoreKit/Sources/Views/Article/SafariView.swift
Normal file
@ -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<SafariView>
|
||||
) -> SFSafariViewController {
|
||||
SFSafariViewController(url: url)
|
||||
}
|
||||
|
||||
// swiftlint:disable:next line_length
|
||||
public func updateUIViewController(_: SFSafariViewController, context _: UIViewControllerRepresentableContext<SafariView>) {}
|
||||
}
|
||||
|
||||
#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
|
||||
@ -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() -> WebView {
|
||||
sharedView
|
||||
}
|
||||
|
||||
public static func create() -> WebView {
|
||||
WebView(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? WebView)?.dispatchEvent(.saveAnnotation(annotation: annotation))
|
||||
}
|
||||
|
||||
if sendIncreaseFontSignal {
|
||||
sendIncreaseFontSignal = false
|
||||
(webView as? WebView)?.updateFontSize()
|
||||
}
|
||||
|
||||
if sendDecreaseFontSignal {
|
||||
sendDecreaseFontSignal = false
|
||||
(webView as? WebView)?.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 = WebView(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? WebView)?.dispatchEvent(.saveAnnotation(annotation: annotation))
|
||||
}
|
||||
|
||||
if sendIncreaseFontSignal {
|
||||
sendIncreaseFontSignal = false
|
||||
(webView as? WebView)?.updateFontSize()
|
||||
}
|
||||
|
||||
if sendDecreaseFontSignal {
|
||||
sendDecreaseFontSignal = false
|
||||
(webView as? WebView)?.updateFontSize()
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -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
|
||||
}
|
||||
@ -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<SafariView>
|
||||
) -> SFSafariViewController {
|
||||
SFSafariViewController(url: url)
|
||||
}
|
||||
|
||||
// swiftlint:disable:next line_length
|
||||
public func updateUIViewController(_: SFSafariViewController, context _: UIViewControllerRepresentableContext<SafariView>) {}
|
||||
}
|
||||
|
||||
#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
|
||||
30
apple/OmnivoreKit/Sources/Views/Article/WebViewManager.swift
Normal file
30
apple/OmnivoreKit/Sources/Views/Article/WebViewManager.swift
Normal file
@ -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())
|
||||
}
|
||||
}
|
||||
@ -5,4 +5,8 @@ public enum ViewsPackage {
|
||||
public static var bundleURL: URL {
|
||||
Bundle.module.bundleURL
|
||||
}
|
||||
|
||||
public static var resourceURL: URL {
|
||||
Bundle.module.resourceURL ?? bundleURL
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -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
|
||||
@ -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) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ const App = () => {
|
||||
article={window.omnivoreArticle}
|
||||
labels={window.omnivoreArticle.labels}
|
||||
isAppleAppEmbed={true}
|
||||
highlightBarDisabled={true}
|
||||
highlightBarDisabled={!window.enableHighlightBar}
|
||||
highlightsBaseURL="https://example.com"
|
||||
fontSize={window.fontSize ?? 18}
|
||||
fontFamily={window.fontFamily ?? 'inter'}
|
||||
|
||||
Reference in New Issue
Block a user