mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge commit 'b84bd77a52b8a08b658c38298a54bc6c47c01f8a' into OMN-617
This commit is contained in:
commit
5abfff4950
22 changed files with 173 additions and 43 deletions
|
|
@ -1251,7 +1251,7 @@
|
|||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 51;
|
||||
DEVELOPMENT_TEAM = QJF2XZ86HB;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
INFOPLIST_FILE = InfoPlists/ShareExtensionMac.plist;
|
||||
|
|
@ -1261,7 +1261,7 @@
|
|||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MARKETING_VERSION = 1.6.0;
|
||||
MARKETING_VERSION = 1.7.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.ShareExtension-Mac";
|
||||
|
|
@ -1282,7 +1282,7 @@
|
|||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 51;
|
||||
DEVELOPMENT_TEAM = QJF2XZ86HB;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
INFOPLIST_FILE = InfoPlists/ShareExtensionMac.plist;
|
||||
|
|
@ -1292,7 +1292,7 @@
|
|||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MARKETING_VERSION = 1.6.0;
|
||||
MARKETING_VERSION = 1.7.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.ShareExtension-Mac";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
|
@ -1363,7 +1363,7 @@
|
|||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 51;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = QJF2XZ86HB;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
|
|
@ -1374,7 +1374,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MARKETING_VERSION = 1.6.0;
|
||||
MARKETING_VERSION = 1.7.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app;
|
||||
|
|
@ -1397,7 +1397,7 @@
|
|||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 51;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = QJF2XZ86HB;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
|
|
@ -1408,7 +1408,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MARKETING_VERSION = 1.6.0;
|
||||
MARKETING_VERSION = 1.7.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
|
@ -1463,7 +1463,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.6.0;
|
||||
MARKETING_VERSION = 1.7.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app;
|
||||
PRODUCT_NAME = Omnivore;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1495,7 +1495,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.6.0;
|
||||
MARKETING_VERSION = 1.7.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_LDFLAGS = (
|
||||
|
|
@ -1534,7 +1534,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.6.0;
|
||||
MARKETING_VERSION = 1.7.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
|
|
@ -1560,7 +1560,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = "Entitlements/SafariExtension-Mac.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 49;
|
||||
CURRENT_PROJECT_VERSION = 51;
|
||||
DEVELOPMENT_TEAM = QJF2XZ86HB;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
|
|
@ -1573,7 +1573,7 @@
|
|||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MARKETING_VERSION = 1.6.0;
|
||||
MARKETING_VERSION = 1.7.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_LDFLAGS = (
|
||||
|
|
@ -1598,7 +1598,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = "Entitlements/SafariExtension-Mac.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 49;
|
||||
CURRENT_PROJECT_VERSION = 51;
|
||||
DEVELOPMENT_TEAM = QJF2XZ86HB;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
|
|
@ -1611,7 +1611,7 @@
|
|||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MARKETING_VERSION = 1.6.0;
|
||||
MARKETING_VERSION = 1.7.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
|
|
@ -1696,7 +1696,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.6.0;
|
||||
MARKETING_VERSION = 1.7.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.share-extension";
|
||||
PRODUCT_NAME = ShareExtension;
|
||||
SDKROOT = iphoneos;
|
||||
|
|
@ -1750,7 +1750,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.6.0;
|
||||
MARKETING_VERSION = 1.7.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app;
|
||||
PRODUCT_NAME = Omnivore;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1778,7 +1778,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.6.0;
|
||||
MARKETING_VERSION = 1.7.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.share-extension";
|
||||
PRODUCT_NAME = ShareExtension;
|
||||
SDKROOT = iphoneos;
|
||||
|
|
|
|||
|
|
@ -79,13 +79,15 @@ final class ShareExtensionViewModel: ObservableObject {
|
|||
}
|
||||
.store(in: &subscriptions)
|
||||
|
||||
// Using viewerPublisher to get fast feedback for auth/network errors
|
||||
// Check connection to get fast feedback for auth/network errors
|
||||
Task {
|
||||
do {
|
||||
_ = try await services.dataService.fetchViewer()
|
||||
} catch {
|
||||
debugText = "saveArticleError: \(error)"
|
||||
status = .failed(error: .unknown(description: ""))
|
||||
let hasConnectionAndValidToken = await services.dataService.hasConnectionAndValidToken()
|
||||
|
||||
if !hasConnectionAndValidToken {
|
||||
DispatchQueue.main.async {
|
||||
self.debugText = "saveArticleError: No connection or invalid token."
|
||||
self.status = .failed(error: .unknown(description: ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import WebKit
|
|||
@Binding var decreaseFontActionID: UUID?
|
||||
@Binding var annotationSaveTransactionID: UUID?
|
||||
@Binding var showNavBarActionID: UUID?
|
||||
@Binding var shareActionID: UUID?
|
||||
@Binding var annotation: String
|
||||
|
||||
func makeCoordinator() -> WebReaderCoordinator {
|
||||
|
|
@ -81,6 +82,11 @@ import WebKit
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import WebKit
|
|||
@State var decreaseFontActionID: UUID?
|
||||
@State var annotationSaveTransactionID: UUID?
|
||||
@State var showNavBarActionID: UUID?
|
||||
@State var shareActionID: UUID?
|
||||
@State var annotation = String()
|
||||
|
||||
@EnvironmentObject var dataService: DataService
|
||||
|
|
@ -104,6 +105,10 @@ import WebKit
|
|||
)
|
||||
}
|
||||
)
|
||||
Button(
|
||||
action: { shareActionID = UUID() },
|
||||
label: { Label("Share Original", systemImage: "square.and.arrow.up") }
|
||||
)
|
||||
Button(
|
||||
action: { showDeleteConfirmation = true },
|
||||
label: { Label("Delete", systemImage: "trash") }
|
||||
|
|
@ -156,6 +161,7 @@ import WebKit
|
|||
decreaseFontActionID: $decreaseFontActionID,
|
||||
annotationSaveTransactionID: $annotationSaveTransactionID,
|
||||
showNavBarActionID: $showNavBarActionID,
|
||||
shareActionID: $shareActionID,
|
||||
annotation: $annotation
|
||||
)
|
||||
.onTapGesture {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ final class WebReaderCoordinator: NSObject {
|
|||
var previousIncreaseFontActionID: UUID?
|
||||
var previousDecreaseFontActionID: UUID?
|
||||
var previousShowNavBarActionID: UUID?
|
||||
var previousShareActionID: UUID?
|
||||
var updateNavBarVisibilityRatio: (Double) -> Void = { _ in }
|
||||
private var yOffsetAtStartOfDrag: Double?
|
||||
private var lastYOffset: Double = 0
|
||||
|
|
|
|||
14
apple/OmnivoreKit/Sources/Models/AuthModels.swift
Normal file
14
apple/OmnivoreKit/Sources/Models/AuthModels.swift
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import Foundation
|
||||
|
||||
public struct AuthVerification: Decodable {
|
||||
public let authStatus: AuthStatus
|
||||
}
|
||||
|
||||
public enum AuthStatus: String, Decodable {
|
||||
case authenticated = "AUTHENTICATED"
|
||||
case unAuthenticated = "NOT_AUTHENTICATED"
|
||||
|
||||
public var isAuthenticated: Bool {
|
||||
self == .authenticated
|
||||
}
|
||||
}
|
||||
|
|
@ -38,6 +38,7 @@
|
|||
<attribute name="readingProgressAnchor" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="savedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="serverSyncStatus" attributeType="Integer 64" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="siteName" optional="YES" attributeType="String"/>
|
||||
<attribute name="slug" attributeType="String"/>
|
||||
<attribute name="title" attributeType="String"/>
|
||||
<relationship name="highlights" toMany="YES" deletionRule="Cascade" destinationEntity="Highlight" inverseName="linkedItem" inverseEntity="Highlight"/>
|
||||
|
|
@ -85,7 +86,7 @@
|
|||
</entity>
|
||||
<elements>
|
||||
<element name="Highlight" positionX="27" positionY="225" width="128" height="224"/>
|
||||
<element name="LinkedItem" positionX="-18" positionY="63" width="128" height="344"/>
|
||||
<element name="LinkedItem" positionX="-18" positionY="63" width="128" height="359"/>
|
||||
<element name="LinkedItemLabel" positionX="-36" positionY="18" width="128" height="134"/>
|
||||
<element name="NewsletterEmail" positionX="0" positionY="180" width="128" height="74"/>
|
||||
<element name="Viewer" positionX="45" positionY="234" width="128" height="89"/>
|
||||
|
|
|
|||
|
|
@ -45,8 +45,8 @@ public extension LinkedItem {
|
|||
return (pageURLString ?? "").hasSuffix("pdf")
|
||||
}
|
||||
|
||||
var publisherHostname: String? {
|
||||
URL(string: publisherURLString ?? pageURLString ?? "")?.host
|
||||
var publisherDisplayName: String? {
|
||||
siteName ?? URL(string: publisherURLString ?? pageURLString ?? "")?.host
|
||||
}
|
||||
|
||||
var imageURL: URL? {
|
||||
|
|
|
|||
|
|
@ -55,6 +55,10 @@ public final class DataService: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
public func hasConnectionAndValidToken() async -> Bool {
|
||||
await networker.hasConnectionAndValidToken()
|
||||
}
|
||||
|
||||
private func resetCoreData() {
|
||||
let storeContainer =
|
||||
persistentContainer.persistentStoreCoordinator
|
||||
|
|
|
|||
|
|
@ -20,3 +20,28 @@ public final class Networker {
|
|||
self.urlSession = urlSession
|
||||
}
|
||||
}
|
||||
|
||||
extension Networker {
|
||||
/// Test if the user has a network connection and a valid auth token
|
||||
/// - Returns: A `Bool` value
|
||||
func hasConnectionAndValidToken() async -> Bool {
|
||||
let urlRequest = URLRequest.create(
|
||||
baseURL: appEnvironment.serverBaseURL,
|
||||
urlPath: "/api/auth/verify",
|
||||
requestMethod: .get,
|
||||
includeAuthToken: true
|
||||
)
|
||||
|
||||
let resource = ServerResource<AuthVerification>(
|
||||
urlRequest: urlRequest,
|
||||
decode: AuthVerification.decode
|
||||
)
|
||||
|
||||
do {
|
||||
let authVerification = try await urlSession.performReq(resource: resource)
|
||||
return authVerification.authStatus.isAuthenticated
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ extension ServerResponse {
|
|||
}
|
||||
}
|
||||
|
||||
/// Empty struct to use when a successful network call does not include any JSON
|
||||
struct EmptyResponse: Decodable {}
|
||||
|
||||
extension URLSession {
|
||||
func performReq<ResponseModel>(
|
||||
resource: ServerResource<ResponseModel>
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ extension DataService {
|
|||
pageURLString: try $0.url(),
|
||||
descriptionText: try $0.description(),
|
||||
publisherURLString: try $0.originalArticleUrl(),
|
||||
siteName: try $0.siteName(),
|
||||
author: try $0.author(),
|
||||
publishDate: try $0.publishedAt()?.value,
|
||||
slug: try $0.slug(),
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ private let articleSelection = Selection.Article {
|
|||
pageURLString: try $0.url(),
|
||||
descriptionText: try $0.description(),
|
||||
publisherURLString: try $0.originalArticleUrl(),
|
||||
siteName: try $0.siteName(),
|
||||
author: try $0.author(),
|
||||
publishDate: try $0.publishedAt()?.value,
|
||||
slug: try $0.slug(),
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ struct InternalLinkedItem {
|
|||
let pageURLString: String
|
||||
let descriptionText: String?
|
||||
let publisherURLString: String?
|
||||
let siteName: String?
|
||||
let author: String?
|
||||
let publishDate: Date?
|
||||
let slug: String
|
||||
|
|
@ -37,6 +38,7 @@ struct InternalLinkedItem {
|
|||
linkedItem.pageURLString = pageURLString
|
||||
linkedItem.descriptionText = descriptionText
|
||||
linkedItem.publisherURLString = publisherURLString
|
||||
linkedItem.siteName = siteName
|
||||
linkedItem.author = author
|
||||
linkedItem.publishDate = publishDate
|
||||
linkedItem.slug = slug
|
||||
|
|
@ -93,6 +95,7 @@ extension JSONArticle {
|
|||
pageURLString: url,
|
||||
descriptionText: title,
|
||||
publisherURLString: nil,
|
||||
siteName: nil,
|
||||
author: nil,
|
||||
publishDate: nil,
|
||||
slug: slug,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ public final class WebView: WKWebView {
|
|||
dispatchEvent("decreaseFontSize")
|
||||
}
|
||||
|
||||
public func shareOriginalItem() {
|
||||
dispatchEvent("share")
|
||||
}
|
||||
|
||||
func dispatchEvent(_ name: String) {
|
||||
let dispatch = "document.dispatchEvent(new Event('\(name)'));"
|
||||
evaluateJavaScript(dispatch) { obj, err in
|
||||
|
|
|
|||
|
|
@ -100,11 +100,10 @@ public struct GridCard: View {
|
|||
.lineLimit(1)
|
||||
}
|
||||
|
||||
if let publisherURL = item.publisherHostname {
|
||||
Text(publisherURL)
|
||||
if let publisherDisplayName = item.publisherDisplayName {
|
||||
Text(publisherDisplayName)
|
||||
.font(.appCaptionTwo)
|
||||
.foregroundColor(.appGrayText)
|
||||
.underline()
|
||||
.lineLimit(1)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,11 +26,10 @@ public struct FeedCard: View {
|
|||
.lineLimit(1)
|
||||
}
|
||||
|
||||
if let publisherURL = item.publisherHostname {
|
||||
Text(publisherURL)
|
||||
if let publisherDisplayName = item.publisherDisplayName {
|
||||
Text(publisherDisplayName)
|
||||
.font(.appCaption)
|
||||
.foregroundColor(.appGrayText)
|
||||
.underline()
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -17,6 +17,7 @@ import { SubstackHandler } from './substack-handler'
|
|||
import { AxiosHandler } from './axios-handler'
|
||||
import { BloombergHandler } from './bloomberg-handler'
|
||||
import { GolangHandler } from './golang-handler'
|
||||
import { MorningBrewHandler } from './morning-brew-handler'
|
||||
|
||||
const NON_NEWSLETTER_EMAIL_TOPIC = 'nonNewsletterEmailReceived'
|
||||
const pubsub = new PubSub()
|
||||
|
|
@ -26,6 +27,7 @@ const NEWSLETTER_HANDLERS = [
|
|||
new AxiosHandler(),
|
||||
new BloombergHandler(),
|
||||
new GolangHandler(),
|
||||
new MorningBrewHandler(),
|
||||
]
|
||||
|
||||
export const getNewsletterHandler = (
|
||||
|
|
|
|||
10
packages/inbound-email-handler/src/morning-brew-handler.ts
Normal file
10
packages/inbound-email-handler/src/morning-brew-handler.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { NewsletterHandler } from './newsletter'
|
||||
|
||||
export class MorningBrewHandler extends NewsletterHandler {
|
||||
constructor() {
|
||||
super()
|
||||
this.senderRegex = /Morning Brew <crew@morningbrew.com>/
|
||||
this.urlRegex = /<a.* href=["']([^"']*)["'].*>View Online<\/a>/
|
||||
this.defaultUrl = 'https://www.morningbrew.com'
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import { AxiosHandler } from '../src/axios-handler'
|
|||
import { BloombergHandler } from '../src/bloomberg-handler'
|
||||
import { GolangHandler } from '../src/golang-handler'
|
||||
import { getNewsletterHandler } from '../src'
|
||||
import { MorningBrewHandler } from '../src/morning-brew-handler'
|
||||
|
||||
describe('Confirmation email test', () => {
|
||||
describe('#isConfirmationEmail()', () => {
|
||||
|
|
@ -77,6 +78,15 @@ describe('Newsletter email test', () => {
|
|||
GolangHandler
|
||||
)
|
||||
})
|
||||
|
||||
it('should return MorningBrewHandler when email is from Morning Brew', () => {
|
||||
const from = 'Morning Brew <crew@morningbrew.com>'
|
||||
const unSubRawUrl = '<https://morningbrew.com/unsubscribe>'
|
||||
|
||||
expect(getNewsletterHandler('', from, unSubRawUrl)).to.be.instanceof(
|
||||
MorningBrewHandler
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getNewsletterUrl()', () => {
|
||||
|
|
@ -114,6 +124,17 @@ describe('Newsletter email test', () => {
|
|||
|
||||
expect(new GolangHandler().parseNewsletterUrl('', html)).to.equal(url)
|
||||
})
|
||||
|
||||
it('returns url when email is from Morning Brew', () => {
|
||||
const url = 'https://www.morningbrew.com/daily/issues/first'
|
||||
const html = `
|
||||
<a style="color: #000000; text-decoration: none;" target="_blank" rel="noopener" href="${url}">View Online</a>
|
||||
`
|
||||
|
||||
expect(new MorningBrewHandler().parseNewsletterUrl('', html)).to.equal(
|
||||
url
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('get author from email address', () => {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ import { updateThemeLocally } from '../../../lib/themeUpdater'
|
|||
import { ArticleMutations } from '../../../lib/articleActions'
|
||||
import { LabelChip } from '../../elements/LabelChip'
|
||||
import { Label } from '../../../lib/networking/fragments/labelFragment'
|
||||
import { HighlightLocation, makeHighlightStartEndOffset } from '../../../lib/highlights/highlightGenerator'
|
||||
import {
|
||||
HighlightLocation,
|
||||
makeHighlightStartEndOffset,
|
||||
} from '../../../lib/highlights/highlightGenerator'
|
||||
|
||||
type ArticleContainerProps = {
|
||||
article: ArticleAttributes
|
||||
|
|
@ -37,7 +40,9 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
|
|||
const [showShareModal, setShowShareModal] = useState(false)
|
||||
const [showReportIssuesModal, setShowReportIssuesModal] = useState(false)
|
||||
const [fontSize, setFontSize] = useState(props.fontSize ?? 20)
|
||||
const highlightHref = useRef(window.location.hash ? window.location.hash.split('#')[1] : null)
|
||||
const highlightHref = useRef(
|
||||
window.location.hash ? window.location.hash.split('#')[1] : null
|
||||
)
|
||||
const [highlightReady, setHighlightReady] = useState(false)
|
||||
const [highlightLocations, setHighlightLocations] = useState<
|
||||
HighlightLocation[]
|
||||
|
|
@ -87,16 +92,27 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
|
|||
updateThemeLocally(ThemeId.Light)
|
||||
}
|
||||
|
||||
const share = () => {
|
||||
if (navigator.share) {
|
||||
navigator.share({
|
||||
title: props.article.title,
|
||||
url: props.article.originalArticleUrl,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('increaseFontSize', increaseFontSize)
|
||||
document.addEventListener('decreaseFontSize', decreaseFontSize)
|
||||
document.addEventListener('switchToDarkMode', switchToDarkMode)
|
||||
document.addEventListener('switchToLightMode', switchToLightMode)
|
||||
document.addEventListener('share', share)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('increaseFontSize', increaseFontSize)
|
||||
document.removeEventListener('decreaseFontSize', decreaseFontSize)
|
||||
document.removeEventListener('switchToDarkMode', switchToDarkMode)
|
||||
document.removeEventListener('switchToLightMode', switchToLightMode)
|
||||
document.removeEventListener('share', share)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -118,7 +134,9 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
|
|||
css={{
|
||||
padding: '16px',
|
||||
maxWidth: '100%',
|
||||
background: props.isAppleAppEmbed ? 'unset' : theme.colors.grayBg.toString(),
|
||||
background: props.isAppleAppEmbed
|
||||
? 'unset'
|
||||
: theme.colors.grayBg.toString(),
|
||||
'--text-font-family': styles.fontFamily,
|
||||
'--text-font-size': `${styles.fontSize}px`,
|
||||
'--line-height': `${styles.lineHeight}%`,
|
||||
|
|
@ -138,12 +156,12 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
|
|||
margin: `30px 0px`,
|
||||
},
|
||||
'@md': {
|
||||
maxWidth: 1024 - (styles.margin),
|
||||
maxWidth: 1024 - styles.margin,
|
||||
},
|
||||
'@lg': {
|
||||
margin: `30px 0`,
|
||||
width: 'auto',
|
||||
maxWidth: 1024 - (styles.margin),
|
||||
maxWidth: 1024 - styles.margin,
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
|
@ -163,10 +181,20 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
|
|||
href={props.article.url}
|
||||
/>
|
||||
{props.labels ? (
|
||||
<SpanBox css={{ pb: '16px', width: '100%', '&:empty': { display: 'none' } }}>
|
||||
{props.labels?.map((label) =>
|
||||
<LabelChip key={label.id} text={label.name} color={label.color} />
|
||||
)}
|
||||
<SpanBox
|
||||
css={{
|
||||
pb: '16px',
|
||||
width: '100%',
|
||||
'&:empty': { display: 'none' },
|
||||
}}
|
||||
>
|
||||
{props.labels?.map((label) => (
|
||||
<LabelChip
|
||||
key={label.id}
|
||||
text={label.name}
|
||||
color={label.color}
|
||||
/>
|
||||
))}
|
||||
</SpanBox>
|
||||
) : null}
|
||||
{props.isAppleAppEmbed && (
|
||||
|
|
|
|||
Loading…
Reference in a new issue